├── 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 |
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 | ',
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 |
8 | <% @jobs.each do |job| %>
9 |
10 | <%= link_to job.title, job, :class => "job--list__item-link" %>
11 |
12 | <% end %>
13 |
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 |
7 |
8 |
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 | [](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 |
2 | <%= link_to image_tag( 'devcongress-jobs-logo.svg', alt: 'devcongress logo', height: '', width: '', class: 'dc-logo' ), root_path, :class => "navbar--logo" %>
3 |
13 |
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 |
9 | <% job.errors.full_messages.each do |message| %>
10 |
11 | <%= message %>
12 |
13 | <% end %>
14 |
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 |
--------------------------------------------------------------------------------