Commit 163f2be9 by Tô Ngọc Ánh

Merge branch 'ID12-8-9' into 'master'

create ID12  - ID8 - ID9

See merge request !13
parents a88e34a0 41d94cb7
Pipeline #1014 failed with stages
in 0 seconds
.fa-file-upload {
cursor: pointer;
}
\ No newline at end of file
...@@ -19,4 +19,8 @@ ...@@ -19,4 +19,8 @@
border-bottom: 20px solid white; border-bottom: 20px solid white;
border-right: 20px solid white; border-right: 20px solid white;
border-left: 20px solid transparent; border-left: 20px solid transparent;
} }
\ No newline at end of file
.fa-file-upload {
cursor: pointer;
}
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
before_action :store_user_location!, if: :storable_location?
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
protected protected
...@@ -7,4 +8,15 @@ class ApplicationController < ActionController::Base ...@@ -7,4 +8,15 @@ class ApplicationController < ActionController::Base
devise_parameter_sanitizer.permit(:sign_up, keys: [:full_name, :curriculum_vitae]) devise_parameter_sanitizer.permit(:sign_up, keys: [:full_name, :curriculum_vitae])
devise_parameter_sanitizer.permit(:account_update, keys: [:full_name, :curriculum_vitae]) devise_parameter_sanitizer.permit(:account_update, keys: [:full_name, :curriculum_vitae])
end end
private
def storable_location?
request.get? && is_navigational_format? && !devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end end
class AppliedJobsController < ApplicationController class AppliedJobsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def index
@applied_jobs = Job.applied_jobs_of(current_user.id).includes(:locations).order(created_at: :asc).page(params[:page])
end
def new def new
job = Job.find_by(id: params[:job_id]) job = Job.find_by(id: params[:job_id])
return redirect_to root_path if job.nil? return redirect_to root_path if job.nil?
......
class FavoritesController < ApplicationController
before_action :authenticate_user!
def index
favorites = current_user.favorites.pluck(:job_id)
@jobs = Job.where(id: favorites).includes(:company, :locations).page(params[:page])
end
def create
@job = Job.find_by(id: params[:job_id])
@favorite = @job.favorites.new(user_id: current_user.id)
@favorite.save
respond_to :js
end
def destroy
@favorite = current_user.favorites.find_by(job_id: params[:job_id])
@favorite.destroy
respond_to :js
end
end
class HistoriesController < ApplicationController
before_action :authenticate_user!
def index
histories = current_user.histories.order(updated_at: :desc).take(History::NUMBER_STORED_HISTORIES).pluck(:job_id)
@jobs = Job.where(id: histories).includes(:company, :locations).sort_by { |job| histories.index(job.id) }
end
end
...@@ -4,13 +4,22 @@ class JobsController < ApplicationController ...@@ -4,13 +4,22 @@ class JobsController < ApplicationController
@industries = Industry.select(:id, :name) @industries = Industry.select(:id, :name)
object = params[:model].classify.constantize.find_by_slug(params[:slug]) object = params[:model].classify.constantize.find_by_slug(params[:slug])
@keyword = object.try(:name) || object.try(:city) @keyword = object.try(:name) || object.try(:city)
@jobs = object.jobs.all.includes(:company, :locations, :industries).page(params[:page]) @jobs = object.jobs.all.includes(:company, :locations).page(params[:page])
end end
def show def show
@job = Job.find_by(id: params[:id]) @job = Job.find_by(id: params[:id])
return redirect_to root_path if @job.nil? return redirect_to root_path if @job.nil?
return @is_not_applied = true unless user_signed_in?
@is_not_applied = user_signed_in? ? current_user.applied_jobs.find_by(job_id: @job.id).blank? : true
@is_not_applied = current_user.applied_jobs.find_by(job_id: @job.id).blank?
save_history(@job.id)
end
private
def save_history(job_id)
history = current_user.histories.find_or_create_by(job_id: job_id)
history.update_attributes(updated_at: Time.now)
end end
end end
...@@ -7,7 +7,7 @@ class AppliedJob < ApplicationRecord ...@@ -7,7 +7,7 @@ class AppliedJob < ApplicationRecord
validates :full_name, presence: true, length: { maximum: 200 } validates :full_name, presence: true, length: { maximum: 200 }
validates :email, presence: true, format: { with: EMAIL_REGEXN } validates :email, presence: true, format: { with: EMAIL_REGEXN }
validates :curriculum_vitae, presence: true validates :curriculum_vitae, presence: true
validates :user, uniqueness: { scope: :job, message: 'has already applied this job' } validates :user, uniqueness: { scope: :job, message: 'have already applied this job.' }
mount_uploader :curriculum_vitae, CurriculumVitaeUploader mount_uploader :curriculum_vitae, CurriculumVitaeUploader
end end
class Favorite < ApplicationRecord class Favorite < ApplicationRecord
belongs_to :user belongs_to :user
belongs_to :job belongs_to :job
validates :user, uniqueness: { scope: :job, message: 'have already favorited this job.' }
end end
class History < ApplicationRecord class History < ApplicationRecord
NUMBER_STORED_HISTORIES = 20
belongs_to :user belongs_to :user
belongs_to :job belongs_to :job
end end
class Job < ApplicationRecord class Job < ApplicationRecord
scope :applied_jobs_of, ->(user_id) do
joins(:applied_jobs)
.where("applied_jobs.user_id = #{user_id}")
.select('jobs.*, applied_jobs.created_at as applied_at')
end
NUMBER_LATEST_JOB = 6 NUMBER_LATEST_JOB = 6
WORDS_SHORT_DESCRIPTION = 250 WORDS_SHORT_DESCRIPTION = 250
......
...@@ -11,4 +11,8 @@ class User < ApplicationRecord ...@@ -11,4 +11,8 @@ class User < ApplicationRecord
devise :database_authenticatable, :registerable, devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :recoverable, :rememberable, :validatable,
:confirmable :confirmable
def favorited?(job_id)
favorites.find_by(job_id: job_id).present?
end
end end
<div class= "card my-2">
<div class="row">
<div class = "col-lg-10">
<div class='card-body'>
<%= link_to job.title, job_path(job), class: 'card-title font-weight-bold text-decoration-none' %>
<p class='mb-0'>
<strong>Work place:</strong>
<% job.locations.each do |location| %>
<%= location.city %>
<% end %>
</p>
<p><strong>Salary: </strong><%= job.salary %></p>
<p class='card-text'><%= strip_tags(job.description).truncate(Job::WORDS_SHORT_DESCRIPTION) %></p>
</div>
</div>
<div class = "col-lg-2 py-4">
<p><strong>Applied at: </strong></p>
<span><%= job.applied_at.localtime.strftime('%d/%m/%y - %H:%M') %></span>
</div>
</div>
</div>
<div class="my-4">
<h3 class='text-center'>Applied Jobs</h3>
<hr class="divider">
<div class='content'>
<% if @applied_jobs.any? %>
<%= paginate @applied_jobs %>
<%= render partial: 'applied_jobs/applied_job', collection: @applied_jobs, as: :job %>
<%= paginate @applied_jobs %>
<% else %>
<h4 class="text-center">Sorry! We can't found your applied job!</h4>
<% end %>
</div>
</div>
<div id="favorited-<%= job.id %>" class="form-item">
<div class='card flex-md-row align-items-center my-2 p-2'>
<%= radio_button_tag :job_id, job.id %>
<%= render 'shared/job_body', job: job %>
<%= link_to 'Remove', job_favorite_path(job.id),
method: :delete, class: 'btn btn-danger btn-lg', remote: true,
data: { disable_with: '<i class="fa fa-spinner fa-spin"></i>'.html_safe } %>
</div>
</div>
<% if user_signed_in? && current_user.favorited?(job_id) %>
<%= link_to 'Unfavorite', job_favorite_path(job_id),
method: :delete, class: 'btn btn-danger btn-lg', remote: true,
data: { disable_with: '<i class="fa fa-spinner fa-spin"></i>'.html_safe } %>
<% else %>
<%= link_to 'Favorite', job_favorites_path(job_id: job_id),
method: :post, class: 'btn btn-outline-danger btn-lg', remote: true,
data: { disable_with: '<i class="fa fa-spinner fa-spin"></i>'.html_safe } %>
<% end %>
$('#favorite-<%= @job.id %>')
.html("<%= j render 'favorites/link_favorite', job_id: @job.id %>")
$('#sum-favorited').html("<%= current_user.favorites.size %>")
$('#favorite-<%= @favorite.job_id %>')
.html("<%= j render 'favorites/link_favorite', job_id: @favorite.job_id %>")
$('#sum-favorited').html("<%= current_user.favorites.size %>")
$('#favorited-<%= @favorite.job_id %>').remove()
var num_child = $('.form-group .form-item').length
if (num_child == 0) {
var prev = $('a[rel=prev]')
if (prev.length > 0) {
window.location = prev.attr('href')
}
else {
window.location = '<%= favorites_path %>'
}
}
<div class="my-4">
<h3 class='text-center'>Favorited Jobs</h3>
<hr class="divider">
<div class='content'>
<% if @jobs.any? %>
<%= paginate @jobs %>
<%= form_tag new_applied_job_path, method: :get, enforce_utf8: false, class: 'form-group' do %>
<%= render partial: 'favorites/job', collection: @jobs %>
<div class="actions text-center">
<%= submit_tag 'Apply Now', name: nil, class: 'btn btn-outline-success btn-lg' %>
</div>
<% end %>
<%= paginate @jobs %>
<% else %>
<h4 class="text-center">Sorry! We can't found what you want!</h4>
<% end %>
</div>
</div>
<div class='card flex-md-row align-items-center my-2 p-2'>
<%= radio_button_tag :job_id, job.id %>
<%= render 'shared/job_body', job: job %>
</div>
<div class="my-4">
<h3 class='text-center'>History Jobs</h3>
<hr class="divider">
<div class='content'>
<% if @jobs.any? %>
<%= form_tag new_applied_job_path, method: :get, enforce_utf8: false do %>
<%= render partial: 'histories/job', collection: @jobs %>
<div class="actions text-center">
<%= submit_tag 'Apply Now', name: nil, class: 'btn btn-outline-success btn-lg' %>
</div>
<% end %>
<% else %>
<h4 class="text-center">Sorry! We can't found your history job!</h4>
<% end %>
</div>
</div>
<div class='card flex-md-row align-items-center my-2'> <div class='card flex-md-row align-items-center my-2'>
<div class='card-body'> <%= render 'shared/job_body', job: job %>
<%= link_to job.title, job_path(job), class: 'card-title font-weight-bold text-decoration-none' %> <div id="favorite-<%= job.id %>" class='p-2'>
<p class='card-text'><%= job.company.name %></p> <%= render 'favorites/link_favorite', job_id: job.id %>
<p class='mb-0'>
<strong>Work place:</strong>
<% job.locations.each do |location| %>
<%= location.city %>
<% end %>
</p>
<p><strong>Salary: </strong><%= job.salary %></p>
<p class='card-text'><%= strip_tags(job.description).truncate(Job::WORDS_SHORT_DESCRIPTION) %></p>
</div>
<div class='btn-favorite p-2'>
<%= link_to 'Favorite', '#', class: 'btn btn-outline-danger btn-lg' %>
</div> </div>
</div> </div>
<div class='px-5'> <div class='px-5'>
<%= render 'shared/searchbar', my_class: 'd-flex flex-column flex-md-row' %> <%= render 'shared/searchbar', my_class: 'd-flex flex-column flex-md-row' %>
</div> </div>
<div> <div class='content'>
<div class='message text-center'> <% if @jobs.any? %>
<h3>We found <%= pluralize(@jobs.total_count, 'result') %> for "<%= @keyword %>" </h3> <div class='message text-center'>
</div> <h3>We found <%= pluralize(@jobs.total_count, 'result') %> for "<%= @keyword %>" </h3>
<hr> </div>
<div class='content'> <hr>
<%= paginate @jobs %> <%= paginate @jobs %>
<%= render partial: 'jobs/job', collection: @jobs %> <%= render partial: 'jobs/job', collection: @jobs %>
<%= paginate @jobs %> <%= paginate @jobs %>
</div> <% else %>
<h4 class="text-center">Sorry! We can't found what you want!</h4>
<% end %>
</div> </div>
...@@ -32,17 +32,17 @@ ...@@ -32,17 +32,17 @@
</div> </div>
<div class="col-2"> <div class="col-2">
<%= link_to_if @is_not_applied, 'Apply Now', new_applied_job_path(job_id: @job.id), class: 'btn btn-outline-success btn-lg' do %> <%= link_to_if @is_not_applied, 'Apply Now', new_applied_job_path(job_id: @job.id), class: 'btn btn-outline-success btn-lg' do %>
<%= link_to 'Apply Now', '#', class: 'btn btn-outline-secondary btn-lg disabled' %> <%= link_to 'Applied', '#', class: 'btn btn-outline-secondary btn-lg disabled' %>
<% end %> <% end %>
</div> </div>
</div> </div>
<div class="row my-3"> <div class="row my-3">
<div class="col-6 text-right"> <div class="col-6 text-right">
<%= link_to_if @is_not_applied, 'Apply Now', new_applied_job_path(job_id: @job.id), class: 'btn btn-outline-success btn-lg' do %> <%= link_to_if @is_not_applied, 'Apply Now', new_applied_job_path(job_id: @job.id), class: 'btn btn-outline-success btn-lg' do %>
<%= link_to 'Apply Now', '#', class: 'btn btn-outline-secondary btn-lg disabled' %> <%= link_to 'Applied', '#', class: 'btn btn-outline-secondary btn-lg disabled' %>
<% end %> <% end %>
</div> </div>
<div class="col-6 text-left"> <div id="favorite-<%= @job.id %>" class="col-6 text-left">
<%= link_to 'Favorite', '#', class: 'btn btn-outline-danger btn-lg' %> <%= render 'favorites/link_favorite', job_id: @job.id %>
</div> </div>
</div> </div>
...@@ -10,12 +10,13 @@ ...@@ -10,12 +10,13 @@
<ul class='navbar-nav ml-auto'> <ul class='navbar-nav ml-auto'>
<% if user_signed_in? %> <% if user_signed_in? %>
<li class="nav-item"> <li class="nav-item">
<%= link_to '#', class: 'nav-link text-white', title: 'Favorite' do %> <%= link_to favorites_path, class: 'nav-link text-white', title: 'Favorite' do %>
<small id="sum-favorited"><%= current_user.favorites.size %></small>
<i class="fas fa-heart"></i> <i class="fas fa-heart"></i>
<% end %> <% end %>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<%= link_to '#', class: 'nav-link text-white', title: 'History' do %> <%= link_to histories_path, class: 'nav-link text-white', title: 'History' do %>
<i class="fas fa-history"></i> <i class="fas fa-history"></i>
<% end %> <% end %>
</li> </li>
......
<div class='card-body'>
<%= link_to job.title, job_path(job), class: 'card-title font-weight-bold text-decoration-none' %>
<p class='card-text'><%= job.company.name %></p>
<p class='mb-0'>
<strong>Work place:</strong>
<% job.locations.each do |location| %>
<%= location.city %>
<% end %>
</p>
<p><strong>Salary: </strong><%= job.salary %></p>
<p class='card-text'><%= strip_tags(job.description).truncate(Job::WORDS_SHORT_DESCRIPTION) %></p>
</div>
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<%= link_to 'Update', edit_user_registration_path, class: 'btn btn-outline-primary btn-lg' %> <%= link_to 'Update', edit_user_registration_path, class: 'btn btn-outline-primary btn-lg' %>
</div> </div>
<div class="col text-left"> <div class="col text-left">
<%= link_to 'My Jobs', '#', class: 'btn btn-outline-primary btn-lg' %> <%= link_to 'My Jobs', my_applied_jobs_path, class: 'btn btn-outline-primary btn-lg' %>
</div> </div>
</div> </div>
<% end %> <% end %>
......
...@@ -73,7 +73,7 @@ Devise.setup do |config| ...@@ -73,7 +73,7 @@ Devise.setup do |config|
# config.http_authenticatable = false # config.http_authenticatable = false
# If 401 status code should be returned for AJAX requests. True by default. # If 401 status code should be returned for AJAX requests. True by default.
# config.http_authenticatable_on_xhr = true config.http_authenticatable_on_xhr = false
# The realm used in Http Basic Authentication. 'Application' by default. # The realm used in Http Basic Authentication. 'Application' by default.
# config.http_authentication_realm = 'Application' # config.http_authentication_realm = 'Application'
...@@ -251,7 +251,7 @@ Devise.setup do |config| ...@@ -251,7 +251,7 @@ Devise.setup do |config|
# should add them to the navigational formats lists. # should add them to the navigational formats lists.
# #
# The "*/*" below is required to match Internet Explorer requests. # The "*/*" below is required to match Internet Explorer requests.
# config.navigational_formats = ['*/*', :html] config.navigational_formats = ['*/*', :html, :json, :js]
# The default HTTP method used to sign out a resource. Default is :delete. # The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :delete config.sign_out_via = :delete
......
Rails.application.routes.draw do Rails.application.routes.draw do
root to: 'home#index' root to: 'home#index'
devise_for :users, controllers: { sessions: 'users/sessions', registrations: 'users/registrations' } devise_for :users, controllers: { sessions: 'users/sessions', registrations: 'users/registrations' }
get 'cities', to: 'locations#index' get 'cities', to: 'locations#index'
get 'industries', to: 'industries#index' get 'industries', to: 'industries#index'
get 'detail/:id', to: 'jobs#show', as: :job get 'detail/:id', to: 'jobs#show', as: :job
get 'jobs/:model/:slug', to: 'jobs#index', as: :jobs get 'jobs/:model/:slug', to: 'jobs#index', as: :jobs
get 'my', to: 'users#my_page', as: :my_page get 'my', to: 'users#my_page', as: :my_page
get 'my/jobs', to: 'applied_jobs#index', as: :my_applied_jobs
post 'jobs/:job_id/favorites', to: 'favorites#create', as: :job_favorites
delete 'jobs/:job_id/favorites', to: 'favorites#destroy', as: :job_favorite
resources :favorites, only: :index
resources :histories, only: :index
resources :users, only: :show resources :users, only: :show
get 'apply', to: 'applied_jobs#new', as: :new_applied_job get 'apply', to: 'applied_jobs#new', as: :new_applied_job
post 'confirm', to: 'applied_jobs#confirm', as: :confirm_applied_job post 'confirm', to: 'applied_jobs#confirm', as: :confirm_applied_job
post 'finish', to: 'applied_jobs#finish', as: :finish_applied_job post 'finish', to: 'applied_jobs#finish', as: :finish_applied_job
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end end
namespace :delete_record do
desc 'Delete history when total greater than default limit'
task histories: :environment do
users = User.joins(:histories)
.group(:user_id)
.select('users.id')
.having("count(histories.id) > #{History::NUMBER_STORED_HISTORIES}")
users.each do |user|
break if user.histories.size <= History::NUMBER_STORED_HISTORIES
lastest = user.histories.order(updated_at: :desc).take(History::NUMBER_STORED_HISTORIES).pluck(:id)
user.histories.where.not(id: lastest).delete_all
end
end
end
window.location = '/users/sign_in';
\ No newline at end of file
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