├── log └── .keep ├── tmp └── .keep ├── vendor └── .keep ├── .ruby-version ├── .vscode └── last.sql ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── public ├── favicon.ico ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── test ├── helpers │ └── .keep ├── mailers │ ├── .keep │ ├── previews │ │ └── jobs_mailer_preview.rb │ └── jobs_mailer_test.rb ├── models │ ├── .keep │ ├── industry_test.rb │ ├── client_test.rb │ ├── user_test.rb │ ├── company_test.rb │ └── job_test.rb ├── system │ ├── .keep │ ├── companies_test.rb │ └── jobs_test.rb ├── controllers │ ├── .keep │ ├── pages_controller_test.rb │ ├── companies_controller_test.rb │ └── jobs_controller_test.rb ├── integration │ └── .keep ├── application_system_test_case.rb ├── factories │ ├── industries.rb │ ├── clients.rb │ ├── users.rb │ ├── companies.rb │ └── jobs.rb └── test_helper.rb ├── app ├── assets │ ├── images │ │ ├── .keep │ │ ├── devcongress-logo-32.ico │ │ ├── devcongress-logo-57.ico │ │ ├── devcongress-logo-72.ico │ │ ├── devcongress-jobs-meta.jpg │ │ ├── devcongress-logo-114.ico │ │ └── devcongress-logo-144.ico │ ├── javascripts │ │ ├── channels │ │ │ └── .keep │ │ ├── cable.js │ │ └── application.js │ ├── stylesheets │ │ ├── purecss │ │ │ ├── base.scss │ │ │ ├── tables.scss │ │ │ ├── grids.scss │ │ │ ├── buttons.scss │ │ │ ├── forms.scss │ │ │ ├── menus.scss │ │ │ ├── all.scss │ │ │ ├── menus │ │ │ │ ├── menus-r.scss │ │ │ │ ├── menus-paginator.scss │ │ │ │ ├── menus.scss │ │ │ │ └── menus-core.scss │ │ │ ├── buttons │ │ │ │ ├── buttons-core.scss │ │ │ │ ├── buttons.scss │ │ │ │ └── buttons-theme.scss │ │ │ ├── forms │ │ │ │ ├── forms-r.scss │ │ │ │ ├── forms-core.scss │ │ │ │ └── forms.scss │ │ │ ├── grids │ │ │ │ ├── grids-core.scss │ │ │ │ ├── grids-r.scss │ │ │ │ └── grids-units.scss │ │ │ ├── defaults.scss │ │ │ ├── tables │ │ │ │ ├── tables.scss │ │ │ │ └── tables-theme.scss │ │ │ ├── base │ │ │ │ └── normalize-context.scss │ │ │ └── lists │ │ │ │ └── lists-theme.scss │ │ ├── pages.scss │ │ ├── companies.scss │ │ ├── navbar.scss │ │ ├── application.css.scss │ │ ├── jobs.scss │ │ ├── scaffolds.scss │ │ └── header.scss │ └── config │ │ └── manifest.js ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── industry.rb │ ├── client.rb │ ├── user.rb │ ├── company.rb │ └── job.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── pages_controller.rb │ ├── application_controller.rb │ ├── companies_controller.rb │ └── jobs_controller.rb ├── views │ ├── layouts │ │ ├── _scripts.html.erb │ │ ├── mailer.text.erb │ │ ├── _footer.html.erb │ │ ├── mailer.html.erb │ │ ├── application.html.erb │ │ ├── _navbar.html.erb │ │ └── _meta_and_styles.html.erb │ ├── companies │ │ ├── edit.html.erb │ │ ├── show.html.erb │ │ └── new.html.erb │ ├── jobs_mailer │ │ ├── published.text.erb │ │ └── published.html.erb │ ├── devise │ │ ├── mailer │ │ │ ├── password_change.html.erb │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── unlock_instructions.html.erb │ │ │ ├── email_changed.html.erb │ │ │ └── reset_password_instructions.html.erb │ │ ├── passwords │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── sessions │ │ │ └── new.html.erb │ │ ├── shared │ │ │ └── _links.html.erb │ │ └── registrations │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ ├── pages │ │ ├── index.html.erb │ │ ├── help.html.erb │ │ ├── about.html.erb │ │ └── privacy.html.erb │ └── jobs │ │ ├── index.html.erb │ │ ├── search.json.jbuilder │ │ ├── new.html.erb │ │ ├── myjobs.html.erb │ │ ├── search.html.erb │ │ ├── show.html.erb │ │ ├── _form.html.erb │ │ └── edit.html.erb ├── helpers │ ├── pages_helper.rb │ ├── application_helper.rb │ └── jobs_helper.rb ├── jobs │ └── application_job.rb ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb └── mailers │ ├── application_mailer.rb │ └── jobs_mailer.rb ├── Procfile ├── package.json ├── entrypoint.sh ├── bin ├── bundle ├── rake ├── rails ├── yarn ├── spring ├── update └── setup ├── config ├── spring.rb ├── environment.rb ├── initializers │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── twitter.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── assets.rb │ ├── inflections.rb │ └── content_security_policy.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── application.rb ├── database.yml ├── locales │ ├── en.yml │ └── devise.en.yml ├── storage.yml ├── puma.rb ├── routes.rb └── environments │ ├── test.rb │ ├── development.rb │ └── production.rb ├── config.ru ├── db ├── migrate │ ├── 20181202105418_add_filled_at_to_jobs.rb │ ├── 20181015055216_rename_company_on_jobs.rb │ ├── 20180601012900_add_archived_to_jobs.rb │ ├── 20180528011504_add_user_to_job.rb │ ├── 20181011121438_remove_poster_name_from_jobs.rb │ ├── 20181011121512_remove_poster_email_from_jobs.rb │ ├── 20181015052951_add_company_reference_to_jobs.rb │ ├── 20181113012450_add_apply_link_to_jobs.rb │ ├── 20181012163952_add_remote_ok_to_jobs.rb │ ├── 20181106011104_add_city_and_country_to_jobs.rb │ ├── 20181015180424_create_clients.rb │ ├── 20181021095117_create_industries.rb │ ├── 20180524143248_create_jobs.rb │ ├── 20181019072018_drop_user_and_poster_details_from_jobs.rb │ ├── 20181015050635_create_companies.rb │ ├── 20180525195859_devise_create_users.rb │ └── 20181205160427_add_document_to_jobs.rb ├── seeds.rb └── countries.csv ├── Rakefile ├── .travis.yml ├── Dockerfile ├── README.md ├── docker-compose.yml ├── .gitignore ├── LICENSE ├── Gemfile └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.3 -------------------------------------------------------------------------------- /.vscode/last.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/_scripts.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec rails server -p $PORT -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/helpers/pages_helper.rb: -------------------------------------------------------------------------------- 1 | module PagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dcjobs", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/base.scss: -------------------------------------------------------------------------------- 1 | @import "base/normalize-context"; 2 | @import "base/normalize"; 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/tables.scss: -------------------------------------------------------------------------------- 1 | @import "tables/tables-theme"; 2 | @import "tables/tables"; 3 | -------------------------------------------------------------------------------- /app/views/companies/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Companies#edit

2 |

Find me in app/views/companies/edit.html.erb

3 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gem install bundler 4 | bundle install --binstubs="$BUNDLE_BIN" 5 | 6 | exec "$@" -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /app/assets/images/devcongress-logo-32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jadijadi/jobs-1/HEAD/app/assets/images/devcongress-logo-32.ico -------------------------------------------------------------------------------- /app/assets/images/devcongress-logo-57.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jadijadi/jobs-1/HEAD/app/assets/images/devcongress-logo-57.ico -------------------------------------------------------------------------------- /app/assets/images/devcongress-logo-72.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jadijadi/jobs-1/HEAD/app/assets/images/devcongress-logo-72.ico -------------------------------------------------------------------------------- /app/assets/images/devcongress-jobs-meta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jadijadi/jobs-1/HEAD/app/assets/images/devcongress-jobs-meta.jpg -------------------------------------------------------------------------------- /app/assets/images/devcongress-logo-114.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jadijadi/jobs-1/HEAD/app/assets/images/devcongress-logo-114.ico -------------------------------------------------------------------------------- /app/assets/images/devcongress-logo-144.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jadijadi/jobs-1/HEAD/app/assets/images/devcongress-logo-144.ico -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/grids.scss: -------------------------------------------------------------------------------- 1 | @import "grids/grids-core"; 2 | @import "grids/grids-r"; 3 | @import "grids/grids-units"; 4 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/views/jobs_mailer/published.text.erb: -------------------------------------------------------------------------------- 1 | Jobs#published 2 | 3 | <%= @greeting %>, find me in app/views/jobs_mailer/published.text.erb 4 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/buttons.scss: -------------------------------------------------------------------------------- 1 | @import "buttons/buttons-core"; 2 | @import "buttons/buttons-theme"; 3 | @import "buttons/buttons"; 4 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /test/controllers/pages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PagesControllerTest < ActionDispatch::IntegrationTest 4 | end 5 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ].each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/forms.scss: -------------------------------------------------------------------------------- 1 | @import "forms/forms-core"; 2 | @import "forms/forms-r"; 3 | @import "forms/forms-theme"; 4 | @import "forms/forms"; 5 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/menus.scss: -------------------------------------------------------------------------------- 1 | @import "menus/menus-core"; 2 | @import "menus/menus-paginator"; 3 | @import "menus/menus-r"; 4 | @import "menus/menus"; 5 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Hello #{@resource.email}! 3 |

4 |

We're contacting you to notify you that your password has been changed.

5 | -------------------------------------------------------------------------------- /db/migrate/20181202105418_add_filled_at_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddFilledAtToJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :jobs, :filled_at, :timestamp 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/all.scss: -------------------------------------------------------------------------------- 1 | @import "defaults"; 2 | @import "base"; 3 | // @import "buttons"; 4 | @import "forms"; 5 | // @import "grids"; 6 | // @import "menus"; 7 | // @import "tables"; -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db/migrate/20181015055216_rename_company_on_jobs.rb: -------------------------------------------------------------------------------- 1 | class RenameCompanyOnJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | rename_column :jobs, :company, :company_name 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /db/migrate/20180601012900_add_archived_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddArchivedToJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :jobs, :archived, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180528011504_add_user_to_job.rb: -------------------------------------------------------------------------------- 1 | class AddUserToJob < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :jobs, :user_id, :integer 4 | add_index :jobs, :user_id 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20181011121438_remove_poster_name_from_jobs.rb: -------------------------------------------------------------------------------- 1 | class RemovePosterNameFromJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :jobs, :poster_name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20181011121512_remove_poster_email_from_jobs.rb: -------------------------------------------------------------------------------- 1 | class RemovePosterEmailFromJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :jobs, :poster_email, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20181015052951_add_company_reference_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddCompanyReferenceToJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | add_reference :jobs, :company, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20181113012450_add_apply_link_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddApplyLinkToJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :jobs, :apply_link, :text, null: false, default: '' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pages.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Pages controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /db/migrate/20181012163952_add_remote_ok_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddRemoteOkToJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :jobs, :remote_ok, :boolean, null: false, default: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'DevCongress Jobs ', 3 | reply_to: 'jobs@devcongress.org' 4 | 5 | layout 'mailer' 6 | end 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/companies.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the companies controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dcjobs_production 11 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | def index 3 | @jobs = Job.all_active 4 | end 5 | 6 | def help 7 | end 8 | 9 | def about 10 | end 11 | 12 | def privacy 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.3 4 | before_script: 5 | - bundle exec rake db:create --all 6 | - bundle exec rake db:migrate 7 | script: 8 | - bin/rails test 9 | services: 10 | - postgresql 11 | notifications: 12 | email: false 13 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | include Devise::Test::IntegrationHelpers 5 | 6 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 7 | end 8 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Welcome #{@email}! 3 |

4 |

You can confirm your account email through the link below:

5 |

6 | <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %> 7 |

8 | -------------------------------------------------------------------------------- /app/views/companies/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= @company.name %>

2 |

<%= @company.industry %>

3 |

target=_blank><%= @company.website %>

4 |

<%= @company.description %>

5 |

<%= @company.city %>

6 |

<%= @company.country %>

