Commit 1fb23a64 by Ngô Trung Hưng

Merge branch 'master' into 'csv'

# Conflicts:
#   app/models/city.rb
#   config/application.rb
parents ba6efd34 c6b99ebd
Pipeline #817 canceled with stages
in 0 seconds
...@@ -26,7 +26,8 @@ gem 'jbuilder', '~> 2.5' ...@@ -26,7 +26,8 @@ gem 'jbuilder', '~> 2.5'
# gem 'redis', '~> 4.0' # gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7' # gem 'bcrypt', '~> 3.1.7'
gem 'nokogiri'
gem 'whenever'
# Use ActiveStorage variant # Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8' # gem 'mini_magick', '~> 4.8'
......
...@@ -64,6 +64,7 @@ GEM ...@@ -64,6 +64,7 @@ GEM
chromedriver-helper (2.1.1) chromedriver-helper (2.1.1)
archive-zip (~> 0.10) archive-zip (~> 0.10)
nokogiri (~> 1.8) nokogiri (~> 1.8)
chronic (0.10.2)
coffee-rails (4.2.2) coffee-rails (4.2.2)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
railties (>= 4.0.0) railties (>= 4.0.0)
...@@ -183,6 +184,8 @@ GEM ...@@ -183,6 +184,8 @@ GEM
websocket-driver (0.7.3) websocket-driver (0.7.3)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
whenever (1.0.0)
chronic (>= 0.6.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
...@@ -198,6 +201,7 @@ DEPENDENCIES ...@@ -198,6 +201,7 @@ DEPENDENCIES
jbuilder (~> 2.5) jbuilder (~> 2.5)
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
mysql2 (>= 0.4.4, < 0.6.0) mysql2 (>= 0.4.4, < 0.6.0)
nokogiri
puma (~> 3.11) puma (~> 3.11)
rails (~> 5.2.4, >= 5.2.4.3) rails (~> 5.2.4, >= 5.2.4.3)
sass-rails (~> 5.0) sass-rails (~> 5.0)
...@@ -208,6 +212,7 @@ DEPENDENCIES ...@@ -208,6 +212,7 @@ DEPENDENCIES
tzinfo-data tzinfo-data
uglifier (>= 1.3.0) uglifier (>= 1.3.0)
web-console (>= 3.3.0) web-console (>= 3.3.0)
whenever
RUBY VERSION RUBY VERSION
ruby 2.6.6p146 ruby 2.6.6p146
......
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
$(document).on("turbolinks:load", function(){
$('.block_click_favorite').click(function (e) {
$('.overlay_login').addClass('show');
});
$('.icon_close_modal_login').click(function (e) {
$('.overlay_login').removeClass('show');
});
// => scroll to top
$(window).scroll(function(){
if ($(this).scrollTop() > 550) {
$(".btn-scroll-top").css('opacity',1);
$(".btn-scroll-top").fadeIn(400);
} else {
$(".btn-scroll-top").fadeOut(400);
}
});
$(".btn-scroll-top").click(function (e){
var body = $("html, body");
body.stop().animate({ scrollTop: 0}, 700, 'swing')
});
// => end
$(".btn_option_location-vn").click(function (){
var body = $("html, body");
$('.btn_option_location-vn').addClass('active');
body.stop().animate({ scrollTop: $('.title_list_city_vn').offset().top}, 700, 'swing');
});
$(".btn_option_location-qt").click(function (){
var body = $("html, body");
console.log('bam')
body.stop().animate({ scrollTop: $('.title_list_city_qt').offset().top}, 700, 'swing');
});
});
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
.box_link_favotite {
position: relative;
width: 100%;
height: 100%;
padding: 10px 10px;
span {
color: #999;
}
}
.block_link_favorite :hover {
color: #e62958;
}
.block_link_favorite.md {
display: none;
}
.link_favorite {
color: #999;
z-index: 100000;
&:hover {
text-decoration: none;
}
}
.block_click_favorite {
color: #999;
cursor: pointer;
}
.block_click_favorite.md {
display: none;
}
.link_favorite_top {
position: absolute;
z-index: 1;
// position: relative;
padding: 15px 20px;
top: 0;
right: 0;
}
.overlay_login {
visibility: hidden;
opacity: 0;
transition: 0.5;
position: fixed;
z-index: 99999;
top: 0;
bottom: 0;
left: 0;
right: 0;
transition: linear 0.2s;
background-color: rgba($color: #000000, $alpha: 0.5);
}
.overlay_login.show {
opacity: 1;
visibility: visible;
transition: linear 0.2s;
}
.modal_login {
position: absolute;
overflow: hidden;
top: 50%;
left: 50%;
height: 300px;
width: 600px;
transform: translateY(-50%) translateX(-50%);
background-color: #fff;
border-radius: 5px;
transition: cubic-bezier(0.165, 0.84, 0.44, 1);
}
// modal
.icon_close_modal_login {
cursor: pointer;
float: right;
transform: translateX(-10px);
font-size: 22px;
font-weight: 100;
color: rgba($color: #999, $alpha: 0.9);
span {
}
}
.modal_login_notice_text {
text-align: center;
color: #666;
font-size: 19px;
color: rgb(60, 71, 121);
span {
}
}
.modal_login_header {
background-color: #eee;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 18px;
color: #333;
font-weight: 600;
font-family: 'Raleway', sans-serif;
}
@media only screen and (max-width: 768px) {
.block_link_favorite.lg {
display: none;
}
.block_click_favorite.lg {
display: none;
}
.block_link_favorite.md {
display: block;
}
.block_click_favorite.md {
display: block;
}
.pagination li a {
font-size: 14px !important;
}
.modal_login {
width: 95vw;
font-size: 14px;
}
.modal_login_header {
font-size: 15px;
}
.icon_close_modal_login {
font-size: 15px;
}
}
@media only screen and (max-width: 576px) {
.pagination li a {
font-size: 13px !important;
}
}
// Place all the styles related to the home controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
// Place all the styles related to the industry controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
// Place all the styles related to the job controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
# frozen_string_literal: true
# City controller
class CityController < ApplicationController
def index
@list_cities_domestic = City.domestic
@list_cities_international = City.international
end
end
# frozen_string_literal: true
# Home page
class HomeController < ApplicationController
def index
@industries = Industry.order(name: :asc).all
@job_count = Job.count
@cities = City.all_cities
@lasted_jobs = Job.order(created_at: :desc).limit(Job::NUMBER_LASTED_JOB)
@top_cities = City.top_cities(9)
@top_industries = Industry.top_industries(9)
end
end
# frozen_string_literal: true
# Industry controller
class IndustryController < ApplicationController
def index
@industries = Industry.all
end
end
# frozen_string_literal: true
# Job controller
class JobController < ApplicationController
end
# frozen_string_literal: true
# Application Helper
module ApplicationHelper module ApplicationHelper
def full_title(page_title)
base_title = 'VenJob'
page_title.empty? ? base_title : "#{base_title} | #{page_title}"
end
end end
module CityHelper
end
module HomeHelper
end
module IndustryHelper
end
module JobHelper
end
...@@ -2,7 +2,17 @@ ...@@ -2,7 +2,17 @@
# Description/Explanation of Person class # Description/Explanation of Person class
class City < ApplicationRecord class City < ApplicationRecord
RANGE = 69
has_many :city_jobs has_many :city_jobs
has_many :jobs, through: :city_jobs has_many :jobs, through: :city_jobs
enum area: { international: 0, domestic:1} enum area: { international: 0, domestic:1}
has_many :jobs, through: :city_jobs
scope :domestic, -> { where(area: 1) }
scope :international, -> { where(area: 0) }
scope :all_cities, -> { select :id, :name }
scope :top_cities, ->(number) do joins(:jobs)
.group(:city_id)
.order(Arel.sql('count(jobs.id) DESC'))
.take(number)
end
end end
...@@ -4,4 +4,9 @@ ...@@ -4,4 +4,9 @@
class Industry < ApplicationRecord class Industry < ApplicationRecord
has_many :industry_jobs has_many :industry_jobs
has_many :jobs, through: :industry_jobs has_many :jobs, through: :industry_jobs
scope :top_industries, ->(number) do joins(:jobs)
.group(:industry_id)
.order(Arel.sql('count(jobs.id) DESC'))
.take(number)
end
end end
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# Description/Explanation of Person class # Description/Explanation of Person class
class Job < ApplicationRecord class Job < ApplicationRecord
NUMBER_LASTED_JOB = 5
belongs_to :company belongs_to :company
has_many :industry_jobs has_many :industry_jobs
......
<% unless city.jobs.count < 1 %>
<div class="col-lg-3 col-md-4">
<div class="box_cityjobs">
<div class="box_cityjobs_name">
<%= link_to city.name, '#', class: 'cityjobs_link' %>
</div>
<div class="box_cityjobs_count">
<span><%= city.jobs.count %> Công việc</span>
</div>
</div>
</div>
<% end %>
<% provide(:title, 'Top thành phố') %>
<%= render 'shared/scroll_top' %>
<div class="container">
<div class="box_option_location">
<div class="icon_box_option_location">
<span class="kl_icon_box_option_location">
<i class="fas fa-search-location"></i>
</span>
</div>
<div class="row">
<div class="col-lg-12">
<div class="box_option_location_header">
<span>Bạn muốn tìm việc trong hay ngoài nước?</span>
</div>
</div>
<div class="col-lg-6">
<button class="btn_option_location btn_option_location-vn">Việt Nam</button>
</div>
<div class="col-lg-6">
<button class="btn_option_location btn_option_location-qt">Nước ngoài</button>
</div>
</div>
</div>
</div>
<!-- Main -->
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="title_list_city title_list_city_vn">
<span>Các tỉnh, thành phố thuôc Việt Nam</span>
</div>
</div>
<div class="container">
<div class="row">
<%= render partial: 'block_city_jobs', collection: @list_cities_domestic, as: :city %>
</div>
</div>
<div class="col-lg-12">
<div class="title_list_city title_list_city_qt">
<span>Quốc tế</span>
</div>
</div>
<div class="container">
<div class="row">
<%= render partial: 'block_city_jobs', collection: @list_cities_international, as: :city %>
</div>
</div>
</div>
</div>
<div class = "box-banner">
<%= image_tag 'banner-search-box-home.png', class: "banner-top" %>
<div class="row">
<div class="col-xs-12">
<div class = "slogan">
<h2 class = "slogan_text">
NỀN TẢNG TUYỂN DỤNG NHÂN SỰ VENJOB
</h2>
<h5 class = "slogan_text_h5">
NỀN TẢNG TUYỂN DỤNG NHÂN SỰ VENJOB
</h5>
</div>
</div>
</div>
<!-- Process search pc-->
<div class="box-search">
<div class="search_scaffold">
<div class = "total_job">
<% if @job_count.blank? %>
<h4 class="show_total_job">Khám phá ngay những việc làm tốt nhất trên VenJob</h4>
<% else %>
<h4 class="show_total_job">Khám phá ngay <b><%= @job_count%></b> việc làm tốt nhất trên VenJob</h4>
<% end %>
</div>
<div class="filter_box">
<div class="search_input">
<input type="search" name="" id="" class="custom_input_search" placeholder="Nhập tiêu đề, công ty, địa điểm...">
</div>
<div class="search_industries">
<div class="icon_list">
<i class="fa fa-list-ul" aria-hidden="true"></i>
</div>
<select class="show_list_industries">
<option value="*" selected disabled hidden class="select_first">Tất cả ngành nghề</option>
<% @industries.each do |val| %>
<option value="<%= val.id %>"><%= val.name %></option>
<% end %>
</select>
</div>
<div class="search_location">
<div class="icon_list">
<i class="fas fa-map-marker-alt"></i>
</div>
<select class="show_list_cities">
<option value="*" selected disabled hidden class="select_first">Tất cả Tỉnh thành</option>
<% @cities.each do |val| %>
<option value="<%= val.id %>"><%= val.name %></option>
<% end %>
</select>
</div>
<div class="search_submit">
<button type="submit" class="btn_search_submit">
<i class="fas fa-search"></i>
Tìm việc
</button>
</div>
</div>
</div>
</div>
<!-- Process search tablet-->
<div class="box-search-tablet">
<div class="search_scaffold-tablet">
<div class = "total_job">
<h4 class="show_total_job">Khám phá ngay <b><%=@job_count%></b> việc làm tốt nhất trên VenJob</h4>
</div>
<div class="filter_box filter_box_tablet">
<div class="search_input">
<input type="search" name="" id="" class="custom_input_search" placeholder="Nhập tiêu đề, công ty, địa điểm...">
</div>
<div class="search_industries">
<div class="icon_list">
<i class="fa fa-list-ul" aria-hidden="true"></i>
</div>
<select class="show_list_industries">
<option value="*" selected disabled hidden class="select_first">Tất cả ngành nghề</option>
<% @industries.each do |val| %>
<option value="<%= val.id %>"><%= val.name %></option>
<% end %>
</select>
</div>
<div class="search_location">
<div class="icon_list">
<i class="fas fa-map-marker-alt"></i>
</div>
<select class="show_list_cities">
<option value="*" selected disabled hidden class="select_first">Tất cả Tỉnh thành</option>
<% @cities.each do |val| %>
<option value="<%= val.id %>"><%= val.name %></option>
<% end %>
</select>
</div>
<div class="search_submit">
<button type="submit" class="btn_search_submit">
<i class="fas fa-search"></i>
Tìm việc
</button>
</div>
</div>
</div>
</div>
<div class="box-show-total-jobs-mobile">
<span>Khám phá ngay <%=@job_count%></b> việc làm tốt nhất trên VenJob</span>
</div>
<!-- Process search mobile-->
<div class="box-search-mobile">
<div class="fillter-search-mobile">
<div class="container">
<div class="row no-gutters">
<div class="col-md-12">
<input type="search" name="" id="" class="custom_input_mobile_search" placeholder="Nhập tiêu đề, công ty, địa điểm...">
</div>
<div class="col-sm-6 col-xs-12">
<div class="search_industries_mobile">
<select class="show_list_industries_mobile">
<option value="*" selected disabled hidden class="select_first">Tất cả ngành nghề</option>
<% @industries.each do |val| %>
<option value="<%= val.id %>"><%= val.name %></option>
<% end %>
</select>
</div>
</div>
<div class="col-sm-6 col-xs-12">
<div class="search_location_mobile">
<select class="show_list_cities_mobile">
<option value="*" selected disabled hidden class="select_first">Tất cả Tỉnh thành</option>
<% @cities.each do |val| %>
<option value="<%= val.id %>"><%= val.name %></option>
<% end %>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row no-gutters">
<div class="col-sm-12">
<button type="submit" class="btn_search_submit">
<i class="fas fa-search"></i>
Tìm việc
</button>
</div>
</div>
</div>
</div>
</div>
<%= render 'job/modal_login'%>
<%cru = 0%>
<div class="container">
<div class="row">
<div class="col-sm-12">
<% @lasted_jobs.each do |val| %>
<div class="box_jobs">
<div class="col-sm-12 d-block d-sm-none">
<% if cru == 1 %>
<div class="link_favorite_top block_link_favorite md">
<span><%= link_to '<i class="far fa-heart"></i>'.html_safe, '#', class: 'link_favorite'%><span>
</div>
<% else %>
<div class="block_click_favorite link_favorite_top ">
<span><i class="far fa-heart"></i><span>
</div>
<% end %>
</div>
<div class="row">
<div class="col-sm-10 col-md-9 col-lg-10">
<div class="box_info">
<div class="lol">
<%= link_to val.name, '#', class: 'job_name' %>
</div>
<div class="cop">
<h5 class="box_info_copany_name"><i class="far fa-building"></i> <%= val.company.name %></h5>
</div>
<div class="loc">
<h5 class="box_info_location"><i class="fas fa-map-marker-alt"></i>
<% dt = [] %>
<% val.cities.each do |x| %>
<% dt << (x.name << ' | ') %>
<% end %>
<%= dt.join('').chomp('| ')%>
</h5>
</div>
<h5 class="box_info_salary"><i class="fas fa-dollar-sign"></i>&nbsp; Lương: <%= val.salary %></h5>
<div class="coc">
<h5 class="box_info_des"><%= strip_tags(val.description) %></h5>
</div>
</div>
</div>
<div class="col-sm-2 col-md-3 d-none d-sm-block col-lg-2">
<% if cru == 1 %>
<div class="box_link_favotite">
<div class="block_link_favorite lg">
<span><i class="far fa-heart"></i> <%= link_to 'Yêu thích'.html_safe, '#', class: 'link_favorite' %><span>
</div>
<div class="block_link_favorite md">
<span><%= link_to '<i class="far fa-heart"></i>'.html_safe, '#', class: 'link_favorite' %><span>
</div>
</div>
<% else %>
<div class="box_link_favotite">
<div class="block_click_favorite lg">
<span><i class="far fa-heart"></i> Yêu thích<span>
</div>
<div class="block_click_favorite md">
<span><i class="far fa-heart"></i><span>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<div class="container">
<div class="border_box_cities">
<div class="box_text_five_jobs box_padding_city">
<span>Top thành phố</span>
</div>
<div class="row no-gutters">
<% @top_cities.each do |city| %>
<div class="col-lg-4 col-md-4 col-sm-6 col-xs-12">
<div class="box_info_city">
<div class="ct_name">
<%= link_to city.name, '#' ,class: 'link_ct' %>
</div>
<div class="ct_jobs_count">
<%= link_to "#{city.jobs.count} công việc", '#' ,class: 'link_ct' %>
</div>
</div>
</div>
<% end %>
<div class="col-lg-12">
<div class="box_info_city">
<div class="ct_name">
<%= link_to 'Xem Thêm', city_index_path, class: 'link_ct' %>
</div>
<div class="ct_jobs_count">
<i class="fas fa-ellipsis-h"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container">
<div class="border_box_cities">
<div class="box_text_five_jobs box_padding_city">
<span>Top các nghành phổ biến</span>
</div>
<div class="row no-gutters">
<% @top_industries.each do |industry| %>
<div class="col-lg-4 col-md-4 col-sm-6 col-xs-12">
<div class="box_info_city">
<div class="ct_name">
<%= link_to industry.name, '#', class: 'link_ct' %>
</div>
<div class="ct_jobs_count">
<%= link_to "#{industry.jobs.count} công việc", '#', class: 'link_ct' %>
</div>
</div>
</div>
<% end %>
<div class="col-lg-12">
<div class="box_info_city">
<div class="ct_name">
<%= link_to 'Xem Thêm', industry_index_path, class: 'link_ct' %>
</div>
<div class="ct_jobs_count">
<i class="fas fa-ellipsis-h"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<% provide(:title ,'Trang chủ') %>
<%= render 'home/banner_and_search' %>
<div class="padding_index"></div>
<% unless @job_count.nil? %>
<div class="box_text_five_jobs">
<span>Năm công việc mới nhất dành cho bạn</span>
</div>
<%= render 'box_five_job' %>
<%= render 'box_nine_city' %>
<%= render 'box_nine_industries' %>
<%= render 'shared/scroll_top' %>
<% end %>
<div class="col-lg-3 col-md-4">
<div class="box_cityjobs">
<div class="box_cityjobs_name">
<%= link_to industry.name, '#', class: 'cityjobs_link' %>
</div>
<div class="box_cityjobs_count">
<span><%= industry.jobs.count %> Công việc</span>
</div>
</div>
</div>
<% provide(:title, 'Top nghành') %>
<%= render 'shared/scroll_top' %>
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="title_list_city title_list_city_vn">
<span>Top nghành</span>
</div>
</div>
<div class="container">
<div class="row">
<%= render partial: 'block_industry', collection: @industries, as: :industry %>
</div>
</div>
</div>
</div>
<div class="overlay_login">
<div class="modal_login">
<div class="modal_login_header">
<span>VUI LÒNG ĐĂNG NHẬP ĐỂ THỰC HIỆN CHỨC NĂNG NÀY</span>
<span class="icon_close_modal_login">
<i class="fas fa-times"></i>
</span>
</div>
<div class="container">
<div class="row">
<div class="col-lg 12">
<div class="modal_login_notice_text">
<span>ĐĂNG NHẬP</span>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class = "custom_footer">
<div class = "container">
<div class = "copyright">
Copyright &copy; ZIGExN VeNtura 2020
</div>
</div>
</footer>
\ No newline at end of file
<header class="cus_header">
<div class="header_top">
<div class="header_top_logo">
<%= link_to image_tag('logo.png', alt: 'logo', class: 'logo_header'), '/' %>
</div>
<input class="check-nav-mobile" type="checkbox" name="" id="mobile-nav" hidden>
<label for="mobile-nav" class="lbn-nav">&#9776</label>
<label for="mobile-nav" class="overlay"></label>
<div class = "override"></div>
<div class="header_top_menu_right">
<ul class="list_menu_header_right">
<li class="list_item_menu_header_right">
<%= link_to 'Yêu thích', '#', class: 'link_item_menu_header_right' %>
</li>
<li class="list_item_menu_header_right">
<%= link_to 'Lịch sử', '#', class: 'link_item_menu_header_right' %>
</li>
<li class="list_item_menu_header_right">
<%= link_to 'Đăng nhập', '#', class: 'link_item_menu_header_right' %>
</li>
<li class="list_item_menu_header_right">
<%= link_to 'Đăng ký', '#', class: 'link_item_menu_header_right' %>
</li>
</ul>
</div>
<%# mobile %>
<div class = "menu__mobile">
<div class = "content_menu_mobile">
<%= link_to image_tag('avatar.png', alt: 'avatar', class: 'avatar_manu'), '#' %>
<div class = "menu_mobile_vertical">
<ul class = "list_menu_mobile_vertical">
<li class = "item_menu_mobile_vertical">
<%= link_to 'Yêu thích', '#', class: 'link_item_menu_mobile' %>
</li>
<li class = "item_menu_mobile_vertical">
<%= link_to 'Lịch sử', '#', class: 'link_item_menu_mobile' %>
</li>
<li class = "item_menu_mobile_vertical">
<%= link_to 'Đăng nhập', '#', class: 'link_item_menu_mobile' %>
</li>
<li class = "item_menu_mobile_vertical">
<%= link_to 'Đăng ký', '#', class: 'link_item_menu_mobile' %>
</li>
</ul>
</div>
<div>
</div>
</div>
</header>
\ No newline at end of file
<div class = "padding_header_to_body">
<div>
\ No newline at end of file
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Venjob</title> <title><%= full_title(yield(:title)) %></title>
<%= favicon_link_tag 'icontitle.png' %>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<%= csp_meta_tag %> <%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= render 'shared/shim' %>
</head> </head>
<body> <body>
<%= yield %> <div class = "app">
<%= render 'layouts/header' %>
<%= render 'layouts/padding' %>
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body> </body>
</html> </html>
<div class="btn-scroll-top">
<div class="box-btn-scroll-top">
<i class="fas fa-caret-up icon-scroll"></i>
</div>
</div>
\ No newline at end of file
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/js/all.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@200&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Raleway&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" rel="stylesheet">
\ No newline at end of file
...@@ -10,8 +10,8 @@ module Venjob ...@@ -10,8 +10,8 @@ module Venjob
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.2 config.load_defaults 5.2
config.autoload_paths << Rails.root.join('lib/service') config.autoload_paths << Rails.root.join('lib/service')
config.autoload_paths << Rails.root.join('lib/src')
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers # Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading # -- all .rb files in that directory are automatically loaded after loading
......
...@@ -52,3 +52,4 @@ production: ...@@ -52,3 +52,4 @@ production:
database: venjob_production database: venjob_production
username: venjob username: venjob
password: <%= ENV['VENJOB_DATABASE_PASSWORD'] %> password: <%= ENV['VENJOB_DATABASE_PASSWORD'] %>
url: <%= ENV['JAWSDB_URL'] %>
...@@ -9,7 +9,7 @@ threads threads_count, threads_count ...@@ -9,7 +9,7 @@ threads threads_count, threads_count
# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
# #
port ENV.fetch("PORT") { 3000 } port ENV.fetch("PORT") { 1234 }
# Specifies the `environment` that Puma will run in. # Specifies the `environment` that Puma will run in.
# #
......
Rails.application.routes.draw do Rails.application.routes.draw do
root 'home#index'
get 'industry/index'
get 'city/index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end end
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
# Example:
#
# set :output, "/path/to/my/cron_log.log"
#
# every 2.hours do
# command "/usr/bin/some_great_command"
# runner "MyModel.some_method"
# rake "some:great:rake:task"
# end
#
# every 4.days do
# runner "AnotherModel.prune_old_records"
# end
# Learn more: http://github.com/javan/whenever
env :PATH, ENV['PATH']
every 1.hours do
rake 'crawler:populate'
end
\ No newline at end of file
class ChangeColumnTableCity < ActiveRecord::Migration[5.2]
def change
change_column :cities, :area, :integer
#Ex:- change_column("admin_users", "email", :string, :limit =>25)
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_07_28_021412) do ActiveRecord::Schema.define(version: 2020_07_29_064551) do
create_table "applied_jobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "applied_jobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.bigint "user_id" t.bigint "user_id"
...@@ -26,7 +26,7 @@ ActiveRecord::Schema.define(version: 2020_07_28_021412) do ...@@ -26,7 +26,7 @@ ActiveRecord::Schema.define(version: 2020_07_28_021412) do
create_table "cities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "cities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.string "name" t.string "name"
t.boolean "area" t.integer "area"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
......
# frozen_string_literal: true
require 'nokogiri'
require 'open-uri'
require 'logger'
# Crawler data
module Base
class Base
attr_accessor :job, :page
def initialize(page)
@job = {}
@page = page
end
def logger
@logger ||= Logger.new(Rails.root.join('log', 'crawl.log'))
end
def create_data
take_data
job
rescue StandardError => e
logger.error "Crawler data job have error: #{e}"
end
private
def take_data
job[:name] = fill_name
job[:company_name] = fill_company_name
job[:city_name] = fill_city_name
job[:created_date] = fill_created_date
job[:expiration_date] = fill_expiration_date
job[:salary] = fill_salary
job[:industry_name] = fill_industry_name
job[:description] = fill_description
job[:level] = fill_lever
job[:exprience] = fill_experience
end
def fill_name
page.search('.apply-now-content .job-desc .title').text
end
def fill_company_name
page.search('.apply-now-content .job-desc .job-company-name').text
end
def fill_city_name
page.search('.detail-box .map p a').map(&:text).join(',')
end
def fill_created_date
page.search('.item-blue .detail-box:nth-child(1) ul li:nth-child(1) p')[0].try(:text)
end
def fill_expiration_date
page.xpath('//ul//li[last()]//p').last.text
end
def fill_salary
page.xpath('//ul//li[position()=1]//p')[1].text
end
def fill_industry_name
industries = page.xpath('//ul//li[position()=2]//p//a').map(&:text)
industries.map(&:strip).join(',')
end
def fill_description
job[:description] = page.search('.tabs .tab-content .detail-row').to_s
end
def exist_experience?
noname = page.search('//ul//li').text
noname.include?('Kinh nghiệm')
end
def fill_lever
exist_experience? ? page.xpath('//ul//li[position()=3]//p')[1].text.strip : page.xpath('//ul//li[position()=2]//p')[1].text
end
def fill_experience
exist_experience? ? page.xpath('//ul//li[position()=2]//p')[1].text.strip : ''
end
end
end
# frozen_string_literal: true
require 'open-uri'
# Crawler data
class Crawler
attr_accessor :number_link
def initialize(number_link)
@number_link = number_link
end
def path_to_first_link
Rails.root.join('tmp', 'link.txt')
end
def logger
@logger ||= Logger.new(Rails.root.join('log', 'crawler.log'))
end
def link_make_stop_crawler
file = File.readlines(path_to_first_link, 'r') if File.exist?(path_to_first_link)
file.blank? ? 'NOT' : file.join
end
def safe_link(url)
Nokogiri::HTML(URI.open(URI.escape(url)))
end
def crawl_link
website_companies = []
number_link.times do |i|
page = Nokogiri::HTML(URI.open("https://careerbuilder.vn/viec-lam/tat-ca-viec-lam-trang-#{i + 1}-vi.html"))
link_companies = page.search('.figcaption .caption @href')
website_companies += link_companies.map(&:value).uniq
link_jobs = page.search('.figcaption .title .job_link @href').text
break if link_jobs.include?(link_make_stop_crawler)
end
website_companies.select(&:present?)
rescue StandardError => e
logger.error "Crawler link on page have error #{e}"
end
def craw_data_cities
page = Nokogiri::HTML(URI.open('https://careerbuilder.vn/viec-lam/tat-ca-viec-lam-vi.html'))
locations = page.search('#location option').map(&:text)
locations.each_with_index do |val, index|
area = index > City::RANGE ? City.areas['international'] : City.areas['domestic']
City.find_or_create_by(name: val) { |city| city.area = area }
end
end
def craw_data_companies
crawl_link.each do |url|
page = safe_link(url)
company_name = page.search('.company-info .content .name').text
Company.find_or_create_by(name: company_name) do |company|
company.address = page.search('.company-info .info .content p:nth-child(3)').text
company.short_description = page.search('.main-about-us .content').text
end
rescue StandardError => e
logger.error "Crawler data companies has error: #{e}"
end
end
end
# frozen_string_literal: true
# Crawler job
class CrawlerJob < Crawler
SIZE_LI = 8
def crawl_link
website_jobs = []
number_link.times do |i|
page = Nokogiri::HTML(URI.open("https://careerbuilder.vn/viec-lam/tat-ca-viec-lam-trang-#{i + 1}-vi.html"))
link_jobs = page.search('.figcaption .title .job_link @href')
link_jobs.each do |val|
link = val.value
return website_jobs if link.include?(link_make_stop_crawler)
website_jobs << link
end
end
website_jobs
rescue StandardError => e
logger.error "Crawler link jobs on page have error #{e}"
end
def parse_data
@parse_data ||= crawl_link.reverse!
end
def refresh_first_link
File.write(path_to_first_link, parse_data.last)
end
def craw_data_jobs
parse_data.each do |path|
page = safe_link(path)
if page.search('.item-blue .detail-box:nth-child(1) ul li:nth-child(1) p')[0].present?
@data = Interface::RedInterface.new(page).create_data
elsif page.search('section .template-200').text.present?
@data = Interface::BlueInterface.new(page).create_data
elsif page.search('.DetailJobNew ul li').size == SIZE_LI && page.search('.right-col ul li').text.exclude?('Độ tuổi')
@data = Interface::GreenInterface.new(page).create_data
end
add_data(@data)
refresh_first_link
end
end
def add_data(data)
id_company = (Company.find_by name: data[:company_name]).try(:id) || Company::COMPANY_SECURITY
job = Job.create(name: data[:name],
company_id: id_company,
level: data[:level],
experience: data[:exprience],
salary: data[:salary],
create_date: data[:created_date],
expiration_date: data[:expiration_date],
description: data[:description])
create_industry_relation(data[:industry_name], job)
create_city_relation(data[:city_name], job)
rescue StandardError => e
logger.error "Crawler data jobs has error: #{e}"
end
def create_industry_relation(data, job)
industries = data.split(',')
industries.each do |val|
val.gsub!('&amp;', '&') if val.include?('&amp;')
industry = Industry.find_or_create_by name: val.strip
job.industries << industry
end
end
def create_city_relation(data, job)
cities = data.split(',')
cities.each do |city|
city = City.find_or_create_by(name: city.strip, area: City.areas['domestic'])
job.cities << city
end
end
end
# frozen_string_literal: true
# Inherience from base
module Interface
class BlueInterface < Base::Base
def fill_company_name
page.search('.top-job .top-job-info .tit_company').text
end
def fill_city_name
page.search('.info-workplace .value a').map(&:text).join(',')
end
def fill_expiration_date
page.xpath('//ul//li[position()=4]//div').text
end
def fill_salary
page.xpath('//ul//li[position()=3]//div').text
end
def fill_industry_name
page.xpath('//ul//li[position()=5]//div').text
end
def fill_description
page.search('.left-col').to_s
end
def exist_level?
noname = page.xpath('//ul//li[position()=2]/b').last.text
noname.include?('Cấp bậc')
end
def fill_lever
exist_level? ? page.xpath('//ul//li[position()=2]/div').last.text : ''
end
def fill_experience
page.xpath('//ul//li[position()=7]/b').text
end
end
end
# frozen_string_literal: true
# Green Interface
module Interface
class GreenInterface < Base::Base
def fill_name
page.search('.info-company h1').text
end
def fill_company_name
page.search('.info-company .text-job h2').text
end
def fill_city_name
page.search('.DetailJobNew ul li:nth-child(1) a').text
end
def fill_expiration_date
page.xpath('//ul//li[last()-1]//span').children[1].text
end
def fill_salary
page.xpath('//ul//li[last()-2]//span').text
end
def fill_industry_name
page.search('.DetailJobNew li:nth-child(3) span').text.strip
end
def fill_description
page.search('.left-col .detail-row').text
end
def fill_lever
page.search('.DetailJobNew li:nth-child(2) span').text.strip
end
def exist_experience?
noname = page.search('.DetailJobNew li span').text
noname.include?('Kinh nghiệm')
end
def fill_experience
exist_experience? ? page.search('.DetailJobNew li:nth-child(5) span').text.strip : ''
end
end
end
# frozen_string_literal: true
# Inherience from base
module Interface
class RedInterface < Base::Base
end
end
# frozen_string_literal: true
require 'open-uri'
# rake task
namespace :crawler do
task populate: :environment do
NUMBER_LINK_WILL_BE_CRAWLER = 5
Company.find_or_create_by(name: 'Bảo mật') do |company|
company.address = 'Vui lòng xem trong mô tả công việc'
company.short_description = 'Vui lòng xem trong mô tả công việc'
end
cw = Crawler.new(NUMBER_LINK_WILL_BE_CRAWLER)
cw.craw_data_cities
cw.craw_data_companies
CrawlerJob.new(NUMBER_LINK_WILL_BE_CRAWLER).craw_data_jobs
end
end
require 'test_helper'
class CityControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get city_index_url
assert_response :success
end
end
require 'test_helper'
class HomeControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get home_index_url
assert_response :success
end
end
require 'test_helper'
class IndustryControllerTest < ActionDispatch::IntegrationTest
test "should get index" do
get industry_index_url
assert_response :success
end
end
require 'test_helper'
class JobControllerTest < ActionDispatch::IntegrationTest
# 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