Commit ea44508a by Mai Hoang Thai Ha

Merge branch 'search'

parents 424b9d2b 9ccd2f4c
=> [#<ApplyJob:0x0000556661e18800
id: 74,
job_id: 893,
user_id: 2,
created_at: Thu, 07 Oct 2021 09:47:21.987489000 UTC +00:00,
updated_at: Thu, 07 Oct 2021 09:47:22.006742000 UTC +00:00,
user_name: "Mai Hoang Thai Ha`",
email: "messidauhoi@gmail.com">,
#<ApplyJob:0x0000556661e68b98
id: 75,
job_id: 893,
user_id: 2,
created_at: Fri, 08 Oct 2021 09:05:41.687605000 UTC +00:00,
updated_at: Fri, 08 Oct 2021 09:05:41.707387000 UTC +00:00,
user_name: "Mai Hoang Thai Ha`",
email: "messidauhoi@gmail.com">,
#<ApplyJob:0x0000556661e68a80
id: 76,
job_id: 890,
user_id: 6,
created_at: Thu, 21 Oct 2021 07:29:30.011249000 UTC +00:00,
updated_at: Thu, 21 Oct 2021 07:29:30.023913000 UTC +00:00,
user_name: "Mai Hoang Thai Ha`",
email: "mhthaiha@gmail.com">,
#<ApplyJob:0x0000556661e688f0
id: 77,
job_id: 889,
user_id: 6,
created_at: Thu, 21 Oct 2021 07:29:45.888611000 UTC +00:00,
updated_at: Thu, 21 Oct 2021 07:29:45.899844000 UTC +00:00,
user_name: "aasfasff ",
email: "mhthaiha@gmail.com">]
=> [#<ApplyJob:0x00005572d6d6c498
id: 74,
job_id: 893,
user_id: 2,
created_at: Thu, 07 Oct 2021 09:47:21.987489000 UTC +00:00,
updated_at: Thu, 07 Oct 2021 09:47:22.006742000 UTC +00:00,
user_name: "Mai Hoang Thai Ha`",
email: "messidauhoi@gmail.com">,
#<ApplyJob:0x00005572d6d6c3d0
id: 75,
job_id: 893,
user_id: 2,
created_at: Fri, 08 Oct 2021 09:05:41.687605000 UTC +00:00,
updated_at: Fri, 08 Oct 2021 09:05:41.707387000 UTC +00:00,
user_name: "Mai Hoang Thai Ha`",
email: "messidauhoi@gmail.com">,
#<ApplyJob:0x00005572d6d6c308
id: 76,
job_id: 890,
user_id: 6,
created_at: Thu, 21 Oct 2021 07:29:30.011249000 UTC +00:00,
updated_at: Thu, 21 Oct 2021 07:29:30.023913000 UTC +00:00,
user_name: "Mai Hoang Thai Ha`",
email: "mhthaiha@gmail.com">,
#<ApplyJob:0x00005572d6d6c240
id: 77,
job_id: 889,
user_id: 6,
created_at: Thu, 21 Oct 2021 07:29:45.888611000 UTC +00:00,
updated_at: Thu, 21 Oct 2021 07:29:45.899844000 UTC +00:00,
user_name: "aasfasff ",
email: "mhthaiha@gmail.com">]
......@@ -66,3 +66,5 @@ gem 'httparty', '~> 0.18.1'
gem 'active_storage_validations', '~> 0.9.5'
gem 'devise', '~> 4.8'
gem 'jquery-rails', '~> 4.4'
gem 'rsolr', '~> 2.3'
gem 'rsolr-ext', '~> 1.0', '>= 1.0.3'
GIT
remote: https://github.com/kaminari/kaminari
revision: 45f13dbdaa98be3733ffe6b0e8e948da6919f116
revision: 73b93405b95615b5ad3f53c3dffe419a59890cad
specs:
kaminari (1.2.1)
activesupport (>= 4.1.0)
......@@ -18,62 +18,62 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.4)
actionpack (= 6.1.4)
activesupport (= 6.1.4)
actioncable (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.4)
actionpack (= 6.1.4)
activejob (= 6.1.4)
activerecord (= 6.1.4)
activestorage (= 6.1.4)
activesupport (= 6.1.4)
actionmailbox (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
mail (>= 2.7.1)
actionmailer (6.1.4)
actionpack (= 6.1.4)
actionview (= 6.1.4)
activejob (= 6.1.4)
activesupport (= 6.1.4)
actionmailer (6.1.4.1)
actionpack (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activesupport (= 6.1.4.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.4)
actionview (= 6.1.4)
activesupport (= 6.1.4)
actionpack (6.1.4.1)
actionview (= 6.1.4.1)
activesupport (= 6.1.4.1)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.4)
actionpack (= 6.1.4)
activerecord (= 6.1.4)
activestorage (= 6.1.4)
activesupport (= 6.1.4)
actiontext (6.1.4.1)
actionpack (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
nokogiri (>= 1.8.5)
actionview (6.1.4)
activesupport (= 6.1.4)
actionview (6.1.4.1)
activesupport (= 6.1.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_storage_validations (0.9.5)
rails (>= 5.2.0)
activejob (6.1.4)
activesupport (= 6.1.4)
activejob (6.1.4.1)
activesupport (= 6.1.4.1)
globalid (>= 0.3.6)
activemodel (6.1.4)
activesupport (= 6.1.4)
activerecord (6.1.4)
activemodel (= 6.1.4)
activesupport (= 6.1.4)
activestorage (6.1.4)
actionpack (= 6.1.4)
activejob (= 6.1.4)
activerecord (= 6.1.4)
activesupport (= 6.1.4)
activemodel (6.1.4.1)
activesupport (= 6.1.4.1)
activerecord (6.1.4.1)
activemodel (= 6.1.4.1)
activesupport (= 6.1.4.1)
activestorage (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activesupport (= 6.1.4.1)
marcel (~> 1.0.0)
mini_mime (>= 1.1.0)
activesupport (6.1.4)
activesupport (6.1.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
......@@ -84,10 +84,10 @@ GEM
ast (2.4.2)
bcrypt (3.1.16)
bindex (0.8.1)
bootsnap (1.7.5)
bootsnap (1.9.1)
msgpack (~> 1.0)
builder (3.2.4)
bullet (6.1.4)
bullet (6.1.5)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
......@@ -110,9 +110,28 @@ GEM
responders
warden (~> 1.2.3)
erubi (1.10.0)
ffi (1.15.3)
globalid (0.4.2)
activesupport (>= 4.2.0)
faraday (1.8.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
ffi (1.15.4)
globalid (0.5.2)
activesupport (>= 5.0)
httparty (0.18.1)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
......@@ -124,29 +143,30 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
listen (3.5.1)
listen (3.7.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.10.0)
loofah (2.12.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (1.0.1)
marcel (1.0.2)
method_source (1.0.0)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0704)
mini_mime (1.1.0)
mime-types-data (3.2021.0901)
mini_mime (1.1.1)
minitest (5.14.4)
msgpack (1.4.2)
multi_xml (0.6.0)
multipart-post (2.1.1)
mysql2 (0.5.3)
nio4r (2.5.7)
nokogiri (1.11.7-x86_64-linux)
nio4r (2.5.8)
nokogiri (1.12.5-x86_64-linux)
racc (~> 1.4)
orm_adapter (0.5.0)
parallel (1.20.1)
parallel (1.21.0)
parser (3.0.2.0)
ast (~> 2.4.1)
pry (0.14.1)
......@@ -155,39 +175,39 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
puma (5.3.2)
puma (5.5.0)
nio4r (~> 2.0)
racc (1.5.2)
rack (2.2.3)
rack-mini-profiler (2.3.2)
rack-mini-profiler (2.3.3)
rack (>= 1.2.0)
rack-proxy (0.7.0)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.1.4)
actioncable (= 6.1.4)
actionmailbox (= 6.1.4)
actionmailer (= 6.1.4)
actionpack (= 6.1.4)
actiontext (= 6.1.4)
actionview (= 6.1.4)
activejob (= 6.1.4)
activemodel (= 6.1.4)
activerecord (= 6.1.4)
activestorage (= 6.1.4)
activesupport (= 6.1.4)
rails (6.1.4.1)
actioncable (= 6.1.4.1)
actionmailbox (= 6.1.4.1)
actionmailer (= 6.1.4.1)
actionpack (= 6.1.4.1)
actiontext (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activemodel (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
bundler (>= 1.15.0)
railties (= 6.1.4)
railties (= 6.1.4.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
railties (6.1.4)
actionpack (= 6.1.4)
activesupport (= 6.1.4)
railties (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
method_source
rake (>= 0.13)
thor (~> 1.0)
......@@ -201,22 +221,28 @@ GEM
actionpack (>= 5.0)
railties (>= 5.0)
rexml (3.2.5)
rubocop (1.18.3)
rsolr (2.3.0)
builder (>= 2.1.2)
faraday (>= 0.9.0)
rsolr-ext (1.0.3)
rsolr (>= 1.0.2)
rubocop (1.22.0)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.7.0, < 2.0)
rubocop-ast (>= 1.12.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.8.0)
rubocop-ast (1.12.0)
parser (>= 3.0.1.1)
rubocop-rails (2.11.3)
rubocop-rails (2.12.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1)
......@@ -239,7 +265,7 @@ GEM
actionpack (>= 3.1)
railties (>= 3.1)
slim (>= 3.0, < 5.0)
spring (2.1.1)
spring (3.0.0)
sprockets (4.0.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
......@@ -255,7 +281,7 @@ GEM
turbolinks-source (5.2.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
unicode-display_width (2.0.0)
unicode-display_width (2.1.0)
uniform_notifier (1.14.2)
warden (1.2.9)
rack (>= 2.0.9)
......@@ -264,11 +290,11 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webdrivers (4.6.0)
webdrivers (4.6.1)
nokogiri (~> 1.6)
rubyzip (>= 1.3.0)
selenium-webdriver (>= 3.0, < 4.0)
webpacker (5.4.0)
webpacker (5.4.3)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
......@@ -302,6 +328,8 @@ DEPENDENCIES
puma (~> 5.0)
rack-mini-profiler (~> 2.0)
rails (~> 6.1.3, >= 6.1.3.2)
rsolr (~> 2.3)
rsolr-ext (~> 1.0, >= 1.0.3)
rubocop-rails (~> 2.11, >= 2.11.3)
sass-rails (~> 6.0)
selenium-webdriver
......
// Place all the styles related to the admins controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/
# frozen_string_literal: true
class Admins::ConfirmationsController < Devise::ConfirmationsController
include Accessible
# GET /resource/confirmation/new
# def new
# super
# end
# POST /resource/confirmation
# def create
# super
# end
# GET /resource/confirmation?confirmation_token=abcdef
# def show
# super
# end
# protected
# The path used after resending confirmation instructions.
# def after_resending_confirmation_instructions_path_for(resource_name)
# super(resource_name)
# end
# The path used after confirmation.
# def after_confirmation_path_for(resource_name, resource)
# super(resource_name, resource)
# end
end
# frozen_string_literal: true
class Admins::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# You should configure your model like this:
# devise :omniauthable, omniauth_providers: [:twitter]
# You should also create an action method in this controller like this:
# def twitter
# end
# More info at:
# https://github.com/heartcombo/devise#omniauth
# GET|POST /resource/auth/twitter
# def passthru
# super
# end
# GET|POST /users/auth/twitter/callback
# def failure
# super
# end
# protected
# The path used when OmniAuth fails
# def after_omniauth_failure_path_for(scope)
# super(scope)
# end
end
# frozen_string_literal: true
class Admins::PasswordsController < Devise::PasswordsController
# GET /resource/password/new
# def new
# super
# end
# POST /resource/password
# def create
# super
# end
# GET /resource/password/edit?reset_password_token=abcdef
# def edit
# super
# end
# PUT /resource/password
# def update
# super
# end
# protected
# def after_resetting_password_path_for(resource)
# super(resource)
# end
# The path used after sending reset password instructions
# def after_sending_reset_password_instructions_path_for(resource_name)
# super(resource_name)
# end
end
# frozen_string_literal: true
class Admins::RegistrationsController < Devise::RegistrationsController
include Accessible
skip_before_action :check_user, except: %i[new create]
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
# def new
# super
# end
# POST /resource
# def create
# super
# end
# GET /resource/edit
# def edit
# super
# end
# PUT /resource
# def update
# super
# end
# DELETE /resource
# def destroy
# super
# end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
# def cancel
# super
# end
# protected
# If you have extra params to permit, append them to the sanitizer.
# def configure_sign_up_params
# devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
# end
# If you have extra params to permit, append them to the sanitizer.
# def configure_account_update_params
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
# end
# The path used after sign up.
# def after_sign_up_path_for(resource)
# super(resource)
# end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end
# frozen_string_literal: true
class Admins::SessionsController < Devise::SessionsController
include Accessible
skip_before_action :check_user, only: :destroy
# before_action :configure_sign_in_params, only: [:create]
# GET /resource/sign_in
# def new
# super
# end
# POST /resource/sign_in
# def create
# super
# end
# DELETE /resource/sign_out
# def destroy
# super
# end
# protected
# If you have extra params to permit, append them to the sanitizer.
# def configure_sign_in_params
# devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
# end
end
# frozen_string_literal: true
class Admins::UnlocksController < Devise::UnlocksController
# GET /resource/unlock/new
# def new
# super
# end
# POST /resource/unlock
# def create
# super
# end
# GET /resource/unlock?unlock_token=abcdef
# def show
# super
# end
# protected
# The path used after sending unlock password instructions
# def after_sending_unlock_instructions_path_for(resource)
# super(resource)
# end
# The path used after unlocking the resource
# def after_unlock_path_for(resource)
# super(resource)
# end
end
class AdminsController < ApplicationController
before_action :authenticate_admin!
before_action :apply_jobs
def applies
@city = City.all.map { |c| [c.name, c.id] }
@industry = Industry.all.map { |i| [i.name, i.id] }
return :applies if params[:search]
redirect_to admin_export_csv_path(format: :csv, params: applied_params) if params[:csv]
end
def export_csv
date = DateTime.current.to_formatted_s(:number)
csv = ExportCsvService.new(@apply_jobs, Admin::CSV_ATTRIBUTES)
respond_to do |format|
format.csv { send_data csv.perform, filename: "#{date}_applied.csv" }
end
end
private
def apply_jobs
admin_search = AdminSearchService.new(applied_params)
@apply_jobs = admin_search.search
end
def applied_params
params.permit(:email, :city, :industry, :date_start, :date_end)
end
end
......@@ -14,10 +14,12 @@ class ApplicationController < ActionController::Base
private
def logged_in_user
unless user_signed_in?
flash[:danger] = 'Please log in.'
redirect_to new_user_session_path
def city_industry_list
@city_slug_list = City.pluck(:name)
@industry_slug_list = Industry.pluck(:name)
end
def salary_search
@salary_range = [3_000_000, 7_000_000, 10_000_000, 15_000_000, 20_000_000, 30_000_000]
end
end
class CitiesController < ApplicationController
def index
solr = Solr.new
@regions = City.regions.keys
@job_quantity_by_region = City.job_quantity_by_region
@job_quantity_by_region = solr.facet_query('city_id')
end
end
module Accessible
extend ActiveSupport::Concern
included do
before_action :check_user
end
protected
def check_user
if current_admin
flash.clear
# if you have rails_admin. You can redirect anywhere really
# redirect_to(rails_admin.dashboard_path) and return
elsif current_user
flash.clear
set_flash_message!(:notice, :signed_in)
# The authenticated root path can be defined in your routes.rb in: devise_scope :user do...
redirect_to(root_path) and return
end
end
end
class FavoriteJobsController < ApplicationController
before_action :logged_in_user
# before_action :logged_in_user
before_action :authenticate_user!
before_action :load_job, only: %i[create destroy]
def index
......
class HistoryJobsController < ApplicationController
before_action :logged_in_user
# before_action :logged_in_user
before_action :authenticate_user!
def index
@history_jobs = current_user.history_jobs.eager_load(job: %i[cities cities_jobs company])
......
class IndustriesController < ApplicationController
def index
@job_quantity_by_industry = Industry.job_quantity_by_industry
solr = Solr.new
@job_quantity_by_industry = solr.facet_query('industry_id')
end
end
class JobsController < ApplicationController
before_action :history, only: :show
before_action :salary_search
before_action :city_industry_list
before_action :name, only: :index
def index
if job_params.present?
search
solr = Solr.new(params)
jobs_query = if params[:city_slug]
solr.query_by_city
elsif params[:industry_slug]
solr.query_by_industry
else
@jobs = Job.sort_by_date(page: params[:page], per_page: Job::JOB_PER_PAGE)
solr.query_all
end
get_jobs(jobs_query)
@jobs = Kaminari.paginate_array(@jobs).page(params[:page]).per(Job::JOB_PER_PAGE)
end
def show
......@@ -23,15 +31,19 @@ class JobsController < ApplicationController
history.update(updated_at: Time.current)
end
def search
if job_params.key?(:model) && job_params.key?(:slug) # search by model
model = params[:model].classify.constantize
@target = model.find_by(slug: job_params[:slug])
@jobs = @target.jobs.sort_by_date(page: params[:page], per_page: Job::JOB_PER_PAGE)
def name
@name = if params[:city_slug]
City.find_by(slug: params[:city_slug]).name
elsif params[:industry_slug]
Industry.find_by(slug: params[:industry_slug]).name
else
'Jobs'
end
end
def job_params
params.permit(:model, :slug, :search)
def get_jobs(query)
jobs_ids = query['response']['docs'].map { |j| j['job_id'] }
@jobs = Job.eager_load(:cities, :cities_jobs, :company).find(jobs_ids)
@jobs_count = query['numFound']
end
end
class TopController < ApplicationController
before_action :salary_search
before_action :city_industry_list
def index
@total_job = Job.count
@latest_jobs = Job.sort_by_date(per_page: Job::LATEST_JOBS_LIMIT)
......
# frozen_string_literal: true
class Users::ConfirmationsController < Devise::ConfirmationsController
include Accessible
# GET /resource/confirmation/new
# POST /resource/confirmation
......
# frozen_string_literal: true
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
include Accessible
# You should configure your model like this:
# devise :omniauthable, omniauth_providers: [:twitter]
......
# frozen_string_literal: true
class Users::PasswordsController < Devise::PasswordsController
include Accessible
# GET /resource/password/new
# def new
# super
......
# frozen_string_literal: true
class Users::RegistrationsController < Devise::RegistrationsController
include Accessible
skip_before_action :check_user, except: %i[new, create]
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
......
# frozen_string_literal: true
class Users::SessionsController < Devise::SessionsController
include Accessible
skip_before_action :check_user, only: :destroy
# before_action :configure_sign_in_params, only: [:create]
# GET /resource/sign_in
......
# frozen_string_literal: true
class Users::UnlocksController < Devise::UnlocksController
include Accessible
# GET /resource/unlock/new
# def new
# super
......
class UsersController < ApplicationController
# before_action :logged_in_user
def show
@user = current_user
end
......
module AdminsHelper
end
......@@ -14,16 +14,34 @@ module ApplicationHelper
def show_breadcrumb(list, model)
a = list.map do |item|
link_to item.name, job_list_path(model: model, slug: item.slug), class: 'mx-1 text-decoration-none text-info'
case model
when :city
link_to item.name, city_jobs_path(city_slug: item.slug), class: 'mx-1 text-decoration-none text-info'
when :industry
link_to item.name, industry_jobs_path(industry_slug: item.slug), class: 'mx-1 text-decoration-none text-info'
end
end
a.join('|').html_safe
end
# breadcrumb
# def show_breadcrumb(job)
# a = link_to 'Home', root_path, class: 'text-decoration-none text-info mx-1 '
# span = tag.span '>', class: 'mx-1'
# arr = [a, show_item(job.cities, :city), show_item(job.industries, :industry), job.title]
# arr.join(span).html_safe
# end
def view_search_result
if params[:search].blank?
if params[:salary].to_i.zero?
@name.to_s
else
"Totals #{@jobs_count} jobs"
end
else
"Totals #{@jobs_count} result for #{params[:search]} in #{@name}"
end
end
def vnd_format(num)
if num.zero?
'Tất cả'
else
num_groups = num.to_s.chars.to_a.reverse.each_slice(3)
"Từ #{num_groups.map(&:join).join('.').reverse} đ"
end
end
end
class Admin < ApplicationRecord
CSV_ATTRIBUTES = %w[id job_id user_id created_at updated_at user_name email].freeze
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
end
......@@ -15,4 +15,9 @@ class ApplyJob < ApplicationRecord
size:
{ less_than: 5.megabytes,
message: 'should be less than 5MB' }
def self.admin_queries
includes(:job, cv_attachment: :blob)
.joins('INNER JOIN cities_jobs ON cities_jobs.job_id = apply_jobs.job_id')
.joins('INNER JOIN industries_jobs ON industries_jobs.job_id = apply_jobs.job_id')
end
end
class AdminSearchService
def initialize(applied_params)
@params = applied_params
end
def search
if @params.present?
date_value
params_query
Kaminari.paginate_array(@query).page(@params[:page]).per(Job::JOB_PER_PAGE)
else
ApplyJob.includes(:job, cv_attachment: :blob).all.page(@params[:page]).per(Job::JOB_PER_PAGE)
end
end
def date_value
return unless @params[:date_start].present? && @params[:date_end].present?
@d_start = Date.parse(@params[:date_start]).beginning_of_day
@d_end = Date.parse(@params[:date_end]).end_of_day
end
def params_query
search_params = { apply_jobs: { email: @params[:email],
created_at: (@d_start..@d_end if @d_start && @d_end) },
cities_jobs: { city_id: @params[:city] },
industries_jobs: { industry_id: @params[:industry] } }
.transform_values { |v| v.delete_if { |_, value| value.blank? } }
mapping = search_params.delete_if { |_, value| value.blank? }
@query = ApplyJob.admin_queries.where(mapping).uniq
end
end
class ExportCsvService
def initialize(object, attributes)
@attributes = attributes
@object = object
@header = attributes.map { |attr| I18n.t("header_csv.#{attr}") }
end
def perform
CSV.generate do |csv|
csv << header
object.each do |object|
csv << attributes.map { |attr| object.public_send(attr) }
end
end
end
private
attr_reader :attributes, :object, :header
end
class Solr
def initialize(params = { search: '*:*' })
@solr = RSolr.connect(url: 'http://localhost:8983/solr/VenJob/')
@params = params
end
def add_db
Job.includes(:cities, :industries, :company).find_in_batches do |jobs|
jobs_index = jobs.map do |job|
{
job_id: job.id,
job_title: job.title,
company_name: job.company.name,
job_level: job.position,
min_salary: job.min_salary,
max_salary: job.max_salary,
cities_name: job.cities.map(&:name),
city_id: job.cities&.ids,
industries_name: job.industries.map(&:name),
industry_id: job.industries&.ids
}
end
jobs_index.each do |add_jobs|
@solr.add(add_jobs)
rescue Exception
next
end
@solr.commit
end
end
def delete_db
@solr.delete_by_query('*:*')
@solr.commit
end
def query_all
q = "search:*#{@params[:search]}*"
fq = []
fq.push("min_salary: [#{@params[:salary]} TO *]") if @params[:salary].present?
fq.push("cities_name: \"#{escape_str(@params[:city])}\"") if @params[:city].present?
fq.push("industries_name: \"#{escape_str(@params[:industry])}\"") if @params[:industry].present?
send_request(q, fq)
end
def query_by_city
city = City.find_by(slug: @params[:city_slug])
return { "numFound": 0, "docs": [] } unless city
city_name = city.name
q = "search:*#{@params[:search]}*"
fq = if @params[:salary]
["cities_name: \"#{escape_str(city_name)}\"", "min_salary: [#{@params[:salary]} TO *]"]
else
"cities_name: \"#{escape_str(city_name)}\""
end
send_request(q, fq)
end
def query_by_industry
industry = Industry.find_by(slug: @params[:industry_slug])
return { "numFound": 0, "docs": [] } unless industry
industry_name = industry.name
q = "search:*#{@params[:search]}*"
fq = if @params[:salary]
["industries_name: \"#{escape_str(industry_name)}\"", "min_salary: [#{@params[:salary]} TO *]"]
else
"industries_name: \"#{escape_str(industry_name)}\""
end
send_request(q, fq)
end
def facet_query(facet_param)
q = '*:*'
fq = '*:*'
facet = facet_param
response = send_request(q, fq, facet)
query = response['facet_counts']['facet_fields'][facet]
ids = query.each_slice(2).map { |a, _| a }
jobs_count = query.each_slice(2).map { |_, b| b }
return city_handle(ids, jobs_count) if facet == 'city_id'
return industry_handle(ids, jobs_count) if facet == 'industry_id'
end
def send_request(q_param, fq_param, facet_param = nil)
@solr.get 'select', params: {
'facet': true,
'facet.field': facet_param,
'q': q_param,
'fq': fq_param,
'rows': Job.count
}
end
def escape_str(str)
# RSolr.solr_escape(str)
CGI::escapeHTML(str)
end
def city_handle(ids, jobs_count)
city = City.find(ids).map { |c| [c.region, c.name, c.slug] }
city_count = city.zip(jobs_count).flatten.compact.each_slice(4).map { |c| c }
city_count.group_by(&:shift)
end
def industry_handle(ids, jobs_count)
industry = Industry.find(ids).map { |i| [i.name, i.slug] }
industry.zip(jobs_count).flatten.compact.each_slice(3).map { |c| c }
end
end
- provide(:title, 'All applies jobs')
/ search
.container.mt-5
= form_with(url: admin_applies_jobs_path, method: :get, local: true) do |f|
.row.mb-2.form-group
.col-2
= f.label :email, 'Email', class: 'form-label'
.col-10
= f.text_field :email, class: 'form-control', value: params[:email]
.row.mb-2.form-group
.col-2
= f.label :city, 'City'
.col-10
= f.select :city, options_for_select(@city, selected: params[:city]), { include_blank: 'Select city' }, { class: 'form-select' }
.row.mb-2.form-group
.col-2
= f.label :industry, 'Industry', class: 'form-label'
.col-10
= f.select :industry, options_for_select(@industry, selected: params[:industry]), { include_blank: 'Select industry' }, { class:'form-select' }
.row.mb-2.form-group
.col-6
.row
.col-4
= f.label :date_start, 'From', class: 'form-label'
.col-8
= f.date_field :date_start, value: params[:date_start], class: 'form-control'
.col-6
.row
.col-4
= f.label :date_end, 'To', class: 'form-label'
.col-8
= f.date_field :date_end, value: params[:date_end], class: 'form-control'
.row
.col-6.d-flex.justify-content-center
= f.submit 'Search', name: 'search', class: 'btn btn-primary w-50 my-4 btn-height',data: { disable_with: false }
.col-6.d-flex.justify-content-center
= f.submit 'Export', name: 'csv',class: 'btn btn-primary w-50 my-4 btn-height',data: { disable_with: false }
/result
.container
h2.my-5.text-center
| All applies jobs
.container
.no-padding.d-flex.align-items-center.flex-column
.page-info.p-2
= page_entries_info @apply_jobs
.page-info.p-2
= paginate @apply_jobs
.container
- @apply_jobs.each do |apply|
.apply-item
.apply-job-title
= link_to apply.job.title, apply.job, class: 'job-title fs-3 text-decoration-none text-reset'
.apply-user-name.text-secondary
p.fw-bold
| Candidate Name:
span.fw-normal.text-dark
= apply.user_name
.apply-cv
p.fw-bold.text-secondary
| Candidate' CV:
- if apply.cv.attached?
span.fw-normal
= link_to apply.cv.filename, rails_blob_path(apply.cv, disposition: "attachment")
- else
span.fw-normal.text-dark
= 'cv file not uploaded yet'
.d-flex.align-items-center.justify-content-between
.apply-user-email
p.fw-bold.text-secondary
| Candidate's Email:
span.fw-normal.text-dark
= apply.email
.apply-date
p.fst-italic.text-secondary
| Applied at:
span.fw-normal.text-dark
= apply.created_at.strftime('%d/%m/%Y')
hr
.container
.no-padding.d-flex.align-items-center.flex-column
.page-info.p-2
= page_entries_info @apply_jobs
.page-info.p-2
= paginate @apply_jobs
<h2>Resend confirmation instructions</h2>
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "admins/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
</div>
<div class="actions">
<%= f.submit "Resend confirmation instructions" %>
</div>
<% end %>
<%= render "admins/shared/links" %>
<p>Welcome <%= @email %>!</p>
<p>You can confirm your account email through the link below:</p>
<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
<p>Hello <%= @email %>!</p>
<% if @resource.try(:unconfirmed_email?) %>
<p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
<% else %>
<p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
<% end %>
<p>Hello <%= @resource.email %>!</p>
<p>We're contacting you to notify you that your password has been changed.</p>
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
<p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
<p>Hello <%= @resource.email %>!</p>
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
<p>Click the link below to unlock your account:</p>
<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>
- provide(:title, 'Admin Change password')
.container
.row.justify-content-center
.col-6
h2.text-center.my-5
| Set your password
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f|
= render 'admins/shared/error_messages', resource: resource
= f.hidden_field :reset_password_token
.field.row.mb-5.form-group
.col-4
= f.label :password, 'New password', class: 'form-label'
.col-8
= f.password_field :password, autofocus: true, autocomplete: 'new-password', class: 'form-control mb-2'
span.form-msg
.field.row.mb-5.form-group
.col-4
= f.label :password_confirmation, 'Password Confirmation', class: 'form-label'
.col-8
= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form-control mb-2'
span.form-msg
.actions.row.justify-content-end
.col-8
= f.submit 'Set my password', class: 'btn btn-primary w-50 my-4', data: { disable_with: false }
javascript:
Validator({
form: '#new_admin',
errorSelector: '.form-msg',
rules: [
Validator.isRequired('#admin_password'),
Validator.minLenght('#admin_password', 6, 'Use 6 characters or more for your password'),
Validator.isRequired('#admin_password_confirmation'),
Validator.isConfirmed('#admin_password_confirmation', function() {
return document.querySelector('#new_admin #admin_password').value;
}, 'Those passwords didn’t match.')
]
})
- provide(:title, 'Admin Forgot password')
.container
.row.justify-content-center
.col-6
h2.text-center.my-5
| Forgot password
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f|
= render 'admins/shared/error_messages', resource: resource
.field.row.form-group.mb-4
.col-2
= f.label :email, class: 'form-label'
.col-10
= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control mb-2'
span.form-msg
- if devise_mapping.confirmable? && controller_name != 'confirmations'
p
= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: 'text-decoration-none'
.actions.row.justify-content-end
.col-10.d-flex.justify-content-center
= f.submit 'Confirm your email', class: 'btn btn-primary w-75 my-4', data: { disable_with: false }
javascript:
Validator({
form: '#new_admin',
errorSelector: '.form-msg',
rules: [
Validator.isRequired('#admin_email'),
Validator.isEmail('#admin_email')
]
})
- provide(:title, 'Admin Edit profile')
.container
.row.justify-content-center
.col-8
h2.text-center.my-5
| Edit
= resource_name.to_s.humanize
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f|
= render 'admins/shared/error_messages', resource: resource
.field.row.mb-2.form-group
.col-3
= f.label :email, class: 'form-label'
.col-9
= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control mb-2'
span.form-msg
.field.row.mb-2.form-group
- if devise_mapping.confirmable? && resource.pending_reconfirmation?
div
| Currently waiting confirmation for:
= resource.unconfirmed_email
.row.my-4.justify-content-end
.col-9
hr
p
|(leave password blank if you don't want to change it)
.field.row.mb-2.form-group
.col-3
= f.label :password, class: 'form-label'
.col-9
= f.password_field :password, autocomplete: 'new-password', class: 'form-control mb-2'
span.form-msg
.field.row.mb-2.form-group
.col-3
= f.label :password_confirmation, class: 'form-label'
.col-9
= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form-control mb-2'
span.form-msg
.field
.col-3
= f.label :current_password, class: 'form-label'
.col-9
= f.password_field :current_password, autocomplete: "current-password", class: 'form-control mb-2'
.actions.row.justify-content-end
.col-9
= f.submit 'Done', class: 'btn btn-primary w-50 my-4', data: { disable_with: false }
hr
h3
| Delete my account
p
| Click the button below to delete account
= button_to 'Delete', registration_path(resource_name), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-primary w-100 my-4'
= link_to 'Home', :back
javascript:
Validator({
form: '#edit_admin',
errorSelector: '.form-msg',
rules: [
Validator.isRequired('#admin_email'),
Validator.isEmail('#admin_email'),
Validator.isRequired('#admin_name')
]
})
\ No newline at end of file
- provide(:title, 'Admin Sign Up')
.container
.row.justify-content-center
.col-6
h2.text-center.my-5
| Sign up
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= render 'users/shared/error_messages', resource: resource
.field.row.form-group.mb-4
.col-2
= f.label :email, class: 'form-label'
.col-10
= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control mb-2'
span.form-msg
.field.row.form-group.mb-4
.col-2
= f.label :password, class: 'form-label'
.col-10
= f.password_field :password, autocomplete: 'new-password', class: 'form-control mb-2'
span.form-msg
.field.row.form-group.mb-4
.col-2
= f.label :password_confirmation, class: 'form-label'
.col-10
= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form-control mb-2'
span.form-msg
.actions
= f.submit 'Sign up', class: 'btn btn-primary w-100 my-5'
javascript:
Validator({
form: '#new_admin',
errorSelector: '.form-msg',
rules: [
Validator.isRequired('#admin_email'),
Validator.isEmail('#admin_email'),
Validator.isRequired('#admin_password'),
Validator.isRequired('#admin_password_confirmation')
Validator.isConfirmed('#admin_password_confirmation', function() {
return document.querySelector('#new_admin #admin_password').value;
}, 'Those passwords didn’t match.')
]
})
- provide(:title, 'Log in')
.container
.row.justify-content-center
.col-6
h2.text-center.my-5
| Log in Admin
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
.field.row.form-group.mb-4
.col-2
= f.label :email, class: 'form-label'
.col-10
= f.email_field :email, autofocus: true, autocomplete: 'email', class: 'form-control mb-2'
span.form-msg
.field.row.form-group.mb-4
.col-2
= f.label :password, class: 'form-label'
.col-10
= f.password_field :password, autocomplete: 'current-password', class: 'form-control mb-2'
span.form-msg
- if devise_mapping.rememberable?
.field
= f.check_box :remember_me
= f.label :remember_me, class: 'form-label mx-2'
- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
= link_to 'Forgot password?', forgot_password_path, class: 'text-decoration-none'
.actions.row.justify-content-end
.col-10.d-flex.justify-content-around
= f.submit 'Log in', class: 'btn btn-primary w-25', data: { disable_with: false }
= link_to 'Sign up', new_registration_path(resource_name), class: 'text-decoration-none w-25 btn btn-secondary'
javascript:
Validator({
form: '#new_admin',
errorSelector: '.form-msg',
rules: [
Validator.isRequired('#admin_email'),
Validator.isEmail('#admin_email'),
Validator.isRequired('#admin_password')
]
})
- if resource.errors.any?
#error_explanation.bg_danger
.alert.alert-danger
= I18n.t("errors.messages.not_saved", count: resource.errors.count, resource: resource.class.model_name.human.downcase)
ul.mb-3
- resource.errors.full_messages.each do |message|
li.text-danger
= message
\ No newline at end of file
- if controller_name != 'sessions'
= link_to 'Log in', new_session_path(resource_name), class: 'text-decoration-none'
br/
- if devise_mapping.registerable? && controller_name != 'registrations'
= link_to 'Sign up', new_registration_path(resource_name), class: 'text-decoration-none btn btn-primary'
br/
- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
= link_to 'Forgot your password?', new_password_path(resource_name), class: 'text-decoration-none'
br/
- if devise_mapping.confirmable? && controller_name != 'confirmations'
= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: 'text-decoration-none'
br/
- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), class: 'text-decoration-none'
br/
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post, class: 'text-decoration-none'
br/
<h2>Resend unlock instructions</h2>
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= render "admins/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="actions">
<%= f.submit "Resend unlock instructions" %>
</div>
<% end %>
<%= render "admins/shared/links" %>
......@@ -18,9 +18,9 @@
= region.upcase
hr.my-4
.row
- city_job_count.each do |city, count|
- city_job_count.each do |city|
.col-lg-4.col-md-6.text-center
.mt-5
.city-item
h3.h4.mb-2.see-more-text= link_to city[0], job_list_path(model: :city, slug: city[1]) , class:'text-decoration-none fw-normal text-reset'
p.text-muted.mb-0= pluralize(count, 'job')
\ No newline at end of file
h3.h4.mb-2.see-more-text= link_to city[0], city_jobs_path(city_slug: city[1]) , class:'text-decoration-none fw-normal text-reset'
p.text-muted.mb-0= pluralize(city[2], 'job')
\ No newline at end of file
......@@ -3,10 +3,10 @@
h2.mt-0 INDUSTRIES
hr.my-4
#info-box.row
- @job_quantity_by_industry.each do |name, job_count|
- @job_quantity_by_industry.each do |industry|
.col-lg-6.col-md-6.text-center
.mt-5
h3.h4.mb-2.see-more-text= link_to name[0], job_list_path(model: :industry, slug: name[1]) , class:'text-decoration-none fw-normal text-reset'
p.text-muted.mb-0= pluralize(job_count, 'job')
h3.h4.mb-2.see-more-text= link_to industry[0], industry_jobs_path(industry_slug: industry[1]) , class:'text-decoration-none fw-normal text-reset'
p.text-muted.mb-0= pluralize(industry[2], 'job')
hr.divider.my-4
- provide(:title, 'Job list page')
/ search box
= render 'shared/search'
.container
h1.mt-5
= @name
h3
= view_search_result
hr.my-4
.no-padding.d-flex.align-items-center.flex-column
.page-info.p-2
......
......@@ -15,8 +15,17 @@ header.navbar.navbar-fixed-top.navbar-inverse
= link_to "Profile", user_profile_path
li
= link_to "Log out", destroy_user_session_path, method: :delete
- elsif admin_signed_in?
li
= link_to "Applies job", admin_applies_jobs_path
li
= link_to "Profile", '#'
li
= link_to "Log out", destroy_admin_session_path, method: :delete
- else
li
= link_to "Admin", admin_login_path
li
= link_to "Log in", login_path
li
= link_to "Sign up", signup_path
\ No newline at end of file
.search.text-center.my-5
.container
-if params[:city_slug]
= form_tag(city_jobs_path(city_slug: params[:city_slug]), method: :get, class: "form-inline") do
.row
.col-md-10.mb-md-0.no-padding
span.fa.fa-search.form-control-feedback
= search_field_tag :search, params[:search], placeholder: 'Find a job', class: 'form-control rounded-left no-border-radius bg-light h-100'
.col-md-8.mb-md-0.no-padding
= search_field_tag :search, params[:search], placeholder: 'Find a job (name, company, position)', class: 'form-control rounded-left no-border-radius bg-light h-100'
.col-md-2.mb-md-0.no-padding
= button_tag '', class: 'h-100 w-100 btn btn-block btn-lg btn-info', name: nil
| Search
\ No newline at end of file
= select_tag(:salary,
options_for_select( @salary_range.collect {|s| [vnd_format(s), s]},
params[:salary] ),
class:"form-select bg-light h-100")
.col-md-2.mb-md-0.no-padding
= submit_tag "Search", class: "h-100 w-100 btn btn-block btn-lg btn-info"
-elsif params[:industry_slug]
= form_tag(industry_jobs_path(industry_slug: params[:industry_slug]), method: :get, class: "form-inline") do
.row
.col-md-8.mb-md-0.no-padding
= search_field_tag :search, params[:search], placeholder: 'Find a job (name, company, position)', class: 'form-control rounded-left no-border-radius bg-light h-100'
.col-md-2.mb-md-0.no-padding
= select_tag(:salary,
options_for_select( @salary_range.collect {|s| [vnd_format(s), s]},
params[:salary] ),
class:"form-select bg-light h-100")
.col-md-2.mb-md-0.no-padding
= submit_tag "Search", class: "h-100 w-100 btn btn-block btn-lg btn-info"
- else
= form_tag(jobs_path, method: :get, class: "form-inline") do
.row
.col-md-4.mb-md-0.no-padding
= search_field_tag :search, params[:search], placeholder: 'Find a job (name, company, position)', class: 'form-control rounded-left no-border-radius bg-light h-100'
.col-md-2.mb-md-0.no-padding
= select_tag(:city,
options_for_select( @city_slug_list,
params[:city] ),
include_blank: 'Select city',
class:"form-select bg-light h-100")
.col-md-2.mb-md-0.no-padding
= select_tag(:industry,
options_for_select( @industry_slug_list,
params[:industry] ),
include_blank: 'Select city',
class:"form-select bg-light h-100")
.col-md-2.mb-md-0.no-padding
= select_tag(:salary,
options_for_select( @salary_range.collect {|s| [vnd_format(s), s]},
params[:salary] ),
include_blank: 'Select salary',
class:"form-select bg-light h-100")
.col-md-2.mb-md-0.no-padding
= submit_tag "Search", class: "h-100 w-100 btn btn-block btn-lg btn-info"
......@@ -7,6 +7,7 @@
h2.text-white
| Total: #{pluralize(@total_job, "job")}
= render 'shared/search'
/ , url_options: jobs_path
/ latest job
.latest-job.mb-5
......@@ -36,7 +37,7 @@
.row.align-items-start.mb-3
- @top_cities.each do |city, city_jobs|
.col-4.city-item
= link_to city[0], job_list_path(model: :city, slug: city[1]), class: 'city-name'
= link_to city[0], city_jobs_path(city_slug: city[1]), class: 'city-name'
span.job-count
| (
= pluralize(city_jobs, 'job')
......@@ -50,7 +51,7 @@
.row.align-items-start
- @top_industries.each do |industry, industry_jobs|
.col-4.industry-item
= link_to industry[0], job_list_path(model: :industry, slug: industry[1]), class: 'industry-name'
= link_to industry[0], industry_jobs_path(industry_slug: industry[1]), class: 'industry-name'
span.job-count
| (
= pluralize(industry_jobs, 'job')
......
<h2><%= t "devise.invitations.edit.header" %></h2>
<%= form_for(resource, as: resource_name, url: invitation_path(resource_name), html: { method: :put }) do |f| %>
<%= render "users/shared/error_messages", resource: resource %>
<%= f.hidden_field :invitation_token, readonly: true %>
<% if f.object.class.require_password_on_accepting %>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>
<% end %>
<div class="actions">
<%= f.submit t("devise.invitations.edit.submit_button") %>
</div>
<% end %>
<h2><%= t "devise.invitations.new.header" %></h2>
<%= form_for(resource, as: resource_name, url: invitation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "users/shared/error_messages", resource: resource %>
<% resource.class.invite_key_fields.each do |field| -%>
<div class="field">
<%= f.label field %><br />
<%= f.text_field field %>
</div>
<% end -%>
<div class="actions">
<%= f.submit t("devise.invitations.new.submit_button") %>
</div>
<% end %>
......@@ -3,6 +3,8 @@
en:
devise:
confirmations:
admin_user:
confirmed: "Your ADMIN email address has been successfully confirmed."
confirmed: "Your email address has been successfully confirmed."
send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
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."
......
......@@ -31,3 +31,11 @@
en:
hello: "Hello world"
header_csv:
id: ID
job_id: Job ID
user_id: User ID
created_at: Applied at
updated_at: Updated at
user_name: User name
email: Email
Rails.application.routes.draw do
devise_for :admins, controllers: {
sessions: 'admins/sessions',
confirmations: 'admins/confirmations',
registrations: 'admins/registrations'
}
devise_scope :admin do
get 'admin/login', to: 'admins/sessions#new', as: :admin_login
end
devise_for :users, controllers: {
sessions: 'users/sessions',
confirmations: 'users/confirmations',
......@@ -18,13 +27,20 @@ Rails.application.routes.draw do
resources :cities, only: %i[index]
resources :industries, only: %i[index]
resources :favorite_jobs, only: %i[index create destroy]
resources :jobs, only: %i[index show]
resources :jobs, only: %i[index show] do
collection do
get 'city/:city_slug', action: :index, as: :city
get 'industry/:industry_slug', action: :index, as: :industry
end
end
get '/jobs/:model/:slug', to: 'jobs#index', as: :job_list
# get '/jobs/:model/:slug', to: 'jobs#index', as: :job_list
get '/my', to: 'users#show', as: :user_profile
get '/my/jobs', to: 'applies#index', as: :apply_jobs
get '/apply', to: 'applies#new', as: :apply_job
post '/confirm', to: 'applies#confirm', as: :confirm_job
post '/done', to: 'applies#create', as: :done_job
get '/history', to: 'history_jobs#index', as: :history_jobs
get '/admin/applies', to: 'admins#applies', as: :admin_applies_jobs
get '/admin/export', to: 'admins#export_csv', as: :admin_export_csv
end
.container.text-center
h2.my-5
| Register
p
| Thank you for register our service, an confirmation email with registration link has been sent to your email. Please check your email within 24 hours.
p
| Please note that the registration link is only valid for 24 hours.
p
| Over that period, you will have to register your email again.
\ No newline at end of file
# frozen_string_literal: true
class DeviseCreateAdmins < ActiveRecord::Migration[6.1]
def change
create_table :admins do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :admins, :email, unique: true
add_index :admins, :reset_password_token, unique: true
# add_index :admins, :confirmation_token, unique: true
# add_index :admins, :unlock_token, unique: true
end
end
class AddSalaryToJobs < ActiveRecord::Migration[6.1]
def change
add_column :jobs, :min_salary, :integer
add_column :jobs, :max_salary, :integer
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_09_07_064156) do
ActiveRecord::Schema.define(version: 2021_09_27_111928) do
create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false
......@@ -40,6 +40,22 @@ ActiveRecord::Schema.define(version: 2021_09_07_064156) do
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "admins", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_admins_on_email", unique: true
t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
end
create_table "apply_jobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "job_id", null: false
t.bigint "user_id", null: false
......@@ -125,6 +141,8 @@ ActiveRecord::Schema.define(version: 2021_09_07_064156) do
t.bigint "company_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "min_salary"
t.integer "max_salary"
t.index ["company_id"], name: "index_jobs_on_company_id"
end
......
require 'rsolr'
namespace :solr do
desc 'solr index data'
task add_data: :environment do
logger.info "start at #{Time.current}"
Solr.new.add_db
logger.info "Solr index data succesfully at #{Time.current}"
logger.info '----------------------------------------------'
end
desc 'delete data'
task delete_data: :environment do
Solr.new.delete_db
logger.info "delete all data at #{Time.current}"
logger.info '----------------------------------------------'
end
def logger
ActiveSupport::Logger.new('log/solr_logs.log')
end
end
......@@ -57,6 +57,33 @@ namespace :crawler do
expiration_date = default_value.to_time
end
end
if salary.blank?
logger.info 'Remove this job because salary is empty'
next
end
# salary
if salary.include?('USD')
parsed = parse_salary(salary.remove('USD', ','), 23_000)
min_salary = parsed[0]
max_salary = if parsed.length == 1
parsed[0]
else
parsed[1]
end
elsif salary.include?('VND')
parsed = parse_salary(salary.remove('tr', 'VND'), 1_000_000)
min_salary = parsed[0]
max_salary = if parsed.length == 1
parsed[0]
else
max_salary = parsed[1]
end
else
min_salary = 999_999_999
max_salary = 999_999_999
end
# benefits, description, requirement, other_info
job_detail_rows = job_page.css('section.job-detail-content div.detail-row')
benefits, description, requirement, other_info = ''
......@@ -80,6 +107,8 @@ namespace :crawler do
job_object = Job.find_or_create_by({ title: job_title,
job_type: job_type,
salary: salary,
min_salary: min_salary,
max_salary: max_salary,
experience: experience,
position: level,
expiration_date: expiration_date,
......@@ -88,11 +117,11 @@ namespace :crawler do
requirement: requirement,
other_info: other_info,
company_id: company_object.id })
industry_objects = industries.map { |industry| Industry.find_or_create_by(name: industry) }
industry_objects = industries.map { |industry| Industry.find_or_create_by(name: industry, slug: industry.to_slug) }
job_object.industries << industry_objects
# Cities
cities = job_page.css('.job-detail-content .detail-box .map p a').map(&:text)
city_objects = cities.map { |city| City.find_or_create_by(name: city) }
city_objects = cities.map { |city| City.find_or_create_by(name: city, slug: city.to_slug) }
job_object.cities << city_objects
rescue URI::InvalidURIError => e
puts "[Error] #{e.message}"
......@@ -114,7 +143,7 @@ namespace :crawler do
list_job = parsed_page.css('div.list-of-working-positions ul.list-jobs li a')
list_job.each do |part|
industry = part.text.squish.strip
Industry.find_or_create_by(name: industry)
Industry.find_or_create_by(name: industry, slug: industry.to_slug)
end
end
......@@ -131,8 +160,15 @@ namespace :crawler do
end
City.find_or_create_by(
name: city_name,
region: region
region: region,
slug: city_name.to_slug
)
end
end
def parse_salary(salary, multiplication)
salary.split('-').map do |i|
i.to_i * multiplication
end
end
end
require "test_helper"
class AdminsControllerTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
# This model initially had no columns defined. If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
one: {}
# column: value
#
two: {}
# column: value
require "test_helper"
class AdminTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment