Initial commit
This commit is contained in:
commit
03399ab51c
38 changed files with 964 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
db/*.db
|
||||
config/app.yml
|
37
.gitlab-ci.yml
Normal file
37
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,37 @@
|
|||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script:
|
||||
- apt-get update -qy
|
||||
- apt-get install -y nodejs libqtwebkit-dev qt4-qmake sqlite3 libsqlite3-dev xvfb
|
||||
- bundle install --path /cache
|
||||
- RACK_ENV=test bundle exec rake db:create
|
||||
- RACK_ENV=test bundle exec rake db:migrate
|
||||
- RACK_ENV=test xvfb-run -a bundle exec rspec
|
||||
tags:
|
||||
- rails
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- docker build -t atomaka/link-share .
|
||||
except:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- VERSION=$(git describe --tags)
|
||||
- docker build -t atomaka/link-share .
|
||||
- docker tag atomaka/link-share:latest docker.atomaka.com/atomaka/link-share:$VERSION
|
||||
- docker tag atomaka/link-share:latest docker.atomaka.com/atomaka/link-share:latest
|
||||
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD -e me@atomaka.com docker.atomaka.com
|
||||
- docker push docker.atomaka.com/atomaka/link-share
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
1
.ruby-version
Normal file
1
.ruby-version
Normal file
|
@ -0,0 +1 @@
|
|||
2.2.2
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
|
@ -0,0 +1,17 @@
|
|||
FROM alpine:latest
|
||||
|
||||
RUN export LANG=en_US.UTF-8 && \
|
||||
export LANGUAGE=en_US.UTF-8 && \
|
||||
export LC_ALL=en_US.UTF-8
|
||||
|
||||
RUN apk update \
|
||||
&& apk add build-base ruby-dev sqlite-dev \
|
||||
&& apk add ruby ruby-bundler ruby-io-console \
|
||||
&& rm -rf /var/cache/apk*
|
||||
|
||||
WORKDIR /app
|
||||
COPY Gemfile* ./
|
||||
RUN bundle install --path=vendor/bundle --jobs=4 --without=development test
|
||||
COPY . /app
|
||||
|
||||
CMD bundle exec rackup -o 0.0.0.0
|
26
Gemfile
Normal file
26
Gemfile
Normal file
|
@ -0,0 +1,26 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'activerecord'
|
||||
gem 'sinatra'
|
||||
gem 'sqlite3'
|
||||
|
||||
gem 'sinatra-activerecord'
|
||||
gem 'sinatra-contrib', require: false
|
||||
gem 'sinatra-flash'
|
||||
gem 'validate_url'
|
||||
|
||||
gem 'slim'
|
||||
|
||||
gem 'bigdecimal'
|
||||
# alpine linux does not include a method to determine the timezone
|
||||
gem 'tzinfo-data'
|
||||
|
||||
group :development do
|
||||
gem 'rspec'
|
||||
gem 'capybara-webkit'
|
||||
gem 'factory_girl'
|
||||
gem 'database_cleaner'
|
||||
gem 'launchy'
|
||||
gem 'pry'
|
||||
gem 'rerun'
|
||||
end
|
143
Gemfile.lock
Normal file
143
Gemfile.lock
Normal file
|
@ -0,0 +1,143 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activemodel (4.2.3)
|
||||
activesupport (= 4.2.3)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.3)
|
||||
activemodel (= 4.2.3)
|
||||
activesupport (= 4.2.3)
|
||||
arel (~> 6.0)
|
||||
activesupport (4.2.3)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.3.8)
|
||||
arel (6.0.2)
|
||||
backports (3.6.6)
|
||||
bigdecimal (1.2.7)
|
||||
builder (3.2.2)
|
||||
capybara (2.5.0)
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (~> 2.0)
|
||||
capybara-webkit (1.7.1)
|
||||
capybara (>= 2.3.0, < 2.6.0)
|
||||
json
|
||||
celluloid (0.16.0)
|
||||
timers (~> 4.0.0)
|
||||
coderay (1.1.0)
|
||||
database_cleaner (1.4.1)
|
||||
diff-lcs (1.2.5)
|
||||
factory_girl (4.5.0)
|
||||
activesupport (>= 3.0.0)
|
||||
ffi (1.9.10)
|
||||
hitimes (1.2.2)
|
||||
i18n (0.7.0)
|
||||
json (1.8.3)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
listen (2.10.1)
|
||||
celluloid (~> 0.16.0)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
method_source (0.8.2)
|
||||
mime-types (3.0)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2015.1120)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (5.7.0)
|
||||
multi_json (1.11.2)
|
||||
nokogiri (1.6.7)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
pry (0.10.1)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
rack (1.6.4)
|
||||
rack-protection (1.5.3)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rb-fsevent (0.9.5)
|
||||
rb-inotify (0.9.5)
|
||||
ffi (>= 0.5.0)
|
||||
rerun (0.10.0)
|
||||
listen (~> 2.7, >= 2.7.3)
|
||||
rspec (3.4.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-core (3.4.1)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-mocks (3.4.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-support (3.4.1)
|
||||
sinatra (1.4.6)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
sinatra-activerecord (2.0.6)
|
||||
activerecord (>= 3.2)
|
||||
sinatra (~> 1.0)
|
||||
sinatra-contrib (1.4.6)
|
||||
backports (>= 2.0)
|
||||
multi_json
|
||||
rack-protection
|
||||
rack-test
|
||||
sinatra (~> 1.4.0)
|
||||
tilt (>= 1.3, < 3)
|
||||
sinatra-flash (0.3.0)
|
||||
sinatra (>= 1.0.0)
|
||||
slim (3.0.6)
|
||||
temple (~> 0.7.3)
|
||||
tilt (>= 1.3.3, < 2.1)
|
||||
slop (3.6.0)
|
||||
sqlite3 (1.3.10)
|
||||
temple (0.7.6)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.1)
|
||||
timers (4.0.1)
|
||||
hitimes
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2015.7)
|
||||
tzinfo (>= 1.0.0)
|
||||
validate_url (1.0.2)
|
||||
activemodel (>= 3.0.0)
|
||||
addressable
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
activerecord
|
||||
bigdecimal
|
||||
capybara-webkit
|
||||
database_cleaner
|
||||
factory_girl
|
||||
launchy
|
||||
pry
|
||||
rerun
|
||||
rspec
|
||||
sinatra
|
||||
sinatra-activerecord
|
||||
sinatra-contrib
|
||||
sinatra-flash
|
||||
slim
|
||||
sqlite3
|
||||
tzinfo-data
|
||||
validate_url
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
21
Makefile
Normal file
21
Makefile
Normal file
|
@ -0,0 +1,21 @@
|
|||
NAME = atomaka/docker-linkshare
|
||||
CONTAINER = sinatra-linkshare
|
||||
DOMAIN = linkshare.atomaka.com
|
||||
PORT = 10081
|
||||
DATABASE = /home/atomaka/linkshare.db
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
docker build -t $(NAME) .
|
||||
|
||||
clean:
|
||||
docker rm -f $(CONTAINER)
|
||||
|
||||
deploy: clean
|
||||
touch $(DATABASE)
|
||||
docker run -e VIRTUAL_HOST=$(DOMAIN) -d -p $(PORT):9292 -v $(DATABASE):/app/db/linkshare.db --name=$(CONTAINER) --restart=always $(NAME)
|
||||
docker exec $(CONTAINER) rake db:migrate
|
||||
|
||||
update:
|
||||
git pull origin master
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
[![build status](https://git.atomaka.com/ci/projects/2/status.png?ref=master)](https://git.atomaka.com/ci/projects/2?ref=master)
|
||||
|
||||
# link-share
|
7
Rakefile
Normal file
7
Rakefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
require './app'
|
||||
require 'sinatra/activerecord/rake'
|
||||
|
||||
desc "Starts the development server"
|
||||
task :serve do
|
||||
`bundle exec rerun -b rackup`
|
||||
end
|
89
app.rb
Normal file
89
app.rb
Normal file
|
@ -0,0 +1,89 @@
|
|||
require './config/init'
|
||||
|
||||
get '/manage' do
|
||||
protected!
|
||||
|
||||
@links = get_links(params[:status])
|
||||
|
||||
slim :manage
|
||||
end
|
||||
|
||||
get '/new' do
|
||||
protected!
|
||||
|
||||
@link = Link.new
|
||||
|
||||
slim :new
|
||||
end
|
||||
|
||||
post '/' do
|
||||
protected!
|
||||
|
||||
@link = Link.new(params[:link])
|
||||
|
||||
if @link.save
|
||||
flash[:success] = 'Link has been created'
|
||||
redirect '/manage'
|
||||
else
|
||||
flash.now[:danger] = 'Did not pass validations'
|
||||
slim :new
|
||||
end
|
||||
end
|
||||
|
||||
get '/send' do
|
||||
protected!
|
||||
|
||||
@link = Link.find(params[:id])
|
||||
|
||||
@link.mark_sent
|
||||
|
||||
flash[:success] = 'Link has been marked as sent'
|
||||
redirect '/manage'
|
||||
end
|
||||
|
||||
get '/destroy' do
|
||||
protected!
|
||||
|
||||
@link = Link.find(params[:id])
|
||||
|
||||
if @link.sent?
|
||||
flash[:warning] = 'Cannot delete sent link'
|
||||
else
|
||||
@link.destroy
|
||||
flash[:success] = 'Link has been deleted'
|
||||
end
|
||||
|
||||
redirect '/manage'
|
||||
end
|
||||
|
||||
get '/' do
|
||||
slim :calendar
|
||||
end
|
||||
|
||||
get '/events' do
|
||||
start = params[:start]
|
||||
finish = params[:end]
|
||||
|
||||
json serialize(Link.calendar(start, finish))
|
||||
end
|
||||
|
||||
private
|
||||
def get_links(status)
|
||||
if status == 'sent'
|
||||
Link.sent
|
||||
elsif status == 'all'
|
||||
Link.all
|
||||
else
|
||||
Link.unsent
|
||||
end
|
||||
end
|
||||
|
||||
def serialize(events)
|
||||
events.map do |event|
|
||||
{
|
||||
title: event.title,
|
||||
url: event.url,
|
||||
start: event.sent_at.in_time_zone('Eastern Time (US & Canada)')
|
||||
}
|
||||
end
|
||||
end
|
2
config.ru
Normal file
2
config.ru
Normal file
|
@ -0,0 +1,2 @@
|
|||
require './app'
|
||||
run Sinatra::Application
|
3
config/app.yml.sample
Normal file
3
config/app.yml.sample
Normal file
|
@ -0,0 +1,3 @@
|
|||
secret: YOUR_SECRET
|
||||
users:
|
||||
USERNAME: PASSWORD
|
15
config/environments.rb
Normal file
15
config/environments.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
set :database, 'sqlite3:db/linkshare.db'
|
||||
|
||||
configure :production do
|
||||
config_file 'config/app.yml'
|
||||
end
|
||||
|
||||
configure :development do
|
||||
set :show_exceptions, true
|
||||
config_file 'config/app.yml'
|
||||
end
|
||||
|
||||
configure :test do
|
||||
set :database, 'sqlite3:db/test.db'
|
||||
config_file 'spec/fixtures/app.yml'
|
||||
end
|
17
config/init.rb
Normal file
17
config/init.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
require 'rubygems'
|
||||
require 'bundler'
|
||||
require 'bundler/setup'
|
||||
require 'sinatra/config_file'
|
||||
require 'sinatra/json'
|
||||
Bundler.require
|
||||
|
||||
set :root, File.dirname('..')
|
||||
|
||||
require_relative 'environments'
|
||||
|
||||
require_relative '../models/link'
|
||||
require_relative '../helpers/application_helper'
|
||||
|
||||
config_file 'config/app.yml'
|
||||
|
||||
use Rack::Session::Cookie, secret: settings.secret
|
0
db/.gitkeep
Normal file
0
db/.gitkeep
Normal file
11
db/migrate/20150721142956_add_links.rb
Normal file
11
db/migrate/20150721142956_add_links.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class AddLinks < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :links do |t|
|
||||
t.string :title
|
||||
t.string :link
|
||||
t.datetime :sent_at
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
5
db/migrate/20150721201947_change_link_to_url.rb
Normal file
5
db/migrate/20150721201947_change_link_to_url.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class ChangeLinkToUrl < ActiveRecord::Migration
|
||||
def change
|
||||
rename_column :links, :link, :url
|
||||
end
|
||||
end
|
24
db/schema.rb
Normal file
24
db/schema.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# encoding: UTF-8
|
||||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# Note that this schema.rb definition is the authoritative source for your
|
||||
# database schema. If you need to create the application database on another
|
||||
# system, you should be using db:schema:load, not running all the migrations
|
||||
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
||||
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20150721201947) do
|
||||
|
||||
create_table "links", force: :cascade do |t|
|
||||
t.string "title"
|
||||
t.string "url"
|
||||
t.datetime "sent_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
end
|
20
helpers/application_helper.rb
Normal file
20
helpers/application_helper.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
helpers do
|
||||
def protected!
|
||||
return if authorized?
|
||||
headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
|
||||
halt 401, "Not authorized\n"
|
||||
end
|
||||
|
||||
def authorized?
|
||||
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
||||
return unless @auth.provided? and @auth.basic?
|
||||
|
||||
username, password = @auth.credentials
|
||||
|
||||
user_exists?(username) && settings.users[username] == password
|
||||
end
|
||||
|
||||
def user_exists?(username)
|
||||
settings.users.keys.include?(username)
|
||||
end
|
||||
end
|
27
models/link.rb
Normal file
27
models/link.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class Link < ActiveRecord::Base
|
||||
validates :title,
|
||||
presence: true
|
||||
validates :url,
|
||||
presence: true,
|
||||
url: true,
|
||||
uniqueness: true
|
||||
|
||||
scope :sent, -> { where('sent_at IS NOT NULL').order('sent_at DESC') }
|
||||
scope :unsent, -> { where('sent_at IS NULL').order('created_at ASC') }
|
||||
scope :sent_after, ->(date) { where('sent_at > ?', date) }
|
||||
scope :sent_before, ->(date) { where('sent_at < ?', date) }
|
||||
scope :calendar, ->(start, finish) { sent_after(start).sent_before(finish) }
|
||||
|
||||
def mark_sent
|
||||
update_attribute(:sent_at, Time.now)
|
||||
end
|
||||
|
||||
def sent?
|
||||
!!self.sent_at
|
||||
end
|
||||
|
||||
def url=(url)
|
||||
url.chomp!('/') if url.respond_to?(:chomp)
|
||||
write_attribute(:url, url)
|
||||
end
|
||||
end
|
17
public/custom.css
Normal file
17
public/custom.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.link {
|
||||
padding: 5px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.link:nth-of-type(odd) {
|
||||
background: #e7e7e7
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.fc-time {
|
||||
display: none;
|
||||
}
|
10
public/custom.js
Normal file
10
public/custom.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
$(document).ready(function() {
|
||||
$('#calendar').fullCalendar({
|
||||
header: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: ''
|
||||
},
|
||||
events: '/events',
|
||||
});
|
||||
});
|
6
spec/factories/link_factory.rb
Normal file
6
spec/factories/link_factory.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
FactoryGirl.define do
|
||||
factory :link do
|
||||
sequence(:title) { |n| "Sequenced Title (#{n})" }
|
||||
sequence(:url) { |n| "http://www.#{n}example#{n}.com/#{n}" }
|
||||
end
|
||||
end
|
49
spec/features/admin/create_link_spec.rb
Normal file
49
spec/features/admin/create_link_spec.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Admin Create Links' do
|
||||
context 'when not logged in' do
|
||||
it 'should not allow access to new link form' do
|
||||
visit '/new'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
before(:each) { basic_auth 'admin', 'password' }
|
||||
|
||||
context 'with valid data' do
|
||||
let(:link) { build(:link) }
|
||||
|
||||
it 'should allow creation of a link' do
|
||||
visit '/new'
|
||||
fill_in :link_title, with: link.title
|
||||
fill_in :link_url, with: link.url
|
||||
click_button 'Submit'
|
||||
|
||||
expect(page).to have_content 'Link has been created'
|
||||
end
|
||||
|
||||
it 'should list the new link' do
|
||||
visit '/new'
|
||||
fill_in :link_title, with: link.title
|
||||
fill_in :link_url, with: link.url
|
||||
click_button 'Submit'
|
||||
|
||||
expect(page).to have_content link.title
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid data' do
|
||||
let(:link) { build(:link, title: '') }
|
||||
|
||||
it 'should not allow link with invalid data' do
|
||||
visit '/new'
|
||||
fill_in :link_title, with: link.title
|
||||
fill_in :link_url, with: link.url
|
||||
click_button 'Submit'
|
||||
|
||||
expect(page).to have_content 'Did not pass validations'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
36
spec/features/admin/delete_link_spec.rb
Normal file
36
spec/features/admin/delete_link_spec.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Admin Delete Links' do
|
||||
context 'when not logged in' do
|
||||
it 'should not allow access to delete links' do
|
||||
visit '/send'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
let!(:links) { 10.times.collect { create(:link) } }
|
||||
before(:each) { basic_auth 'admin', 'password' }
|
||||
|
||||
it 'should allow deleting of a link' do
|
||||
visit '/manage'
|
||||
first('a', text: 'Delete').click
|
||||
expect(page).to have_content('Link has been deleted')
|
||||
end
|
||||
|
||||
it 'should remove deleted link all lists' do
|
||||
link = create(:link)
|
||||
|
||||
visit '/manage'
|
||||
|
||||
find('a', text: link.title)
|
||||
.find(:xpath, '../..')
|
||||
.find('a', text: 'Delete')
|
||||
.click
|
||||
|
||||
visit '/manage?status=all'
|
||||
|
||||
expect(page).to_not have_content(link.title)
|
||||
end
|
||||
end
|
||||
end
|
53
spec/features/admin/list_links_spec.rb
Normal file
53
spec/features/admin/list_links_spec.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Admin List Links' do
|
||||
context 'when not logged in' do
|
||||
it 'should not allow access to list links' do
|
||||
visit '/manage'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
let!(:sent_link) { create(:link, sent_at: Time.now) }
|
||||
let!(:links) { 10.times.collect { create(:link) } }
|
||||
before(:each) { basic_auth 'admin', 'password' }
|
||||
|
||||
context 'unsent' do
|
||||
it 'should show unsent links by default' do
|
||||
visit '/manage'
|
||||
|
||||
expect(page).to_not have_content(sent_link.title)
|
||||
links.each { |link| expect(page).to have_content(link.title) }
|
||||
end
|
||||
|
||||
it 'should only list unsent links' do
|
||||
visit '/manage'
|
||||
first('a', text: 'Unsent Links').click
|
||||
|
||||
expect(page).to_not have_content(sent_link.title)
|
||||
links.each { |link| expect(page).to have_content(link.title) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'sent' do
|
||||
it 'should only list sent links' do
|
||||
visit '/manage'
|
||||
first('a', text: 'Sent Links').click
|
||||
|
||||
expect(page).to have_content(sent_link.title)
|
||||
links.each { |link| expect(page).to_not have_content(link.title) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'all' do
|
||||
it 'should list all links' do
|
||||
visit '/manage'
|
||||
first('a', text: 'All Links').click
|
||||
|
||||
expect(page).to have_content(sent_link.title)
|
||||
links.each { |link| expect(page).to have_content(link.title) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
spec/features/admin/send_link_spec.rb
Normal file
34
spec/features/admin/send_link_spec.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Admin Send Links' do
|
||||
context 'when not logged in' do
|
||||
it 'should not allow access to send links' do
|
||||
visit '/send'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
let!(:links) { 10.times.collect { create(:link) } }
|
||||
before(:each) { basic_auth 'admin', 'password' }
|
||||
|
||||
it 'should allow sending of a link' do
|
||||
visit '/manage'
|
||||
first('a', text: 'Send').click
|
||||
expect(page).to have_content('Link has been marked as sent')
|
||||
end
|
||||
|
||||
it 'should remove sent link from unsent list' do
|
||||
link = create(:link)
|
||||
|
||||
visit '/manage'
|
||||
|
||||
find('a', text: link.title)
|
||||
.find(:xpath, '../..')
|
||||
.find('a', text: 'Send')
|
||||
.click
|
||||
|
||||
expect(page).to_not have_content(link.title)
|
||||
end
|
||||
end
|
||||
end
|
56
spec/features/authorization_spec.rb
Normal file
56
spec/features/authorization_spec.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Authorization' do
|
||||
context 'when not logged in' do
|
||||
it 'should not allow access to manage' do
|
||||
visit '/manage'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
|
||||
it 'should not allow access to the create form' do
|
||||
visit '/new'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
|
||||
it 'should not allow access to send' do
|
||||
visit '/send'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
|
||||
it 'should not allow access to delete' do
|
||||
visit '/destroy'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should allow accessing the home page' do
|
||||
visit '/'
|
||||
expect(page.status_code).to be(200)
|
||||
end
|
||||
|
||||
it 'should allow accessing the events page' do
|
||||
visit '/events'
|
||||
expect(page.status_code).to be(200)
|
||||
end
|
||||
|
||||
context 'when logging in' do
|
||||
context 'with incorrect credentials' do
|
||||
before(:each) { basic_auth 'baduser', 'badpassword' }
|
||||
|
||||
it 'should allow not allow access' do
|
||||
visit '/manage'
|
||||
expect(page.status_code).to be(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with correct credentials' do
|
||||
before(:each) { basic_auth 'admin', 'password' }
|
||||
|
||||
it 'should allow access' do
|
||||
visit '/manage'
|
||||
|
||||
expect(page.status_code).to be(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
spec/features/calendar_spec.rb
Normal file
17
spec/features/calendar_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Calendar View' do
|
||||
OVER_ONE_MONTH = 60*60*24*40
|
||||
|
||||
let!(:showing_link) { create(:link, sent_at: Time.now) }
|
||||
let!(:before_link) { create(:link, sent_at: Time.now - OVER_ONE_MONTH) }
|
||||
let!(:after_link) { create(:link, sent_at: Time.now + OVER_ONE_MONTH) }
|
||||
|
||||
it 'should only show links for the current month', js: true do
|
||||
visit '/'
|
||||
|
||||
expect(page).to have_content(showing_link.title)
|
||||
expect(page).to_not have_content(before_link.title)
|
||||
expect(page).to_not have_content(after_link.title)
|
||||
end
|
||||
end
|
3
spec/fixtures/app.yml
vendored
Normal file
3
spec/fixtures/app.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
secret: my_secret_something_blan
|
||||
users:
|
||||
admin: password
|
65
spec/models/link_spec.rb
Normal file
65
spec/models/link_spec.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Link do
|
||||
let(:link) { build(:link) }
|
||||
|
||||
context 'with valid data' do
|
||||
it 'should be valid' do
|
||||
expect(link).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid data' do
|
||||
it 'should not be valid with blank title' do
|
||||
link.title = ''
|
||||
|
||||
expect(link).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not be valid with a blank url' do
|
||||
link.url = ''
|
||||
|
||||
expect(link).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not be valid with a bad url' do
|
||||
link.url = 'bad'
|
||||
|
||||
expect(link).to be_invalid
|
||||
end
|
||||
|
||||
it 'should not be valid with a duplicate url' do
|
||||
create(:link, url: link.url)
|
||||
|
||||
expect(link).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
context '#mark_sent' do
|
||||
it 'should set a time to sent_at' do
|
||||
link.mark_sent
|
||||
|
||||
expect(link.sent_at).to_not be(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context '#sent?' do
|
||||
context 'when already sent' do
|
||||
it 'should respond with true' do
|
||||
link.mark_sent
|
||||
expect(link.sent?).to be(true)
|
||||
end
|
||||
|
||||
it 'should respond with false' do
|
||||
expect(link.sent?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#url' do
|
||||
it 'removes a trailing slash' do
|
||||
link.url = 'http://www.url.com/'
|
||||
expect(link.url).to eq('http://www.url.com')
|
||||
end
|
||||
end
|
||||
end
|
72
spec/spec_helper.rb
Normal file
72
spec/spec_helper.rb
Normal file
|
@ -0,0 +1,72 @@
|
|||
require 'rack/test'
|
||||
require 'rspec'
|
||||
require 'capybara/rspec'
|
||||
require 'capybara/webkit'
|
||||
require 'factory_girl'
|
||||
require 'database_cleaner'
|
||||
|
||||
ENV['RACK_ENV'] = 'test'
|
||||
|
||||
require File.expand_path '../../app.rb', __FILE__
|
||||
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
|
||||
module TestingMixin
|
||||
include Rack::Test::Methods
|
||||
include RSpec::Matchers
|
||||
include Capybara::DSL
|
||||
include FactoryGirl::Syntax::Methods
|
||||
|
||||
Capybara.app = Sinatra::Application
|
||||
Capybara.javascript_driver = :webkit
|
||||
Capybara.asset_host = 'http://localhost:3000'
|
||||
|
||||
FactoryGirl.definition_file_paths = %w{./factories ./test/factories ./spec/factories}
|
||||
FactoryGirl.find_definitions
|
||||
|
||||
def app() Sinatra::Application end
|
||||
|
||||
def basic_auth(username, password)
|
||||
if page.driver.respond_to?(:basic_auth)
|
||||
page.driver.basic_auth(username, password)
|
||||
elsif page.driver.respond_to?(:basic_authorize)
|
||||
page.driver.basic_authorize(username, password)
|
||||
elsif page.driver.respond_to?(:browser) && page.driver.browser.respond_to?(:basic_authorize)
|
||||
page.driver.browser.basic_authorize(username, password)
|
||||
else
|
||||
raise "I don't know how to log in!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Capybara::Webkit.configure do |config|
|
||||
config.allow_unknown_urls
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include TestingMixin
|
||||
|
||||
config.before(:suite) do
|
||||
DatabaseCleaner.clean_with(:truncation)
|
||||
end
|
||||
|
||||
config.before(:each) do
|
||||
DatabaseCleaner.strategy = :truncation
|
||||
end
|
||||
|
||||
config.before(:each, :js => true) do
|
||||
DatabaseCleaner.strategy = :truncation
|
||||
end
|
||||
|
||||
config.before(:each) do
|
||||
DatabaseCleaner.start
|
||||
end
|
||||
|
||||
config.append_after(:each) do
|
||||
DatabaseCleaner.clean
|
||||
Capybara.reset_sessions!
|
||||
end
|
||||
|
||||
config.order = :random
|
||||
Kernel.srand config.seed
|
||||
end
|
1
views/calendar.slim
Normal file
1
views/calendar.slim
Normal file
|
@ -0,0 +1 @@
|
|||
#calendar
|
4
views/form_errors.slim
Normal file
4
views/form_errors.slim
Normal file
|
@ -0,0 +1,4 @@
|
|||
- if @link.errors.any?
|
||||
ul
|
||||
- @link.errors.full_messages.each do |error|
|
||||
li= error
|
33
views/layout.slim
Normal file
33
views/layout.slim
Normal file
|
@ -0,0 +1,33 @@
|
|||
doctype html
|
||||
html
|
||||
head
|
||||
title Links App
|
||||
link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"
|
||||
link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.3.2/fullcalendar.min.css"
|
||||
link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.3.2/fullcalendar.print.css" media="print"
|
||||
link rel="stylesheet" href="/custom.css"
|
||||
body
|
||||
.navbar.navbar-default.navbar-static-top.navbar-custom
|
||||
.container
|
||||
.navbar-header
|
||||
button.navbar-toggle.collapsed type='button' data-toggle='collapse' data-target='.navbar-collapse'
|
||||
span.sr-only Toggle navigation
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
span.icon-bar
|
||||
a href="/" class="navbar-brand" Links
|
||||
.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav
|
||||
li
|
||||
a href="/manage" Manage
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
.container
|
||||
- unless flash.empty?
|
||||
- flash.each_key do |key|
|
||||
.alert class="alert-#{key}" role="alert"= flash[key]
|
||||
== yield
|
||||
script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"
|
||||
script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"
|
||||
script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"
|
||||
script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.3.2/fullcalendar.min.js"
|
||||
script src="custom.js"
|
14
views/link_list.slim
Normal file
14
views/link_list.slim
Normal file
|
@ -0,0 +1,14 @@
|
|||
.links
|
||||
- @links.each do |link|
|
||||
.row.link
|
||||
.col-md-9
|
||||
a href="#{link.url}" #{link.title}
|
||||
.col-md-3
|
||||
- if link.sent_at
|
||||
= link.sent_at
|
||||
- else
|
||||
ul.list-inline style="margin: 0"
|
||||
li
|
||||
a href="/send?id=#{link.id}" Send
|
||||
li
|
||||
a href="/destroy?id=#{link.id}" Delete
|
15
views/manage.slim
Normal file
15
views/manage.slim
Normal file
|
@ -0,0 +1,15 @@
|
|||
.row.controls
|
||||
.btn-toolbar.pull-right
|
||||
a href="/manage?status=unsent" class="btn btn-primary" Unsent Links
|
||||
a href="/manage?status=sent" class="btn btn-primary" Sent Links
|
||||
a href="/manage?status=all" class="btn btn-primary" All Links
|
||||
a href="/new" class="btn btn-primary" New Link
|
||||
|
||||
== slim :link_list
|
||||
|
||||
.row.controls
|
||||
.btn-toolbar
|
||||
a href="/manage?status=unsent" class="btn btn-primary" Unsent Links
|
||||
a href="/manage?status=sent" class="btn btn-primary" Sent Links
|
||||
a href="/manage?status=all" class="btn btn-primary" All Links
|
||||
a href="/new" class="btn btn-primary" New Link
|
9
views/new.slim
Normal file
9
views/new.slim
Normal file
|
@ -0,0 +1,9 @@
|
|||
== slim :form_errors
|
||||
form action="/" method="post"
|
||||
.form-group
|
||||
label for="link_title" Title
|
||||
input type="text" name="link[title]" id="link_title" class="form-control" value="#{@link.title}"
|
||||
.form-group
|
||||
label for="link_url" Link
|
||||
input type="text" name="link[url]" id="link_url" class="form-control" value="#{@link.url}"
|
||||
button type="submit" class="btn btn-primary" Submit
|
Loading…
Reference in a new issue