7 | -------------------------------------------------------------------------------- /db/migrate/20181106011104_add_city_and_country_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddCityAndCountryToJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :jobs, :city, :string, null: false, default: '' 4 | add_column :jobs, :country, :string, null: false, default: '' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/menus/menus-r.scss: -------------------------------------------------------------------------------- 1 | /* RESPONSIVE */ 2 | 3 | @media (max-width: 480px) { 4 | 5 | .pure-menu-horizontal { 6 | width:100%; 7 | } 8 | 9 | .pure-menu-children li { 10 | display: block; 11 | border-bottom:1px solid block; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/views/pages/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Home | #{t('app.name')}" %> 3 | <% end %> 4 |

DevCongress Jobs

5 |
6 |

Welcome to the DevCongress Job Board. Hire great people here.

7 |
-------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /db/migrate/20181015180424_create_clients.rb: -------------------------------------------------------------------------------- 1 | class CreateClients < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :clients do |t| 4 | t.references :company, foreign_key: true, null: false 5 | t.references :user, foreign_key: true, null: false 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/mailers/previews/jobs_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/jobs_mailer 2 | class JobsMailerPreview < ActionMailer::Preview 3 | 4 | # Preview this email at http://localhost:3000/rails/mailers/jobs_mailer/published 5 | def published 6 | JobsMailer.published 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Hello #{@resource.email}! 3 |

4 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

5 |

Click the link below to unlock your account:

6 |

7 | <%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %> 8 |

9 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/pages/help.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Help | #{t('app.name')}" %> 3 | <% end %> 4 |

Help

5 |
6 |

Find help in times of need

7 |
8 |
9 |

Any questions? Mail us at jobs@devcongress.org

10 |
11 | -------------------------------------------------------------------------------- /config/initializers/twitter.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # $tweetBot = Twitter::REST::Client.new do |config| 4 | # config.consumer_key = ENV['DCJOBS_TW_CONSUMER_KEY'] 5 | # config.consumer_secret = ENV['DCJOBS_TW_CONSUMER_SECRET'] 6 | # config.access_token = ENV['DCJOBS_TW_ACCESS_TOKEN'] 7 | # config.access_token_secret = ENV['DCJOBS_TW_ACCESS_SECRET'] 8 | # end 9 | -------------------------------------------------------------------------------- /app/models/industry.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: industries 4 | # 5 | # id :bigint(8) not null, primary key 6 | # name :text not null 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | class Industry < ApplicationRecord 12 | validates :name, presence: true, uniqueness: true 13 | end 14 | -------------------------------------------------------------------------------- /app/views/devise/mailer/email_changed.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Hello #{@email}! 3 |

4 | <% if @resource.try(:unconfirmed_email?) %> 5 |

6 | We're contacting you to notify you that your email is being changed to #{@resource.unconfirmed_email}. 7 |

8 | <% else %> 9 |

10 | We're contacting you to notify you that your email has been changed to #{@resource.email}. 11 |

12 | <% end %> 13 | -------------------------------------------------------------------------------- /test/factories/industries.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: industries 4 | # 5 | # id :bigint(8) not null, primary key 6 | # name :text not null 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | FactoryBot.define do 12 | factory :industry do 13 | name { Faker::Company.industry } 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /test/factories/clients.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: clients 4 | # 5 | # id :bigint(8) not null, primary key 6 | # company_id :bigint(8) not null 7 | # user_id :bigint(8) not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | 12 | FactoryBot.define do 13 | factory :client do 14 | company 15 | user 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/jobs_helper.rb: -------------------------------------------------------------------------------- 1 | module JobsHelper 2 | 3 | def tweetButtonText 4 | return "https://twitter.com/intent/tweet?text=" + @job.company.name + " is looking for a " + @job.role + ". Read more on DevCongress Jobs, " + job_url + (" &hash;devcongressjobs &hash;hiring via @DevCongress").html_safe 5 | end 6 | 7 | def fbButtonText 8 | return "https://www.facebook.com/sharer.php?u=" + job_url 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | 3 | $tweetBot = Twitter::REST::Client.new do |config| 4 | config.consumer_key = ENV['TWITTER_CONSUMER_KEY'] 5 | config.consumer_secret = ENV['TWITTER_CONSUMER_SECRET'] 6 | config.access_token = ENV['TWITTER_ACCESS_TOKEN'] 7 | config.access_token_secret = ENV['TWITTER_ACCESS_SECRET'] 8 | end 9 | 10 | end 11 | -------------------------------------------------------------------------------- /app/views/jobs_mailer/published.html.erb: -------------------------------------------------------------------------------- 1 |

2 | A new job has been posted on <%= link_to "The Jobs Board", root_url %> 3 | for your company. Please find the details below: 4 |

5 | 11 |

12 | Here's a link to the job post: <%= link_to @job.title, job_url(@job) %>. 13 |

14 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | r/giL7NuaP7iKf634VkBA8WrnT8yPQ7GlyvrjioiqIXAeV3qmWa6LHe2XQyXXvIosS7d/ZNe4pmZ7y7nb8uobecjj9D4rod4ylOtxOWg2iYs88bQVUyHJm1JY6i7GCpLhqu84E1OTS380shsOQXBwgdIOqY3zQ1zwUnevdoY0UaucaD36J4nP92B/wwOtCO65XmvxWGkDqtKRUHoUAIzcA8bZTvGFgH5xeUfTCYMNIkJhn2Iwwif/aDgPcwD3a0PcaV+nD650wSiwRPVManiXa0ab34YSitMBISfXOPxu+QeR15EFG/N8n1d/XOQ7orw5Tq2nu7HQSX/z0jAEirDdKw3itFNRrXzkXPHXUvcQCb6n4LrIkdQ1lpkk0GQik7WlZNdLMnxq9Q1vhHMXtg24NVOXM3WsH1aw1bt--i49/KqtTaPvnRWPS--WH21+I5kYhelmakTU92L2g== -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Hello #{@resource.email}! 3 |

4 |

Someone has requested a link to change your password. You can do this through the link below.

5 |

6 | <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %> 7 |

8 |

If you didn't request this, please ignore this email.

9 |

Your password won't change until you access the link above and create a new one.

10 | -------------------------------------------------------------------------------- /app/views/pages/about.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "About | #{t('app.name')}" %> 3 | <% end %> 4 |

About DevCongress Jobs

5 |
6 |

All you have wanted to know about this website

7 |
8 |
9 |

DevCongress Jobs has been created to help employers find tech talent across Africa. Have any more quesitons? Email us at jobs@devcongress.org

10 |
11 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require_relative '../config/environment' 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Add more helper methods to be used by all tests here... 7 | include FactoryBot::Syntax::Methods 8 | include ActiveJob::TestHelper 9 | 10 | Shoulda::Matchers.configure do |config| 11 | config.integrate do |with| 12 | with.test_framework :minitest 13 | with.library :rails 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/jobs/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "All Jobs | #{t('app.name')}" %> 3 | <% end %> 4 |

All Jobs

5 |

All the DevCongress Job Board listings here

6 |
7 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /db/migrate/20181021095117_create_industries.rb: -------------------------------------------------------------------------------- 1 | class CreateIndustries < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :industries do |t| 4 | t.text :name, null: false 5 | 6 | t.timestamps 7 | end 8 | 9 | reversible do |m| 10 | m.up do 11 | execute "CREATE UNIQUE INDEX lower_industry_name ON industries (lower(name))" 12 | end 13 | 14 | m.down do 15 | execute "DROP INDEX lower_industry_name" 16 | end 17 | end 18 | 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20180524143248_create_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :jobs do |t| 4 | t.string :role 5 | t.string :duration 6 | t.string :salary 7 | t.string :requirements 8 | t.string :qualification 9 | t.string :perks 10 | t.string :company 11 | t.string :contact_email 12 | t.string :poster_name 13 | t.string :poster_email 14 | t.string :phone 15 | 16 | t.timestamps 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/mailers/jobs_mailer.rb: -------------------------------------------------------------------------------- 1 | class JobsMailer < ApplicationMailer 2 | 3 | # Subject can be set in your I18n file at config/locales/en.yml 4 | # with the following lookup: 5 | # 6 | # en.jobs_mailer.published.subject 7 | # 8 | def published 9 | @job = params[:job] 10 | @company = @job.company 11 | @company_people = @company.users 12 | 13 | subject = "New job published: #{@job.role} @ #{@company.name}" 14 | to = @company_people.collect { |u| u.email } 15 | 16 | mail(to: to, subject: subject) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/client.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: clients 4 | # 5 | # id :bigint(8) not null, primary key 6 | # company_id :bigint(8) not null 7 | # user_id :bigint(8) not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | 12 | class Client < ApplicationRecord 13 | # Associations 14 | belongs_to :company 15 | belongs_to :user 16 | 17 | # Validations 18 | validates :company, presence: true 19 | validates :user, presence: true 20 | end 21 | -------------------------------------------------------------------------------- /app/views/pages/privacy.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Privacy | #{t('app.name')}" %> 3 | <% end %> 4 |

Privacy

5 |
6 |

Errm, no personal information but still...

7 |
8 |
9 |

We don't keep any private information about you. We only store your email and the name you share when creating a job post. Both of them are open to users of the website. Beyond that, we keep no other information about you.

10 |
11 | -------------------------------------------------------------------------------- /app/views/jobs/search.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @matches do |match| 2 | json.( 3 | match, 4 | :created_at, 5 | :role, 6 | :duration, 7 | :salary, 8 | :requirements, 9 | :qualification, 10 | :perks, 11 | :remote_ok, 12 | :city, 13 | :country, 14 | :apply_link) 15 | json.url job_url(match) 16 | 17 | json.company( 18 | match.company, 19 | :name, 20 | :industry, 21 | :logo, 22 | :website, 23 | :description, 24 | :city, 25 | :state_or_region, 26 | :post_code, 27 | :country) 28 | end 29 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= render 'layouts/meta_and_styles' %> 4 | 5 | 6 | 9 |
10 | <%= render 'layouts/navbar' %> 11 | 12 | <%= notice %> 13 | 14 | <%= yield %> 15 | <%= render 'layouts/footer' %> 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/models/industry_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: industries 4 | # 5 | # id :bigint(8) not null, primary key 6 | # name :text not null 7 | # created_at :datetime not null 8 | # updated_at :datetime not null 9 | # 10 | 11 | require 'test_helper' 12 | 13 | class IndustryTest < ActiveSupport::TestCase 14 | setup do 15 | @subject = FactoryBot.create(:industry) 16 | end 17 | 18 | test "validations" do 19 | must validate_presence_of :name 20 | must validate_uniqueness_of :name 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20181019072018_drop_user_and_poster_details_from_jobs.rb: -------------------------------------------------------------------------------- 1 | class DropUserAndPosterDetailsFromJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | remove_column :jobs, :user_id, :integer 4 | remove_column :jobs, :company_name, :string 5 | remove_column :jobs, :contact_email, :string 6 | remove_column :jobs, :phone, :string 7 | 8 | [ 9 | :company_id, 10 | :role, 11 | :salary, 12 | :requirements, 13 | :qualification 14 | ].each do |col| 15 | change_column_null :jobs, col, false 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.3.3 2 | 3 | # Install basic packages 4 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs 5 | 6 | # Make app dir 7 | RUN mkdir /app 8 | WORKDIR /app 9 | 10 | # Import Gemfile 11 | COPY Gemfile Gemfile.lock ./ 12 | 13 | EXPOSE 3000 14 | ENV RAILS_ENV=development 15 | 16 | # Import entrypoint script 17 | COPY ./entrypoint.sh /entrypoint.sh 18 | RUN chmod +x /entrypoint.sh 19 | 20 | ENTRYPOINT ["/entrypoint.sh"] 21 | 22 | ENV BUNDLE_PATH=/bundle \ 23 | BUNDLE_BIN=/bundle/bin \ 24 | GEM_HOME=/bundle \ 25 | BUNDLE_JOBS=4 26 | ENV PATH="${GEM_HOME}/:${PATH}" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/devcongress/jobs.svg?branch=master)](https://travis-ci.org/devcongress/jobs) 2 | 3 | # DevCongress Jobs 4 | 5 | This is the code which runs the DevCongress Jobs website, which lives at [jobs.devcongress.org](http://jobs.devcongress.org) 6 | 7 | ## Docker setup 8 | 9 | ### Requirements 10 | 11 | Install Docker and Docker Compose (Docker Compose comes with Docker on Windows and MacOS) 12 | 13 | ### Init projet 14 | 15 | Init database with `docker-compose run --rm web rails db:create db:migrate db:seed` 16 | 17 | ### Start Project 18 | 19 | Launch docker with `docker-compose up` -------------------------------------------------------------------------------- /app/assets/stylesheets/navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | display: flex; 3 | height: 50px; 4 | width: 100%; 5 | justify-content: space-between; 6 | } 7 | 8 | .navbar--logo { 9 | display: flex; 10 | } 11 | 12 | .navbar--menu { 13 | display: flex; 14 | } 15 | 16 | .navbar--menu__item { 17 | margin: 7px 5px; 18 | padding: 5px 2.5px; 19 | text-decoration: none; 20 | transition: all 0.5s; 21 | &:hover { 22 | border-bottom: 2px solid $dc-blue; 23 | ; 24 | } 25 | &:visited { 26 | &:hover { 27 | border-bottom: 2px solid #3705ec; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/views/jobs/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Add New Job | #{t('app.name')}" %> 3 | <% end %> 4 |

Add New Job

5 |

Provide candid, relevant information for best results

6 |
7 | <% if user_signed_in? %> 8 | <%= render 'form', job: @job, companies: @companies %> 9 | <%= link_to 'Back', jobs_path %> 10 | <% else %> 11 |

Please sign in to create a job listing

12 | <%= link_to('Sign in', new_user_session_path) %> 13 |

or

14 | <%= link_to('Sign up', new_user_registration_path) %> 15 | <% end %> 16 |
17 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/buttons/buttons-core.scss: -------------------------------------------------------------------------------- 1 | .pure-button { 2 | /* Structure */ 3 | display: inline-block; 4 | *display: inline; /*IE 6/7*/ 5 | zoom: 1; 6 | line-height: normal; 7 | white-space: nowrap; 8 | vertical-align: baseline; 9 | text-align: center; 10 | cursor: pointer; 11 | -webkit-user-drag: none; 12 | -webkit-user-select: none; 13 | -moz-user-select: none; 14 | user-select: none; 15 | } 16 | 17 | 18 | /* Firefox: Get rid of the inner focus border */ 19 | .pure-button::-moz-focus-inner{ 20 | padding: 0; 21 | border: 0; 22 | } 23 | 24 | a:focus { 25 | outline: none; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.1' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /test/models/client_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: clients 4 | # 5 | # id :bigint(8) not null, primary key 6 | # company_id :bigint(8) not null 7 | # user_id :bigint(8) not null 8 | # created_at :datetime not null 9 | # updated_at :datetime not null 10 | # 11 | 12 | require 'test_helper' 13 | 14 | class ClientTest < ActiveSupport::TestCase 15 | setup do 16 | @subject = FactoryBot.build(:client) 17 | end 18 | 19 | test "validations" do 20 | must validate_presence_of :user 21 | must validate_presence_of :company 22 | end 23 | 24 | test "associations" do 25 | must belong_to :user 26 | must belong_to :company 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | # 9 | # Industries: 10 | # Initial list of known industries. 11 | 12 | open("https://gist.githubusercontent.com/claudey/679c682e4291d7b54059c7c19b1dfdf2/raw/3687da0264ca4fa6867d12b561c164a8b9c29af6/industries.txt", "r") do |content| 13 | content.each_line do |industry_name| 14 | Industry.create name: industry_name 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/jobs/myjobs.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "My Jobs | #{t('app.name')}" %> 3 | <% end %> 4 |

All My Jobs

5 |
6 |

Jobs you have listed are here. Archived jobs are hidden from public view, and have a cream background.

7 |
8 |
9 | <% if @jobs.empty? %> 10 |

You have no jobs listed yet. 🤔

11 | <% # %p Once a post has been created, you will not be able to delete it. Instead, you will be able to unhide it from public view. This is . %> 12 | <%= link_to 'List your first Job', new_job_path %> 13 | <% else %> 14 | <%= render 'jobs/my-jobs-list' %> 15 | <% end %> 16 |
17 |
18 | -------------------------------------------------------------------------------- /app/views/layouts/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /db/migrate/20181015050635_create_companies.rb: -------------------------------------------------------------------------------- 1 | class CreateCompanies < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :companies do |t| 4 | t.text :name, null: false 5 | t.text :industry, null: false 6 | t.text :logo, null: false, default: '' 7 | t.text :website, null: false, default: '' 8 | t.text :description, null: false 9 | t.text :email, null: false, unique: true 10 | t.text :phone, null: false 11 | t.text :city, null: false 12 | t.text :state_or_region, null: false 13 | t.text :post_code, null: false 14 | t.text :country, null: false 15 | 16 | t.timestamps 17 | end 18 | 19 | add_index :companies, :email, unique: true 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/forms/forms-r.scss: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width : 480px) { 2 | .pure-form button[type='submit'] { 3 | margin: 0.7em 0 0; 4 | } 5 | 6 | .pure-form input[type='text'], .pure-form button, .pure-form label { 7 | margin-bottom: 0.3em; 8 | display: block; 9 | } 10 | 11 | .pure-group input[type='text'] { 12 | margin-bottom: 0; 13 | } 14 | 15 | .pure-form-aligned .pure-control-group label { 16 | margin-bottom: 0.3em; 17 | text-align: left; 18 | display: block; 19 | width: 100%; 20 | } 21 | 22 | .pure-form-aligned .pure-controls { 23 | margin: 1.5em 0 0 0; 24 | } 25 | 26 | .pure-form .pure-help-inline { 27 | display: block; 28 | font-size: 80%; 29 | padding: 0.2em 0 0.8em; /* increased bottom padding to make it group with its related input element */ 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/menus/menus-paginator.scss: -------------------------------------------------------------------------------- 1 | .pure-paginator { 2 | list-style: none; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | .pure-paginator li { 7 | display: inline-block; 8 | *display: inline; 9 | /* IE 7 inline-block hack */ 10 | *zoom: 1; 11 | margin: 0 -0.35em 0 0; 12 | } 13 | .pure-paginator .pure-button { 14 | border-radius: 0; 15 | padding: 0.8em 1.4em; 16 | vertical-align: top; 17 | height: 1.1em; 18 | } 19 | .pure-paginator .pure-button:focus { 20 | outline-style: none; 21 | } 22 | .pure-paginator .prev, .pure-paginator .next { 23 | color: #C0C1C3; 24 | text-shadow: 0px -1px 0px rgba(0,0,0, 0.45); 25 | } 26 | .pure-paginator .prev { 27 | border-radius: 2px 0 0 2px; 28 | } 29 | .pure-paginator .next { 30 | border-radius: 0 2px 2px 0; 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | x-db_config: &db_config 4 | POSTGRES_PASSWORD: password 5 | POSTGRES_USER: interne 6 | POSTGRES_DB: jobs_dev 7 | 8 | services: 9 | postgres: 10 | image: postgres:9.5 11 | volumes: 12 | - postgres:/var/lib/postgresql/data:cached 13 | environment: 14 | <<: *db_config 15 | networks: 16 | - jobs 17 | 18 | web: 19 | build: . 20 | environment: 21 | <<: *db_config 22 | POSTGRES_HOST: postgres 23 | volumes: 24 | - ./:/app 25 | - bundle:/bundle:cached 26 | command: bundle exec rails s -p 3000 -b 0.0.0.0 27 | networks: 28 | - jobs 29 | ports: 30 | - "3000:3000" 31 | depends_on: 32 | - postgres 33 | 34 | volumes: 35 | postgres: 36 | bundle: 37 | 38 | networks: 39 | jobs: 40 | driver: bridge -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore uploaded files in development 21 | /storage/* 22 | 23 | /node_modules 24 | /yarn-error.log 25 | 26 | /public/assets 27 | .byebug_history 28 | 29 | # Ignore master key for decrypting credentials and more. 30 | /config/master.key 31 | 32 | # IDE folders 33 | .idea/ -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Forgot Your Password? | #{t('app.name')}" %> 3 | <% end %> 4 |

Forgot your password?

5 |
6 |

Don't forget set a strong password

7 |
8 |
9 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 10 | <%= devise_error_messages! %> 11 |
12 | <%= f.label :email %> 13 |
14 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 15 |
16 |
17 | <%= f.submit "Send me reset password instructions" %> 18 |
19 | <% end %> 20 | <%= render "devise/shared/links" %> 21 |
22 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Resend Unlock Instructions | #{t('app.name')}" %> 3 | <% end %> 4 |

Resend unlock instructions

5 |
6 |

Be sure to enter the correct email

7 |
8 |
9 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 10 | <%= devise_error_messages! %> 11 |
12 | <%= f.label :email %> 13 |
14 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 15 |
16 |
17 | <%= f.submit "Resend unlock instructions" %> 18 |
19 | <% end %> 20 | <%= render "devise/shared/links" %> 21 |
22 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :bigint(8) not null, primary key 6 | # email :string default(""), not null 7 | # encrypted_password :string default(""), not null 8 | # reset_password_token :string 9 | # reset_password_sent_at :datetime 10 | # remember_created_at :datetime 11 | # sign_in_count :integer default(0), not null 12 | # current_sign_in_at :datetime 13 | # last_sign_in_at :datetime 14 | # current_sign_in_ip :string 15 | # last_sign_in_ip :string 16 | # created_at :datetime not null 17 | # updated_at :datetime not null 18 | # 19 | 20 | require 'test_helper' 21 | 22 | class UserTest < ActiveSupport::TestCase 23 | end 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/grids/grids-core.scss: -------------------------------------------------------------------------------- 1 | .pure-g { 2 | letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ 3 | *letter-spacing: normal; /* reset IE < 8 */ 4 | *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */ 5 | text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ 6 | } 7 | 8 | /* Opera as of 12 on Windows needs word-spacing. 9 | The ".opera-only" selector is used to prevent actual prefocus styling 10 | and is not required in markup. 11 | */ 12 | .opera-only :-o-prefocus, 13 | .pure-g { 14 | word-spacing: -0.43em; 15 | } 16 | 17 | .pure-u { 18 | display: inline-block; 19 | zoom: 1; *display: inline; /* IE < 8: fake inline-block */ 20 | letter-spacing: normal; 21 | word-spacing: normal; 22 | vertical-align: top; 23 | text-rendering: auto; 24 | } 25 | -------------------------------------------------------------------------------- /test/mailers/jobs_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class JobsMailerTest < ActionMailer::TestCase 4 | include Rails.application.routes.url_helpers 5 | 6 | def default_url_options 7 | Rails.application.config.action_mailer.default_url_options 8 | end 9 | 10 | test "published" do 11 | client = FactoryBot.create(:client) 12 | job = FactoryBot.create(:job, company: client.company) 13 | mail = JobsMailer.with(job: job).published 14 | 15 | assert_equal "New job published: #{job.role} @ #{job.company.name}", mail.subject 16 | assert_equal [client.user.email], mail.to 17 | assert_equal ["jobs@devcongress.org"], mail.from 18 | assert_equal ["jobs@devcongress.org"], mail.reply_to 19 | 20 | mail_body = mail.body.encoded 21 | assert_match job.role, mail_body 22 | assert_match root_url, mail_body 23 | assert_match job_url(job), mail_body 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Resend Confirmation Instructions | #{t('app.name')}" %> 3 | <% end %> 4 |

Resend confirmation instructions

5 |
6 |

Fill this form to resend confirmation

7 |
8 |
9 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 10 | <%= devise_error_messages! %> 11 |
12 | <%= f.label :email %> 13 |
14 | <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> 15 |
16 |
17 | <%= f.submit "Resend confirmation instructions" %> 18 |
19 | <% end %> 20 | <%= render "devise/shared/links" %> 21 |
22 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Dcjobs 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.2 13 | 14 | # enabling rails assest pipleline on heroku for rails 4+. Can't believe I had missed this all this time along. 15 | config.public_file_server.enabled = false 16 | 17 | # Settings in config/environments/* take precedence over those specified here. 18 | # Application configuration can go into files in config/initializers 19 | # -- all .rb files in that directory are automatically loaded after loading 20 | # the framework and any gems in your application. 21 | config.active_record.schema_format = :sql 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 4 | timeout: 5000 5 | username: <%= ENV["POSTGRES_USER"] || ENV["JOBS_DB_USERNAME"] %> 6 | password: <%= ENV["POSTGRES_PASSWORD"] || ENV["JOBS_DB_PASSWORD"] %> 7 | host: <%= ENV["POSTGRES_HOST"] || 'localhost' %> 8 | port: 5432 9 | 10 | development: 11 | <<: *default 12 | database: <%= ENV["POSTGRES_DB"] || 'jobs_development' %> 13 | 14 | # Warning: The database defined as "test" will be erased and 15 | # re-generated from your development database when you run "rake". 16 | # Do not set this db to the same as development or production. 17 | test: 18 | <<: *default 19 | database: <%= ENV["POSTGRES_DB"] || 'jobs_test' %> 20 | 21 | # We use Heroku's Postgres service in production. 22 | # Easier to configure with the $DATABASE_URL env var 23 | # they set than to build using username, etc. 24 | production: 25 | url: <%= ENV["DATABASE_URL"] %> 26 | -------------------------------------------------------------------------------- /test/factories/users.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :bigint(8) not null, primary key 6 | # email :string default(""), not null 7 | # encrypted_password :string default(""), not null 8 | # reset_password_token :string 9 | # reset_password_sent_at :datetime 10 | # remember_created_at :datetime 11 | # sign_in_count :integer default(0), not null 12 | # current_sign_in_at :datetime 13 | # last_sign_in_at :datetime 14 | # current_sign_in_ip :string 15 | # last_sign_in_ip :string 16 | # created_at :datetime not null 17 | # updated_at :datetime not null 18 | # 19 | 20 | FactoryBot.define do 21 | sequence(:user_email) { |n| "user#{n}@example.org" } 22 | 23 | factory :user do 24 | email { generate(:user_email) } 25 | password { Faker::Internet.password } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Log In | #{t('app.name')}" %> 3 | <% end %> 4 |

Log in

5 |

Welcome back.

6 |
7 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 8 |
9 | <%= f.label :email %> 10 |
11 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 12 |
13 |
14 | <%= f.label :password %> 15 |
16 | <%= f.password_field :password, autocomplete: "off" %> 17 |
18 | <% if devise_mapping.rememberable? %> 19 |
20 | <%= f.check_box :remember_me %> 21 | <%= f.label :remember_me %> 22 |
23 | <% end %> 24 |
25 | <%= f.submit "Log in" %> 26 |
27 | <% end %> 28 | <%= render "devise/shared/links" %> 29 |
30 | -------------------------------------------------------------------------------- /app/views/jobs/search.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Search Results | #{t('app.name')}" %> 3 | <% end %> 4 |

Search Results

5 | <% if @matches.empty? %> 6 |

No results for the search term you entered.

7 |

Return to <%= link_to('all Jobs', jobs_path)%> or <%= link_to('create a new job', new_job_path)%>

8 | <% else %> 9 |

<%= pluralize(@matches.length, 'match') %> were found:

10 | 26 | <% end %> 27 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | * require_tree . 14 | *= require_self 15 | */ 16 | 17 | // $dc-blue: #0097C6; 18 | $dc-blue: #05AFEB; 19 | // $dc-fuschia: #ea048b; 20 | $dc-fuschia: #EC058C; 21 | $dc-yellow: #FFF100; 22 | $dc-charcoal: #58595B; 23 | @import "purecss/all"; 24 | @import "header"; 25 | @import "navbar"; 26 | @import "scaffolds"; 27 | @import "pages"; 28 | @import "jobs"; -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's 5 | // vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require rails-ujs 14 | //= require activestorage 15 | //= require turbolinks 16 | //= require_tree . 17 | 18 | // var archiveSwitch = new Boolean(false); 19 | 20 | // $(".job--list__archive-link").on("click", function() { 21 | // archiveSwitch = !archiveSwitch; 22 | // var temp_ = tempSwitch == true ? "Unarchive" : "Archive"; 23 | // $(".job--list__archive-link").html(temp_); 24 | // }); -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:setup' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 DevCongress 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | 19 | # If you are using UJS then enable automatic nonce generation 20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 21 | 22 | # Report CSP violations to a specified URI 23 | # For further information see the following documentation: 24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # Rails.application.config.content_security_policy_report_only = true 26 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | app: 35 | author: "DevCongress" 36 | author_website: "http://devcongress.org" 37 | name: "DevCongress Jobs" 38 | description: "DevCongress Jobs makes it easier for companies to find technical talent across Africa." 39 | -------------------------------------------------------------------------------- /app/controllers/companies_controller.rb: -------------------------------------------------------------------------------- 1 | class CompaniesController < ApplicationController 2 | before_action :set_company, only: [:show] 3 | before_action :authenticate_user!, only: [:new, :create, :edit] 4 | 5 | def new 6 | @company = current_user.companies.build 7 | end 8 | 9 | def create 10 | @company = Company.new(company_params) 11 | if @company.save 12 | current_user.companies << @company 13 | redirect_to @company 14 | else 15 | puts @company.errors.full_messages.inspect 16 | render :new 17 | end 18 | end 19 | 20 | def edit 21 | end 22 | 23 | def show 24 | end 25 | 26 | private 27 | 28 | def set_company 29 | @company = Company.find_by(id: params[:id]) 30 | raise_not_found unless @company 31 | end 32 | 33 | def raise_not_found 34 | raise ActionController::RoutingError.new("not found") 35 | end 36 | 37 | def company_params 38 | params.require(:company).permit( 39 | :name, 40 | :industry, 41 | :website, 42 | :description, 43 | :email, 44 | :phone, 45 | :city, 46 | :state_or_region, 47 | :post_code, 48 | :country 49 | ) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /test/system/companies_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class CompaniesTest < ApplicationSystemTestCase 4 | setup do 5 | @user = FactoryBot.create(:user) 6 | sign_in @user 7 | end 8 | 9 | test "creating a company" do 10 | industry = FactoryBot.create(:industry) 11 | company = FactoryBot.build(:company, industry: industry.name) 12 | 13 | visit new_company_url 14 | 15 | fill_in "Name", with: company.name 16 | select company.industry, from: "Industry" 17 | fill_in "About", with: company.description 18 | fill_in "Website", with: company.website 19 | fill_in "Contact Email", with: company.email 20 | fill_in "Contact Phone", with: company.phone 21 | fill_in "City", with: company.city 22 | fill_in "State/Region", with: company.state_or_region 23 | fill_in "Post/Zip Code", with: company.post_code 24 | fill_in "Country", with: company.country 25 | 26 | click_on "Register" 27 | 28 | assert_text company.name 29 | assert_text company.industry 30 | assert_text company.website 31 | assert_text company.description 32 | assert_text company.city 33 | assert_text company.country 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '2.3.3' 5 | 6 | gem 'rails', '~> 5.2.2' 7 | gem 'puma', '~> 3.12' 8 | gem 'sass-rails', '~> 5.0' 9 | gem 'uglifier', '>= 1.3.0' 10 | 11 | gem 'coffee-rails', '~> 4.2' 12 | gem 'turbolinks', '~> 5' 13 | gem 'jbuilder', '~> 2.5' 14 | 15 | gem 'bootsnap', '~> 1.3', require: false 16 | 17 | gem 'devise', '~> 4.4', '>= 4.4.3' 18 | gem 'sendgrid', '~> 1.2', '>= 1.2.4' 19 | gem 'twitter', '~> 6.2' 20 | gem 'oauth' 21 | 22 | gem 'pg' 23 | 24 | group :development, :test do 25 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 26 | gem 'factory_bot_rails' 27 | end 28 | 29 | group :development do 30 | gem 'web-console', '>= 3.3.0' 31 | gem 'listen', '>= 3.0.5', '< 3.2' 32 | gem 'spring' 33 | gem 'spring-watcher-listen', '~> 2.0.0' 34 | gem 'annotate' 35 | end 36 | 37 | group :test do 38 | gem 'capybara', '>= 2.15', '< 4.0' 39 | gem 'selenium-webdriver' 40 | gem 'chromedriver-helper' 41 | gem 'faker' 42 | gem 'shoulda-matchers' 43 | gem 'minitest-matchers_vaccine' 44 | end 45 | 46 | group :production do 47 | gem 'rails_12factor' 48 | end 49 | 50 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 51 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :bigint(8) not null, primary key 6 | # email :string default(""), not null 7 | # encrypted_password :string default(""), not null 8 | # reset_password_token :string 9 | # reset_password_sent_at :datetime 10 | # remember_created_at :datetime 11 | # sign_in_count :integer default(0), not null 12 | # current_sign_in_at :datetime 13 | # last_sign_in_at :datetime 14 | # current_sign_in_ip :string 15 | # last_sign_in_ip :string 16 | # created_at :datetime not null 17 | # updated_at :datetime not null 18 | # 19 | 20 | class User < ApplicationRecord 21 | # Include default devise modules. Others available are: 22 | # :confirmable, :lockable, :timeoutable and :omniauthable 23 | devise :database_authenticatable, :registerable, 24 | :recoverable, :rememberable, :trackable, :validatable 25 | 26 | has_many :jobs 27 | has_many :clients 28 | has_many :companies, through: :clients 29 | 30 | def is_owner?(job) 31 | if job.user_id == self.id 32 | return true 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Change Your Password | #{t('app.name')}" %> 3 | <% end %> 4 |

Change your password

5 |
6 |

Stay secure, keep password strong!

7 |
8 |
9 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 10 | <%= devise_error_messages! %> 11 | <%= f.hidden_field :reset_password_token %> 12 |
13 | <%= f.label :password, "New password" %> 14 |
15 | <% if @minimum_password_length %> 16 | 17 | (#{@minimum_password_length} characters minimum) 18 | 19 |
20 | <% end %> 21 | <%= f.password_field :password, autofocus: true, autocomplete: "off" %> 22 |
23 |
24 | <%= f.label :password_confirmation, "Confirm new password" %> 25 |
26 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 27 |
28 |
29 | <%= f.submit "Change my password" %> 30 |
31 | <% end %> 32 | <%= render "devise/shared/links" %> 33 |
34 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <% if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %> 3 |
4 | <% end %> 5 | <% if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %> 7 |
8 | <% end %> 9 | <% if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %> 11 |
12 | <% end %> 13 | <% if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %> 15 |
16 | <% end %> 17 | <% if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %> 19 |
20 | <% end %> 21 | <% if devise_mapping.omniauthable? %> 22 | <% resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %> 24 |
25 | <% end %> 26 | <% end %> 27 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Sign Up | #{t('app.name')}" %> 3 | <% end %> 4 |

Sign up

5 |
6 |

Get ready to hire the best talent ever!

7 |
8 |
9 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 10 | <%= devise_error_messages! %> 11 |
12 | <%= f.label :email %> 13 |
14 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 15 |
16 |
17 | <%= f.label :password %> 18 | <% if @minimum_password_length %> 19 | 20 | (#{@minimum_password_length} characters minimum) 21 | 22 | <% end %> 23 |
24 | <%= f.password_field :password, autocomplete: "off" %> 25 |
26 |
27 | <%= f.label :password_confirmation %> 28 |
29 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 30 |
31 |
32 | <%= f.submit "Sign up" %> 33 |
34 | <% end %> 35 | <%= render "devise/shared/links" %> 36 |
37 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/grids/grids-r.scss: -------------------------------------------------------------------------------- 1 | .pure-g-r { 2 | letter-spacing: -0.31em; 3 | *letter-spacing: normal; 4 | *word-spacing: -0.43em; 5 | } 6 | 7 | /* Opera as of 12 on Windows needs word-spacing. 8 | The ".opera-only" selector is used to prevent actual prefocus styling 9 | and is not required in markup. 10 | */ 11 | .opera-only :-o-prefocus, 12 | .pure-g-r { 13 | word-spacing: -0.43em; 14 | } 15 | 16 | .pure-g-r img { 17 | max-width: 100%; 18 | } 19 | 20 | @media (min-width:980px) { 21 | .pure-visible-phone { 22 | display: none; 23 | } 24 | .pure-visible-tablet { 25 | display: none; 26 | } 27 | .pure-hidden-desktop { 28 | display: none; 29 | } 30 | } 31 | @media (max-width:480px) { 32 | .pure-g-r > [class ^= "pure-u"] { 33 | width: 100%; 34 | } 35 | } 36 | @media (max-width:767px) { 37 | .pure-g-r > [class ^= "pure-u"] { 38 | width: 100%; 39 | } 40 | .pure-hidden-phone { 41 | display: none; 42 | } 43 | .pure-visible-desktop { 44 | display: none; 45 | } 46 | } 47 | @media (min-width:768px) and (max-width:979px) { 48 | .pure-hidden-tablet { 49 | display: none; 50 | } 51 | .pure-visible-desktop { 52 | display: none; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/models/company.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: companies 4 | # 5 | # id :bigint(8) not null, primary key 6 | # name :text not null 7 | # industry :text not null 8 | # logo :text default(""), not null 9 | # website :text default(""), not null 10 | # description :text not null 11 | # email :text not null 12 | # phone :text not null 13 | # city :text not null 14 | # state_or_region :text not null 15 | # post_code :text not null 16 | # country :text not null 17 | # created_at :datetime not null 18 | # updated_at :datetime not null 19 | # 20 | 21 | class Company < ApplicationRecord 22 | # Associations 23 | has_many :jobs 24 | has_many :clients 25 | has_many :users, through: :clients 26 | 27 | # Validations 28 | validates :name, presence: true 29 | validates :industry, presence: true 30 | validates :website, presence: true 31 | validates :description, presence: true 32 | validates :email, presence: true, uniqueness: true 33 | validates :phone, presence: true 34 | validates :city, presence: true 35 | validates :state_or_region, presence: true 36 | validates :post_code, presence: true 37 | validates :country, presence: true 38 | end 39 | -------------------------------------------------------------------------------- /test/factories/companies.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: companies 4 | # 5 | # id :bigint(8) not null, primary key 6 | # name :text not null 7 | # industry :text not null 8 | # logo :text default(""), not null 9 | # website :text default(""), not null 10 | # description :text not null 11 | # email :text not null 12 | # phone :text not null 13 | # city :text not null 14 | # state_or_region :text not null 15 | # post_code :text not null 16 | # country :text not null 17 | # created_at :datetime not null 18 | # updated_at :datetime not null 19 | # 20 | 21 | FactoryBot.define do 22 | sequence(:company_email) { |n| "company_#{n}@example.com" } 23 | factory :company do 24 | name { Faker::Company.name } 25 | industry { Faker::Company.industry } 26 | logo { Faker::Company.logo } 27 | website { Faker::Internet.url } 28 | description { Faker::Lorem.sentence } 29 | email { generate(:company_email) } 30 | phone { Faker::PhoneNumber.phone_number } 31 | city { Faker::Address.city } 32 | state_or_region { Faker::Address.state } 33 | post_code { Faker::Address.postcode } 34 | country { Faker::Address.country } 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. 30 | # 31 | # preload_app! 32 | 33 | # Allow puma to be restarted by `rails restart` command. 34 | plugin :tmp_restart 35 | -------------------------------------------------------------------------------- /test/factories/jobs.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: jobs 4 | # 5 | # id :bigint(8) not null, primary key 6 | # role :string not null 7 | # duration :string 8 | # salary :string not null 9 | # requirements :string not null 10 | # qualification :string not null 11 | # perks :string 12 | # created_at :datetime not null 13 | # updated_at :datetime not null 14 | # archived :boolean default(FALSE) 15 | # remote_ok :boolean default(TRUE), not null 16 | # company_id :bigint(8) not null 17 | # city :string default(""), not null 18 | # country :string default(""), not null 19 | # apply_link :text default(""), not null 20 | # filled_at :datetime 21 | # 22 | 23 | FactoryBot.define do 24 | sequence (:contact_email) { |n| "company#{n}@example.org" } 25 | 26 | factory :job do 27 | company 28 | 29 | qualification { Faker::Job.key_skill } 30 | requirements { Faker::Lorem.paragraph(2) } 31 | role { Faker::Job.title } 32 | salary { "USD #{rand(1..2)} - #{rand(3..5)}" } 33 | duration { "#{rand(3)} - #{rand(5..10)} months" } 34 | archived { false } 35 | remote_ok { true } 36 | city { Faker::Address.city } 37 | country { Faker::Address.country } 38 | apply_link { Faker::Internet.url } 39 | created_at { 1.day.ago } 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/assets/stylesheets/jobs.scss: -------------------------------------------------------------------------------- 1 | .job--list { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .job--list__item { 7 | display: flex; 8 | list-style: none; 9 | padding: 10px; 10 | justify-content: space-between; 11 | &:not(:last-child) { 12 | border-bottom: 1px solid rgba(151, 151, 151, .2); 13 | } 14 | } 15 | 16 | .job--list__item-archived { 17 | background: #fcffc9; 18 | } 19 | 20 | .job-list__action-group { 21 | display: flex; 22 | } 23 | 24 | .job--list__item-link { 25 | display: flex; 26 | &:hover { 27 | color: #EC058C; 28 | } 29 | } 30 | 31 | .job--list__action { 32 | font-size: 0.9em; 33 | padding: 1px 5px 2px 5px; 34 | color: #fff; 35 | border-radius: 3px; 36 | &:visited { 37 | color: #fff; 38 | text-decoration: none; 39 | } 40 | } 41 | 42 | .job--list__action-edit { 43 | background: $dc-blue; 44 | &:hover { 45 | background: #056ceb; 46 | } 47 | } 48 | 49 | .job--list__action-archive { 50 | background: #eb8f05; 51 | &:hover { 52 | background: #eb4f05; 53 | } 54 | } 55 | 56 | .job--list__action-unarchive { 57 | background: #32b32a; 58 | &:hover { 59 | background: #287323; 60 | } 61 | } 62 | 63 | .job--field__title { 64 | font-weight: 500; 65 | color: #0097C6; 66 | margin: 1em 0 0; 67 | &:first-of-type { 68 | margin: 0; 69 | } 70 | } 71 | 72 | .job--field__content { 73 | margin: 0 0 1em; 74 | &:first-of-type { 75 | margin: 0; 76 | } 77 | } -------------------------------------------------------------------------------- /test/models/company_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: companies 4 | # 5 | # id :bigint(8) not null, primary key 6 | # name :text not null 7 | # industry :text not null 8 | # logo :text default(""), not null 9 | # website :text default(""), not null 10 | # description :text not null 11 | # email :text not null 12 | # phone :text not null 13 | # city :text not null 14 | # state_or_region :text not null 15 | # post_code :text not null 16 | # country :text not null 17 | # created_at :datetime not null 18 | # updated_at :datetime not null 19 | # 20 | 21 | require 'test_helper' 22 | 23 | class CompanyTest < ActiveSupport::TestCase 24 | setup do 25 | @subject = FactoryBot.create(:company) 26 | end 27 | 28 | test "associations" do 29 | must have_many :jobs 30 | must have_db_index :email 31 | end 32 | 33 | test "validations" do 34 | must validate_presence_of :name 35 | must validate_presence_of :industry 36 | must validate_presence_of :description 37 | must validate_presence_of :email 38 | must validate_presence_of :phone 39 | must validate_presence_of :city 40 | must validate_presence_of :state_or_region 41 | must validate_presence_of :post_code 42 | must validate_presence_of :country 43 | 44 | must validate_uniqueness_of :email 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /db/migrate/20180525195859_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateUsers < ActiveRecord::Migration[5.2] 4 | def change 5 | create_table :users do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: "" 8 | t.string :encrypted_password, null: false, default: "" 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## Trackable 18 | t.integer :sign_in_count, default: 0, null: false 19 | t.datetime :current_sign_in_at 20 | t.datetime :last_sign_in_at 21 | t.string :current_sign_in_ip 22 | t.string :last_sign_in_ip 23 | 24 | ## Confirmable 25 | # t.string :confirmation_token 26 | # t.datetime :confirmed_at 27 | # t.datetime :confirmation_sent_at 28 | # t.string :unconfirmed_email # Only if using reconfirmable 29 | 30 | ## Lockable 31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 32 | # t.string :unlock_token # Only if unlock strategy is :email or :both 33 | # t.datetime :locked_at 34 | 35 | 36 | t.timestamps null: false 37 | end 38 | 39 | add_index :users, :email, unique: true 40 | add_index :users, :reset_password_token, unique: true 41 | # add_index :users, :confirmation_token, unique: true 42 | # add_index :users, :unlock_token, unique: true 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/controllers/companies_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CompaniesControllerTest < ActionDispatch::IntegrationTest 4 | include Devise::Test::IntegrationHelpers 5 | include ERB::Util 6 | 7 | setup do 8 | @company = FactoryBot.create(:company) 9 | @user = FactoryBot.create(:user) 10 | @user.companies << @company 11 | end 12 | 13 | test "guest users should not be able to register a company" do 14 | company_params = attributes_for(:company) 15 | 16 | post companies_url, params: {company: company_params} 17 | assert_redirected_to new_user_session_url 18 | 19 | company = Company.find_by(name: company_params[:name]) 20 | assert company.nil? 21 | end 22 | 23 | test "should create a new company" do 24 | company_params = attributes_for(:company) 25 | 26 | sign_in @user 27 | post companies_url, params: {company: company_params} 28 | 29 | company = Company.find_by(name: company_params[:name]) 30 | 31 | assert_redirected_to company 32 | assert company.website == company_params[:website] 33 | assert company.description == company_params[:description] 34 | 35 | assert @user.companies.find_by(id: company.id) 36 | end 37 | 38 | test "should show existing company" do 39 | get company_url(@company) 40 | 41 | assert_response :ok 42 | assert_match html_escape(@company.name), @response.body 43 | assert_match html_escape(@company.industry), @response.body 44 | assert_match @company.website, @response.body 45 | assert_match @company.description, @response.body 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | root to: "pages#index" 4 | 5 | get '/help' => 'pages#help' 6 | get '/about' => 'pages#about' 7 | get '/privacy' => 'pages#privacy' 8 | 9 | resources :companies 10 | resources :jobs do 11 | collection do 12 | get 'search' 13 | end 14 | 15 | member do 16 | post "filled" 17 | post "vacant" 18 | end 19 | end 20 | 21 | devise_for :users, skip: [:sessions, :registrations, :passwords] 22 | 23 | devise_scope :user do 24 | # sessions 25 | get 'login', to: 'devise/sessions#new', as: :new_user_session 26 | post 'login', to: 'devise/sessions#create', as: :user_session 27 | delete 'logout', to: 'devise/sessions#destroy', as: :destroy_user_session 28 | # registrations 29 | put '/account', to: 'devise/registrations#update' 30 | delete '/account', to: 'devise/registrations#destroy' 31 | post '/account', to: 'devise/registrations#create' 32 | get '/register', to: 'devise/registrations#new', as: :new_user_registration 33 | get '/account', to: 'devise/registrations#edit', as: :edit_user_registration 34 | patch '/account', to: 'devise/registrations#update', as: :user_registration 35 | get '/account/cancel', to: 'devise/registrations#cancel', as: :cancel_user_registration# passwords 36 | # passwords 37 | get 'new-pass', to: 'devise/passwords#new', as: :new_user_password 38 | get 'edit-pass', to: 'devise/passwords#edit', as: :edit_user_password 39 | patch 'edit-pass', to: 'devise/passwords#update', as: :update_user_password 40 | post 'new-pass', to: 'devise/passwords#create', as: :user_password 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/defaults.scss: -------------------------------------------------------------------------------- 1 | $background: #b0bcdd !default; 2 | $border: #e6eaf4 !default; 3 | $border-low: #a6b3d9 !default; 4 | $border-radius: 4px !default; 5 | $button-radius: 2px !default; 6 | $caption-text: #adadad !default; 7 | $cell-background: #f1f3f9 !default; 8 | $cell-odd-background: #e6eaf4 !default; 9 | $cell-odd-text: #273259 !default; 10 | $cell-padding: 0.3em 0.6em !default; 11 | $cell-text: #344579 !default; 12 | $disabled-text: #adadad !default; 13 | $fixed-menu-border-bottom: #e6eaf4 !default; 14 | $head-background: #d1d7eb !default; 15 | $head-text: #1c2440 !default; 16 | $heading-text: #171f36 !default; 17 | $inline-help-text: #adadad !default; 18 | $input-border: #e6e6e6 !default; 19 | $input-group-first-radius: 4px 4px 0px 0px !default; 20 | $input-group-last-radius: 0px 0px 4px 4px !default; 21 | $input-padding: 0.5em 0.6em !default; 22 | $input-radius: 4px !default; 23 | $input-radius-rounded: 30px !default; 24 | $input-shadow-inset: #ededed !default; 25 | $label: #4f4f4f !default; 26 | $legend-rule: #ededed !default; 27 | $legend-text: #262626 !default; 28 | $menu-background: #f1f3f9 !default; 29 | $menu-border: #e6eaf4 !default; 30 | $menu-hover-background: #d8ddee !default; 31 | $menu-hover-text: #273259 !default; 32 | $menu-item-padding: 0.35em 1.4em !default; 33 | $menu-selected-background: #3355BA !default; 34 | $menu-selected-text: #fbfcfe !default; 35 | $menu-text: #344579 !default; 36 | $menu-text-disabled: #97a6d3 !default; 37 | $padding: 0.5em 1.5em 0.5em !default; 38 | $paginator-radius-next: 0px 4px 4px 0px !default; 39 | $paginator-radius-prev: 4px 0px 0px 4px !default; 40 | $selected-background: #3355BA !default; 41 | $selected-text: #fbfcfe !default; 42 | $separator: #e6e6e6 !default; 43 | $text: #0e1320 !default; 44 | 45 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scaffolds.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgba(5, 175, 235, .10); 3 | color: #333; 4 | font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; 5 | font-size: 16px; 6 | line-height: 1.5; 7 | width: 100%; 8 | } 9 | 10 | a { 11 | color: $dc-blue; 12 | text-decoration: none; 13 | -webkit-transition: all .25s ease; 14 | -moz-transition: all .25s ease; 15 | -ms-transition: all .25s ease; 16 | -o-transition: all .25s ease; 17 | transition: all .25s ease; 18 | &:visited { 19 | color: #3705ec; 20 | } 21 | } 22 | 23 | h1, 24 | h2, 25 | h3, 26 | h4, 27 | h5, 28 | h6 { 29 | font-family: Cairo, serif; 30 | } 31 | 32 | h1 { 33 | font-weight: 400; 34 | font-size: 2em; 35 | color: #05AFEB; 36 | letter-spacing: 0.04px; 37 | margin: 0.4em 0 0; 38 | } 39 | 40 | div { 41 | &.field, 42 | &.actions { 43 | margin-bottom: 10px; 44 | } 45 | } 46 | 47 | .error_explanation { 48 | width: 450px; 49 | border: 2px solid red; 50 | padding: 7px 7px 0; 51 | margin-bottom: 20px; 52 | background-color: #f0f0f0; 53 | h2 { 54 | text-align: left; 55 | font-weight: bold; 56 | padding: 5px 5px 5px 15px; 57 | font-size: 12px; 58 | margin: -7px -7px 0; 59 | background-color: #c00; 60 | color: #fff; 61 | } 62 | ul li { 63 | font-size: 12px; 64 | list-style: square; 65 | } 66 | } 67 | 68 | .main { 69 | width: 100%; 70 | max-width: 640px; 71 | margin: 0 auto; 72 | } 73 | 74 | footer { 75 | margin-bottom: 100px; 76 | } 77 | 78 | @media (max-width: 640px) { 79 | .navbar, 80 | .main--content { 81 | margin: 0 2.5%; 82 | } 83 | .navbar { 84 | max-width: 95%; 85 | } 86 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/menus/menus.scss: -------------------------------------------------------------------------------- 1 | /* MAIN MENU STYLING */ 2 | 3 | .pure-menu.pure-menu-open, 4 | .pure-menu.pure-menu-horizontal li .pure-menu-children { 5 | background: #ffffff; /* Old browsers */ 6 | border: 1px solid #b7b7b7; 7 | } 8 | 9 | /* remove borders for horizontal menus */ 10 | .pure-menu.pure-menu-horizontal, .pure-menu.pure-menu-horizontal .pure-menu-heading { 11 | border: none; 12 | } 13 | 14 | 15 | 16 | /* LINK STYLES */ 17 | 18 | .pure-menu a { 19 | border: 1px solid transparent; 20 | border-left: none; 21 | border-right: none; 22 | 23 | } 24 | 25 | .pure-menu a, 26 | .pure-menu .pure-menu-can-have-children > li:after { 27 | color: #777; 28 | } 29 | 30 | .pure-menu .pure-menu-can-have-children > li:hover:after { 31 | color: #fff; 32 | } 33 | 34 | 35 | 36 | /* HOVER STATES */ 37 | .pure-menu li a:hover { 38 | background: #eee; 39 | } 40 | 41 | /* DISABLED STATES */ 42 | .pure-menu li.pure-menu-disabled a:hover { 43 | background: #fff; 44 | color: #bfbfbf; 45 | } 46 | 47 | .pure-menu .pure-menu-disabled > a { 48 | background-image: none; 49 | border-color: transparent; 50 | cursor: default; 51 | } 52 | 53 | .pure-menu .pure-menu-disabled > a, 54 | .pure-menu .pure-menu-can-have-children.pure-menu-disabled > a:after { 55 | color: #bfbfbf; 56 | } 57 | 58 | /* HEADINGS */ 59 | .pure-menu .pure-menu-heading { 60 | color: #565d64; 61 | text-transform: uppercase; 62 | font-size:90%; 63 | margin-top:0.5em; 64 | border-bottom-width: 1px; 65 | border-bottom-style: solid; 66 | border-bottom-color: #dfdfdf; 67 | } 68 | 69 | /* ACTIVE MENU ITEM */ 70 | .pure-menu .pure-menu-selected a { 71 | color: #000; 72 | } 73 | 74 | /* FIXED MENU */ 75 | .pure-menu.pure-menu-open.pure-menu-fixed { 76 | border: none; 77 | border-bottom: 1px solid #b7b7b7; 78 | } 79 | -------------------------------------------------------------------------------- /app/views/jobs/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "#{@job.role} at #{@job.company.name} | #{t('app.name')}" %> 3 | <% end %> 4 |

5 | <%= @job.title %> 6 |

7 |
8 | <%= link_to('Tweet', tweetButtonText, :target => "_blank", :class => "share--button share--buttons__tweet", "data-hashtags" => "devcongressjobs", "data-related" => "DevCongress") %> 9 | <%= link_to('Share', fbButtonText, :target => "_blank", :class => "share--button share--buttons__fb", "data-hashtags" => "devcongressjobs", "data-related" => "DevCongress") %> 10 |
11 |
12 |

Role

13 |

14 | <%= @job.role %> 15 |

16 |

Duration

17 |

18 | <%= @job.duration %> 19 |

20 |

Salary

21 |

22 | <%= @job.salary %> 23 |

24 |

Remote OK

25 |

26 | <%= @job.remote_ok ? "Yes" : "No" %> 27 |

28 |

Requirements

29 | <%= simple_format h(@job.requirements), :class => "job--field__content" %> 30 |

Qualification

31 | <%= simple_format h(@job.qualification), :class => "job--field__content" %> 32 |

Perks

33 | <%= simple_format h(@job.perks), :class => "job--field__content" %> 34 |

Company

35 |

36 | <%= @job.company.name %> 37 |

38 |

Contact email

39 |

40 | <%= mail_to @job.company.email, @job.company.email %> 41 |

42 | <%= link_to 'View All Jobs', jobs_path %> 43 |
44 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/system/jobs_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class JobsTest < ApplicationSystemTestCase 4 | setup do 5 | @job = jobs(:one) 6 | end 7 | 8 | test "visiting the index" do 9 | visit jobs_url 10 | assert_selector "h1", text: "Jobs" 11 | end 12 | 13 | test "creating a Job" do 14 | visit jobs_url 15 | click_on "New Job" 16 | 17 | fill_in "Company", with: @job.company 18 | fill_in "Contact Email", with: @job.contact_email 19 | fill_in "Duration", with: @job.duration 20 | fill_in "Perks", with: @job.perks 21 | fill_in "Phone", with: @job.phone 22 | fill_in "Poster Email", with: @job.poster_email 23 | fill_in "Poster Name", with: @job.poster_name 24 | fill_in "Qualification", with: @job.qualification 25 | fill_in "Requirements", with: @job.requirements 26 | fill_in "Role", with: @job.role 27 | fill_in "Salary", with: @job.salary 28 | click_on "Create Job" 29 | 30 | assert_text "Job was successfully created" 31 | click_on "Back" 32 | end 33 | 34 | test "updating a Job" do 35 | visit jobs_url 36 | click_on "Edit", match: :first 37 | 38 | fill_in "Company", with: @job.company 39 | fill_in "Contact Email", with: @job.contact_email 40 | fill_in "Duration", with: @job.duration 41 | fill_in "Perks", with: @job.perks 42 | fill_in "Phone", with: @job.phone 43 | fill_in "Poster Email", with: @job.poster_email 44 | fill_in "Poster Name", with: @job.poster_name 45 | fill_in "Qualification", with: @job.qualification 46 | fill_in "Requirements", with: @job.requirements 47 | fill_in "Role", with: @job.role 48 | fill_in "Salary", with: @job.salary 49 | click_on "Update Job" 50 | 51 | assert_text "Job was successfully updated" 52 | click_on "Back" 53 | end 54 | 55 | test "destroying a Job" do 56 | visit jobs_url 57 | page.accept_confirm do 58 | click_on "Destroy", match: :first 59 | end 60 | 61 | assert_text "Job was successfully destroyed" 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Edit My Account | #{t('app.name')}" %> 3 | <% end %> 4 |

Edit #{resource_name.to_s.humanize}

5 |
6 |

Update your account details

7 |
8 |
9 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 10 | <%= devise_error_messages! %> 11 |
12 | <%= f.label :email %> 13 |
14 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 15 |
16 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 17 |
18 | Currently waiting confirmation for: #{resource.unconfirmed_email} 19 |
20 | <% end %> 21 |
22 | <%= f.label :password %> 23 | (leave blank if you don't want to change it) 24 |
25 | <%= f.password_field :password, autocomplete: "off" %> 26 | <% if @minimum_password_length %> 27 |
28 | 29 | <%= @minimum_password_length %> 30 | characters minimum 31 | 32 | <% end %> 33 |
34 |
35 | <%= f.label :password_confirmation %> 36 |
37 | <%= f.password_field :password_confirmation, autocomplete: "off" %> 38 |
39 |
40 | <%= f.label :current_password %> 41 | (we need your current password to confirm your changes) 42 |
43 | <%= f.password_field :current_password, autocomplete: "off" %> 44 |
45 |
46 | <%= f.submit "Update" %> 47 |
48 | <% end %> 49 |

Cancel my account

50 |

51 | Unhappy? #{button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete} 52 |

53 | <%= link_to "Back", :back %> 54 |
55 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/tables/tables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * CSS TABLES 3 | * ========== 4 | * Simple CSS for HTML Tables 5 | * Author: tilomitra 6 | */ 7 | 8 | /* foundational CSS */ 9 | .pure-table { 10 | /* Remove spacing between table cells (from Normalize.css) */ 11 | border-collapse: collapse; 12 | border-spacing: 0; 13 | empty-cells: show; 14 | border: 1px solid #cbcbcb; 15 | } 16 | 17 | .pure-table caption { 18 | color: #000; 19 | font: italic 85%/1 arial, sans-serif; 20 | padding: 1em 0; 21 | text-align: center; 22 | } 23 | 24 | .pure-table td, 25 | .pure-table th { 26 | border-left: 1px solid #cbcbcb;/* inner column border */ 27 | border-width: 0 0 0 1px; 28 | font-size: inherit; 29 | margin: 0; 30 | overflow: visible; /*to make ths where the title is really long work*/ 31 | padding: 6px 12px; /* cell padding */ 32 | } 33 | .pure-table td:first-child, 34 | .pure-table th:first-child { 35 | border-left-width: 0; 36 | } 37 | 38 | .pure-table thead { 39 | background: #e0e0e0; 40 | color: #000; 41 | text-align: left; 42 | vertical-align: bottom; 43 | white-space: nowrap; 44 | } 45 | 46 | /* 47 | striping: 48 | even - #fff (white) 49 | odd - #edf5ff (light blue) 50 | */ 51 | .pure-table td { 52 | background-color: transparent; 53 | } 54 | .pure-table-odd td { 55 | background-color: #f2f2f2; 56 | } 57 | 58 | /* nth-child selector for modern browsers */ 59 | .pure-table-striped tr:nth-child(2n-1) td { 60 | background-color: #f2f2f2; 61 | } 62 | 63 | /* BORDERED TABLES */ 64 | .pure-table-bordered td { 65 | border-bottom:1px solid #cbcbcb; 66 | } 67 | .pure-table-bordered tbody > tr:last-child td, 68 | .pure-table-horizontal tbody > tr:last-child td { 69 | border-bottom-width: 0; 70 | } 71 | 72 | 73 | /* HORIZONTAL BORDERED TABLES */ 74 | 75 | .pure-table-horizontal td, 76 | .pure-table-horizontal th { 77 | border-width: 0 0 1px 0; 78 | border-bottom:1px solid #cbcbcb; 79 | } 80 | .pure-table-horizontal tbody > tr:last-child td { 81 | border-bottom-width: 0; 82 | } 83 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/tables/tables-theme.scss: -------------------------------------------------------------------------------- 1 | 2 | /* foundational CSS */ 3 | .pure-table { 4 | /* Remove spacing between table cells (from Normalize.css) */ 5 | border-collapse: collapse; 6 | border-spacing: 0; 7 | empty-cells: show; 8 | border: 1px solid $border; 9 | } 10 | 11 | .pure-table caption { 12 | color: $caption-text; 13 | font: italic 85%/1 arial, sans-serif; 14 | padding: 1em 0; 15 | text-align: center; 16 | } 17 | 18 | .pure-table td, 19 | .pure-table th { 20 | border-left: 1px solid $border;/* inner column border */ 21 | border-width: 0 0 0 1px; 22 | font-size: inherit; 23 | margin: 0; 24 | overflow: visible; /*to make ths where the title is really long work*/ 25 | padding: $cell-padding; /* cell padding */ 26 | } 27 | 28 | .pure-table td:first-child, 29 | .pure-table th:first-child { 30 | border-left-width: 0; 31 | } 32 | 33 | .pure-table thead { 34 | background-color: $head-background; 35 | color: $head-text; 36 | text-align: left; 37 | vertical-align: bottom; 38 | } 39 | 40 | /* 41 | striping: 42 | even - #fff (white) 43 | odd - #f2f2f2 (light gray) 44 | */ 45 | .pure-table td { 46 | background-color: $cell-background; 47 | color: $cell-text; 48 | } 49 | .pure-table-odd td { 50 | background-color: $cell-odd-background; 51 | color: $cell-odd-text; 52 | } 53 | 54 | /* nth-child selector for modern browsers */ 55 | .pure-table-striped tr:nth-child(2n-1) td { 56 | background-color: $cell-odd-background; 57 | color: $cell-odd-text; 58 | } 59 | 60 | 61 | /* BORDERED TABLES */ 62 | .pure-table-bordered td { 63 | border-bottom: 1px solid $border; 64 | } 65 | .pure-table-bordered tbody > tr:last-child td, 66 | .pure-table-horizontal tbody > tr:last-child td { 67 | border-bottom-width: 0; 68 | } 69 | 70 | /* HORIZONTAL BORDERED TABLES */ 71 | .pure-table-horizontal td, 72 | .pure-table-horizontal th { 73 | border-width: 0 0 1px 0; 74 | border-bottom:1px solid $border; 75 | } 76 | .pure-table-horizontal tbody > tr:last-child td { 77 | border-bottom-width: 0; 78 | } 79 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Store uploaded files on the local file system in a temporary directory 32 | config.active_storage.service = :test 33 | 34 | config.action_mailer.perform_caching = false 35 | 36 | # Tell Action Mailer not to deliver emails to the real world. 37 | # The :test delivery method accumulates sent emails in the 38 | # ActionMailer::Base.deliveries array. 39 | config.action_mailer.delivery_method = :test 40 | 41 | # Print deprecation notices to the stderr. 42 | config.active_support.deprecation = :stderr 43 | 44 | # Raises error for missing translations 45 | # config.action_view.raise_on_missing_translations = true 46 | 47 | 48 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 49 | end 50 | -------------------------------------------------------------------------------- /app/assets/stylesheets/header.scss: -------------------------------------------------------------------------------- 1 | .notice { 2 | color: transparent; 3 | margin: 0.5em 0 0; 4 | display: inherit; 5 | min-height: 24px; 6 | text-align: center; 7 | animation: notice-disappear 8s; 8 | } 9 | 10 | .page--title { 11 | color: #EC058C; 12 | line-height: 1.2; 13 | margin: 0; 14 | } 15 | 16 | .page--title, 17 | .page--legend, 18 | footer, 19 | .text--center { 20 | text-align: center; 21 | } 22 | 23 | .page--legend-buttons { 24 | margin: 0 0 40px; 25 | } 26 | 27 | .page--legend { 28 | text-align: center; 29 | line-height: 1.275; 30 | min-height: 22px; 31 | margin: 0; 32 | margin-bottom: 40px; 33 | } 34 | 35 | .share--buttons { 36 | text-align: center; 37 | margin-top: 5px; 38 | margin-bottom: 40px; 39 | } 40 | 41 | .share--button { 42 | color: #fff; 43 | border-radius: 3px; 44 | padding: 4px 6px 5px 6px; 45 | -webkit-transition: all .25s ease; 46 | -moz-transition: all .25s ease; 47 | -ms-transition: all .25s ease; 48 | -o-transition: all .25s ease; 49 | transition: all .25s ease; 50 | &:hover { 51 | background-color: #333; 52 | } 53 | &:visited { 54 | color: white; 55 | } 56 | } 57 | 58 | .share--buttons__tweet { 59 | background-color: #1DA1F2; 60 | } 61 | 62 | .share--buttons__fb { 63 | background-color: #3B5998; 64 | } 65 | 66 | .main--content { 67 | background: #fff; 68 | border: 1px solid black; 69 | padding: 5%; 70 | } 71 | 72 | @keyframes notice-disappear { 73 | 0% { 74 | color: green; 75 | } 76 | 100% { 77 | opacity: 0; 78 | color: transparent; 79 | } 80 | } 81 | 82 | @-webkit-keyframes notice-disappear { 83 | 0% { 84 | opacity: 100; 85 | color: green; 86 | } 87 | 100% { 88 | opacity: 0; 89 | color: transparent; 90 | } 91 | } 92 | 93 | @-moz-keyframes notice-disappear { 94 | 0% { 95 | opacity: 100; 96 | color: green; 97 | } 98 | 100% { 99 | opacity: 0; 100 | color: transparent; 101 | } 102 | } -------------------------------------------------------------------------------- /app/views/companies/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= "Register Your Company | #{t('app.name')}" %> 3 | <% end %> 4 |

Register Your Company

5 |

In order to post jobs, you should register your company. You're allowed to register more than one company which means recruiters can register as many clients as they want.

6 |
7 |
8 | <%= form_with(model: @company, :class => "pure-form pure-form-stacked") do |f| %> 9 |
10 | <%= f.label :name %> 11 | <%= f.text_field :name, required: true, placeholder: "Acme Widgets Inc." %> 12 |
13 |
14 | <%= f.label :industry, "Industry" %> 15 | <%= f.collection_select :industry, Industry.all, :name, :name do %> 16 | <% end %> 17 |
18 |
19 | <%= f.label :description, "About" %> 20 | <%= f.text_area :description, required: true %> 21 |
22 |
23 | <%= f.label :website, "Website" %> 24 | <%= f.url_field :website %> 25 |
26 |
27 | <%= f.label :email, "Contact Email" %> 28 | <%= f.email_field :email, required: true %> 29 |
30 |
31 | <%= f.label :phone, "Contact Phone" %> 32 | <%= f.telephone_field :phone, required: true %> 33 |
34 |
35 | <%= f.label :city, "City" %> 36 | <%= f.text_field :city, require: true %> 37 |
38 |
39 | <%= f.label :state_or_region, "State/Region" %> 40 | <%= f.text_field :state_or_region, require: true %> 41 |
42 |
43 | <%= f.label :post_code, "Post/Zip Code" %> 44 | <%= f.text_field :post_code, require: true %> 45 |
46 |
47 | <%= f.label :country, "Country" %> 48 | <%= f.text_field :country, require: true %> 49 |
50 |
51 | <%= f.submit "Register" %> 52 |
53 | <% end %> 54 |
55 |
56 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/grids/grids-units.scss: -------------------------------------------------------------------------------- 1 | .pure-u-1, 2 | .pure-u-1-2, 3 | .pure-u-1-3, 4 | .pure-u-2-3, 5 | .pure-u-1-4, 6 | .pure-u-3-4, 7 | .pure-u-1-5, 8 | .pure-u-2-5, 9 | .pure-u-3-5, 10 | .pure-u-4-5, 11 | .pure-u-1-6, 12 | .pure-u-5-6, 13 | .pure-u-1-8, 14 | .pure-u-3-8, 15 | .pure-u-5-8, 16 | .pure-u-7-8, 17 | .pure-u-1-12, 18 | .pure-u-5-12, 19 | .pure-u-7-12, 20 | .pure-u-11-12, 21 | .pure-u-1-24, 22 | .pure-u-5-24, 23 | .pure-u-7-24, 24 | .pure-u-11-24, 25 | .pure-u-13-24, 26 | .pure-u-17-24, 27 | .pure-u-19-24, 28 | .pure-u-23-24 { 29 | display: inline-block; 30 | zoom: 1; *display: inline; /* IE < 8: fake inline-block */ 31 | letter-spacing: normal; 32 | word-spacing: normal; 33 | vertical-align: top; 34 | text-rendering: auto; 35 | } 36 | 37 | .pure-u-1 { 38 | display: block; 39 | } 40 | 41 | .pure-u-1-2 { 42 | width: 50%; 43 | } 44 | 45 | .pure-u-1-3 { 46 | width: 33.33333%; 47 | } 48 | 49 | .pure-u-2-3 { 50 | width: 66.66666%; 51 | } 52 | 53 | .pure-u-1-4 { 54 | width: 25%; 55 | } 56 | 57 | .pure-u-3-4 { 58 | width: 75%; 59 | } 60 | 61 | .pure-u-1-5 { 62 | width: 20%; 63 | } 64 | 65 | .pure-u-2-5 { 66 | width: 40%; 67 | } 68 | 69 | .pure-u-3-5 { 70 | width: 60%; 71 | } 72 | 73 | .pure-u-4-5 { 74 | width: 80%; 75 | } 76 | 77 | .pure-u-1-6 { 78 | width: 16.656%; 79 | } 80 | 81 | .pure-u-5-6 { 82 | width: 83.33%; 83 | } 84 | 85 | .pure-u-1-8 { 86 | width: 12.5%; 87 | } 88 | 89 | .pure-u-3-8 { 90 | width: 37.5%; 91 | } 92 | 93 | .pure-u-5-8 { 94 | width: 62.5%; 95 | } 96 | 97 | .pure-u-7-8 { 98 | width: 87.5%; 99 | } 100 | 101 | .pure-u-1-12 { 102 | width: 8.3333%; 103 | } 104 | 105 | .pure-u-5-12 { 106 | width: 41.6666%; 107 | } 108 | 109 | .pure-u-7-12 { 110 | width: 58.3333%; 111 | } 112 | 113 | .pure-u-11-12 { 114 | width: 91.6666%; 115 | } 116 | 117 | .pure-u-1-24 { 118 | width: 4.1666%; 119 | } 120 | 121 | .pure-u-5-24 { 122 | width: 20.8333%; 123 | } 124 | 125 | .pure-u-7-24 { 126 | width: 29.1666%; 127 | } 128 | 129 | .pure-u-11-24 { 130 | width: 45.8333%; 131 | } 132 | 133 | .pure-u-13-24 { 134 | width: 54.1666%; 135 | } 136 | 137 | .pure-u-17-24 { 138 | width: 70.8333%; 139 | } 140 | 141 | .pure-u-19-24 { 142 | width: 79.1666%; 143 | } 144 | 145 | .pure-u-23-24 { 146 | width: 95.8333%; 147 | } 148 | -------------------------------------------------------------------------------- /app/models/job.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: jobs 4 | # 5 | # id :bigint(8) not null, primary key 6 | # role :string not null 7 | # duration :string 8 | # salary :string not null 9 | # requirements :string not null 10 | # qualification :string not null 11 | # perks :string 12 | # created_at :datetime not null 13 | # updated_at :datetime not null 14 | # archived :boolean default(FALSE) 15 | # remote_ok :boolean default(TRUE), not null 16 | # company_id :bigint(8) not null 17 | # city :string default(""), not null 18 | # country :string default(""), not null 19 | # apply_link :text default(""), not null 20 | # filled_at :datetime 21 | # 22 | 23 | class Job < ApplicationRecord 24 | belongs_to :company 25 | 26 | validates :company, presence: true 27 | validates :duration, presence: true 28 | validates :salary, presence: true 29 | validates :requirements, presence: true 30 | validates :qualification, presence: true 31 | validates :role, presence: true 32 | 33 | def to_param 34 | "#{id}-#{role}-#{company.name}".parameterize 35 | end 36 | 37 | def title 38 | "#{role} at #{company.name}" 39 | end 40 | 41 | # `Job.active` is a version of `all` that returns 42 | # jobs that haven't been archived and are still within 43 | # the validity period since they were posted. 44 | def self.all_active 45 | Job.find_by_sql <<-SQL 46 | SELECT * 47 | FROM jobs 48 | WHERE NOT archived 49 | AND filled_at IS NULL 50 | AND tsrange( 51 | created_at, 52 | created_at + INTERVAL '#{self.validity_period}' DAY, '[]' 53 | ) @> now()::timestamp 54 | ORDER BY created_at DESC 55 | SQL 56 | end 57 | 58 | def self.validity_period 59 | (ENV['JOB_VALIDITY_PERIOD'] || 30).to_i.abs 60 | end 61 | 62 | def self.search(query) 63 | Job.find_by_sql [<<-SQL, query] 64 | SELECT * 65 | FROM jobs 66 | WHERE NOT archived 67 | AND filled_at IS NULL 68 | AND tsrange( 69 | created_at, 70 | created_at + INTERVAL '#{self.validity_period}' DAY, '[]' 71 | ) @> now()::timestamp 72 | AND plainto_tsquery(?) @@ full_text_search 73 | ORDER BY created_at DESC 74 | SQL 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /db/migrate/20181205160427_add_document_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddDocumentToJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | enable_extension :citext 4 | add_column :jobs, :full_text_search, :tsvector 5 | 6 | reversible do |m| 7 | m.up do 8 | # Update existing job posts. 9 | execute <<-SQL 10 | UPDATE jobs 11 | SET full_text_search = 12 | to_tsvector( 13 | 'english', -- We only support English 14 | coalesce(role, '') 15 | || ' ' || coalesce(qualification, '') 16 | || ' ' || coalesce(requirements, '') 17 | || ' ' || coalesce(perks, '') 18 | ); 19 | SQL 20 | 21 | # Add `NOT NULL` constraint. The following `BEFORE INSERT` 22 | # trigger ensures that the value of `full_text_search` is always 23 | # set. 24 | change_column_null :jobs, :full_text_search, false 25 | 26 | execute <<-SQL 27 | -- 28 | -- Add a `BEFORE INSERT, UPDATE` trigger to update 29 | -- the contents of full_text_search for this job post. 30 | -- Any time any of the input columns change we 31 | -- should build a new body. 32 | -- 33 | CREATE OR REPLACE FUNCTION prepare_full_text_search_document() 34 | RETURNS TRIGGER 35 | AS $prepare_full_text_search_document$ 36 | DECLARE 37 | new_document tsvector; 38 | BEGIN 39 | -- We expect all columns used in preparing the document 40 | -- to have a `NOT NULL` constraint. Regardless, we `coalesce` 41 | -- just to be double sure that we're dealing with strings. 42 | new_document := 43 | to_tsvector( 44 | 'english', -- We only support English 45 | coalesce(NEW.role, '') 46 | || ' ' || coalesce(NEW.qualification, '') 47 | || ' ' || coalesce(NEW.requirements, '') 48 | || ' ' || coalesce(NEW.perks, '') 49 | ); 50 | 51 | NEW.full_text_search := new_document; 52 | RETURN NEW; 53 | END; 54 | $prepare_full_text_search_document$ LANGUAGE plpgsql; 55 | 56 | CREATE TRIGGER trg_prepare_full_text_search_document 57 | BEFORE INSERT OR UPDATE OF role, qualification, requirements, perks ON jobs 58 | FOR EACH ROW 59 | EXECUTE PROCEDURE prepare_full_text_search_document(); 60 | SQL 61 | end 62 | 63 | m.down do 64 | execute <<-SQL 65 | -- 66 | -- Drop both trigger and function, in that order. 67 | -- 68 | DROP TRIGGER IF EXISTS trg_prepare_full_text_search_document ON jobs; 69 | DROP FUNCTION IF EXISTS prepare_full_text_search_document(); 70 | SQL 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 18 | config.action_controller.perform_caching = true 19 | 20 | config.cache_store = :memory_store 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 23 | } 24 | else 25 | config.action_controller.perform_caching = false 26 | 27 | config.cache_store = :null_store 28 | end 29 | 30 | # Store uploaded files on the local file system (see config/storage.yml for options) 31 | config.active_storage.service = :local 32 | 33 | # Don't care if the mailer can't send. 34 | config.action_mailer.raise_delivery_errors = false 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 39 | 40 | # Print deprecation notices to the Rails logger. 41 | config.active_support.deprecation = :log 42 | 43 | # Raise an error on page load if there are pending migrations. 44 | config.active_record.migration_error = :page_load 45 | 46 | # Highlight code that triggered database queries in logs. 47 | config.active_record.verbose_query_logs = true 48 | 49 | # Debug mode disables concatenation and preprocessing of assets. 50 | # This option may cause significant delays in view rendering with a large 51 | # number of complex assets. 52 | config.assets.debug = true 53 | 54 | # Suppress logger output for asset requests. 55 | config.assets.quiet = true 56 | 57 | # Raises error for missing translations 58 | # config.action_view.raise_on_missing_translations = true 59 | 60 | # Use an evented file watcher to asynchronously detect changes in source code, 61 | # routes, locales, etc. This feature depends on the listen gem. 62 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 63 | end 64 | -------------------------------------------------------------------------------- /app/views/layouts/_meta_and_styles.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= yield(:title) %> 3 | 4 | 5 | 6 | <%= favicon_link_tag asset_path('devcongress-logo-32.ico'), :rel => "shortcut icon", :type => "image/x-icon" %> 7 | <%= favicon_link_tag asset_path('devcongress-logo-57.ico'), :rel => "apple-touch-icon", :sizes => "57x57" %> 8 | <%= favicon_link_tag asset_path('devcongress-logo-72.ico'), :rel => "apple-touch-icon", :sizes => "72x72" %> 9 | <%= favicon_link_tag asset_path('devcongress-logo-114.ico'), :rel => "apple-touch-icon", :sizes => "114x114" %> 10 | <%= favicon_link_tag asset_path('devcongress-logo-144.ico'), :rel => "apple-touch-icon", :sizes => "144x144" %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | <%# Open Graph %> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 41 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 42 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 43 | <%= csrf_meta_tags %> 44 | <%= csp_meta_tag %> 45 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/menus/menus-core.scss: -------------------------------------------------------------------------------- 1 | .pure-menu ul { 2 | position: absolute; 3 | visibility: hidden; 4 | } 5 | 6 | .pure-menu.pure-menu-open { 7 | visibility: visible; 8 | z-index: 2; 9 | width: 100%; 10 | } 11 | 12 | .pure-menu ul { 13 | left: -10000px; 14 | list-style: none; 15 | margin: 0; 16 | padding: 0; 17 | top: -10000px; 18 | z-index: 1; 19 | } 20 | 21 | .pure-menu > ul { position: relative; } 22 | 23 | .pure-menu-open > ul { 24 | left: 0; 25 | top: 0; 26 | visibility: visible; 27 | } 28 | 29 | .pure-menu li { position: relative; } 30 | 31 | .pure-menu a, .pure-menu .pure-menu-heading { 32 | display: block; 33 | color: inherit; 34 | line-height: 1.5em; 35 | padding: 5px 20px; 36 | text-decoration: none; 37 | white-space: nowrap; 38 | } 39 | 40 | .pure-menu.pure-menu-horizontal > .pure-menu-heading { 41 | display: inline-block; 42 | margin: 0; 43 | zoom: 1; 44 | *display: inline; 45 | vertical-align: middle; 46 | } 47 | .pure-menu.pure-menu-horizontal > ul { 48 | display: inline-block; 49 | zoom: 1; 50 | *display: inline; 51 | vertical-align: middle; 52 | } 53 | 54 | .pure-menu li a { padding: 5px 20px; } 55 | 56 | .pure-menu-can-have-children > .pure-menu-label:after { 57 | content: '\25B8'; 58 | float: right; 59 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'DejaVu Sans', sans-serif; /* These specific fonts have the Unicode char we need. */ 60 | margin-right: -20px; 61 | margin-top: -1px; 62 | } 63 | 64 | .pure-menu-can-have-children > .pure-menu-label { 65 | padding-right:30px; 66 | } 67 | 68 | .pure-menu-separator { 69 | background-color: #dfdfdf; 70 | display: block; 71 | height: 1px; 72 | font-size: 0; 73 | margin: 7px 2px; 74 | overflow: hidden; 75 | } 76 | 77 | .pure-menu-hidden { display: none; } 78 | 79 | /* FIXED MENU */ 80 | .pure-menu-fixed { 81 | position: fixed; 82 | top:0; 83 | left:0; 84 | width: 100%; 85 | } 86 | 87 | 88 | /* HORIZONTAL MENU CODE */ 89 | 90 | /* Initial menus should be inline-block so that they are horizontal */ 91 | .pure-menu-horizontal li { 92 | display: inline-block; 93 | zoom: 1; 94 | *display: inline; 95 | vertical-align: middle; 96 | } 97 | 98 | /* Submenus should still be display:block; */ 99 | .pure-menu-horizontal li li { 100 | display: block; 101 | } 102 | 103 | /* Content after should be down arrow */ 104 | .pure-menu-horizontal > .pure-menu-children > .pure-menu-can-have-children > .pure-menu-label:after { 105 | content: "\25BE"; 106 | } 107 | /*Add extra padding to elements that have the arrow so that the hover looks nice */ 108 | .pure-menu-horizontal > .pure-menu-children > .pure-menu-can-have-children > .pure-menu-label { 109 | padding-right:30px; 110 | } 111 | 112 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/buttons/buttons.scss: -------------------------------------------------------------------------------- 1 | .pure-button { 2 | font-size: 100%; 3 | *font-size: 90%; /*IE 6/7 - To reduce IE's oversized button text*/ 4 | *overflow: visible; /*IE 6/7 - Because of IE's overly large left/right padding on buttons */ 5 | padding: 0.5em 1.5em 0.5em; 6 | color: #444; /* rgba not supported (IE 8) */ 7 | color: rgba(0, 0, 0, 0.80); /* rgba supported */ 8 | *color: #444; /* IE 6 & 7 */ 9 | border: 1px solid #999; /*IE 6/7/8*/ 10 | border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/ 11 | background-color: #E6E6E6; 12 | text-decoration: none; 13 | -webkit-border-radius: 2px; 14 | -moz-border-radius: 2px; 15 | border-radius: 2px; 16 | -webkit-font-smoothing: antialiased; 17 | /* Transitions */ 18 | -webkit-transition: 0.1s linear -webkit-box-shadow; 19 | -moz-transition: 0.1s linear -moz-box-shadow; 20 | -ms-transition: 0.1s linear box-shadow; 21 | -o-transition: 0.1s linear box-shadow; 22 | transition: 0.1s linear box-shadow; 23 | } 24 | 25 | 26 | .pure-button-hover, 27 | .pure-button:hover { 28 | 29 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#00000000', GradientType=0); 30 | 31 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0,0,0, 0.05)), to(rgba(0,0,0, 0.05))); 32 | background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.15)); 33 | background-image: -moz-linear-gradient(top, rgba(0,0,0, 0.05) 0%, rgba(0,0,0, 0.05)); 34 | background-image: -ms-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.15)); 35 | background-image: -o-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.05)); 36 | background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.05)); 37 | } 38 | 39 | .pure-button-active, 40 | .pure-button:active { 41 | -webkit-box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; 42 | -moz-box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; 43 | box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; 44 | } 45 | 46 | .pure-button[disabled], 47 | .pure-button-disabled, 48 | .pure-button-disabled:hover, 49 | .pure-button-disabled:active { 50 | border: none; 51 | background-image: none; 52 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 53 | filter: alpha(opacity=40); 54 | -khtml-opacity: 0.40; 55 | -moz-opacity: 0.40; 56 | opacity: 0.40; 57 | cursor: not-allowed; 58 | box-shadow: none; 59 | } 60 | 61 | .pure-button-hidden { 62 | display:none; 63 | } 64 | 65 | /* Firefox: Get rid of the inner focus border */ 66 | .pure-button::-moz-focus-inner{ 67 | padding: 0; 68 | border: 0; 69 | } 70 | 71 | 72 | /* Sam */ 73 | .pure-button-primary, 74 | .pure-button-selected, 75 | a.pure-button-primary, 76 | a.pure-button-selected { 77 | background-color: rgb(0, 120, 231); 78 | color: #fff; 79 | } 80 | 81 | .pure-button:-moz-focusring { 82 | outline-color: rgba(0, 0, 0, 0.85); 83 | } 84 | -------------------------------------------------------------------------------- /app/views/jobs/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: job, local: true, :class => "pure-form pure-form-stacked") do |form| %> 2 | <% if job.errors.any? %> 3 |
4 |

5 | <%= pluralize(job.errors.count, "error") %> 6 | prohibited this job from being saved: 7 |

8 | 15 |
16 | <% end %> 17 |
18 | <%= form.label :role %> 19 | <%= form.text_field :role %> 20 | What will be their title at the new role? 21 |
22 |
23 | <%= form.label :company_id, "Company Name" %> 24 | <%= form.collection_select(:company_id, @current_user.companies, :id, :name) %> 25 |
26 |
27 | <%= form.check_box :remote_ok %> Remote OK 28 |
29 |
30 | <%= form.label :duration %> 31 | <%= form.select(:duration, {"Full-time" => "Full-Time", "Contract" => "Contract", "Internship" => "Internship"}) %> 32 | What is the duration for this project? 33 |
34 |
35 | <%= form.label :salary %> 36 | <%= form.select(:salary, {"Up to USD 500" => "Up to USD 500", "USD 500 - 1k" => "USD 500 - 1k", "USD 1k - 2k" => "USD 1k - 2k", "USD 2k - 3k" => "USD 2k - 3k", "USD 3k+" => "USD 3k+"}) %> 37 | Please note: there's a correlation between experience/expertise and compensation. Therefore, it's important to provide a figure to attract the right pool of candidates. 38 |
39 |
40 | <%= form.label :requirements %> 41 | <%= form.text_area :requirements %> 42 | What are the 1 to 3 main things you need your future developer to help with? Please feel free to keep this as simple as possible and use every day language. Please be as specific as possible. For example: "I need the developer to help us build a new iPhone app" 43 |
44 |
45 | <%= form.label :qualification %> 46 | <%= form.text_area :qualification %> 47 | This could be anything from "I require the developer to be fluent in French" to something as technical as "I require the developer to have an expert-level familiarity with PHP" 48 |
49 |
50 | <%= form.label :perks %> 51 | <%= form.text_area :perks %> 52 | Please remember that the best developers are motivated by a mission. Please *sell* the project succinctly. 53 |
54 |
55 | <%= form.label :contact_email %> 56 | <%= form.email_field :contact_email %> 57 |
58 |
59 | <%= form.label :city %> 60 | <%= form.text_field :city %> 61 |
62 |
63 | <%= form.label :country %> 64 | <%= form.text_field :country %> 65 |
66 |
67 | <%= form.label :apply_link, "Link to Apply" %> 68 | <%= form.url_field :apply_link %> 69 |
70 |
71 | <%= form.submit %> 72 |
73 | <% end %> 74 | -------------------------------------------------------------------------------- /app/views/jobs/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %> 2 | <%= " Edit Job - #{@job.role} at #{@job.company.name} | #{t('app.name')}" %> 3 | <% end %> 4 |

Editing Job

5 |
6 |

7 | <%= @job.role + " at " + @job.company.name %> 8 |

9 |
10 |
11 | <%= form_with(model: @job, local: true, :class => "pure-form pure-form-stacked") do |form| %> 12 | <% if @job.errors.any? %> 13 |
14 |

15 | <%= pluralize(job.errors.count, "error") %> 16 | prohibited this job from being saved: 17 |

18 |
    19 | <% @job.errors.full_messages.each do |message| %> 20 |
  • 21 | <%= message %> 22 |
  • 23 | <% end %> 24 |
25 |
26 | <% end %> 27 |
28 | <%= form.label :role %> 29 | <%= form.text_field :role %> 30 | What will be their title at the new role? 31 |
32 |
33 | <%= form.check_box :remote_ok %> Remote OK 34 |
35 |
36 | <%= form.label :duration %> 37 | <%= form.select(:duration, {"Full-time" => "Full-Time", "Contract" => "Contract", "Internship" => "Internship"}) %> 38 | What is the duration for this project? 39 |
40 |
41 | <%= form.label :salary %> 42 | <%= form.select(:salary, {"Up to USD 500" => "Up to USD 500", "USD 500 - 1k" => "USD 500 - 1k", "USD 1k - 2k" => "USD 1k - 2k", "USD 2k - 3k" => "USD 2k - 3k", "USD 3k+" => "USD 3k+"}) %> 43 | Please note: there's a correlation between experience/expertise and compensation. Therefore, it's important to provide a figure to attract the right pool of candidates. 44 |
45 |
46 | <%= form.label :requirements %> 47 | <%= form.text_area :requirements %> 48 | What are the 1 to 3 main things you need your future developer to help with? Please feel free to keep this as simple as possible and use every day language. Please be as specific as possible. For example: "I need the developer to help us build a new iPhone app" 49 |
50 |
51 | <%= form.label :qualification %> 52 | <%= form.text_area :qualification %> 53 | This could be anything from "I require the developer to be fluent in French" to something as technical as "I require the developer to have an expert-level familiarity with PHP" 54 |
55 |
56 | <%= form.label :perks %> 57 | <%= form.text_area :perks %> 58 | Please remember that the best developers are motivated by a mission. Please *sell* the project succinctly. 59 |
60 |
61 | <%= form.label :city %> 62 | <%= form.text_field :city %> 63 |
64 |
65 | <%= form.label :country %> 66 | <%= form.text_field :country %> 67 |
68 |
69 | <%= form.label :apply_link, "Link to Apply" %> 70 | <%= form.url_field :apply_link %> 71 |
72 |
73 | <%= form.submit %> 74 |
75 | <% end %> 76 | <%= link_to 'Show', @job %> 77 | | 78 | <%= link_to 'Back', jobs_path %> 79 |
80 | -------------------------------------------------------------------------------- /app/controllers/jobs_controller.rb: -------------------------------------------------------------------------------- 1 | # require "twitter" 2 | 3 | class JobsController < ApplicationController 4 | before_action :set_job, except: [:new, :index] 5 | before_action :authenticate_user!, except: [:index, :show, :search] 6 | before_action :require_ownership, only: [:edit, :destroy] 7 | 8 | def index 9 | @jobs = Job.all_active 10 | end 11 | 12 | def new 13 | @current_user = current_user 14 | if @current_user.companies.empty? 15 | redirect_to new_company_path, notice: "Register company first" 16 | return 17 | end 18 | @job = Job.new 19 | end 20 | 21 | def create 22 | company = current_user.companies.find_by(id: job_params[:company_id]) 23 | raise_not_found unless company 24 | 25 | @job = company.jobs.build(job_params) 26 | if @job.save 27 | redirect_to @job, status: :created 28 | job_post_successful 29 | else 30 | render :new, status: :bad_request 31 | end 32 | end 33 | 34 | def show 35 | raise_not_found if @job.archived? 36 | end 37 | 38 | def edit 39 | end 40 | 41 | def update 42 | if @job.update(edit_job_params) 43 | redirect_to @job, notice: "Job has been updated." 44 | else 45 | render :edit 46 | end 47 | end 48 | 49 | def filled 50 | @job.update_attribute(:filled_at, DateTime.now) 51 | redirect_to @job 52 | end 53 | 54 | def vacant 55 | @job.update_attribute(:filled_at, nil) 56 | redirect_to @job 57 | end 58 | 59 | def search 60 | @matches = Job.search(params[:q]) 61 | respond_to do |format| 62 | format.html 63 | format.json 64 | end 65 | end 66 | 67 | # DELETE /jobs/1 68 | # DELETE /jobs/1.json 69 | def destroy 70 | @job.destroy 71 | respond_to do |format| 72 | format.html { redirect_to jobs_url, notice: "Job was successfully destroyed." } 73 | format.json { head :no_content } 74 | end 75 | end 76 | 77 | private 78 | 79 | def require_ownership 80 | unless current_user.companies.includes(@job.company) 81 | redirect_to root_path, notice: "You are not authorized to edit this job post." 82 | end 83 | end 84 | 85 | def set_company 86 | @company = current_user.companies.find_by(id: params[:job][:company_id]) 87 | raise_not_found unless @company 88 | end 89 | 90 | def set_job 91 | @job = Job.find_by(id: params[:id]) 92 | end 93 | 94 | def job_params 95 | params.require(:job).permit( 96 | :role, 97 | :duration, 98 | :salary, 99 | :requirements, 100 | :qualification, 101 | :perks, 102 | :company_id, 103 | :remote_ok, 104 | :city, 105 | :country, 106 | :apply_link) 107 | end 108 | 109 | def edit_job_params 110 | params.require(:job).permit( 111 | :role, 112 | :duration, 113 | :salary, 114 | :requirements, 115 | :qualification, 116 | :perks, 117 | :remote_ok, 118 | :city, 119 | :country, 120 | :apply_link) 121 | end 122 | 123 | def raise_not_found 124 | raise ActionController::RoutingError.new("not found") 125 | end 126 | 127 | def job_post_successful 128 | JobsMailer.with(job: @job).published.deliver_later 129 | # $tweetBot.update("New Job Vacancy: " + @job.title + ". Read more at " + job_url) 130 | end 131 | 132 | end 133 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/buttons/buttons-theme.scss: -------------------------------------------------------------------------------- 1 | /* from PURE buttons-core.css */ 2 | 3 | .pure-button { 4 | /* Structure */ 5 | display: inline-block; 6 | *display: inline; 7 | /*IE 6/7*/ 8 | zoom: 1; 9 | line-height: normal; 10 | white-space: nowrap; 11 | vertical-align: baseline; 12 | text-align: center; 13 | cursor: pointer; 14 | -webkit-user-drag: none; 15 | -webkit-user-select: none; 16 | -moz-user-select: none; 17 | -ms-user-select: none; 18 | user-select: none; 19 | } 20 | 21 | 22 | /* Firefox: Get rid of the inner focus border */ 23 | 24 | .pure-button::-moz-focus-inner { 25 | padding: 0; 26 | border: 0; 27 | } 28 | 29 | 30 | /* end from PURE buttons-core.css */ 31 | 32 | 33 | /* from PURE buttons.css */ 34 | 35 | 36 | /*csslint unqualified-attributes:false*/ 37 | 38 | .pure-button { 39 | font-size: 100%; 40 | *font-size: 90%; 41 | /*IE 6/7 - To reduce IE's oversized button text*/ 42 | *overflow: visible; 43 | /*IE 6/7 - Because of IE's overly large left/right padding on buttons */ 44 | padding: $padding; 45 | color: $text; 46 | /* rgba not supported (IE 8) */ 47 | /* color: rgba(0, 0, 0, 0.80); rgba supported */ 48 | /* *color: #444; IE 6 & 7 */ 49 | border: 1px solid $border-low; 50 | /*IE 6/7/8*/ 51 | border: none rgba(0, 0, 0, 0); 52 | /*IE9 + everything else*/ 53 | background-color: $background; 54 | text-decoration: none; 55 | border-radius: $button-radius; 56 | -webkit-font-smoothing: antialiased; 57 | /* Transitions */ 58 | -webkit-transition: 0.1s linear -webkit-box-shadow; 59 | -moz-transition: 0.1s linear -moz-box-shadow; 60 | -ms-transition: 0.1s linear box-shadow; 61 | -o-transition: 0.1s linear box-shadow; 62 | transition: 0.1s linear box-shadow; 63 | } 64 | 65 | .pure-button-hover, 66 | .pure-button:hover { 67 | // filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#00000000', GradientType=0); 68 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0, 0, 0, 0.05)), to(rgba(0, 0, 0, 0.05))); 69 | background-image: -webkit-linear-gradient(transparent, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.15)); 70 | background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0.05) 0%, rgba(0, 0, 0, 0.05)); 71 | background-image: -ms-linear-gradient(transparent, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.15)); 72 | background-image: -o-linear-gradient(transparent, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.05)); 73 | background-image: linear-gradient(transparent, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.05)); 74 | } 75 | 76 | .pure-button:focus { 77 | outline: 0; 78 | } 79 | 80 | .pure-button-active, 81 | .pure-button:active { 82 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, 0 0 6px rgba(0, 0, 0, 0.20) inset; 83 | } 84 | 85 | .pure-button[disabled], 86 | .pure-button-disabled, 87 | .pure-button-disabled:hover, 88 | .pure-button-disabled:active { 89 | border: none; 90 | background-image: none; 91 | // filter: progid: DXImageTransform.Microsoft.gradient(enabled=false); 92 | filter: alpha(opacity=40); 93 | -khtml-opacity: 0.40; 94 | -moz-opacity: 0.40; 95 | opacity: 0.40; 96 | cursor: not-allowed; 97 | box-shadow: none; 98 | } 99 | 100 | .pure-button-hidden { 101 | display: none; 102 | } 103 | 104 | 105 | /* Firefox: Get rid of the inner focus border */ 106 | 107 | .pure-button::-moz-focus-inner { 108 | padding: 0; 109 | border: 0; 110 | } 111 | 112 | .pure-button-primary, 113 | .pure-button-selected, 114 | a.pure-button-primary, 115 | a.pure-button-selected { 116 | background-color: $selected-background; 117 | color: $selected-text; 118 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/forms/forms-core.scss: -------------------------------------------------------------------------------- 1 | /* This page has Normalize.css form-specific style rules applied to a .pure-form context */ 2 | 3 | /* ========== 4 | Forms Core 5 | =========*/ 6 | 7 | /* 8 | * Corrects margin displayed oddly in IE 6/7. 9 | */ 10 | 11 | .pure-form { 12 | margin: 0; 13 | } 14 | 15 | /* 16 | * Define consistent border, margin, and padding. 17 | */ 18 | 19 | .pure-form fieldset { 20 | border: 1px solid #c0c0c0; 21 | margin: 0 2px; 22 | padding: 0.35em 0.625em 0.75em; 23 | } 24 | 25 | /* 26 | * 1. Corrects color not being inherited in IE 6/7/8/9. 27 | * 2. Corrects text not wrapping in Firefox 3. 28 | * 3. Corrects alignment displayed oddly in IE 6/7. 29 | */ 30 | 31 | .pure-form legend { 32 | border: 0; /* 1 */ 33 | padding: 0; 34 | white-space: normal; /* 2 */ 35 | *margin-left: -7px; /* 3 */ 36 | } 37 | 38 | /* 39 | * 1. Corrects font size not being inherited in all browsers. 40 | * 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5, 41 | * and Chrome. 42 | * 3. Improves appearance and consistency in all browsers. 43 | */ 44 | 45 | .pure-form button, 46 | .pure-form input, 47 | .pure-form select, 48 | .pure-form textarea { 49 | font-size: 100%; /* 1 */ 50 | margin: 0; /* 2 */ 51 | vertical-align: baseline; /* 3 */ 52 | *vertical-align: middle; /* 3 */ 53 | } 54 | 55 | /* 56 | * Addresses Firefox 3+ setting `line-height` on `input` using `!important` in 57 | * the UA stylesheet. 58 | */ 59 | 60 | .pure-form button, 61 | .pure-form input { 62 | line-height: normal; 63 | } 64 | 65 | /* 66 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 67 | * and `video` controls. 68 | * 2. Corrects inability to style clickable `input` types in iOS. 69 | * 3. Improves usability and consistency of cursor style between image-type 70 | * `input` and others. 71 | * 4. Removes inner spacing in IE 7 without affecting normal text inputs. 72 | * Known issue: inner spacing remains in IE 6. 73 | */ 74 | 75 | .pure-form button, 76 | .pure-form input[type="button"], /* 1 */ 77 | .pure-form input[type="reset"], 78 | .pure-form input[type="submit"] { 79 | -webkit-appearance: button; /* 2 */ 80 | cursor: pointer; /* 3 */ 81 | *overflow: visible; /* 4 */ 82 | } 83 | 84 | /* 85 | * Re-set default cursor for disabled elements. 86 | */ 87 | 88 | .pure-form button[disabled], 89 | .pure-form input[disabled] { 90 | cursor: default; 91 | } 92 | 93 | /* 94 | * 1. Addresses box sizing set to content-box in IE 8/9. 95 | * 2. Removes excess padding in IE 8/9. 96 | * 3. Removes excess padding in IE 7. 97 | * Known issue: excess padding remains in IE 6. 98 | */ 99 | 100 | .pure-form input[type="checkbox"], 101 | .pure-form input[type="radio"] { 102 | box-sizing: border-box; /* 1 */ 103 | padding: 0; /* 2 */ 104 | *height: 13px; /* 3 */ 105 | *width: 13px; /* 3 */ 106 | } 107 | 108 | /* 109 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 110 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 111 | * (include `-moz` to future-proof). 112 | */ 113 | 114 | .pure-form input[type="search"] { 115 | -webkit-appearance: textfield; /* 1 */ 116 | -moz-box-sizing: content-box; 117 | -webkit-box-sizing: content-box; /* 2 */ 118 | box-sizing: content-box; 119 | } 120 | 121 | /* 122 | * Removes inner padding and search cancel button in Safari 5 and Chrome 123 | * on OS X. 124 | */ 125 | 126 | .pure-form input[type="search"]::-webkit-search-cancel-button, 127 | .pure-form input[type="search"]::-webkit-search-decoration { 128 | -webkit-appearance: none; 129 | } 130 | 131 | /* 132 | * Removes inner padding and border in Firefox 3+. 133 | */ 134 | 135 | .pure-form button::-moz-focus-inner, 136 | .pure-form input::-moz-focus-inner { 137 | border: 0; 138 | padding: 0; 139 | } 140 | 141 | /* 142 | * 1. Removes default vertical scrollbar in IE 6/7/8/9. 143 | * 2. Improves readability and alignment in all browsers. 144 | */ 145 | 146 | .pure-form textarea { 147 | overflow: auto; /* 1 */ 148 | vertical-align: top; /* 2 */ 149 | } 150 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/forms/forms.scss: -------------------------------------------------------------------------------- 1 | .pure-form input, 2 | .pure-form select { 3 | padding: 0.5em 0.6em; 4 | display: inline-block; 5 | border: 1px solid #ccc; 6 | font-size: 0.8em; 7 | box-shadow: inset 0 1px 3px #ddd; 8 | border-radius: 4px; 9 | -webkit-transition: 0.3s linear border; 10 | -moz-transition: 0.3s linear border; 11 | -ms-transition: 0.3s linear border; 12 | -o-transition: 0.3s linear border; 13 | transition: 0.3s linear border; 14 | -moz-box-sizing: border-box; 15 | -webkit-box-sizing: border-box; 16 | box-sizing: border-box; 17 | -webkit-font-smoothing: antialiased; 18 | } 19 | 20 | .pure-form input:focus, 21 | .pure-form select:focus { 22 | outline: 0; 23 | outline: thin dotted \9; /* IE6-9 */ 24 | border-color: #129FEA; 25 | } 26 | .pure-form .pure-checkbox, 27 | .pure-form .pure-radio { 28 | margin: 0.5em 0; 29 | display: block; 30 | } 31 | .pure-form input[disabled], 32 | .pure-form select[disabled], 33 | .pure-form textarea[disabled], 34 | .pure-form input[readonly], 35 | .pure-form select[readonly], 36 | .pure-form textarea[readonly] { 37 | cursor: not-allowed; 38 | background-color: #eaeded; 39 | color: #cad2d3; 40 | border-color: transparent; 41 | } 42 | .pure-form input:focus:invalid, 43 | .pure-form textarea:focus:invalid, 44 | .pure-form select:focus:invalid { 45 | color: #b94a48; 46 | border: 1px solid #ee5f5b; 47 | } 48 | .pure-form input:focus:invalid:focus, 49 | .pure-form textarea:focus:invalid:focus, 50 | .pure-form select:focus:invalid:focus { 51 | border-color: #e9322d; 52 | } 53 | .pure-form select { 54 | border: 1px solid #ccc; 55 | background-color: white; 56 | } 57 | .pure-form select[multiple] { 58 | height: auto; 59 | } 60 | .pure-form label { 61 | margin: 0.5em 0 0.2em; 62 | color: #999; 63 | font-size:90%; 64 | } 65 | .pure-form fieldset { 66 | margin: 0; 67 | padding: 0.35em 0 0.75em; 68 | border: 0; 69 | } 70 | .pure-form legend { 71 | display: block; 72 | width: 100%; 73 | padding: 0.3em 0; 74 | margin-bottom: 0.3em; 75 | font-size: 125%; 76 | color: #333; 77 | border-bottom: 1px solid #e5e5e5; 78 | } 79 | 80 | .pure-form.pure-form-stacked input[type='text'], 81 | .pure-form.pure-form-stacked select, 82 | .pure-form.pure-form-stacked label { 83 | display: block; 84 | } 85 | 86 | .pure-form-aligned input, 87 | .pure-form-aligned textarea, 88 | .pure-form-aligned select, 89 | .pure-form-aligned .pure-help-inline { 90 | display: inline-block; 91 | *display: inline; /* IE7 inline-block hack */ 92 | *zoom: 1; 93 | vertical-align: middle; 94 | } 95 | 96 | /* aligned Forms */ 97 | .pure-form-aligned .pure-control-group { 98 | margin-bottom: 0.5em; 99 | } 100 | .pure-form-aligned .pure-control-group label { 101 | text-align: right; 102 | display: inline-block; 103 | vertical-align: middle; 104 | width: 10em; 105 | margin: 0 1em 0 0; 106 | } 107 | .pure-form-aligned .pure-controls { 108 | margin: 1.5em 0 0 10em; 109 | } 110 | 111 | /* Rounded Inputs */ 112 | .pure-form .pure-input-rounded { 113 | border-radius: 20px; 114 | padding-left:1em; 115 | } 116 | 117 | /* Grouped Inputs */ 118 | .pure-form .pure-group fieldset { 119 | margin-bottom: 10px; 120 | } 121 | .pure-form .pure-group input { 122 | display: block; 123 | padding: 10px; 124 | margin: 0; 125 | border-radius: 0; 126 | position: relative; 127 | top: -1px; 128 | } 129 | .pure-form .pure-group input:focus { 130 | z-index: 2; 131 | } 132 | .pure-form .pure-group input:first-child { 133 | top: 1px; 134 | border-radius: 4px 4px 0 0; 135 | } 136 | .pure-form .pure-group input:last-child { 137 | top: -2px; 138 | border-radius: 0 0 4px 4px; 139 | } 140 | .pure-form .pure-group button { 141 | margin: 0.35em 0; 142 | } 143 | 144 | .pure-form .pure-input-1 { 145 | width: 100%; 146 | } 147 | .pure-form .pure-input-2-3 { 148 | width: 66%; 149 | } 150 | .pure-form .pure-input-1-2 { 151 | width: 50%; 152 | } 153 | .pure-form .pure-input-1-3 { 154 | width: 33%; 155 | } 156 | .pure-form .pure-input-1-4 { 157 | width: 25%; 158 | } 159 | 160 | /* Inline help for forms */ 161 | .pure-form .pure-help-inline { 162 | display: inline-block; 163 | padding-left: 0.3em; 164 | color: #666; 165 | vertical-align: middle; 166 | font-size: 90%; 167 | } 168 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | email_changed: 27 | subject: "Email Changed" 28 | password_change: 29 | subject: "Password Changed" 30 | omniauth_callbacks: 31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 32 | success: "Successfully authenticated from %{kind} account." 33 | passwords: 34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 37 | updated: "Your password has been changed successfully. You are now signed in." 38 | updated_not_active: "Your password has been changed successfully." 39 | registrations: 40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 41 | signed_up: "Welcome! You have signed up successfully." 42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." 46 | updated: "Your account has been updated successfully." 47 | sessions: 48 | signed_in: "Signed in successfully." 49 | signed_out: "Signed out successfully." 50 | already_signed_out: "Signed out successfully." 51 | unlocks: 52 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 53 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 54 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 55 | errors: 56 | messages: 57 | already_confirmed: "was already confirmed, please try signing in" 58 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 59 | expired: "has expired, please request a new one" 60 | not_found: "not found" 61 | not_locked: "was not locked" 62 | not_saved: 63 | one: "1 error prohibited this %{resource} from being saved:" 64 | other: "%{count} errors prohibited this %{resource} from being saved:" 65 | -------------------------------------------------------------------------------- /test/controllers/jobs_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class JobsControllerTest < ActionDispatch::IntegrationTest 4 | include Devise::Test::IntegrationHelpers 5 | include ERB::Util 6 | 7 | setup do 8 | @available_jobs = create_list(:job, 3, created_at: 1.day.ago) 9 | @archived_job = create(:job, archived: true) # one archived job 10 | @user = create(:user) 11 | @company = create(:company) 12 | @user.companies << @company 13 | end 14 | 15 | test "should get index" do 16 | get jobs_url 17 | 18 | assert_response :success 19 | 20 | @available_jobs.each do |job| 21 | job_title = "#{job.role} at #{job.company.name}" 22 | assert_match html_escape(job_title), @response.body 23 | end 24 | 25 | # archived jobs are not displayed 26 | archived_job_title = "#{@archived_job.role} at #{@archived_job.company.name}" 27 | refute_match archived_job_title, @response.body 28 | end 29 | 30 | test "should show a job" do 31 | job = @available_jobs.first 32 | 33 | get job_url(job) 34 | 35 | assert_response :success 36 | assert_match job.role, @response.body 37 | assert_match job.qualification, @response.body 38 | assert_match job.salary, @response.body 39 | assert_match job.duration, @response.body 40 | assert_match html_escape(job.company.name), @response.body 41 | end 42 | 43 | test "should not find archived job" do 44 | assert_raise ActionController::RoutingError do 45 | get job_url(@archived_job) 46 | end 47 | end 48 | 49 | test "should fail for unauthenticated user" do 50 | job_params = attributes_for(:job) 51 | post jobs_url, params: {job: job_params} 52 | assert_redirected_to new_user_session_url 53 | end 54 | 55 | test "should create a new job post for user's client" do 56 | sign_in @user 57 | 58 | assert_enqueued_jobs @company.users.count do 59 | assert_difference('Job.count') do 60 | job_params = attributes_for(:job, company_id: @company.id) 61 | post jobs_url, params: {job: job_params} 62 | 63 | assert_response :created 64 | end 65 | end 66 | end 67 | 68 | test "should fail if company is not client of user" do 69 | sign_in @user 70 | 71 | job_params = attributes_for(:job, company_id: create(:company).id) 72 | 73 | assert_raise ActionController::RoutingError do 74 | post jobs_url, params: {job: job_params} 75 | end 76 | end 77 | 78 | test "should be able to edit a job post" do 79 | job = FactoryBot.create(:job, company: @company) 80 | job_params = attributes_for(:job, company: FactoryBot.create(:company)) 81 | 82 | sign_in @user 83 | put job_url(job), params: {job: job_params} 84 | 85 | job.reload 86 | assert_redirected_to job 87 | assert_equal job.company, @company # Job's company cannot be changed. 88 | assert_equal job.role, job_params[:role] 89 | 90 | updated_attrs = job.attributes.except("id", "created_at", "updated_at", "company_id", "full_text_search") 91 | updated_attrs.each { |k, v| assert_equal v, job_params[k.to_sym] if v } 92 | end 93 | 94 | test "should be able to mark a job post (position) as filled" do 95 | job = FactoryBot.create(:job, company: @company) 96 | 97 | sign_in @user 98 | post filled_job_url(job) 99 | 100 | job.reload 101 | assert_redirected_to job 102 | refute_nil job.filled_at 103 | assert job.filled_at < DateTime.now 104 | end 105 | 106 | test "should be able to mark a filled job as vacant" do 107 | job = FactoryBot.create(:job, company: @company, filled_at: DateTime.now) 108 | 109 | sign_in @user 110 | post vacant_job_url(job) 111 | 112 | job.reload 113 | assert_redirected_to job 114 | assert_nil job.filled_at 115 | end 116 | 117 | test "search" do 118 | first = FactoryBot.create(:job, role: "Senior Ruby on Rails Developer") 119 | second = FactoryBot.create(:job, role: "Senior JavaScript Developer") 120 | FactoryBot.create(:job) # not found 121 | 122 | get search_jobs_url(q: "senior developer") 123 | 124 | assert_match /2 matches were found/i, @response.body 125 | assert_match first.role, @response.body 126 | assert_match first.requirements, @response.body 127 | assert_match second.role, @response.body 128 | assert_match second.requirements, @response.body 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /test/models/job_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: jobs 4 | # 5 | # id :bigint(8) not null, primary key 6 | # role :string not null 7 | # duration :string 8 | # salary :string not null 9 | # requirements :string not null 10 | # qualification :string not null 11 | # perks :string 12 | # created_at :datetime not null 13 | # updated_at :datetime not null 14 | # archived :boolean default(FALSE) 15 | # remote_ok :boolean default(TRUE), not null 16 | # company_id :bigint(8) not null 17 | # city :string default(""), not null 18 | # country :string default(""), not null 19 | # apply_link :text default(""), not null 20 | # filled_at :datetime 21 | # 22 | 23 | require 'test_helper' 24 | 25 | class JobTest < ActiveSupport::TestCase 26 | 27 | setup do 28 | @subject = FactoryBot.create(:job) 29 | end 30 | 31 | test "associations" do 32 | must belong_to :company 33 | end 34 | 35 | test "validations" do 36 | must validate_presence_of :duration 37 | must validate_presence_of :salary 38 | must validate_presence_of :requirements 39 | must validate_presence_of :qualification 40 | must validate_presence_of :role 41 | end 42 | 43 | test "all_active" do 44 | # Uses the default validity period. See the 45 | # self.validity_period in model for what the 46 | # current value is. 47 | past_date = DateTime.now - (Job.validity_period + 1).days 48 | future_date = DateTime.now + (Job.validity_period + 1).days 49 | 50 | # These jobs are not matched since they fall 51 | # outside of the range of active job post. One of 52 | # them is archived and another is already filled. 53 | FactoryBot.create(:job, archived: true) 54 | FactoryBot.create(:job, created_at: future_date) 55 | FactoryBot.create(:job, created_at: past_date) 56 | FactoryBot.create(:job, filled_at: past_date) 57 | 58 | active_job_posts = Job.all_active 59 | 60 | assert_equal 1, active_job_posts.length 61 | assert_equal @subject.id, active_job_posts.first.id 62 | end 63 | 64 | test "search - role" do 65 | found = FactoryBot.create(:job, role: "full-stack developer") 66 | FactoryBot.create(:job) # not found 67 | 68 | jobs = Job.search("full stack developer") 69 | 70 | assert_equal 1, jobs.length 71 | 72 | match = jobs.first 73 | match.attributes.except("created_at", "updated_at", "full_text_search").each do |k, v| 74 | k = k.to_sym 75 | 76 | if v.nil? 77 | assert_nil found[k] 78 | else 79 | assert_equal v, found[k] 80 | end 81 | end 82 | end 83 | 84 | test "search - qualification" do 85 | found = FactoryBot.create(:job, qualification: "minimum 5 years experience") 86 | FactoryBot.create(:job) # not found 87 | 88 | jobs = Job.search("minimum experience") 89 | assert_equal 1, jobs.length 90 | 91 | match = jobs.first 92 | match.attributes.except("created_at", "updated_at", "full_text_search").each do |k, v| 93 | k = k.to_sym 94 | 95 | if v.nil? 96 | assert_nil found[k] 97 | else 98 | assert_equal v, found[k] 99 | end 100 | end 101 | end 102 | 103 | test "search - requirements" do 104 | found = FactoryBot.create(:job, requirements: "ruby proficiency\nopen source contributions\njavascript") 105 | FactoryBot.create(:job) # not found 106 | 107 | jobs = Job.search("proficient ruby") 108 | assert_equal 1, jobs.length 109 | 110 | match = jobs.first 111 | match.attributes.except("created_at", "updated_at", "full_text_search").each do |k, v| 112 | k = k.to_sym 113 | 114 | if v.nil? 115 | assert_nil found[k] 116 | else 117 | assert_equal v, found[k] 118 | end 119 | end 120 | end 121 | 122 | test "search - perks" do 123 | found = FactoryBot.create(:job, perks: "health insurance\n30-day holidays\nsummer vacation") 124 | FactoryBot.create(:job) # not found 125 | 126 | jobs = Job.search("insure") 127 | assert_equal 1, jobs.length 128 | 129 | match = jobs.first 130 | match.attributes.except("created_at", "updated_at", "full_text_search").each do |k, v| 131 | k = k.to_sym 132 | 133 | if v.nil? 134 | assert_nil found[k] 135 | else 136 | assert_equal v, found[k] 137 | end 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /db/countries.csv: -------------------------------------------------------------------------------- 1 | Afghanistan,AF 2 | Albania,AL 3 | Algeria,DZ 4 | American Samoa,AS 5 | Andorra,AD 6 | Angola,AO 7 | Anguilla,AI 8 | Antarctica,AQ 9 | Antigua and Barbuda,AG 10 | Argentina,AR 11 | Armenia,AM 12 | Aruba,AW 13 | Australia,AU 14 | Austria,AT 15 | Azerbaijan,AZ 16 | Bahamas,BS 17 | Bahrain,BH 18 | Bangladesh,BD 19 | Barbados,BB 20 | Belarus,BY 21 | Belgium,BE 22 | Belize,BZ 23 | Benin,BJ 24 | Bermuda,BM 25 | Bhutan,BT 26 | Bolivia,BO 27 | Bosnia and Herzegovina,BA 28 | Botswana,BW 29 | Brazil,BR 30 | British Indian Ocean Territory,IO 31 | British Virgin Islands,VG 32 | Brunei,BN 33 | Bulgaria,BG 34 | Burkina Faso,BF 35 | Burundi,BI 36 | Cambodia,KH 37 | Cameroon,CM 38 | Canada,CA 39 | Cape Verde,CV 40 | Cayman Islands,KY 41 | Central African Republic,CF 42 | Chad,TD 43 | Chile,CL 44 | China,CN 45 | Christmas Island,CX 46 | Cocos Islands,CC 47 | Colombia,CO 48 | Comoros,KM 49 | Cook Islands,CK 50 | Costa Rica,CR 51 | Croatia,HR 52 | Cuba,CU 53 | Curacao,CW 54 | Cyprus,CY 55 | Czech Republic,CZ 56 | Democratic Republic of the Congo,CD 57 | Denmark,DK 58 | Djibouti,DJ 59 | Dominica,DM 60 | Dominican Republic,DO 61 | East Timor,TL 62 | Ecuador,EC 63 | Egypt,EG 64 | El Salvador,SV 65 | Equatorial Guinea,GQ 66 | Eritrea,ER 67 | Estonia,EE 68 | Ethiopia,ET 69 | Falkland Islands,FK 70 | Faroe Islands,FO 71 | Fiji,FJ 72 | Finland,FI 73 | France,FR 74 | French Polynesia,PF 75 | Gabon,GA 76 | Gambia,GM 77 | Georgia,GE 78 | Germany,DE 79 | Ghana,GH 80 | Gibraltar,GI 81 | Greece,GR 82 | Greenland,GL 83 | Grenada,GD 84 | Guam,GU 85 | Guatemala,GT 86 | Guernsey,GG 87 | Guinea,GN 88 | Guinea-Bissau,GW 89 | Guyana,GY 90 | Haiti,HT 91 | Honduras,HN 92 | Hong Kong,HK 93 | Hungary,HU 94 | Iceland,IS 95 | India,IN 96 | Indonesia,ID 97 | Iran,IR 98 | Iraq,IQ 99 | Ireland,IE 100 | Isle of Man,IM 101 | Israel,IL 102 | Italy,IT 103 | Ivory Coast,CI 104 | Jamaica,JM 105 | Japan,JP 106 | Jersey,JE 107 | Jordan,JO 108 | Kazakhstan,KZ 109 | Kenya,KE 110 | Kiribati,KI 111 | Kosovo,XK 112 | Kuwait,KW 113 | Kyrgyzstan,KG 114 | Laos,LA 115 | Latvia,LV 116 | Lebanon,LB 117 | Lesotho,LS 118 | Liberia,LR 119 | Libya,LY 120 | Liechtenstein,LI 121 | Lithuania,LT 122 | Luxembourg,LU 123 | Macau,MO 124 | Macedonia,MK 125 | Madagascar,MG 126 | Malawi,MW 127 | Malaysia,MY 128 | Maldives,MV 129 | Mali,ML 130 | Malta,MT 131 | Marshall Islands,MH 132 | Mauritania,MR 133 | Mauritius,MU 134 | Mayotte,YT 135 | Mexico,MX 136 | Micronesia,FM 137 | Moldova,MD 138 | Monaco,MC 139 | Mongolia,MN 140 | Montenegro,ME 141 | Montserrat,MS 142 | Morocco,MA 143 | Mozambique,MZ 144 | Myanmar,MM 145 | Namibia,NA 146 | Nauru,NR 147 | Nepal,NP 148 | Netherlands,NL 149 | Netherlands Antilles,AN 150 | New Caledonia,NC 151 | New Zealand,NZ 152 | Nicaragua,NI 153 | Niger,NE 154 | Nigeria,NG 155 | Niue,NU 156 | North Korea,KP 157 | Northern Mariana Islands,MP 158 | Norway,NO 159 | Oman,OM 160 | Pakistan,PK 161 | Palau,PW 162 | Palestine,PS 163 | Panama,PA 164 | Papua New Guinea,PG 165 | Paraguay,PY 166 | Peru,PE 167 | Philippines,PH 168 | Pitcairn,PN 169 | Poland,PL 170 | Portugal,PT 171 | Puerto Rico,PR 172 | Qatar,QA 173 | Republic of the Congo,CG 174 | Reunion,RE 175 | Romania,RO 176 | Russia,RU 177 | Rwanda,RW 178 | Saint Barthelemy,BL 179 | Saint Helena,SH 180 | Saint Kitts and Nevis,KN 181 | Saint Lucia,LC 182 | Saint Martin,MF 183 | Saint Pierre and Miquelon,PM 184 | Saint Vincent and the Grenadines,VC 185 | Samoa,WS 186 | San Marino,SM 187 | Sao Tome and Principe,ST 188 | Saudi Arabia,SA 189 | Senegal,SN 190 | Serbia,RS 191 | Seychelles,SC 192 | Sierra Leone,SL 193 | Singapore,SG 194 | Sint Maarten,SX 195 | Slovakia,SK 196 | Slovenia,SI 197 | Solomon Islands,SB 198 | Somalia,SO 199 | South Africa,ZA 200 | South Korea,KR 201 | South Sudan,SS 202 | Spain,ES 203 | Sri Lanka,LK 204 | Sudan,SD 205 | Suriname,SR 206 | Svalbard and Jan Mayen,SJ 207 | Swaziland,SZ 208 | Sweden,SE 209 | Switzerland,CH 210 | Syria,SY 211 | Taiwan,TW 212 | Tajikistan,TJ 213 | Tanzania,TZ 214 | Thailand,TH 215 | Togo,TG 216 | Tokelau,TK 217 | Tonga,TO 218 | Trinidad and Tobago,TT 219 | Tunisia,TN 220 | Turkey,TR 221 | Turkmenistan,TM 222 | Turks and Caicos Islands,TC 223 | Tuvalu,TV 224 | U.S. Virgin Islands,VI 225 | Uganda,UG 226 | Ukraine,UA 227 | United Arab Emirates,AE 228 | United Kingdom,GB 229 | United States,US 230 | Uruguay,UY 231 | Uzbekistan,UZ 232 | Vanuatu,VU 233 | Vatican,VA 234 | Venezuela,VE 235 | Vietnam,VN 236 | Wallis and Futuna,WF 237 | Western Sahara,EH 238 | Yemen,YE 239 | Zambia,ZM 240 | Zimbabwe,ZW -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | # config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 36 | 37 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 38 | # config.action_controller.asset_host = 'http://assets.example.com' 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Store uploaded files on the local file system (see config/storage.yml for options) 45 | # config.active_storage.service = :local 46 | 47 | # Mount Action Cable outside main process or domain 48 | # config.action_cable.mount_path = nil 49 | # config.action_cable.url = 'wss://example.com/cable' 50 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 51 | 52 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 53 | config.force_ssl = true 54 | 55 | # Use the lowest log level to ensure availability of diagnostic information 56 | # when problems arise. 57 | config.log_level = :debug 58 | 59 | # Prepend all log lines with the following tags. 60 | config.log_tags = [ :request_id ] 61 | 62 | # Use a different cache store in production. 63 | # config.cache_store = :mem_cache_store 64 | 65 | # Use a real queuing backend for Active Job (and separate queues per environment) 66 | # config.active_job.queue_adapter = :resque 67 | # config.active_job.queue_name_prefix = "dcjobs_#{Rails.env}" 68 | 69 | config.action_mailer.perform_caching = false 70 | 71 | # Ignore bad email addresses and do not raise email delivery errors. 72 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 73 | # config.action_mailer.raise_delivery_errors = false 74 | 75 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 76 | # the I18n.default_locale when a translation cannot be found). 77 | config.i18n.fallbacks = true 78 | 79 | # Send deprecation notices to registered listeners. 80 | config.active_support.deprecation = :notify 81 | 82 | # Use default logging formatter so that PID and timestamp are not suppressed. 83 | config.log_formatter = ::Logger::Formatter.new 84 | 85 | # Use a different logger for distributed setups. 86 | # require 'syslog/logger' 87 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 88 | 89 | if ENV["RAILS_LOG_TO_STDOUT"].present? 90 | logger = ActiveSupport::Logger.new(STDOUT) 91 | logger.formatter = config.log_formatter 92 | config.logger = ActiveSupport::TaggedLogging.new(logger) 93 | end 94 | 95 | # Do not dump schema after migrations. 96 | config.active_record.dump_schema_after_migration = false 97 | 98 | config.action_mailer.default_url_options = { :host => 'jobs.devcongress.org' } 99 | 100 | ActionMailer::Base.smtp_settings = { 101 | address: "smtp.sendgrid.net", 102 | port: "25", 103 | authentication: :plain, 104 | user_name: ENV['SENDGRID_USERNAME'], 105 | password: ENV['SENDGRID_PASSWORD'], 106 | domain: ENV['SENDGRID_DOMAIN'] 107 | } 108 | 109 | end 110 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/base/normalize-context.scss: -------------------------------------------------------------------------------- 1 | /* THIS FILE IS GENERATED BY A BUILD SCRIPT - DO NOT EDIT! */ 2 | .pure article, 3 | .pure aside, 4 | .pure details, 5 | .pure figcaption, 6 | .pure figure, 7 | .pure footer, 8 | .pure header, 9 | .pure hgroup, 10 | .pure main, 11 | .pure nav, 12 | .pure section, 13 | .pure summary { 14 | display: block; 15 | } 16 | .pure audio, 17 | .pure canvas, 18 | .pure video { 19 | display: inline-block; 20 | } 21 | .pure audio:not([controls]) { 22 | display: none; 23 | height: 0; 24 | } 25 | .pure [hidden] { 26 | display: none; 27 | } 28 | .pure { 29 | font-size: 100%; 30 | -ms-text-size-adjust: 100%; 31 | -webkit-text-size-adjust: 100%; 32 | } 33 | .pure, 34 | .pure button, 35 | .pure input, 36 | .pure select, 37 | .pure textarea { 38 | font-family: sans-serif; 39 | } 40 | .pure body { 41 | margin: 0; 42 | } 43 | .pure a:focus { 44 | outline: thin dotted; 45 | } 46 | .pure a:active, 47 | .pure a:hover { 48 | outline: 0; 49 | } 50 | .pure h1 { 51 | font-size: 2em; 52 | margin: 0.67em 0; 53 | } 54 | .pure h2 { 55 | font-size: 1.5em; 56 | margin: 0.83em 0; 57 | } 58 | .pure h3 { 59 | font-size: 1.17em; 60 | margin: 1em 0; 61 | } 62 | .pure h4 { 63 | font-size: 1em; 64 | margin: 1.33em 0; 65 | } 66 | .pure h5 { 67 | font-size: 0.83em; 68 | margin: 1.67em 0; 69 | } 70 | .pure h6 { 71 | font-size: 0.67em; 72 | margin: 2.33em 0; 73 | } 74 | .pure abbr[title] { 75 | border-bottom: 1px dotted; 76 | } 77 | .pure b, 78 | .pure strong { 79 | font-weight: bold; 80 | } 81 | .pure blockquote { 82 | margin: 1em 40px; 83 | } 84 | .pure dfn { 85 | font-style: italic; 86 | } 87 | .pure hr { 88 | -moz-box-sizing: content-box; 89 | box-sizing: content-box; 90 | height: 0; 91 | } 92 | .pure mark { 93 | background: #ff0; 94 | color: #000; 95 | } 96 | .pure p, 97 | .pure pre { 98 | margin: 1em 0; 99 | } 100 | .pure code, 101 | .pure kbd, 102 | .pure pre, 103 | .pure samp { 104 | font-family: monospace , serif; 105 | _font-family: 'courier new' , monospace; 106 | font-size: 1em; 107 | } 108 | .pure pre { 109 | white-space: pre; 110 | white-space: pre-wrap; 111 | word-wrap: break-word; 112 | } 113 | .pure q { 114 | quotes: none; 115 | } 116 | .pure q:before, 117 | .pure q:after { 118 | content: ''; 119 | content: none; 120 | } 121 | .pure small { 122 | font-size: 80%; 123 | } 124 | .pure sub, 125 | .pure sup { 126 | font-size: 75%; 127 | line-height: 0; 128 | position: relative; 129 | vertical-align: baseline; 130 | } 131 | .pure sup { 132 | top: -0.5em; 133 | } 134 | .pure sub { 135 | bottom: -0.25em; 136 | } 137 | .pure dl, 138 | .pure menu, 139 | .pure ol, 140 | .pure ul { 141 | margin: 1em 0; 142 | } 143 | .pure dd { 144 | margin: 0 0 0 40px; 145 | } 146 | .pure menu, 147 | .pure ol, 148 | .pure ul { 149 | padding: 0 0 0 40px; 150 | } 151 | .pure nav ul, 152 | .pure nav ol { 153 | list-style: none; 154 | list-style-image: none; 155 | } 156 | .pure img { 157 | border: 0; 158 | -ms-interpolation-mode: bicubic; 159 | } 160 | .pure svg:not(:root) { 161 | overflow: hidden; 162 | } 163 | .pure figure { 164 | margin: 0; 165 | } 166 | .pure form { 167 | margin: 0; 168 | } 169 | .pure fieldset { 170 | border: 1px solid #c0c0c0; 171 | margin: 0 2px; 172 | padding: 0.35em 0.625em 0.75em; 173 | } 174 | .pure legend { 175 | border: 0; 176 | padding: 0; 177 | white-space: normal; 178 | } 179 | .pure button, 180 | .pure input, 181 | .pure select, 182 | .pure textarea { 183 | font-size: 100%; 184 | margin: 0; 185 | vertical-align: baseline; 186 | } 187 | .pure button, 188 | .pure input { 189 | line-height: normal; 190 | } 191 | .pure button, 192 | .pure select { 193 | text-transform: none; 194 | } 195 | .pure button, 196 | .pure input[type="button"], 197 | .pure input[type="reset"], 198 | .pure input[type="submit"] { 199 | -webkit-appearance: button; 200 | cursor: pointer; 201 | } 202 | .pure button[disabled], 203 | .pure input[disabled] { 204 | cursor: default; 205 | } 206 | .pure input[type="checkbox"], 207 | .pure input[type="radio"] { 208 | box-sizing: border-box; 209 | padding: 0; 210 | } 211 | .pure input[type="search"] { 212 | -webkit-appearance: textfield; 213 | -moz-box-sizing: content-box; 214 | -webkit-box-sizing: content-box; 215 | box-sizing: content-box; 216 | } 217 | .pure input[type="search"]::-webkit-search-cancel-button, 218 | .pure input[type="search"]::-webkit-search-decoration { 219 | -webkit-appearance: none; 220 | } 221 | .pure button::-moz-focus-inner, 222 | .pure input::-moz-focus-inner { 223 | border: 0; 224 | padding: 0; 225 | } 226 | .pure textarea { 227 | overflow: auto; 228 | vertical-align: top; 229 | } 230 | .pure table { 231 | border-collapse: collapse; 232 | border-spacing: 0; 233 | } 234 | -------------------------------------------------------------------------------- /app/assets/stylesheets/purecss/lists/lists-theme.scss: -------------------------------------------------------------------------------- 1 | 2 | /* from PURE menu-core.css */ 3 | /*csslint adjoining-classes:false, outline-none:false*/ 4 | /*TODO: Remove this lint rule override after a refactor of this code.*/ 5 | 6 | .pure-menu ul { 7 | position: absolute; 8 | visibility: hidden; 9 | } 10 | 11 | .pure-menu.pure-menu-open { 12 | visibility: visible; 13 | z-index: 2; 14 | width: 100%; 15 | } 16 | 17 | .pure-menu ul { 18 | left: -10000px; 19 | list-style: none; 20 | margin: 0; 21 | padding: 0; 22 | top: -10000px; 23 | z-index: 1; 24 | } 25 | 26 | .pure-menu > ul { position: relative; } 27 | 28 | .pure-menu-open > ul { 29 | left: 0; 30 | top: 0; 31 | visibility: visible; 32 | } 33 | 34 | .pure-menu-open > ul:focus { 35 | outline: 0; 36 | } 37 | 38 | .pure-menu li { 39 | position: relative; 40 | } 41 | 42 | .pure-menu a, .pure-menu .pure-menu-heading { 43 | display: block; 44 | color: inherit; 45 | line-height: 1.5em; 46 | padding: $menu-item-padding; 47 | text-decoration: none; 48 | white-space: nowrap; 49 | } 50 | 51 | .pure-menu.pure-menu-horizontal > .pure-menu-heading { 52 | display: inline-block; 53 | *display: inline; 54 | zoom: 1; 55 | margin: 0; 56 | vertical-align: middle; 57 | } 58 | .pure-menu.pure-menu-horizontal > ul { 59 | display: inline-block; 60 | *display: inline; 61 | zoom: 1; 62 | vertical-align: middle; 63 | /* height: 2.4em; removed for Skin Builder */ 64 | } 65 | 66 | .pure-menu li a { padding: $menu-item-padding; } 67 | 68 | .pure-menu-can-have-children > .pure-menu-label:after { 69 | content: '\25B8'; 70 | float: right; 71 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'DejaVu Sans', sans-serif; /* These specific fonts have the Unicode char we need. */ 72 | margin-right: -20px; 73 | margin-top: -1px; 74 | } 75 | 76 | .pure-menu-can-have-children > .pure-menu-label { 77 | padding-right: 30px; 78 | } 79 | 80 | .pure-menu-separator { 81 | background-color: $separator; 82 | display: block; 83 | height: 1px; 84 | font-size: 0; 85 | margin: 7px 2px; 86 | overflow: hidden; 87 | } 88 | 89 | .pure-menu-hidden { 90 | display: none; 91 | } 92 | 93 | /* FIXED MENU */ 94 | .pure-menu-fixed { 95 | position: fixed; 96 | top:0; 97 | left:0; 98 | width: 100%; 99 | } 100 | 101 | 102 | /* HORIZONTAL MENU CODE */ 103 | 104 | /* Initial menus should be inline-block so that they are horizontal */ 105 | .pure-menu-horizontal li { 106 | display: inline-block; 107 | *display: inline; 108 | zoom: 1; 109 | vertical-align: middle; 110 | } 111 | 112 | /* Submenus should still be display:block; */ 113 | .pure-menu-horizontal li li { 114 | display: block; 115 | } 116 | 117 | /* Content after should be down arrow */ 118 | .pure-menu-horizontal > .pure-menu-children > .pure-menu-can-have-children > .pure-menu-label:after { 119 | content: "\25BE"; 120 | } 121 | /*Add extra padding to elements that have the arrow so that the hover looks nice */ 122 | .pure-menu-horizontal > .pure-menu-children > .pure-menu-can-have-children > .pure-menu-label { 123 | padding-right: 30px; 124 | } 125 | 126 | /* Adjusting separator for vertical menus */ 127 | .pure-menu-horizontal li.pure-menu-separator { 128 | height: 50%; 129 | width: 1px; 130 | margin: 0 7px; 131 | } 132 | 133 | /* Submenus should be horizontal separator again */ 134 | .pure-menu-horizontal li li.pure-menu-separator { 135 | height: 1px; 136 | width: auto; 137 | margin: 7px 2px; 138 | } 139 | 140 | 141 | /* end from yuicss/menu-core.css *******************************************/ 142 | /* from yuicss menu-paginator.css */ 143 | /*csslint box-model:false*/ 144 | /*TODO: Remove this lint rule override after a refactor of this code.*/ 145 | 146 | .pure-paginator { 147 | 148 | /* `pure-g` Grid styles */ 149 | letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ 150 | *letter-spacing: normal; /* reset IE < 8 */ 151 | *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */ 152 | text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ 153 | 154 | /* `pure-paginator` Specific styles */ 155 | list-style: none; 156 | margin: 0; 157 | padding: 0; 158 | } 159 | .opera-only :-o-prefocus, 160 | .pure-paginator { 161 | word-spacing: -0.43em; 162 | } 163 | 164 | /* `pure-u` Grid styles */ 165 | .pure-paginator li { 166 | display: inline-block; 167 | *display: inline; /* IE < 8: fake inline-block */ 168 | zoom: 1; 169 | letter-spacing: normal; 170 | word-spacing: normal; 171 | vertical-align: top; 172 | text-rendering: auto; 173 | } 174 | .pure-paginator .pure-button { 175 | border-radius: 0; 176 | padding: 0.8em 1.4em; 177 | vertical-align: top; 178 | height: 1.1em; 179 | } 180 | .pure-paginator .pure-button:focus, 181 | .pure-paginator .pure-button:active { 182 | outline-style: none; 183 | } 184 | .pure-paginator .prev, 185 | .pure-paginator .next { 186 | /*color: #C0C1C3; allow .pure-button to color text*/ 187 | } 188 | .pure-paginator .prev { 189 | border-radius: $paginator-radius-prev; 190 | } 191 | .pure-paginator .next { 192 | border-radius: $paginator-radius-next; 193 | } 194 | /* end from PURE menu-paginator.css ******************************/ 195 | /* from PURE menu.css *******************************************/ 196 | /* MAIN MENU STYLING */ 197 | /*csslint adjoining-classes:false*/ 198 | /*TODO: Remove this lint rule override after a refactor of this code.*/ 199 | 200 | .pure-menu.pure-menu-open, 201 | .pure-menu.pure-menu-horizontal li .pure-menu-children { 202 | background: $menu-background; /* Old browsers */ 203 | border: 1px solid $menu-border; 204 | } 205 | 206 | /* remove borders for horizontal menus */ 207 | .pure-menu.pure-menu-horizontal, 208 | .pure-menu.pure-menu-horizontal .pure-menu-heading { 209 | border: none; 210 | } 211 | 212 | 213 | /* LINK STYLES */ 214 | 215 | .pure-menu a { 216 | border: 1px solid transparent; 217 | border-left: none; 218 | border-right: none; 219 | 220 | } 221 | 222 | .pure-menu a, 223 | .pure-menu .pure-menu-can-have-children > li:after { 224 | color: $menu-text; 225 | } 226 | 227 | .pure-menu .pure-menu-can-have-children > li:hover:after { 228 | color: $menu-hover-text; 229 | } 230 | 231 | /* Focus style for a dropdown menu-item when the parent has been opened */ 232 | .pure-menu .pure-menu-open { 233 | background: $menu-hover-background; 234 | } 235 | 236 | .pure-menu li a:hover, 237 | .pure-menu li a:focus { 238 | background: $menu-hover-background; 239 | } 240 | 241 | /* DISABLED STATES */ 242 | .pure-menu li.pure-menu-disabled a:hover, 243 | .pure-menu li.pure-menu-disabled a:focus { 244 | background: $menu-background; 245 | color: $menu-text-disabled; 246 | } 247 | 248 | .pure-menu .pure-menu-disabled > a { 249 | background-image: none; 250 | border-color: transparent; 251 | cursor: default; 252 | } 253 | 254 | .pure-menu .pure-menu-disabled > a, 255 | .pure-menu .pure-menu-can-have-children.pure-menu-disabled > a:after { 256 | color: $menu-text-disabled; 257 | } 258 | 259 | /* HEADINGS */ 260 | .pure-menu .pure-menu-heading { 261 | color: $heading-text; 262 | text-transform: uppercase; 263 | font-size: 90%; 264 | margin-top: 0.5em; 265 | border-bottom: solid 1px $fixed-menu-border-bottom; 266 | } 267 | 268 | 269 | /* SELECTED MENU ITEM */ 270 | .pure-menu li.pure-menu-selected a { 271 | background-color: $menu-selected-background; 272 | color: $menu-selected-text; 273 | } 274 | 275 | /* FIXED MENU */ 276 | .pure-menu.pure-menu-open.pure-menu-fixed { 277 | border: none; 278 | border-bottom: 1px solid $fixed-menu-border-bottom; 279 | } 280 | /* end from PURE menu.css ***********************************/ 281 | /* from PURE menu-responsive.css ****************************/ 282 | /* RESPONSIVE */ 283 | 284 | @media (max-width: 480px) { 285 | 286 | .pure-menu-horizontal { 287 | width:100%; 288 | } 289 | 290 | .pure-menu-children li { 291 | display: block; 292 | border-bottom:1px solid $menu-border; 293 | } 294 | 295 | } 296 | /* end from menu-responsive.css ******************/ 297 | 298 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.2.2) 5 | actionpack (= 5.2.2) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.2) 9 | actionpack (= 5.2.2) 10 | actionview (= 5.2.2) 11 | activejob (= 5.2.2) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.2) 15 | actionview (= 5.2.2) 16 | activesupport (= 5.2.2) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.2.2) 22 | activesupport (= 5.2.2) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.2.2) 28 | activesupport (= 5.2.2) 29 | globalid (>= 0.3.6) 30 | activemodel (5.2.2) 31 | activesupport (= 5.2.2) 32 | activerecord (5.2.2) 33 | activemodel (= 5.2.2) 34 | activesupport (= 5.2.2) 35 | arel (>= 9.0) 36 | activestorage (5.2.2) 37 | actionpack (= 5.2.2) 38 | activerecord (= 5.2.2) 39 | marcel (~> 0.3.1) 40 | activesupport (5.2.2) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 0.7, < 2) 43 | minitest (~> 5.1) 44 | tzinfo (~> 1.1) 45 | addressable (2.6.0) 46 | public_suffix (>= 2.0.2, < 4.0) 47 | annotate (2.7.4) 48 | activerecord (>= 3.2, < 6.0) 49 | rake (>= 10.4, < 13.0) 50 | archive-zip (0.11.0) 51 | io-like (~> 0.3.0) 52 | arel (9.0.0) 53 | bcrypt (3.1.12) 54 | bindex (0.5.0) 55 | bootsnap (1.3.2) 56 | msgpack (~> 1.0) 57 | buftok (0.2.0) 58 | builder (3.2.3) 59 | byebug (10.0.2) 60 | capybara (3.13.2) 61 | addressable 62 | mini_mime (>= 0.1.3) 63 | nokogiri (~> 1.8) 64 | rack (>= 1.6.0) 65 | rack-test (>= 0.6.3) 66 | regexp_parser (~> 1.2) 67 | xpath (~> 3.2) 68 | childprocess (0.9.0) 69 | ffi (~> 1.0, >= 1.0.11) 70 | chromedriver-helper (2.1.0) 71 | archive-zip (~> 0.10) 72 | nokogiri (~> 1.8) 73 | coffee-rails (4.2.2) 74 | coffee-script (>= 2.2.0) 75 | railties (>= 4.0.0) 76 | coffee-script (2.4.1) 77 | coffee-script-source 78 | execjs 79 | coffee-script-source (1.12.2) 80 | concurrent-ruby (1.1.4) 81 | crass (1.0.4) 82 | devise (4.5.0) 83 | bcrypt (~> 3.0) 84 | orm_adapter (~> 0.1) 85 | railties (>= 4.1.0, < 6.0) 86 | responders 87 | warden (~> 1.2.3) 88 | domain_name (0.5.20180417) 89 | unf (>= 0.0.5, < 1.0.0) 90 | equalizer (0.0.11) 91 | erubi (1.8.0) 92 | execjs (2.7.0) 93 | factory_bot (5.0.0) 94 | activesupport (>= 4.2.0) 95 | factory_bot_rails (5.0.0) 96 | factory_bot (~> 5.0.0) 97 | railties (>= 4.2.0) 98 | faker (1.9.1) 99 | i18n (>= 0.7) 100 | ffi (1.10.0) 101 | globalid (0.4.1) 102 | activesupport (>= 4.2.0) 103 | http (3.3.0) 104 | addressable (~> 2.3) 105 | http-cookie (~> 1.0) 106 | http-form_data (~> 2.0) 107 | http_parser.rb (~> 0.6.0) 108 | http-cookie (1.0.3) 109 | domain_name (~> 0.5) 110 | http-form_data (2.1.1) 111 | http_parser.rb (0.6.0) 112 | i18n (1.5.3) 113 | concurrent-ruby (~> 1.0) 114 | io-like (0.3.0) 115 | jbuilder (2.8.0) 116 | activesupport (>= 4.2.0) 117 | multi_json (>= 1.2) 118 | json (2.1.0) 119 | listen (3.1.5) 120 | rb-fsevent (~> 0.9, >= 0.9.4) 121 | rb-inotify (~> 0.9, >= 0.9.7) 122 | ruby_dep (~> 1.2) 123 | loofah (2.2.3) 124 | crass (~> 1.0.2) 125 | nokogiri (>= 1.5.9) 126 | mail (2.7.1) 127 | mini_mime (>= 0.1.1) 128 | marcel (0.3.3) 129 | mimemagic (~> 0.3.2) 130 | memoizable (0.4.2) 131 | thread_safe (~> 0.3, >= 0.3.1) 132 | method_source (0.9.2) 133 | mimemagic (0.3.3) 134 | mini_mime (1.0.1) 135 | mini_portile2 (2.4.0) 136 | minitest (5.11.3) 137 | minitest-matchers_vaccine (1.0.4) 138 | minitest (~> 5.0) 139 | msgpack (1.2.6) 140 | multi_json (1.13.1) 141 | multipart-post (2.0.0) 142 | naught (1.1.0) 143 | nio4r (2.3.1) 144 | nokogiri (1.10.1) 145 | mini_portile2 (~> 2.4.0) 146 | oauth (0.5.4) 147 | orm_adapter (0.5.0) 148 | pg (1.1.4) 149 | public_suffix (3.0.3) 150 | puma (3.12.0) 151 | rack (2.0.6) 152 | rack-test (1.1.0) 153 | rack (>= 1.0, < 3) 154 | rails (5.2.2) 155 | actioncable (= 5.2.2) 156 | actionmailer (= 5.2.2) 157 | actionpack (= 5.2.2) 158 | actionview (= 5.2.2) 159 | activejob (= 5.2.2) 160 | activemodel (= 5.2.2) 161 | activerecord (= 5.2.2) 162 | activestorage (= 5.2.2) 163 | activesupport (= 5.2.2) 164 | bundler (>= 1.3.0) 165 | railties (= 5.2.2) 166 | sprockets-rails (>= 2.0.0) 167 | rails-dom-testing (2.0.3) 168 | activesupport (>= 4.2.0) 169 | nokogiri (>= 1.6) 170 | rails-html-sanitizer (1.0.4) 171 | loofah (~> 2.2, >= 2.2.2) 172 | rails_12factor (0.0.3) 173 | rails_serve_static_assets 174 | rails_stdout_logging 175 | rails_serve_static_assets (0.0.5) 176 | rails_stdout_logging (0.0.5) 177 | railties (5.2.2) 178 | actionpack (= 5.2.2) 179 | activesupport (= 5.2.2) 180 | method_source 181 | rake (>= 0.8.7) 182 | thor (>= 0.19.0, < 2.0) 183 | rake (12.3.2) 184 | rb-fsevent (0.10.3) 185 | rb-inotify (0.10.0) 186 | ffi (~> 1.0) 187 | regexp_parser (1.3.0) 188 | responders (2.4.0) 189 | actionpack (>= 4.2.0, < 5.3) 190 | railties (>= 4.2.0, < 5.3) 191 | ruby_dep (1.5.0) 192 | rubyzip (1.2.2) 193 | sass (3.7.3) 194 | sass-listen (~> 4.0.0) 195 | sass-listen (4.0.0) 196 | rb-fsevent (~> 0.9, >= 0.9.4) 197 | rb-inotify (~> 0.9, >= 0.9.7) 198 | sass-rails (5.0.7) 199 | railties (>= 4.0.0, < 6) 200 | sass (~> 3.1) 201 | sprockets (>= 2.8, < 4.0) 202 | sprockets-rails (>= 2.0, < 4.0) 203 | tilt (>= 1.1, < 3) 204 | selenium-webdriver (3.141.0) 205 | childprocess (~> 0.5) 206 | rubyzip (~> 1.2, >= 1.2.2) 207 | sendgrid (1.2.4) 208 | json 209 | shoulda-matchers (3.1.3) 210 | activesupport (>= 4.0.0) 211 | simple_oauth (0.3.1) 212 | spring (2.0.2) 213 | activesupport (>= 4.2) 214 | spring-watcher-listen (2.0.1) 215 | listen (>= 2.7, < 4.0) 216 | spring (>= 1.2, < 3.0) 217 | sprockets (3.7.2) 218 | concurrent-ruby (~> 1.0) 219 | rack (> 1, < 3) 220 | sprockets-rails (3.2.1) 221 | actionpack (>= 4.0) 222 | activesupport (>= 4.0) 223 | sprockets (>= 3.0.0) 224 | thor (0.20.3) 225 | thread_safe (0.3.6) 226 | tilt (2.0.9) 227 | turbolinks (5.2.0) 228 | turbolinks-source (~> 5.2) 229 | turbolinks-source (5.2.0) 230 | twitter (6.2.0) 231 | addressable (~> 2.3) 232 | buftok (~> 0.2.0) 233 | equalizer (~> 0.0.11) 234 | http (~> 3.0) 235 | http-form_data (~> 2.0) 236 | http_parser.rb (~> 0.6.0) 237 | memoizable (~> 0.4.0) 238 | multipart-post (~> 2.0) 239 | naught (~> 1.0) 240 | simple_oauth (~> 0.3.0) 241 | tzinfo (1.2.5) 242 | thread_safe (~> 0.1) 243 | uglifier (4.1.20) 244 | execjs (>= 0.3.0, < 3) 245 | unf (0.1.4) 246 | unf_ext 247 | unf_ext (0.0.7.5) 248 | warden (1.2.8) 249 | rack (>= 2.0.6) 250 | web-console (3.7.0) 251 | actionview (>= 5.0) 252 | activemodel (>= 5.0) 253 | bindex (>= 0.4.0) 254 | railties (>= 5.0) 255 | websocket-driver (0.7.0) 256 | websocket-extensions (>= 0.1.0) 257 | websocket-extensions (0.1.3) 258 | xpath (3.2.0) 259 | nokogiri (~> 1.8) 260 | 261 | PLATFORMS 262 | ruby 263 | 264 | DEPENDENCIES 265 | annotate 266 | bootsnap (~> 1.3) 267 | byebug 268 | capybara (>= 2.15, < 4.0) 269 | chromedriver-helper 270 | coffee-rails (~> 4.2) 271 | devise (~> 4.4, >= 4.4.3) 272 | factory_bot_rails 273 | faker 274 | jbuilder (~> 2.5) 275 | listen (>= 3.0.5, < 3.2) 276 | minitest-matchers_vaccine 277 | oauth 278 | pg 279 | puma (~> 3.12) 280 | rails (~> 5.2.2) 281 | rails_12factor 282 | sass-rails (~> 5.0) 283 | selenium-webdriver 284 | sendgrid (~> 1.2, >= 1.2.4) 285 | shoulda-matchers 286 | spring 287 | spring-watcher-listen (~> 2.0.0) 288 | turbolinks (~> 5) 289 | twitter (~> 6.2) 290 | tzinfo-data 291 | uglifier (>= 1.3.0) 292 | web-console (>= 3.3.0) 293 | 294 | RUBY VERSION 295 | ruby 2.3.3p222 296 | 297 | BUNDLED WITH 298 | 1.16.6 299 | --------------------------------------------------------------------------------