├── log └── .keep ├── storage └── .keep ├── tmp └── .keep ├── vendor └── .keep ├── lib ├── assets │ └── .keep ├── tasks │ └── .keep └── monkey_patches │ ├── active_support.rb │ └── active_support │ └── concern+prependable.rb ├── public ├── favicon.ico ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── test ├── helpers │ └── .keep ├── mailers │ └── .keep ├── models │ └── .keep ├── system │ └── .keep ├── controllers │ ├── .keep │ └── user │ │ └── sessions_controller_test.rb ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ └── user.yml ├── integration │ └── .keep ├── application_system_test_case.rb ├── channels │ └── application_cable │ │ └── connection_test.rb └── test_helper.rb ├── app ├── assets │ ├── images │ │ └── .keep │ └── stylesheets │ │ ├── .keep │ │ ├── application.scss │ │ └── _custom.scss ├── models │ ├── concerns │ │ ├── .keep │ │ ├── user │ │ │ └── devise_failsafe.rb │ │ ├── enum_attribute_localizable.rb │ │ └── acts_as_default_value.rb │ ├── application_record.rb │ └── user.rb ├── controllers │ ├── concerns │ │ ├── .keep │ │ └── store_location.rb │ ├── home_controller.rb │ ├── admin │ │ ├── home_controller.rb │ │ ├── application_controller.rb │ │ ├── users │ │ │ └── invitations_controller.rb │ │ └── users_controller.rb │ ├── users │ │ ├── invitations_controller.rb │ │ ├── sessions_controller.rb │ │ ├── confirmations_controller.rb │ │ ├── passwords_controller.rb │ │ └── registrations_controller.rb │ ├── accounts │ │ ├── application_controller.rb │ │ ├── profiles_controller.rb │ │ └── passwords_controller.rb │ ├── errors_controller.rb │ └── application_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ ├── application │ │ │ ├── _action_bar.html.erb │ │ │ ├── _footer.html.erb │ │ │ ├── _aside_menu.html.erb │ │ │ ├── _notice.html.erb │ │ │ ├── _favicon.html.erb │ │ │ ├── _breadcrumb.html.erb │ │ │ └── _header.html.erb │ │ ├── sign_in.html.erb │ │ ├── mailer.html.erb │ │ ├── sign_in │ │ │ └── _notice.html.erb │ │ ├── sidebars │ │ │ ├── _application.html.erb │ │ │ ├── _admin.html.erb │ │ │ └── _accounts.html.erb │ │ ├── base.html.erb │ │ └── application.html.erb │ ├── errors │ │ ├── _back.html.erb │ │ ├── forbidden.html.erb │ │ ├── not_found.html.erb │ │ └── unauthorized.html.erb │ ├── home │ │ └── index.html.erb │ ├── kaminari │ │ ├── _gap.html.erb │ │ ├── _last_page.html.erb │ │ ├── _first_page.html.erb │ │ ├── _next_page.html.erb │ │ ├── _page.html.erb │ │ ├── _prev_page.html.erb │ │ └── _paginator.html.erb │ ├── admin │ │ ├── users │ │ │ ├── invitations │ │ │ │ ├── _form.html.erb │ │ │ │ └── new.html.erb │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── _form.html.erb │ │ │ ├── index.html.erb │ │ │ └── show.html.erb │ │ └── home │ │ │ └── index.html.erb │ ├── accounts │ │ ├── profiles │ │ │ └── show.html.erb │ │ └── passwords │ │ │ └── show.html.erb │ └── users │ │ ├── passwords │ │ ├── new.html.erb │ │ └── edit.html.erb │ │ ├── confirmations │ │ └── new.html.erb │ │ ├── invitations │ │ └── edit.html.erb │ │ ├── registrations │ │ └── new.html.erb │ │ └── sessions │ │ └── new.html.erb ├── mailers │ ├── devise_mailer.rb │ └── application_mailer.rb ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── packs │ ├── turbolinks │ │ └── coreui.js │ ├── channels │ │ ├── index.js │ │ └── consumer.js │ ├── controllers │ │ ├── index.js │ │ └── coreui_sidebar_controller.js │ └── entrypoints │ │ └── application.js ├── helpers │ ├── aside_helper.rb │ ├── action_bar_helper.rb │ ├── breadcrumbs_helper.rb │ ├── sidebar_helper.rb │ ├── application_helper.rb │ └── navigation_helper.rb ├── jobs │ └── application_job.rb └── overrides │ └── action_view │ └── helpers │ └── form_builder_override.rb ├── config ├── locales │ ├── models │ │ ├── .keep │ │ └── user │ │ │ ├── zh-CN.yml │ │ │ └── en.yml │ ├── views │ │ ├── admin │ │ │ ├── home │ │ │ │ ├── en.yml │ │ │ │ └── zh-CN.yml │ │ │ └── users │ │ │ │ ├── invitations │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ ├── errors │ │ │ ├── _back │ │ │ │ ├── en.yml │ │ │ │ └── zh-CN.yml │ │ │ ├── forbidden │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ ├── not_found │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ └── unauthorized │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ ├── users │ │ │ ├── confirmations │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ ├── invitations │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ ├── registrations │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ ├── sessions │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ └── passwords │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ ├── accounts │ │ │ ├── profiles │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ │ └── passwords │ │ │ │ ├── zh-CN.yml │ │ │ │ └── en.yml │ │ ├── _shared │ │ │ ├── zh-CN.yml │ │ │ └── en.yml │ │ └── layouts │ │ │ ├── en.yml │ │ │ └── zh-CN.yml │ ├── kaminari.zh-CN.yml │ ├── kaminari.en.yml │ ├── en.yml │ ├── zh-CN.yml │ └── devise.zh-CN.yml ├── webpack │ ├── base.js │ ├── test.js │ ├── development.js │ ├── production.js │ └── webpack.config.js ├── initializers │ ├── action_view.rb │ ├── mime_types.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── feature_policy.rb │ ├── permissions_policy.rb │ ├── filter_parameter_logging.rb │ ├── wrap_parameters.rb │ ├── backtrace_silencers.rb │ ├── inflections.rb │ ├── content_security_policy.rb │ └── config.rb ├── spring.rb ├── environment.rb ├── settings.yml ├── cable.yml ├── boot.rb ├── mailer.yml.example ├── credentials.yml.example ├── storage.yml.example ├── routes.rb ├── puma.rb ├── webpacker.yml ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb ├── database.yml.example └── application.rb ├── .yamllint ├── _screenshots ├── sign_in_page.png ├── sign_up_page.png └── admin_user_page.png ├── bin ├── rake ├── webpacker ├── webpack ├── webpacker-dev-server ├── webpack-dev-server ├── yarn ├── update ├── setup ├── rails └── bundle ├── config.ru ├── .circleci ├── app_config │ └── database.yml └── config.yml ├── Rakefile ├── postcss.config.js ├── db ├── seeds.rb ├── migrate │ └── 20190302174954_devise_create_users.rb └── schema.rb ├── .editorconfig ├── cybros.sublime-project ├── babel.config.js ├── .gitattributes ├── MIT-LICENSE ├── .gitignore ├── .gitlab-ci.yml ├── package.json ├── Gemfile ├── patches └── @fortawesome+fontawesome-free+5.15.4.patch ├── .rubocop.yml └── README.md /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/locales/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | rules: 4 | line-length: 5 | max: 120 6 | level: warning 7 | -------------------------------------------------------------------------------- /_screenshots/sign_in_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasl/cybros_core/HEAD/_screenshots/sign_in_page.png -------------------------------------------------------------------------------- /_screenshots/sign_up_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasl/cybros_core/HEAD/_screenshots/sign_up_page.png -------------------------------------------------------------------------------- /_screenshots/admin_user_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasl/cybros_core/HEAD/_screenshots/admin_user_page.png -------------------------------------------------------------------------------- /app/mailers/devise_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseMailer < Devise::Mailer 4 | end 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /config/locales/views/admin/home/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | admin: 3 | home: 4 | index: 5 | title: "Home" 6 | -------------------------------------------------------------------------------- /config/webpack/base.js: -------------------------------------------------------------------------------- 1 | const { webpackConfig } = require('shakapacker') 2 | 3 | module.exports = webpackConfig 4 | -------------------------------------------------------------------------------- /app/views/layouts/application/_action_bar.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= yield :action_bar %> 3 |
4 | -------------------------------------------------------------------------------- /config/locales/views/admin/home/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | admin: 3 | home: 4 | index: 5 | title: "首页" 6 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /lib/monkey_patches/active_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "active_support/concern+prependable" 4 | -------------------------------------------------------------------------------- /config/locales/views/errors/_back/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | back: 4 | back: 'Back' 5 | back_to_home: 'Back to home' 6 | -------------------------------------------------------------------------------- /config/locales/views/errors/_back/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | errors: 3 | back: 4 | back: '返回' 5 | back_to_home: '回到首页' 6 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | layout "mailer" 5 | end 6 | -------------------------------------------------------------------------------- /app/views/layouts/application/_footer.html.erb: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /config/locales/views/errors/forbidden/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | errors: 3 | forbidden: 4 | code: '403' 5 | title: '禁止访问' 6 | content: '你没有权限访问这个页面。' 7 | -------------------------------------------------------------------------------- /config/locales/views/errors/not_found/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | errors: 3 | not_found: 4 | code: '404' 5 | title: '找不到页面' 6 | content: '该页面可能已经失效。' 7 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/action_view.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActionView::Base.field_error_proc = Proc.new do |html_tag, _instance_tag| 4 | html_tag 5 | end 6 | -------------------------------------------------------------------------------- /config/locales/views/errors/unauthorized/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | errors: 3 | unauthorized: 4 | code: '401' 5 | title: '禁止访问' 6 | content: '你没有权限访问这个页面。' 7 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Spring.watch( 4 | ".ruby-version", 5 | ".rbenv-vars", 6 | "tmp/restart.txt", 7 | "tmp/caching-dev.txt" 8 | ) 9 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const webpackConfig = require('./base') 4 | 5 | module.exports = webpackConfig 6 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const webpackConfig = require('./base') 4 | 5 | module.exports = webpackConfig 6 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const webpackConfig = require('./base') 4 | 5 | module.exports = webpackConfig 6 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/locales/views/errors/forbidden/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | forbidden: 4 | code: '403' 5 | title: 'Forbidden' 6 | content: 'You have no permission for this page.' 7 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HomeController < ApplicationController 4 | before_action :authenticate_user! 5 | 6 | def index 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /config/locales/views/errors/not_found/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | not_found: 4 | code: '404' 5 | title: 'Page not found' 6 | content: 'You may have typed the URL incorrectly.' 7 | -------------------------------------------------------------------------------- /app/packs/turbolinks/coreui.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("turbolinks:load", function() { 2 | // Make coreui toggle happy 3 | $('.sidebar').sidebar(); 4 | $('.aside-menu')['aside-menu'](); 5 | }); 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative "application" 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /config/locales/views/errors/unauthorized/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | unauthorized: 4 | code: '401' 5 | title: 'No authority' 6 | content: 'You have no permission for this page.' 7 | -------------------------------------------------------------------------------- /config/locales/views/users/confirmations/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | users: 3 | confirmations: 4 | new: 5 | title: "重新发送确认邮件" 6 | submit: "重新发送确认邮件" 7 | back_to_sign_in_instructions: "返回到登录" 8 | -------------------------------------------------------------------------------- /config/locales/views/users/invitations/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | users: 3 | invitations: 4 | edit: 5 | title: "接受邀请" 6 | submit: "提交" 7 | confirm_password: "确认密码" 8 | password: "密码" 9 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative "config/environment" 6 | 7 | run Rails.application 8 | Rails.application.load_server 9 | -------------------------------------------------------------------------------- /config/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | seo_meta: 3 | name: Cybros Core 4 | description: Cybros Core 5 | keywords: [] 6 | 7 | # url_options: 8 | # host: 0.0.0.0:3000 9 | # protocol: http 10 | 11 | admin: 12 | emails: [] 13 | -------------------------------------------------------------------------------- /app/controllers/admin/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Admin::HomeController < Admin::ApplicationController 4 | def index 5 | prepare_meta_tags title: t("admin.home.index.title") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | 6 | include ActsAsDefaultValue 7 | include EnumAttributeLocalizable 8 | end 9 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /config/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { webpackConfig } = require('shakapacker') 2 | 3 | // See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig. 4 | 5 | module.exports = webpackConfig 6 | -------------------------------------------------------------------------------- /app/views/errors/_back.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if stored_location? %> 3 | <%= link_to t(".back"), stored_location %> 4 | <% else %> 5 | <%= link_to t(".back_to_home"), root_path %> 6 | <% end %> 7 |
8 | -------------------------------------------------------------------------------- /config/locales/views/admin/users/invitations/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | admin: 3 | users: 4 | invitations: 5 | shared: 6 | notice: 7 | created: "邀请已发送" 8 | new: 9 | title: "邀请用户" 10 | -------------------------------------------------------------------------------- /test/fixtures/user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | user_eric: 3 | id: 1 4 | email: "eric@cloud-mes.com" 5 | confirmed_at: <%= Time.zone.now %> 6 | # password: 123456 7 | encrypted_password: '$2a$11$I3DE/JkhWB03DUC.LFaoEuwVRU7Kk474udMsmF/AiX5IAxm5CoXcS' 8 | -------------------------------------------------------------------------------- /config/locales/views/users/invitations/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | users: 3 | invitations: 4 | edit: 5 | title: "Accept Invitation" 6 | submit: "Submit" 7 | confirm_password: "Confirm Password" 8 | password: "Password" 9 | -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 6 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 7 | end 8 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: cybros_production 11 | -------------------------------------------------------------------------------- /app/packs/channels/index.js: -------------------------------------------------------------------------------- 1 | // Load all the channels within this directory and all subdirectories. 2 | // Channel files must be named *_channel.js. 3 | 4 | const channels = require.context('.', true, /_channel\.js$/) 5 | channels.keys().forEach(channels) 6 | -------------------------------------------------------------------------------- /config/locales/views/admin/users/invitations/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | admin: 3 | users: 4 | invitations: 5 | shared: 6 | notice: 7 | created: "Invitation created" 8 | new: 9 | title: "New Invitation" 10 | -------------------------------------------------------------------------------- /config/locales/views/users/confirmations/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | users: 3 | confirmations: 4 | new: 5 | title: "Resend Confirmation Email" 6 | submit: "Resend Confirmation Email" 7 | back_to_sign_in_instructions: "Back to sign in" 8 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 4 | 5 | require "bundler/setup" # Set up gems listed in the Gemfile. 6 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /app/views/layouts/application/_aside_menu.html.erb: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /app/helpers/aside_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AsideHelper 4 | def has_aside? 5 | content_for?(:aside) 6 | end 7 | 8 | def render_aside 9 | return unless has_aside? 10 | render "layouts/application/aside_menu" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /.circleci/app_config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | pool: 5 5 | 6 | test: 7 | <<: *default 8 | database: cybros_test 9 | host: <%= ENV['PG_HOST'] %> 10 | username: <%= ENV['PG_USERNAME'] %> 11 | password: cybros_test 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative "config/application" 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-flexbugs-fixes'), 5 | require('postcss-preset-env')({ 6 | autoprefixer: { 7 | flexbox: 'no-2009' 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 |

Home#index

2 |

Find me in app/views/home/index.html.erb

3 | 4 |

5 | <%= link_to t("layouts.header.account_menu.profile"), account_profile_path %> | 6 | <%= link_to "Sign out", destroy_user_session_path %> 7 |

8 | -------------------------------------------------------------------------------- /app/helpers/action_bar_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ActionBarHelper 4 | def has_action_bar? 5 | content_for?(:action_bar) 6 | end 7 | 8 | def render_action_bar 9 | return unless has_action_bar? 10 | render "layouts/application/action_bar" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/locales/views/users/registrations/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | users: 3 | registrations: 4 | new: 5 | title: "注册" 6 | submit: "注册" 7 | sign_in: "登录" 8 | signed_up_user_instructions: "已经注册过了?" 9 | didn_t_receive_confirmation_instructions: "没有收到确认邮件?" 10 | -------------------------------------------------------------------------------- /app/packs/channels/consumer.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | 4 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /app/views/layouts/sign_in.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:body_class, "app flex-row pt-4 bg-gray-100") %> 2 | <% content_for :content do %> 3 |
4 | <%= render "layouts/sign_in/notice" %> 5 | <%= yield %> 6 |
7 | <% end %> 8 | 9 | <%= render template: "layouts/base" %> 10 | -------------------------------------------------------------------------------- /config/locales/models/user/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | activerecord: 3 | models: 4 | user: "用户" 5 | attributes: 6 | user: 7 | current_password: "当前密码" 8 | email: "Email" 9 | password: "密码" 10 | password_confirmation: "密码确认" 11 | remember_me: "记住登录信息" 12 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /config/locales/views/accounts/profiles/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | accounts: 3 | profiles: 4 | show: 5 | title: "编辑个人资料" 6 | submit: "提交" 7 | updated: "个人资料更新成功" 8 | section: 9 | email: 10 | title: "邮箱" 11 | description: "这些信息会影响到下一次登录,并且不能和其他用户的重复" 12 | -------------------------------------------------------------------------------- /config/locales/views/users/sessions/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | users: 3 | sessions: 4 | new: 5 | title: "登录" 6 | submit: "登录" 7 | forgot_your_password: "忘记密码?" 8 | new_user_instructions: "新人?" 9 | sign_up: "注册一个账号" 10 | didn_t_receive_confirmation_instructions: "没有收到确认邮件?" 11 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # ActiveSupport::Reloader.to_prepare do 5 | # ApplicationController.renderer.defaults.merge!( 6 | # http_host: 'example.org', 7 | # https: false 8 | # ) 9 | # end 10 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Specify a serializer for the signed and encrypted cookie jars. 6 | # Valid options are :json, :marshal, and :hybrid. 7 | Rails.application.config.action_dispatch.cookies_serializer = :json 8 | -------------------------------------------------------------------------------- /config/locales/models/user/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activerecord: 3 | models: 4 | user: "User" 5 | attributes: 6 | user: 7 | current_password: "Current password" 8 | email: "Email" 9 | password: "Password" 10 | password_confirmation: "Password confirmation" 11 | remember_me: "Remember me" 12 | -------------------------------------------------------------------------------- /config/locales/views/users/registrations/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | users: 3 | registrations: 4 | new: 5 | title: "Sign Up" 6 | submit: "Sign up" 7 | sign_in: "Sign in" 8 | signed_up_user_instructions: "Already signed up?" 9 | didn_t_receive_confirmation_instructions: "Didn't received confirmation email?" 10 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | # Automatically retry jobs that encountered a deadlock 5 | # retry_on ActiveRecord::Deadlocked 6 | 7 | # Most jobs are safe to ignore if the underlying records are no longer available 8 | # discard_on ActiveJob::DeserializationError 9 | end 10 | -------------------------------------------------------------------------------- /config/locales/views/users/passwords/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | users: 3 | passwords: 4 | edit: 5 | title: "更改密码" 6 | submit: "更改我的密码" 7 | confirm_new_password: "确认新密码" 8 | new_password: "新密码" 9 | new: 10 | title: "忘记密码" 11 | submit: "发送重设密码的邮件" 12 | back_to_sign_in_instructions: "回到登录" 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | @import "~@mixtint/coreui/scss/coreui"; 3 | @import "custom"; 4 | 5 | $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; 6 | @import "~@fortawesome/fontawesome-free/scss/fontawesome"; 7 | @import "~@fortawesome/fontawesome-free/scss/regular"; 8 | @import "~@fortawesome/fontawesome-free/scss/solid"; 9 | -------------------------------------------------------------------------------- /app/views/layouts/application/_notice.html.erb: -------------------------------------------------------------------------------- 1 | <% flash.each do |k, v| %> 2 |
3 | 6 | 7 | <%= sanitize v.html_safe %> 8 |
9 | <% end %> 10 | -------------------------------------------------------------------------------- /config/locales/views/accounts/passwords/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | accounts: 3 | passwords: 4 | show: 5 | title: "更改密码" 6 | description: "这些信息会影响到下一次登录,并且不能和其他用户的重复" 7 | updated: "密码更改成功" 8 | form: 9 | confirm_new_password: "确认新密码" 10 | new_password: "新密码" 11 | current_password: "当前密码" 12 | submit: "更新" 13 | -------------------------------------------------------------------------------- /config/locales/views/users/sessions/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | users: 3 | sessions: 4 | new: 5 | title: "Sign in" 6 | submit: "Sign in" 7 | forgot_your_password: "Forgot your password?" 8 | new_user_instructions: "New user?" 9 | sign_up: "Sign up" 10 | didn_t_receive_confirmation_instructions: "Didn't received confirmation email?" 11 | -------------------------------------------------------------------------------- /test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase 6 | # test "connects with cookies" do 7 | # cookies.signed[:user_id] = 42 8 | # 9 | # connect 10 | # 11 | # assert_equal connection.user_id, "42" 12 | # end 13 | end 14 | -------------------------------------------------------------------------------- /config/locales/views/accounts/profiles/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | accounts: 3 | profiles: 4 | show: 5 | title: "Edit Profile" 6 | submit: "Submit" 7 | updated: "Profile update successfully" 8 | section: 9 | email: 10 | title: "Email" 11 | description: "After a successful email update, you should use new email on next log in." 12 | -------------------------------------------------------------------------------- /test/controllers/user/sessions_controller_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SessionsControllerTest < ActionDispatch::IntegrationTest 4 | test "should sign in user-sign-in form" do 5 | post user_session_path, 6 | params: { user: { email: "eric@cloud-mes.com", 7 | password: "123456" } } 8 | assert_response :redirect 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /config/locales/views/_shared/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | shared: 3 | actions: 4 | back: '返回' 5 | show: '查看' 6 | edit: '编辑' 7 | destroy: '删除' 8 | submit: '提交' 9 | new: '新建' 10 | form: 11 | submit: 12 | create: "创建" 13 | update: "更新" 14 | submit: "提交" 15 | destroy: "删除" 16 | confirmation: 17 | destroy: "确定要删除吗?" 18 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /config/locales/views/users/passwords/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | users: 3 | passwords: 4 | edit: 5 | title: "Update Password" 6 | submit: "Update my password" 7 | confirm_new_password: "Confirm new password" 8 | new_password: "New password" 9 | new: 10 | title: "Forget Password" 11 | submit: "Resend Reset Email" 12 | back_to_sign_in_instructions: "Back to sign in" 13 | -------------------------------------------------------------------------------- /app/views/layouts/application/_favicon.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /config/locales/views/_shared/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | shared: 3 | actions: 4 | back: 'Back' 5 | show: 'Show' 6 | edit: 'Edit' 7 | destroy: 'Delete' 8 | submit: 'Submit' 9 | new: 'New' 10 | form: 11 | submit: 12 | create: "Create" 13 | update: "Update" 14 | submit: "Submit" 15 | destroy: "Delete" 16 | confirmation: 17 | destroy: "Do you want to delete?" 18 | -------------------------------------------------------------------------------- /app/helpers/breadcrumbs_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module BreadcrumbsHelper 4 | def breadcrumbs 5 | @_breadcrumbs ||= [] 6 | end 7 | 8 | def add_to_breadcrumbs(text, link = nil) 9 | breadcrumbs.push( 10 | text: text, 11 | link: link 12 | ) 13 | end 14 | 15 | def render_breadcrumbs 16 | return unless breadcrumbs.any? 17 | render "layouts/application/breadcrumb" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/packs/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Load all the controllers within this directory and all subdirectories. 2 | // Controller files must be named *_controller.js. 3 | 4 | import { Application } from "stimulus" 5 | import { definitionsFromContext } from "stimulus/webpack-helpers" 6 | 7 | const application = Application.start() 8 | const context = require.context("controllers", true, /_controller\.js$/) 9 | application.load(definitionsFromContext(context)) 10 | -------------------------------------------------------------------------------- /app/views/errors/forbidden.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | <%= t(".code") %> 6 |

7 |

8 | <%= t(".title") %> 9 |

10 |

11 | <%= t(".content") %> 12 |

13 |
14 |
15 |
16 | 17 | <%= render "back" %> 18 | -------------------------------------------------------------------------------- /app/views/errors/not_found.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | <%= t(".code") %> 6 |

7 |

8 | <%= t(".title") %> 9 |

10 |

11 | <%= t(".content") %> 12 |

13 |
14 |
15 |
16 | 17 | <%= render "back" %> 18 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.erb: -------------------------------------------------------------------------------- 1 | <%# Non-link tag that stands for skipped pages... 2 | - available local variables 3 | current_page: a page object for the currently displayed page 4 | num_pages: total number of pages 5 | per_page: number of items to fetch per page 6 | remote: data-remote 7 | -%> 8 |
  • <%= raw(t("views.pagination.truncate")) %>
  • 9 | -------------------------------------------------------------------------------- /app/views/errors/unauthorized.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    5 | <%= t(".code") %> 6 |

    7 |

    8 | <%= t(".title") %> 9 |

    10 |

    11 | <%= t(".content") %> 12 |

    13 |
    14 |
    15 |
    16 | 17 | <%= render "back" %> 18 | -------------------------------------------------------------------------------- /bin/webpacker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "pathname" 4 | require "bundler/setup" 5 | require "webpacker" 6 | require "webpacker/webpack_runner" 7 | 8 | ENV["RAILS_ENV"] ||= "development" 9 | ENV["NODE_ENV"] ||= ENV["RAILS_ENV"] 10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) 11 | 12 | APP_ROOT = File.expand_path("..", __dir__) 13 | Dir.chdir(APP_ROOT) do 14 | Webpacker::WebpackRunner.run(ARGV) 15 | end 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | end_of_line = lf 13 | 14 | [node_modules/**] 15 | charset = ignore 16 | end_of_line = ignore 17 | indent_size = ignore 18 | indent_style = ignore 19 | trim_trailing_whitespace = ignore 20 | -------------------------------------------------------------------------------- /app/helpers/sidebar_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SidebarHelper 4 | def collapsed_sidebar? 5 | cookies["sidebar_collapsed"] == "true" 6 | end 7 | 8 | def use_sidebar(name = "application") 9 | @_sidebar_name = name 10 | end 11 | 12 | def has_sidebar? 13 | @_sidebar_name.present? 14 | end 15 | 16 | def render_sidebar 17 | return unless has_sidebar? 18 | render "layouts/sidebars/#{@_sidebar_name}" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "pathname" 4 | require "bundler/setup" 5 | require "webpacker" 6 | require "webpacker/webpack_runner" 7 | 8 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 9 | ENV["NODE_ENV"] ||= "development" 10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) 11 | 12 | APP_ROOT = File.expand_path("..", __dir__) 13 | Dir.chdir(APP_ROOT) do 14 | Webpacker::WebpackRunner.run(ARGV) 15 | end 16 | -------------------------------------------------------------------------------- /config/locales/kaminari.zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | helpers: 3 | page_entries_info: 4 | more_pages: 5 | display_entries: "显示 %{entry_name} %{first} - %{last}%{total} " 6 | one_page: 7 | display_entries: "显示 所有 %{count} %{entry_name}" 8 | views: 9 | pagination: 10 | first: "« 第一页" 11 | last: "最后一页 »" 12 | next: "下一页 ›" 13 | previous: "‹ 上一页" 14 | truncate: "…" 15 | -------------------------------------------------------------------------------- /config/initializers/feature_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Define an application-wide HTTP feature policy. For further 3 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 4 | # 5 | # Rails.application.config.feature_policy do |f| 6 | # f.camera :none 7 | # f.gyroscope :none 8 | # f.microphone :none 9 | # f.usb :none 10 | # f.fullscreen :self 11 | # f.payment :self, "https://secure.example.com" 12 | # end 13 | -------------------------------------------------------------------------------- /app/controllers/users/invitations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Users 4 | class InvitationsController < Devise::InvitationsController 5 | layout "sign_in" 6 | 7 | def edit 8 | prepare_meta_tags title: t("users.invitations.edit.title") 9 | super 10 | end 11 | 12 | def update 13 | super 14 | end 15 | 16 | protected 17 | 18 | def after_accept_path_for(_resource) 19 | root_path 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /bin/webpacker-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= "development" 4 | ENV["NODE_ENV"] ||= ENV["RAILS_ENV"] 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Define an application-wide HTTP permissions policy. For further 3 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 4 | # 5 | # Rails.application.config.permissions_policy do |f| 6 | # f.camera :none 7 | # f.gyroscope :none 8 | # f.microphone :none 9 | # f.usb :none 10 | # f.fullscreen :self 11 | # f.payment :self, "https://secure.example.com" 12 | # end 13 | -------------------------------------------------------------------------------- /config/locales/views/accounts/passwords/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | accounts: 3 | passwords: 4 | show: 5 | title: "Change Password" 6 | description: "After a successful password update, you should use new password on next log in." 7 | updated: "Password update successful" 8 | form: 9 | confirm_new_password: "Confirm new password" 10 | new_password: "New password" 11 | current_password: "Current password" 12 | submit: "Update" 13 | -------------------------------------------------------------------------------- /app/controllers/accounts/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Accounts 4 | class ApplicationController < ::ApplicationController 5 | before_action :authenticate_user! 6 | before_action :set_page_layout_data, if: -> { request.format.html? } 7 | 8 | protected 9 | 10 | def set_page_layout_data 11 | prepare_meta_tags title: t("accounts.#{controller_name}.show.title") 12 | @_sidebar_name = "accounts" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 6 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 7 | # notations and behaviors. 8 | Rails.application.config.filter_parameters += [ 9 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 10 | ] 11 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /app/views/layouts/application/_breadcrumb.html.erb: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /config/locales/views/layouts/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | layouts: 3 | header: 4 | brand: "Cybros Core" 5 | account_menu: 6 | admin: "Administration" 7 | profile: "Edit Profile" 8 | sign_out: "Sign Out" 9 | footer: 10 | copyright: "© 2019 Jasl, MIT license" 11 | sidebar: 12 | application: 13 | home: "Home" 14 | accounts: 15 | profile: "Profile" 16 | password: "Password" 17 | admin: 18 | home: "Home" 19 | users: "Users" 20 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class User < ApplicationRecord 4 | # Include default devise modules. Others available are: 5 | # :timeoutable and :omniauthable 6 | devise :database_authenticatable, 7 | :registerable, :lockable, :invitable, 8 | :recoverable, :rememberable, :confirmable, :trackable, :validatable 9 | 10 | scope :active, -> { where(locked_at: nil) } 11 | 12 | def admin? 13 | email.in? Settings.admin.emails 14 | end 15 | 16 | include DeviseFailsafe 17 | end 18 | -------------------------------------------------------------------------------- /app/views/admin/users/invitations/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: user, url: admin_invitations_path, method: :post, local: true) do |f| %> 2 |
    3 | <%= f.label :email %> 4 | <%= f.email_field :email, id: "user_email", autofocus: true, required: "required", class: "form-control", class_for_error: "is-invalid" %> 5 | <%= f.error_message :email, class: "invalid-feedback" %> 6 |
    7 | 8 | <%= f.submit t("shared.form.submit.create"), class: "btn btn-block btn-primary" %> 9 | <% end %> 10 | -------------------------------------------------------------------------------- /app/views/admin/users/new.html.erb: -------------------------------------------------------------------------------- 1 | <%- content_for :action_bar do %> 2 |
      3 |
    1. 4 | <%= link_to t("shared.actions.back"), admin_users_path, class: "btn text-primary" %> 5 |
    2. 6 |
    7 | <% end %> 8 | 9 |
    10 |
    11 |
    12 |

    13 | <%= t(".title") %> 14 |

    15 | 16 | <%= render 'form', user: @user %> 17 |
    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /app/views/admin/users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%- content_for :action_bar do %> 2 |
      3 |
    1. 4 | <%= link_to t("shared.actions.back"), admin_user_path(@user), class: "btn text-primary" %> 5 |
    2. 6 |
    7 | <% end %> 8 | 9 |
    10 |
    11 |
    12 |

    13 | <%= t(".title") %> 14 |

    15 | 16 | <%= render 'form', user: @user %> 17 |
    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /app/views/admin/users/invitations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%- content_for :action_bar do %> 2 |
      3 |
    1. 4 | <%= link_to t("shared.actions.back"), admin_users_path, class: "btn text-primary" %> 5 |
    2. 6 |
    7 | <% end %> 8 | 9 |
    10 |
    11 |
    12 |

    13 | <%= t(".title") %> 14 |

    15 | 16 | <%= render 'form', user: @user %> 17 |
    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /app/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ErrorsController < ApplicationController 4 | layout "sign_in" 5 | 6 | before_action :set_page_meta_tags 7 | 8 | def unauthorized 9 | render status: :unauthorized 10 | end 11 | 12 | def forbidden 13 | render status: :forbidden 14 | end 15 | 16 | def not_found 17 | render status: :not_found 18 | end 19 | 20 | protected 21 | 22 | def set_page_meta_tags 23 | prepare_meta_tags title: t("errors.#{action_name}.title") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/views/layouts/sign_in/_notice.html.erb: -------------------------------------------------------------------------------- 1 | <% flash.each do |k, v| %> 2 |
    3 |
    4 |
    5 |
    6 | 9 | 10 | <%= sanitize v.html_safe %> 11 |
    12 |
    13 |
    14 |
    15 | <% end %> 16 | -------------------------------------------------------------------------------- /config/locales/views/layouts/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | layouts: 3 | header: 4 | brand: "Cybros Core" 5 | account_menu: 6 | admin: "后台管理" 7 | profile: "编辑个人资料" 8 | sign_out: "登出" 9 | footer: 10 | copyright: "© 2019 Jasl, MIT license" 11 | sidebar: 12 | application: 13 | header: "Home" 14 | home: "首页" 15 | accounts: 16 | header: "用户设置" 17 | profile: "个人信息" 18 | password: "密码" 19 | admin: 20 | header: "后台管理" 21 | home: "总览" 22 | users: "用户" 23 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "Last" page 2 | - available local variables 3 | url: url to the last page 4 | current_page: a page object for the currently displayed page 5 | num_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | <% unless current_page.last? %> 10 |
  • 11 | <%= link_to_unless current_page.last?, raw(t("views.pagination.last")), url, class: "page-link", remote: remote %> 12 |
  • 13 | <% end %> 14 | -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "First" page 2 | - available local variables 3 | url: url to the first page 4 | current_page: a page object for the currently displayed page 5 | num_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | <% unless current_page.first? %> 10 |
  • 11 | <%= link_to_unless current_page.first?, raw(t("views.pagination.first")), url, class: "page-link", remote: remote %> 12 |
  • 13 | <% end %> 14 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "Next" page 2 | - available local variables 3 | url: url to the next page 4 | current_page: a page object for the currently displayed page 5 | num_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | <% unless current_page.last? %> 10 |
  • 11 | <%= link_to_unless current_page.last?, raw(t("views.pagination.next")), url, class: "page-link", rel: "next", remote: remote %> 12 |
  • 13 | <% end %> 14 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link showing page number 2 | - available local variables 3 | page: a page object for "this" page 4 | url: url to this page 5 | current_page: a page object for the currently displayed page 6 | num_pages: total number of pages 7 | per_page: number of items to fetch per page 8 | remote: data-remote 9 | -%> 10 |
  • "> 11 | <%= link_to page, url, remote: remote, class: "page-link", rel: (page.next? ? "next" : (page.prev? ? "prev" : nil)) %> 12 |
  • 13 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.erb: -------------------------------------------------------------------------------- 1 | <%# Link to the "Previous" page 2 | - available local variables 3 | url: url to the previous page 4 | current_page: a page object for the currently displayed page 5 | num_pages: total number of pages 6 | per_page: number of items to fetch per page 7 | remote: data-remote 8 | -%> 9 | <% unless current_page.first? %> 10 |
  • 11 | <%= link_to_unless current_page.first?, raw(t("views.pagination.previous")), url, rel: "prev", remote: remote, class: "page-link" %> 12 |
  • 13 | <% end %> 14 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | ActiveSupport.on_load(:active_record) do 15 | self.include_root_in_json = true 16 | end 17 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | APP_ROOT = File.expand_path("..", __dir__) 4 | Dir.chdir(APP_ROOT) do 5 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 6 | select { |dir| File.expand_path(dir) != __dir__ }. 7 | product(["yarn", "yarnpkg", "yarn.cmd", "yarn.ps1"]). 8 | map { |dir, file| File.expand_path(file, dir) }. 9 | find { |file| File.executable?(file) } 10 | 11 | if yarn 12 | exec yarn, *ARGV 13 | else 14 | $stderr.puts "Yarn executable was not detected in the system." 15 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 16 | exit 1 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/locales/kaminari.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | helpers: 3 | page_entries_info: 4 | more_pages: 5 | display_entries: Displaying %{entry_name} %{first} - %{last} of %{total} in total 6 | one_page: 7 | display_entries: 8 | one: Displaying %{count} %{entry_name} 9 | other: Displaying all %{count} %{entry_name} 10 | zero: No %{entry_name} found 11 | views: 12 | pagination: 13 | first: "« First" 14 | last: Last » 15 | next: Next › 16 | previous: "‹ Prev" 17 | truncate: "…" 18 | -------------------------------------------------------------------------------- /app/controllers/admin/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin 4 | class ApplicationController < ::ApplicationController 5 | before_action :authenticate_user! 6 | before_action :require_admin! 7 | 8 | before_action :set_page_layout_data, if: -> { request.format.html? } 9 | 10 | protected 11 | 12 | def set_page_layout_data 13 | @_sidebar_name = "admin" 14 | end 15 | 16 | def require_admin! 17 | unless user_signed_in? && current_user.admin? 18 | redirect_back fallback_location: root_url 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 6 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 7 | 8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 9 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 10 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 11 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["RAILS_ENV"] ||= "test" 4 | require_relative "../config/environment" 5 | require "rails/test_help" 6 | 7 | if ENV["CIRCLECI"] 8 | gem "minitest-ci" 9 | require "minitest-ci" 10 | 11 | Minitest::Ci.report_dir = "/tmp/test-results" 12 | end 13 | 14 | class ActiveSupport::TestCase 15 | # Run tests in parallel with specified workers 16 | parallelize(workers: :number_of_processors) 17 | 18 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 19 | fixtures :all 20 | 21 | # Add more helper methods to be used by all tests here... 22 | end 23 | -------------------------------------------------------------------------------- /app/models/concerns/user/devise_failsafe.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class User 4 | module DeviseFailsafe 5 | extend ActiveSupport::Concern 6 | 7 | # Failsafe when disabled confirmable 8 | def confirmed? 9 | if defined? super 10 | super 11 | else 12 | true 13 | end 14 | end 15 | 16 | def pending_reconfirmation? 17 | if defined? super 18 | super 19 | else 20 | false 21 | end 22 | end 23 | 24 | def created_by_invite? 25 | if defined? super 26 | super 27 | else 28 | false 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /cybros.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | "folder_exclude_patterns": ["_screenshots", ".bundle",".idea",".capistrano","log","tmp", 7 | "public/assets","coverage","node_modules","public/packs", "storage"], 8 | "file_exclude_patterns": ["*.sublime-workspace",".byebug_history",".DS_Store","config/master.key", 9 | "config/credentials.yml.enc", "db/*.sqlite3",] 10 | } 11 | ], 12 | "settings": 13 | { 14 | "translate_tabs_to_spaces": true, 15 | "trim_trailing_white_space_on_save": true, 16 | "tab_size": 2 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/mailer.yml.example: -------------------------------------------------------------------------------- 1 | default: &default 2 | perform_caching: false 3 | raise_delivery_errors: false 4 | perform_deliveries: true 5 | delivery_method: :smtp 6 | deliver_later_queue_name: 'mailers' 7 | 8 | development: 9 | <<: *default 10 | smtp_settings: 11 | # see https://github.com/sj26/mailcatcher 12 | address: 127.0.0.1 13 | port: 1025 14 | domain: localhost 15 | default_options: 16 | reply_to: admin@cybros.local 17 | from: admin@cybros.local 18 | default_url_options: 19 | host: localhost 20 | port: 3000 21 | 22 | test: 23 | <<: *default 24 | delivery_method: :test 25 | 26 | production: 27 | <<: *default 28 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(false); 3 | const presets = [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "corejs": { "version": 3 }, 8 | "useBuiltIns": "usage", 9 | "targets": { 10 | "edge": "17", 11 | "firefox": "60", 12 | "chrome": "67", 13 | "safari": "11.1", 14 | "ie": "11" 15 | } 16 | } 17 | ] 18 | ]; 19 | const plugins = [ 20 | ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }], 21 | ["@babel/plugin-proposal-class-properties"], 22 | ["@babel/transform-runtime"] 23 | ]; 24 | 25 | return { 26 | presets, 27 | plugins 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /app/controllers/admin/users/invitations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Admin::Users 4 | class InvitationsController < Admin::ApplicationController 5 | def new 6 | prepare_meta_tags title: t(".title") 7 | @user = User.new 8 | end 9 | 10 | def create 11 | @user = User.invite!(user_params, current_user) 12 | 13 | if @user 14 | redirect_to admin_user_url(@user), notice: t(".shared.notice.created") 15 | else 16 | prepare_meta_tags title: t("admin.users.invitations.new.title") 17 | render :new 18 | end 19 | end 20 | 21 | private 22 | 23 | def user_params 24 | params.fetch(:user, {}).permit(:email) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/layouts/sidebars/_application.html.erb: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, "\\1en" 10 | # inflect.singular /^(ox)en/i, "\\1" 11 | # inflect.irregular "person", "people" 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | inflect.acronym "OAuth" 18 | inflect.acronym "API" 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/accounts/profiles_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Accounts 4 | class ProfilesController < Accounts::ApplicationController 5 | before_action :set_user 6 | 7 | # GET /account/profile 8 | def show 9 | end 10 | 11 | # PUT /account/profile 12 | def update 13 | if @user.update_without_password(user_params) 14 | redirect_to after_update_url, notice: t("accounts.profiles.show.updated") 15 | else 16 | render :show 17 | end 18 | end 19 | 20 | private 21 | 22 | def set_user 23 | @user = current_user 24 | end 25 | 26 | def user_params 27 | params.require(:user).permit(:email) 28 | end 29 | 30 | def after_update_url 31 | account_profile_url 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /config/credentials.yml.example: -------------------------------------------------------------------------------- 1 | # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. 2 | # Generated by `rails secret`. 3 | secret_key_base: de2661d1ace79293b827ed3949b72b25a2d9f7aa18ef60e17f7c9e86bf925b66a1ea71b1d434b2306c7d5d7e86f46407aff3061c70afc0337c7ecb11f5c90492 4 | 5 | # Used as the base secret for ActiveStorage MessageVerifiers in Rails. 6 | # Generated by `rails secret`. 7 | storage_key_base: 50f06c284dc313e39758225742017a69504d2675e8e6a71cea9878dc95fc07c2fc08ed94bc4634e7aacf53d8c58827e9cd17527769ef6518defe88b619b9fc1c 8 | 9 | # Used as the base secret for Devise to generate random tokens. 10 | # Generated by `rails secret`. 11 | devise_key_base: ff6ec88f8014d272b51502d81ef9dd07ed09628ff2062fb4d78c0e5c117d66c211439c9933a589a819c20828addcf727567acc6e79e2996e782d9045023b35a1 12 | -------------------------------------------------------------------------------- /app/controllers/users/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Users 4 | class SessionsController < Devise::SessionsController 5 | before_action -> { prepare_meta_tags title: t("users.sessions.new.title") }, 6 | if: -> { request.format.html? }, only: [:new, :create] 7 | layout "sign_in" 8 | 9 | # before_action :configure_sign_in_params, only: [:create] 10 | 11 | def new 12 | super 13 | end 14 | 15 | def create 16 | super 17 | end 18 | 19 | # DELETE /resource/sign_out 20 | # def destroy 21 | # super 22 | # end 23 | 24 | protected 25 | 26 | # If you have extra params to permit, append them to the sanitizer. 27 | # def configure_sign_in_params 28 | # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) 29 | # end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/views/layouts/base.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | <%= display_meta_tags separator: "-", reverse: true %> 10 | <%#= render "layouts/favicon" %> 11 | 12 | <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 13 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 14 | 15 | <%= yield :head %> 16 | 17 | 18 | 19 | <%= content_for?(:content) ? yield(:content) : yield %> 20 | <%= yield :body %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/views/layouts/sidebars/_admin.html.erb: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | <%- body_class = "app header-fixed" %> 2 | <%- body_class << " sidebar-fixed sidebar-lg-show" if has_sidebar? %> 3 | <%- body_class << " aside-menu-fixed" if has_aside? %> 4 | <% content_for(:body_class, body_class) %> 5 | <% content_for :content do %> 6 | <%= render "layouts/application/header" %> 7 |
    8 | <%= render_sidebar %> 9 |
    10 |
    11 | <%= render_breadcrumbs %> 12 | <%= render_action_bar %> 13 | <%= render "layouts/application/notice" %> 14 |
    15 |
    16 | <%= yield %> 17 |
    18 |
    19 | <%= render_aside %> 20 |
    21 | <%= render "layouts/application/footer" %> 22 | <% end %> 23 | 24 | <%= render template: "layouts/base" %> 25 | 26 | -------------------------------------------------------------------------------- /app/views/layouts/sidebars/_accounts.html.erb: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "fileutils" 5 | 6 | # path to your application root. 7 | APP_ROOT = File.expand_path("..", __dir__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | FileUtils.chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts "== Installing dependencies ==" 18 | system! "gem install bundler --conservative" 19 | system("bundle check") || system!("bundle install") 20 | 21 | # Install JavaScript dependencies 22 | # system('bin/yarn') 23 | 24 | puts "\n== Updating database ==" 25 | system! "rails db:migrate" 26 | 27 | puts "\n== Removing old logs and tempfiles ==" 28 | system! "rails log:clear tmp:clear" 29 | 30 | puts "\n== Restarting application server ==" 31 | system! "rails restart" 32 | end 33 | -------------------------------------------------------------------------------- /app/controllers/accounts/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Accounts 4 | class PasswordsController < Accounts::ApplicationController 5 | before_action :set_user 6 | 7 | # GET /account/password 8 | def show 9 | end 10 | 11 | # PUT /account/password/ 12 | def update 13 | if @user.update_with_password(user_params) 14 | bypass_sign_in @user, scope: :user 15 | redirect_to after_update_url, notice: t("accounts.passwords.show.updated") 16 | else 17 | @user.clean_up_passwords 18 | render :show 19 | end 20 | end 21 | 22 | private 23 | 24 | def set_user 25 | @user = current_user 26 | end 27 | 28 | def user_params 29 | params.require(:user).permit(:password, :password_confirmation, :current_password) 30 | end 31 | 32 | def after_update_url 33 | account_password_url 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Custom for Visual Studio, very unlikely, but lets keep it 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | 24 | # Enforce Unix newlines 25 | *.css text eol=lf 26 | *.html text eol=lf 27 | *.erb text eol=lf 28 | *.js text eol=lf 29 | *.json text eol=lf 30 | *.md text eol=lf 31 | *.py text eol=lf 32 | *.rb text eol=lf 33 | *.scss text eol=lf 34 | *.svg text eol=lf 35 | *.yml text eol=lf 36 | 37 | # Don't diff or textually merge source maps 38 | *.map binary 39 | -------------------------------------------------------------------------------- /app/views/admin/home/index.html.erb: -------------------------------------------------------------------------------- 1 | <%- content_for :aside do %> 2 |
    3 |
    4 |
    Cybros details
    5 |
    6 | 7 | Feature complete % 8 | 9 |
    10 |
    11 |
    12 |
    13 | source code 14 |
    15 |
    16 | <% end %> 17 | 18 | <%- content_for :action_bar do %> 19 |
      20 |
    1. 21 | <%= link_to "Home", root_path %> 22 |
    2. 23 |
    3. 24 | <%= link_to "Home", root_path %> 25 |
    4. 26 |
    5. 27 | <%= link_to "Home", root_path %> 28 |
    6. 29 |
    30 | <% end %> 31 | 32 |

    Home#index

    33 |

    Find me in app/views/home/index.html.erb

    34 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | values: 34 | 'true': "Yes" 35 | 'false': "No" 36 | -------------------------------------------------------------------------------- /config/locales/zh-CN.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | zh-CN: 33 | values: 34 | 'true': "是" 35 | 'false': "否" 36 | -------------------------------------------------------------------------------- /app/controllers/users/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Users 4 | class ConfirmationsController < Devise::ConfirmationsController 5 | layout "sign_in" 6 | 7 | # GET /resource/confirmation/new 8 | def new 9 | prepare_meta_tags title: t("users.confirmations.new.title") 10 | super 11 | end 12 | 13 | # POST /resource/confirmation 14 | def create 15 | prepare_meta_tags title: t("users.confirmations.new.title") 16 | super 17 | end 18 | 19 | # GET /resource/confirmation?confirmation_token=abcdef 20 | # def show 21 | # super 22 | # end 23 | 24 | # protected 25 | 26 | # The path used after resending confirmation instructions. 27 | # def after_resending_confirmation_instructions_path_for(resource_name) 28 | # super(resource_name) 29 | # end 30 | 31 | # The path used after confirmation. 32 | # def after_confirmation_path_for(resource_name, resource) 33 | # super(resource_name, resource) 34 | # end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/packs/entrypoints/application.js: -------------------------------------------------------------------------------- 1 | // This file is automatically compiled by Webpack, along with any other files 2 | // present in this directory. You're encouraged to place your actual application logic in 3 | // a relevant structure within app/javascript and only use these pack files to reference 4 | // that code so it'll be compiled. 5 | 6 | const importAll = (r) => r.keys().map(r) 7 | importAll(require.context('images', false, /\.(png|jpe?g|svg)$/i)); 8 | //importAll(require.context('@fortawesome/fontawesome-free/webfonts', false, /\.(eot|svg|ttf|woff2?)$/i)); 9 | 10 | import "core-js/stable"; 11 | import "regenerator-runtime/runtime"; 12 | import "@stimulus/polyfills"; 13 | 14 | import JQuery from 'jquery'; 15 | window.$ = window.JQuery = JQuery; 16 | 17 | import "bootstrap"; 18 | import "@mixtint/coreui" 19 | 20 | require("@rails/ujs").start() 21 | require("turbolinks").start() 22 | //require("@rails/activestorage").start() 23 | require("channels") 24 | 25 | import "turbolinks/coreui"; 26 | import "controllers"; 27 | import "stylesheets/application.scss" 28 | -------------------------------------------------------------------------------- /app/views/accounts/profiles/show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    7 | <%= t(".section.email.title") %> 8 |
    9 |

    10 | <%= t(".section.email.description") %> 11 |

    12 |
    13 |
    14 | <%= form_with(model: @user, url: account_profile_path, html: {method: :put}, local: true) do |f| %> 15 |
    16 | <%= f.label :email %> 17 | <%= f.email_field :email, id: "user_email", autofocus: true, required: "required", class: "form-control", class_for_error: "is-invalid" %> 18 | <%= f.error_message :email, class: "invalid-feedback" %> 19 |
    20 | 21 | <%= f.submit t(".submit"), class: "btn btn-block btn-primary" %> 22 | <% end %> 23 |
    24 |
    25 |
    26 |
    27 |
    28 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 jasl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.erb: -------------------------------------------------------------------------------- 1 | <%# The container tag 2 | - available local variables 3 | current_page: a page object for the currently displayed page 4 | num_pages: total number of pages 5 | per_page: number of items to fetch per page 6 | remote: data-remote 7 | paginator: the paginator that renders the pagination tags inside 8 | -%> 9 | <%- wrapper_class ||= "" %> 10 | <%- pagination_class ||= "" %> 11 | <%= paginator.render do -%> 12 | 27 | <% end -%> 28 | -------------------------------------------------------------------------------- /app/views/users/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 | <%= t(".title") %> 7 |
    8 | 9 | <%= form_with(model: resource, as: resource_name, url: password_path(resource_name), local: true) do |f| %> 10 |
    11 | <%= f.label :email %> 12 | <%= f.email_field :email, id: "user_email", autofocus: true, autocomplete: "username", required: "required", class: "form-control", class_for_error: "is-invalid" %> 13 | <%= f.error_message :email, class: "invalid-feedback" %> 14 |
    15 | 16 | <%= f.submit t(".submit"), class: "btn btn-block btn-primary" %> 17 | <% end %> 18 |
    19 |
    20 |
    21 |
    22 | 23 | <%- if devise_mapping.database_authenticatable? %> 24 |
    25 | <%= link_to t(".back_to_sign_in_instructions"), new_session_path(resource_name) %> 26 |
    27 | <% end %> 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't put *.swp, *.bak, etc here; those belong in a global .gitignore. 2 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 3 | # 4 | # If you find yourself ignoring temporary files generated by your text editor 5 | # or operating system, you probably want to add a global ignore instead: 6 | # git config --global core.excludesfile '~/.gitignore_global' 7 | 8 | # Ignore bundler config. 9 | /.bundle 10 | 11 | # Ignore all logfiles and tempfiles. 12 | /log/* 13 | /tmp/* 14 | !/log/.keep 15 | !/tmp/.keep 16 | 17 | /db/*.sqlite3 18 | 19 | # Ignore uploaded files in development. 20 | /storage/* 21 | !/storage/.keep 22 | 23 | /public/assets 24 | .byebug_history 25 | 26 | # Ignore encryted credentials. 27 | /config/master.key 28 | /config/credentials.yml 29 | /config/credentials.yml.enc 30 | 31 | /public/packs 32 | /public/packs-test 33 | /node_modules 34 | /yarn-error.log 35 | yarn-debug.log* 36 | .yarn-integrity 37 | 38 | /config/database.yml 39 | /config/mailer.yml 40 | /config/storage.yml 41 | 42 | config/settings.local.yml 43 | config/settings/*.local.yml 44 | config/environments/*.local.yml 45 | -------------------------------------------------------------------------------- /app/packs/controllers/coreui_sidebar_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "stimulus"; 2 | 3 | const SIDEBAR_MINIMIZER = 'button.sidebar-minimizer'; 4 | 5 | export default class extends Controller { 6 | connect() { 7 | $(SIDEBAR_MINIMIZER).on('click', this.handleStoreSidebarStates); 8 | if(localStorage.getItem('coreui-sidebar-minimized') == 'true') { 9 | setTimeout(function(){ 10 | $(SIDEBAR_MINIMIZER).click(); 11 | setTimeout(function(){ 12 | $('body').addClass('sidebar-minimized'); 13 | }, 150); 14 | }, 20); 15 | } 16 | } 17 | 18 | handleStoreSidebarStates = (event) => { 19 | setTimeout(function(){ 20 | if($('body.sidebar-minimized').length) { 21 | localStorage.setItem('coreui-sidebar-minimized', true); 22 | } else { 23 | localStorage.removeItem('coreui-sidebar-minimized'); 24 | } 25 | }, 200); 26 | } 27 | 28 | disconnect() { 29 | if($('body.sidebar-minimized').length) { 30 | localStorage.setItem('coreui-sidebar-minimized', true); 31 | } 32 | $(SIDEBAR_MINIMIZER).off('click', this.handleStoreSidebarStates); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/controllers/users/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Users 4 | class PasswordsController < Devise::PasswordsController 5 | layout "sign_in" 6 | 7 | # GET /resource/password/new 8 | def new 9 | prepare_meta_tags title: t("users.passwords.new.title") 10 | super 11 | end 12 | 13 | # POST /resource/password 14 | def create 15 | prepare_meta_tags title: t("users.passwords.new.title") 16 | super 17 | end 18 | 19 | # GET /resource/password/edit?reset_password_token=abcdef 20 | def edit 21 | prepare_meta_tags title: t("users.passwords.edit.title") 22 | super 23 | end 24 | 25 | # PUT /resource/password 26 | def update 27 | prepare_meta_tags title: t("users.passwords.edit.title") 28 | super 29 | end 30 | 31 | # protected 32 | 33 | # def after_resetting_password_path_for(resource) 34 | # super(resource) 35 | # end 36 | 37 | # The path used after sending reset password instructions 38 | # def after_sending_reset_password_instructions_path_for(resource_name) 39 | # super(resource_name) 40 | # end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/views/users/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 | <%= t(".title") %> 7 |
    8 | 9 | <%= form_with(model: resource, as: resource_name, url: confirmation_path(resource_name), local: true) do |f| %> 10 |
    11 | <%= f.label :email %> 12 | <%= f.email_field :email, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), id: "user_email", autofocus: true, required: "required", class: "form-control", class_for_error: "is-invalid" %> 13 | <%= f.error_message :email, class: "invalid-feedback" %> 14 |
    15 | 16 | <%= f.submit t(".submit"), class: "btn btn-block btn-primary" %> 17 | <% end %> 18 |
    19 |
    20 |
    21 |
    22 | 23 | <%- if devise_mapping.database_authenticatable? %> 24 |
    25 | <%= link_to t(".back_to_sign_in_instructions"), new_session_path(resource_name) %> 26 |
    27 | <% end %> 28 | -------------------------------------------------------------------------------- /app/models/concerns/enum_attribute_localizable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module EnumAttributeLocalizable 4 | extend ActiveSupport::Concern 5 | 6 | module ClassMethods 7 | def human_enum_value(attribute, value, options = {}) 8 | parts = attribute.to_s.split(".") 9 | attribute = parts.pop.pluralize 10 | attributes_scope = "#{i18n_scope}.attributes" 11 | 12 | if parts.any? 13 | namespace = parts.join("/") 14 | defaults = lookup_ancestors.map do |klass| 15 | :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}.#{value}" 16 | end 17 | defaults << :"#{attributes_scope}.#{namespace}.#{attribute}.#{value}" 18 | else 19 | defaults = lookup_ancestors.map do |klass| 20 | :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}.#{value}" 21 | end 22 | end 23 | 24 | defaults << :"attributes.#{attribute}.#{value}" 25 | defaults << options.delete(:default) if options[:default] 26 | defaults << value.to_s.humanize 27 | 28 | options[:default] = defaults 29 | I18n.translate(defaults.shift, **options) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/views/admin/users/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: [:admin, user], local: true) do |f| %> 2 |
    3 | <%= f.label :email %> 4 | <%= f.email_field :email, id: "user_email", autofocus: true, required: "required", class: "form-control", class_for_error: "is-invalid" %> 5 | <%= f.error_message :email, class: "invalid-feedback" %> 6 |
    7 | 8 |
    9 | <%= f.label :password %> 10 | <%= f.password_field :password, id: "user_password", autocomplete: "off", required: !user.persisted?, class: "form-control", class_for_error: "is-invalid" %> 11 | <%= f.error_message :password, class: "invalid-feedback" %> 12 |
    13 | 14 |
    15 | <%= f.label :password_confirmation %> 16 | <%= f.password_field :password_confirmation, id: "user_password_confirmation", autocomplete: "off", required: !user.persisted?, class: "form-control", class_for_error: "is-invalid" %> 17 | <%= f.error_message :password_confirmation, class: "invalid-feedback" %> 18 |
    19 | 20 | <%= f.submit (user.persisted? ? t("shared.form.submit.update") : t("shared.form.submit.create")), class: "btn btn-block btn-primary" %> 21 | <% end %> 22 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # Install JavaScript dependencies 21 | system! 'bin/yarn' 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?("config/database.yml") 25 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! "bin/rails db:prepare" 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! "bin/rails log:clear tmp:clear" 33 | 34 | puts "\n== Restarting application server ==" 35 | system! "bin/rails restart" 36 | end 37 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Define an application-wide content security policy 5 | # For further information see the following documentation 6 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 7 | 8 | # Rails.application.configure do 9 | # config.content_security_policy do |policy| 10 | # policy.default_src :self, :https 11 | # policy.font_src :self, :https, :data 12 | # policy.img_src :self, :https, :data 13 | # policy.object_src :none 14 | # policy.script_src :self, :https 15 | # policy.style_src :self, :https 16 | # # Specify URI for violation reports 17 | # # policy.report_uri "/csp-violation-report-endpoint" 18 | # end 19 | # 20 | # # Generate session nonces for permitted importmap and inline scripts 21 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 22 | # config.content_security_policy_nonce_directives = %w(script-src) 23 | # 24 | # # Report CSP violations to a specified URI. See: 25 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 26 | # # config.content_security_policy_report_only = true 27 | # end 28 | -------------------------------------------------------------------------------- /config/storage.yml.example: -------------------------------------------------------------------------------- 1 | development: 2 | service: Disk 3 | root: <%= Rails.root.join("storage") %> 4 | 5 | test: 6 | service: Disk 7 | root: <%= Rails.root.join("tmp/storage") %> 8 | 9 | production: 10 | service: Disk 11 | root: <%= Rails.root.join("storage") %> 12 | 13 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 14 | # amazon: 15 | # service: S3 16 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 17 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 18 | # region: us-east-1 19 | # bucket: your_own_bucket 20 | 21 | # Remember not to checkin your GCS keyfile to a repository 22 | # google: 23 | # service: GCS 24 | # project: your_project 25 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 26 | # bucket: your_own_bucket 27 | 28 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 29 | # microsoft: 30 | # service: AzureStorage 31 | # storage_account_name: your_account_name 32 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 33 | # container: your_container_name 34 | 35 | # mirror: 36 | # service: Mirror 37 | # primary: local 38 | # mirrors: [ amazon, google, microsoft ] 39 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | image: circleci/ruby:3.0.3-node-browsers 3 | 4 | # Pick zero or more services to be used on all builds. 5 | # Only needed when using a docker container to run your tests in. 6 | # Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service 7 | services: 8 | - postgres:12.9 9 | 10 | variables: 11 | POSTGRES_HOST: postgres 12 | POSTGRES_DB: thape_web_test 13 | POSTGRES_USER: postgres 14 | POSTGRES_PASSWORD: "postgres" 15 | CACHE_FALLBACK_KEY: $CI_COMMIT_REF_SLUG 16 | 17 | # Cache gems in between builds 18 | cache: 19 | - key: 20 | files: 21 | - Gemfile.lock 22 | paths: 23 | - vendor/ruby 24 | - key: 25 | files: 26 | - yarn.lock 27 | paths: 28 | - .yarn-cache/ 29 | 30 | before_script: 31 | - bundle config mirror.https://rubygems.org https://gems.ruby-china.com 32 | - bundle config set path 'vendor' # Install dependencies into ./vendor/ruby 33 | - bundle install -j $(nproc) 34 | - yarn install --cache-folder .yarn-cache 35 | 36 | test: 37 | script: | 38 | cp config/database.yml.example config/database.yml 39 | cp config/mailer.yml.example config/mailer.yml 40 | cp config/credentials.yml.example config/credentials.yml 41 | bin/rails credentials:encrypt 42 | bin/rails db:drop RAILS_ENV=test 43 | bin/rails db:setup RAILS_ENV=test 44 | bin/rails test 45 | -------------------------------------------------------------------------------- /app/views/users/invitations/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 | <%= t(".title") %> 7 |
    8 | 9 | <%= form_with(model: resource, as: resource_name, url: invitation_path(resource_name), method: :put, local: true) do |f| %> 10 | <%= f.hidden_field :invitation_token, readonly: true %> 11 | 12 |
    13 | <%= f.label :password, t(".password") %> 14 | <%= f.password_field :password, id: "user_password", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 15 | <%= f.error_message :password, class: "invalid-feedback" %> 16 |
    17 | 18 |
    19 | <%= f.label :password_confirmation, t(".confirm_password") %> 20 | <%= f.password_field :password_confirmation, id: "user_password_confirmation", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 21 | <%= f.error_message :password_confirmation, class: "invalid-feedback" %> 22 |
    23 | 24 | <%= f.submit t(".submit"), class: "btn btn-block btn-primary" %> 25 | <% end %> 26 |
    27 |
    28 |
    29 |
    30 | -------------------------------------------------------------------------------- /app/views/users/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 | <%= t(".title") %> 7 |
    8 | 9 | <%= form_with(model: resource, as: resource_name, url: password_path(resource_name), html: {method: :put}, local: true) do |f| %> 10 | <%= f.hidden_field :reset_password_token %> 11 | 12 |
    13 | <%= f.label :password, t(".new_password") %> 14 | <%= f.password_field :password, id: "user_password", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 15 | <%= f.error_message :password, class: "invalid-feedback" %> 16 |
    17 | 18 |
    19 | <%= f.label :password_confirmation, t(".confirm_new_password") %> 20 | <%= f.password_field :password_confirmation, id: "user_password_confirmation", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 21 | <%= f.error_message :password_confirmation, class: "invalid-feedback" %> 22 |
    23 | 24 | <%= f.submit t(".submit"), class: "btn btn-block btn-primary" %> 25 | <% end %> 26 |
    27 |
    28 |
    29 |
    30 | -------------------------------------------------------------------------------- /config/locales/views/admin/users/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | admin: 3 | users: 4 | shared: 5 | status: 6 | admin: "管理员" 7 | pending_confirmation: "未验证邮箱" 8 | pending_reconfirmation: "未验证新邮箱" 9 | inviting: "待接受邀请" 10 | locked: "已禁用" 11 | notice: 12 | locked: "用户已被禁用" 13 | unlocked: "用户已恢复使用" 14 | updated: "用户更新成功" 15 | created: "用户创建成功" 16 | sent_confirmation_mail: "激活邮件已发送" 17 | sent_invitation_mail: "邀请邮件已发送" 18 | confirmation: 19 | lock: "确定要禁用该用户吗?" 20 | unlock: "确定要恢复该用户吗?" 21 | index: 22 | title: "用户管理" 23 | actions: 24 | new: "新建用户" 25 | invite: "邀请用户" 26 | search: "搜索" 27 | table: 28 | id: "Id" 29 | email: "邮箱" 30 | created_at: "创建于" 31 | current_sign_in_at: "最后登录" 32 | status: "状态" 33 | actions: 34 | show: "查看" 35 | show: 36 | actions: 37 | lock: "禁用" 38 | unlock: "启用" 39 | resend_confirmation_mail: "重发激活邮件" 40 | resend_invitation_mail: "重发邀请邮件" 41 | email: "邮箱" 42 | created_at: "创建于" 43 | current_sign_in_at: "最后登录" 44 | status: "状态" 45 | memberships: "加入的组织" 46 | table: 47 | tenant: "租户" 48 | role: "角色" 49 | created_at: "创建于" 50 | new: 51 | title: "新建用户" 52 | edit: 53 | title: "编辑用户" 54 | -------------------------------------------------------------------------------- /app/views/layouts/application/_header.html.erb: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /app/controllers/users/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Users 4 | class RegistrationsController < Devise::RegistrationsController 5 | layout "sign_in", only: [:new, :create] 6 | 7 | before_action :configure_sign_up_params, only: [:create] 8 | 9 | # GET /resource/sign_up 10 | def new 11 | prepare_meta_tags title: t("users.registrations.new.title") 12 | super 13 | end 14 | 15 | # POST /resource 16 | def create 17 | prepare_meta_tags title: t("users.registrations.new.title") 18 | super 19 | end 20 | 21 | # DELETE /resource 22 | # def destroy 23 | # super 24 | # end 25 | 26 | # GET /resource/cancel 27 | # Forces the session data which is usually expired after sign 28 | # in to be expired now. This is useful if the user wants to 29 | # cancel oauth signing in/up in the middle of the process, 30 | # removing all OAuth session data. 31 | # def cancel 32 | # super 33 | # end 34 | 35 | protected 36 | 37 | # If you have extra params to permit, append them to the sanitizer. 38 | def configure_sign_up_params 39 | devise_parameter_sanitizer.permit(:sign_up) do |user_params| 40 | user_params.permit(:email, :password, :password_confirmation) 41 | end 42 | end 43 | 44 | # The path used after sign up. 45 | def after_sign_up_path_for(resource) 46 | super(resource) 47 | end 48 | 49 | # The path used after sign up for inactive accounts. 50 | def after_inactive_sign_up_path_for(resource) 51 | new_session_path(resource_name) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | before_action :prepare_meta_tags, if: -> { request.format.html? } 5 | 6 | include StoreLocation 7 | 8 | protected 9 | 10 | def prepare_meta_tags(options = {}) 11 | site_name = Settings.seo_meta.name 12 | title = nil 13 | description = Settings.seo_meta.description 14 | current_url = request.url 15 | 16 | # Let's prepare a nice set of defaults 17 | defaults = { 18 | site: site_name, 19 | title: title, 20 | description: description, 21 | keywords: Settings.seo_meta.keywords, 22 | og: { 23 | url: current_url, 24 | site_name: site_name, 25 | title: title, 26 | description: description, 27 | type: "website" 28 | } 29 | } 30 | 31 | options.reverse_merge!(defaults) 32 | 33 | set_meta_tags options 34 | end 35 | 36 | def forbidden!(redirect_url: nil) 37 | if redirect_url.present? 38 | store_location redirect_url 39 | else 40 | store_location request.referrer 41 | end 42 | 43 | redirect_to forbidden_url 44 | end 45 | 46 | %w(forbidden unauthorized not_found).each do |s| 47 | class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 48 | def #{s}!(redirect_url: nil) 49 | if redirect_url.present? 50 | store_location redirect_url 51 | else 52 | store_location request.referrer 53 | end 54 | 55 | redirect_to #{s}_url 56 | end 57 | RUBY_EVAL 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/monkey_patches/active_support/concern+prependable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Cheated from https://gitlab.com/gitlab-org/gitlab-ee/blob/master/config/initializers/0_as_concern.rb 4 | # This module is based on: https://gist.github.com/bcardarella/5735987 5 | 6 | module Prependable 7 | class MultiplePrependedBlocks < StandardError 8 | def initialize 9 | super "Cannot define multiple 'prepended' blocks for a Concern" 10 | end 11 | end 12 | 13 | def prepend_features(base) 14 | return false if prepended?(base) 15 | 16 | super 17 | 18 | if const_defined?(:ClassMethods) 19 | klass_methods = const_get(:ClassMethods, false) 20 | base.singleton_class.prepend klass_methods 21 | base.instance_variable_set(:@_prepended_class_methods, klass_methods) 22 | end 23 | 24 | if instance_variable_defined?(:@_prepended_block) 25 | base.class_eval(&@_prepended_block) 26 | end 27 | 28 | true 29 | end 30 | 31 | def class_methods 32 | super 33 | 34 | if instance_variable_defined?(:@_prepended_class_methods) 35 | const_get(:ClassMethods, false).prepend @_prepended_class_methods 36 | end 37 | end 38 | 39 | def prepended(base = nil, &block) 40 | if base.nil? 41 | raise MultiplePrependedBlocks if 42 | instance_variable_defined?(:@_prepended_block) 43 | 44 | @_prepended_block = block 45 | else 46 | super 47 | end 48 | end 49 | 50 | def prepended?(base) 51 | index = base.ancestors.index(base) 52 | 53 | base.ancestors[0...index].index(self) 54 | end 55 | end 56 | 57 | module ActiveSupport 58 | module Concern 59 | prepend Prependable 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | namespace :account, module: "accounts" do 5 | resource :password, only: %i[show update] 6 | resource :profile, only: %i[show update] 7 | end 8 | 9 | namespace :admin do 10 | root to: "home#index" 11 | 12 | resources :users, except: %i[destroy] do 13 | collection do 14 | resources :invitations, only: %i[new create], module: :users 15 | end 16 | 17 | member do 18 | patch :lock 19 | patch :unlock 20 | patch :resend_confirmation_mail 21 | patch :resend_invitation_mail 22 | end 23 | end 24 | end 25 | 26 | devise_scope :user do 27 | namespace :users, as: "user" do 28 | resource :registration, 29 | only: %i[new create], 30 | path: "", 31 | path_names: { new: "sign_up" } 32 | 33 | resource :invitation, 34 | path: "invitation" do 35 | get :edit, path: "accept", as: :accept 36 | get :destroy, path: "remove", as: :remove 37 | end 38 | end 39 | end 40 | 41 | devise_for :users, skip: %i[registrations invitations], controllers: { 42 | confirmations: "users/confirmations", 43 | passwords: "users/passwords", 44 | sessions: "users/sessions" 45 | } 46 | 47 | get "users", to: redirect("/users/sign_up") 48 | 49 | get "401", to: "errors#unauthorized", as: :unauthorized 50 | get "403", to: "errors#forbidden", as: :forbidden 51 | get "404", to: "errors#not_found", as: :not_found 52 | 53 | root to: "home#index" 54 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html 55 | end 56 | -------------------------------------------------------------------------------- /db/migrate/20190302174954_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :users do |t| 4 | # Database authenticatable 5 | t.string :email, null: false, default: "", index: { unique: true } 6 | t.string :encrypted_password, null: false, default: "" 7 | 8 | # Recoverable 9 | t.string :reset_password_token, index: { unique: true } 10 | t.datetime :reset_password_sent_at 11 | 12 | # Rememberable 13 | t.datetime :remember_created_at 14 | 15 | # Trackable 16 | t.integer :sign_in_count, default: 0, null: false 17 | t.datetime :current_sign_in_at 18 | t.datetime :last_sign_in_at 19 | t.string :current_sign_in_ip 20 | t.string :last_sign_in_ip 21 | 22 | # Confirmable 23 | t.string :confirmation_token, index: { unique: true } 24 | t.datetime :confirmed_at 25 | t.datetime :confirmation_sent_at 26 | t.string :unconfirmed_email # Only if using reconfirmable 27 | 28 | # Lockable 29 | t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 30 | t.string :unlock_token, index: { unique: true } # Only if unlock strategy is :email or :both 31 | t.datetime :locked_at 32 | 33 | # Invitable 34 | t.string :invitation_token, index: { unique: true } 35 | t.datetime :invitation_created_at 36 | t.datetime :invitation_sent_at 37 | t.datetime :invitation_accepted_at 38 | t.integer :invitation_limit 39 | t.references :invited_by, polymorphic: true 40 | t.integer :invitations_count, default: 0 41 | 42 | t.timestamps null: false 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /config/locales/views/admin/users/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | admin: 3 | users: 4 | shared: 5 | status: 6 | admin: "Admin" 7 | pending_confirmation: "Pending confirmation" 8 | pending_reconfirmation: "Pending reconfirmation" 9 | inviting: "Inviting" 10 | locked: "Locked" 11 | notice: 12 | locked: "User disabled" 13 | unlocked: "User enabled" 14 | updated: "User updated" 15 | created: "User created" 16 | sent_confirmation_mail: "Confirmation mail sent" 17 | sent_invitation_mail: "Invitation mail sent" 18 | confirmation: 19 | lock: "Are you sure lock the user?" 20 | unlock: "Are you sure unlock the user?" 21 | index: 22 | title: "User Management" 23 | actions: 24 | new: "New User" 25 | invite: "Invite" 26 | search: "Search" 27 | table: 28 | id: "Id" 29 | uid: "UID" 30 | email: "Email" 31 | created_at: "Created at" 32 | current_sign_in_at: "Current sign in at" 33 | status: "Status" 34 | actions: 35 | show: "Show" 36 | show: 37 | actions: 38 | lock: "Lock" 39 | unlock: "Unlock" 40 | resend_confirmation_mail: "Resend confirmation mail" 41 | resend_invitation_mail: "Resend invitation mail" 42 | email: "Email" 43 | uid: "UID" 44 | phone: "Phone" 45 | name: "Full name" 46 | created_at: "Created at" 47 | current_sign_in_at: "Current sign in at" 48 | status: "Status" 49 | memberships: "Memberships" 50 | table: 51 | tenant: "Tenant" 52 | role: "Role" 53 | created_at: "Created at" 54 | new: 55 | title: "New User" 56 | edit: 57 | title: "Edit User" 58 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | 5 | # TODO: Removing the hack if https://github.com/rails/rails/pull/34777 merged 6 | 7 | require "rails/command" 8 | require "rails/command/base" 9 | 10 | module Rails 11 | module Command 12 | class CredentialsCommand < Rails::Command::Base 13 | option :force, type: :boolean, 14 | desc: "Overwrite encrypted file if already existed" 15 | option :file, type: :string, 16 | desc: "Specify a YAML file that you want to be encrypted as credentials" 17 | option :keep_cleartext, type: :boolean, 18 | desc: "Don't delete the cleartext YAML file after encrypted" 19 | 20 | def encrypt 21 | require_application_and_environment! 22 | 23 | file_path = options[:file] || cleartext_content_path 24 | unless File.exist? file_path 25 | say "Couldn't find #{file_path}." 26 | exit 27 | end 28 | content = File.read file_path 29 | 30 | if File.exist?(content_path) && !options[:force] 31 | say "Encrypted file already existed: #{content_path}. Use --force to replace it." 32 | exit 33 | end 34 | 35 | encrypted = credentials 36 | 37 | ensure_encryption_key_has_been_added if credentials.key.nil? 38 | ensure_credentials_have_been_added 39 | 40 | encrypted.write content 41 | 42 | say "File encrypted and saved." 43 | 44 | unless options[:keep_cleartext] 45 | File.delete file_path 46 | end 47 | end 48 | 49 | private 50 | 51 | def cleartext_content_path 52 | options[:environment] ? "config/credentials/#{options[:environment]}.yml" : "config/credentials.yml" 53 | end 54 | end 55 | end 56 | end 57 | 58 | require "rails/commands" 59 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    We're sorry, but something went wrong.

    62 |
    63 |

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

    64 |
    65 | 66 | 67 | -------------------------------------------------------------------------------- /config/initializers/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Config.setup do |config| 4 | # Name of the constant exposing loaded settings 5 | config.const_name = "Settings" 6 | 7 | # Ability to remove elements of the array set in earlier loaded settings file. For example value: '--'. 8 | # 9 | # config.knockout_prefix = nil 10 | 11 | # Overwrite an existing value when merging a `nil` value. 12 | # When set to `false`, the existing value is retained after merge. 13 | # 14 | # config.merge_nil_values = true 15 | 16 | # Overwrite arrays found in previously loaded settings file. When set to `false`, arrays will be merged. 17 | # 18 | # config.overwrite_arrays = true 19 | 20 | # Load environment variables from the `ENV` object and override any settings defined in files. 21 | # 22 | # config.use_env = false 23 | 24 | # Define ENV variable prefix deciding which variables to load into config. 25 | # 26 | # config.env_prefix = 'Settings' 27 | 28 | # What string to use as level separator for settings loaded from ENV variables. Default value of '.' works well 29 | # with Heroku, but you might want to change it for example for '__' to easy override settings from command line, where 30 | # using dots in variable names might not be allowed (eg. Bash). 31 | # 32 | # config.env_separator = '.' 33 | 34 | # Ability to process variables names: 35 | # * nil - no change 36 | # * :downcase - convert to lower case 37 | # 38 | # config.env_converter = :downcase 39 | 40 | # Parse numeric values as integers instead of strings. 41 | # 42 | # config.env_parse_values = true 43 | 44 | # Validate presence and type of specific config values. Check https://github.com/dry-rb/dry-validation for details. 45 | # 46 | # config.schema do 47 | # required(:name).filled 48 | # required(:age).maybe(:int?) 49 | # required(:email).filled(format?: EMAIL_REGEX) 50 | # end 51 | end 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cybros", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "syncyarnlock": "syncyarnlock -s -k && yarn install --check-files", 7 | "postinstall": "patch-package" 8 | }, 9 | "dependencies": { 10 | "@babel/core": "^7.17.10", 11 | "@babel/plugin-proposal-decorators": "^7.17.9", 12 | "@babel/plugin-transform-runtime": "^7.17.10", 13 | "@babel/preset-env": "^7.17.10", 14 | "@babel/runtime": "^7.17.9", 15 | "@fortawesome/fontawesome-free": "^5.15.4", 16 | "@mixtint/coreui": "^2.1.16-2", 17 | "@rails/actioncable": "^7.0.3", 18 | "@rails/activestorage": "^7.0.3", 19 | "@rails/ujs": "^7.0.3", 20 | "@stimulus/polyfills": "^2.0.0", 21 | "babel-loader": "^8.2.5", 22 | "bootstrap": "^4.6.1", 23 | "chokidar": "^3.5.2", 24 | "compression-webpack-plugin": "^9.2.0", 25 | "core-js": "^3.22.5", 26 | "css-loader": "^6.7.1", 27 | "css-minimizer-webpack-plugin": "^3.4.1", 28 | "jquery": "^3.6.0", 29 | "mini-css-extract-plugin": "~> 2.5.3", 30 | "patch-package": "^6.4.7", 31 | "perfect-scrollbar": "^1.5.5", 32 | "popper.js": "^1.16.1", 33 | "postinstall-postinstall": "^2.1.0", 34 | "regenerator-runtime": "^0.13.9", 35 | "sass": "^1.51.0", 36 | "sass-loader": "^12.6.0", 37 | "shakapacker": "^6.2.1", 38 | "stimulus": "^2.0.0", 39 | "style-loader": "^3.3.1", 40 | "terser-webpack-plugin": "^5.3.1", 41 | "turbolinks": "^5.2.0", 42 | "webpack": "^5.72.1", 43 | "webpack-assets-manifest": "^5.1.0", 44 | "webpack-cli": "^4.9.2", 45 | "webpack-merge": "^5.8.0", 46 | "webpack-sources": "^3.2.3" 47 | }, 48 | "resolutions": { 49 | "chokidar": "^3.5.3" 50 | }, 51 | "browserslist": [ 52 | "defaults" 53 | ], 54 | "devDependencies": { 55 | "@webpack-cli/serve": "^1.6.1", 56 | "typescript": "^3.9.10", 57 | "webpack-dev-server": "^4.9.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The change you wanted was rejected.

    62 |

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

    63 |
    64 |

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

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 10 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 11 | threads min_threads_count, max_threads_count 12 | 13 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 14 | # terminating a worker in development environments. 15 | # 16 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 17 | 18 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 19 | # 20 | port ENV.fetch("PORT") { 3000 } 21 | 22 | # Specifies the `environment` that Puma will run in. 23 | # 24 | environment ENV.fetch("RAILS_ENV") { "development" } 25 | 26 | # Specifies the `pidfile` that Puma will use. 27 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 28 | 29 | # Specifies the number of `workers` to boot in clustered mode. 30 | # Workers are forked web server processes. If using threads and workers together 31 | # the concurrency of the application would be max `threads` * `workers`. 32 | # Workers do not work on JRuby or Windows (both of which do not support 33 | # processes). 34 | # 35 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 36 | 37 | # Use the `preload_app!` method when specifying a `workers` number. 38 | # This directive tells Puma to first boot the application and load code 39 | # before forking the application. This takes advantage of Copy On Write 40 | # process behavior so workers use less memory. 41 | # 42 | # preload_app! 43 | 44 | # Allow puma to be restarted by `rails restart` command. 45 | plugin :tmp_restart 46 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The page you were looking for doesn't exist.

    62 |

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

    63 |
    64 |

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

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /app/views/accounts/passwords/show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    7 | <%= t(".title") %> 8 |
    9 |

    10 | <%= t(".description") %> 11 |

    12 |
    13 |
    14 | <%= form_with(model: @user, url: account_password_path, html: {method: :put}, local: true) do |f| %> 15 |
    16 | <%= f.label :current_password, t(".form.current_password") %> 17 | <%= f.password_field :current_password, id: "user_current_password", autocomplete: "off", required: "required", class: "form-control", class_for_error: "is-invalid" %> 18 | <%= f.error_message :current_password, class: "invalid-feedback" %> 19 |
    20 | 21 |
    22 | <%= f.label :password, t(".form.new_password") %> 23 | <%= f.password_field :password, id: "user_password", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 24 | <%= f.error_message :password, class: "invalid-feedback" %> 25 |
    26 | 27 |
    28 | <%= f.label :password_confirmation, t(".form.confirm_new_password") %> 29 | <%= f.password_field :password_confirmation, id: "user_password_confirmation", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 30 | <%= f.error_message :password_confirmation, class: "invalid-feedback" %> 31 |
    32 | 33 | <%= f.submit t(".form.submit"), class: "btn btn-block btn-primary" %> 34 | <% end %> 35 |
    36 |
    37 |
    38 |
    39 |
    40 | -------------------------------------------------------------------------------- /app/controllers/concerns/store_location.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "uri" 4 | 5 | # Provide the ability to store a location. 6 | # Used to redirect back to a desired path after sign in. 7 | # Included by default in all controllers. 8 | module StoreLocation 9 | extend ActiveSupport::Concern 10 | 11 | included do 12 | helper_method :stored_location?, :stored_location 13 | end 14 | 15 | # Returns and delete (if it's navigational format) the url stored in the session for 16 | # the given scope. Useful for giving redirect backs after sign up: 17 | # 18 | # Example: 19 | # 20 | # redirect_to stored_location || root_path 21 | # 22 | def stored_location(scope: nil) 23 | session_key = stored_location_key(scope) 24 | 25 | if is_navigational_format? 26 | session.delete(session_key) 27 | else 28 | session[session_key] 29 | end 30 | end 31 | 32 | def stored_location?(scope: nil) 33 | session_key = stored_location_key(scope) 34 | session[session_key].present? 35 | end 36 | 37 | # Stores the provided location to redirect the user after signing in. 38 | # Useful in combination with the `stored_location_for` helper. 39 | # 40 | # Example: 41 | # 42 | # store_location_for(dashboard_path) 43 | # redirect_to user_facebook_omniauth_authorize_path 44 | # 45 | def store_location(location, scope: nil) 46 | session_key = stored_location_key(scope) 47 | uri = parse_uri(location) 48 | if uri 49 | path = [uri.path.sub(/\A\/+/, "/"), uri.query].compact.join("?") 50 | path = [path, uri.fragment].compact.join("#") 51 | session[session_key] = path 52 | end 53 | end 54 | 55 | private 56 | 57 | def parse_uri(location) 58 | location && URI.parse(location) 59 | rescue URI::InvalidURIError 60 | nil 61 | end 62 | 63 | def stored_location_key(scope = nil) 64 | if scope.blank? 65 | "return_to" 66 | else 67 | "#{scope}_return_to" 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | --- 3 | default: &default 4 | source_path: app/packs 5 | source_entry_path: entrypoints 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/webpacker 9 | webpack_compile_output: true 10 | 11 | # Additional paths webpack should look up modules 12 | # ['app/assets', 'engine/foo/app/assets'] 13 | additional_paths: ['app/assets'] 14 | 15 | # Reload manifest.json on all requests so we reload latest compiled packs 16 | cache_manifest: false 17 | 18 | development: 19 | <<: *default 20 | compile: true 21 | 22 | # Reference: https://webpack.js.org/configuration/dev-server/ 23 | dev_server: 24 | https: false 25 | host: localhost 26 | port: 3035 27 | # Hot Module Replacement updates modules while the application is running without a full reload 28 | hmr: false 29 | # Defaults to the inverse of hmr. Uncomment to manually set this. 30 | # live_reload: true 31 | client: 32 | # Should we show a full-screen overlay in the browser when there are compiler errors or warnings? 33 | overlay: true 34 | # May also be a string 35 | # webSocketURL: 36 | # hostname: "0.0.0.0" 37 | # pathname: "/ws" 38 | # port: 8080 39 | # Should we use gzip compression? 40 | compress: true 41 | # Note that apps that do not check the host are vulnerable to DNS rebinding attacks 42 | allowed_hosts: "all" 43 | pretty: true 44 | headers: 45 | 'Access-Control-Allow-Origin': '*' 46 | static: 47 | watch: 48 | ignored: '**/node_modules/**' 49 | 50 | test: 51 | <<: *default 52 | compile: true 53 | 54 | # Compile test packs to a separate directory 55 | public_output_path: packs-test 56 | 57 | production: 58 | <<: *default 59 | 60 | # Production depends on precompilation of packs prior to booting for performance. 61 | compile: false 62 | 63 | # Cache manifest.json for performance 64 | cache_manifest: true 65 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | ruby ">= 2.7" 7 | 8 | gem "rails", "~> 7.0.3" 9 | gem "rails-i18n" 10 | 11 | # Use postgresql as the database for Active Record 12 | gem "pg" 13 | 14 | # Use Puma as the app server 15 | gem "puma" 16 | # Use development version of Webpacker 17 | gem "shakapacker" 18 | 19 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 20 | gem "turbolinks", "~> 5" 21 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 22 | gem "jbuilder" 23 | # Use Redis adapter to run Action Cable in production 24 | # gem 'redis', '~> 4.0' 25 | # Use Active Model has_secure_password 26 | # gem 'bcrypt', '~> 3.1.7' 27 | 28 | # Use Active Storage variant 29 | # gem 'image_processing', '~> 1.2' 30 | 31 | # Reduces boot times through caching; required in config/boot.rb 32 | gem "bootsnap", ">= 1.9.3", require: false 33 | 34 | gem "config" 35 | 36 | gem "devise" 37 | gem "devise_invitable" 38 | gem "devise-i18n" 39 | 40 | gem "meta-tags" 41 | 42 | gem "browser" 43 | 44 | gem "kaminari" 45 | 46 | group :development, :test do 47 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 48 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw] 49 | end 50 | 51 | group :development do 52 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 53 | gem "web-console" 54 | gem "listen" 55 | 56 | gem "brakeman", require: false 57 | gem "rubocop", require: false 58 | gem "rubocop-rails", require: false 59 | gem "rubocop-performance", require: false 60 | end 61 | 62 | group :test do 63 | # Adds support for Capybara system testing and selenium driver 64 | gem "capybara" 65 | gem "selenium-webdriver" 66 | gem "webdrivers" 67 | end 68 | 69 | group :circle_ci do 70 | gem "minitest-ci" 71 | end 72 | 73 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 74 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] 75 | -------------------------------------------------------------------------------- /patches/@fortawesome+fontawesome-free+5.15.4.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@fortawesome/fontawesome-free/scss/_larger.scss b/node_modules/@fortawesome/fontawesome-free/scss/_larger.scss 2 | index 27c2ad5..6233427 100644 3 | --- a/node_modules/@fortawesome/fontawesome-free/scss/_larger.scss 4 | +++ b/node_modules/@fortawesome/fontawesome-free/scss/_larger.scss 5 | @@ -1,10 +1,11 @@ 6 | +@use "sass:math"; 7 | // Icon Sizes 8 | // ------------------------- 9 | 10 | // makes the font 33% larger relative to the icon container 11 | .#{$fa-css-prefix}-lg { 12 | - font-size: (4em / 3); 13 | - line-height: (3em / 4); 14 | + font-size: math.div(4em, 3); 15 | + line-height: math.div(3em, 4); 16 | vertical-align: -.0667em; 17 | } 18 | 19 | diff --git a/node_modules/@fortawesome/fontawesome-free/scss/_list.scss b/node_modules/@fortawesome/fontawesome-free/scss/_list.scss 20 | index 8ebf333..abb3c41 100644 21 | --- a/node_modules/@fortawesome/fontawesome-free/scss/_list.scss 22 | +++ b/node_modules/@fortawesome/fontawesome-free/scss/_list.scss 23 | @@ -1,9 +1,10 @@ 24 | +@use "sass:math"; 25 | // List Icons 26 | // ------------------------- 27 | 28 | .#{$fa-css-prefix}-ul { 29 | list-style-type: none; 30 | - margin-left: $fa-li-width * 5/4; 31 | + margin-left: math.div($fa-li-width * 5, 4); 32 | padding-left: 0; 33 | 34 | > li { position: relative; } 35 | diff --git a/node_modules/@fortawesome/fontawesome-free/scss/_variables.scss b/node_modules/@fortawesome/fontawesome-free/scss/_variables.scss 36 | index b39f35e..c6bf168 100644 37 | --- a/node_modules/@fortawesome/fontawesome-free/scss/_variables.scss 38 | +++ b/node_modules/@fortawesome/fontawesome-free/scss/_variables.scss 39 | @@ -1,3 +1,4 @@ 40 | +@use "sass:math"; 41 | // Variables 42 | // -------------------------- 43 | 44 | @@ -9,7 +10,7 @@ $fa-version: "5.15.4" !default; 45 | $fa-border-color: #eee !default; 46 | $fa-inverse: #fff !default; 47 | $fa-li-width: 2em !default; 48 | -$fa-fw-width: (20em / 16); 49 | +$fa-fw-width: math.div(20em, 16); 50 | $fa-primary-opacity: 1 !default; 51 | $fa-secondary-opacity: .4 !default; 52 | 53 | -------------------------------------------------------------------------------- /app/views/users/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 14 | 15 |
    16 | <%= form_with(model: resource, as: resource_name, url: registration_path(resource_name), local: true) do |f| %> 17 |
    18 | <%= f.label :email %> 19 | <%= f.email_field :email, id: "user_email", autocomplete: "username", autofocus: true, required: "required", class: "form-control", class_for_error: "is-invalid" %> 20 | <%= f.error_message :email, class: "invalid-feedback" %> 21 |
    22 | 23 |
    24 | <%= f.label :password %> 25 | <%= f.password_field :password, id: "user_password", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 26 | <%= f.error_message :password, class: "invalid-feedback" %> 27 |
    28 | 29 |
    30 | <%= f.label :password_confirmation %> 31 | <%= f.password_field :password_confirmation, id: "user_password_confirmation", autocomplete: "new-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 32 | <%= f.error_message :password_confirmation, class: "invalid-feedback" %> 33 |
    34 | 35 | <%= f.submit t(".submit"), class: "btn btn-block btn-primary" %> 36 | <% end %> 37 |
    38 |
    39 |
    40 |
    41 | 42 | <%- if devise_mapping.confirmable? %> 43 |
    44 | <%= link_to t(".didn_t_receive_confirmation_instructions"), new_confirmation_path(resource_name) %> 45 |
    46 | <% end -%> 47 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support/core_ext/integer/time" 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # In the development environment your application's code is reloaded any time 9 | # it changes. This slows down response time but is perfect for development 10 | # since you don't have to restart the web server when you make code changes. 11 | config.cache_classes = false 12 | 13 | # Do not eager load code on boot. 14 | config.eager_load = false 15 | 16 | # Show full error reports. 17 | config.consider_all_requests_local = true 18 | 19 | # Enable server timing 20 | config.server_timing = true 21 | 22 | # Enable/disable caching. By default caching is disabled. 23 | # Run rails dev:cache to toggle caching. 24 | if Rails.root.join("tmp/caching-dev.txt").exist? 25 | config.action_controller.perform_caching = true 26 | config.action_controller.enable_fragment_cache_logging = true 27 | 28 | config.cache_store = :memory_store 29 | config.public_file_server.headers = { 30 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 31 | } 32 | else 33 | config.action_controller.perform_caching = false 34 | 35 | config.cache_store = :null_store 36 | end 37 | 38 | # Don't care if the mailer can't send. 39 | config.action_mailer.raise_delivery_errors = false 40 | 41 | config.action_mailer.perform_caching = false 42 | 43 | # Print deprecation notices to the Rails logger. 44 | config.active_support.deprecation = :log 45 | 46 | # Raise exceptions for disallowed deprecations. 47 | config.active_support.disallowed_deprecation = :raise 48 | 49 | # Tell Active Support which deprecation messages to disallow. 50 | config.active_support.disallowed_deprecation_warnings = [] 51 | 52 | # Raise an error on page load if there are pending migrations. 53 | config.active_record.migration_error = :page_load 54 | 55 | # Highlight code that triggered database queries in logs. 56 | config.active_record.verbose_query_logs = true 57 | 58 | 59 | # Raises error for missing translations. 60 | # config.i18n.raise_on_missing_translations = true 61 | 62 | # Annotate rendered view with file names. 63 | # config.action_view.annotate_rendered_view_with_filenames = true 64 | 65 | # Uncomment if you wish to allow Action Cable access from any origin. 66 | # config.action_cable.disable_request_forgery_protection = true 67 | end 68 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support/core_ext/integer/time" 4 | 5 | # The test environment is used exclusively to run your application's 6 | # test suite. You never need to work with it otherwise. Remember that 7 | # your test database is "scratch space" for the test suite and is wiped 8 | # and recreated between test runs. Don't rely on the data there! 9 | 10 | Rails.application.configure do 11 | # Settings specified here will take precedence over those in config/application.rb. 12 | 13 | # Turn false under Spring and add config.action_view.cache_template_loading = true. 14 | config.cache_classes = true 15 | 16 | # Eager loading loads your whole application. When running a single test locally, 17 | # this probably isn't necessary. It's a good idea to do in a continuous integration 18 | # system, or in some way before deploying your code. 19 | config.eager_load = ENV["CI"].present? 20 | 21 | # Configure public file server for tests with Cache-Control for performance. 22 | config.public_file_server.enabled = true 23 | config.public_file_server.headers = { 24 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 25 | } 26 | 27 | # Show full error reports and disable caching. 28 | config.consider_all_requests_local = true 29 | config.action_controller.perform_caching = false 30 | config.cache_store = :null_store 31 | 32 | # Raise exceptions instead of rendering exception templates. 33 | config.action_dispatch.show_exceptions = false 34 | 35 | # Disable request forgery protection in test environment. 36 | config.action_controller.allow_forgery_protection = false 37 | 38 | config.action_mailer.perform_caching = false 39 | 40 | # Tell Action Mailer not to deliver emails to the real world. 41 | # The :test delivery method accumulates sent emails in the 42 | # ActionMailer::Base.deliveries array. 43 | config.action_mailer.delivery_method = :test 44 | 45 | # Print deprecation notices to the stderr. 46 | config.active_support.deprecation = :stderr 47 | 48 | # Raise exceptions for disallowed deprecations. 49 | config.active_support.disallowed_deprecation = :raise 50 | 51 | # Tell Active Support which deprecation messages to disallow. 52 | config.active_support.disallowed_deprecation_warnings = [] 53 | 54 | # Raises error for missing translations. 55 | # config.i18n.raise_on_missing_translations = true 56 | 57 | # Annotate rendered view with file names. 58 | # config.action_view.annotate_rendered_view_with_filenames = true 59 | end 60 | -------------------------------------------------------------------------------- /app/views/users/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 14 |
    15 | <%= form_with(model: resource, as: resource_name, url: session_path(resource_name), id: "user-sign-in", local: true) do |f| %> 16 |
    17 | <%= f.label :email %> 18 | <%= f.email_field :email, id: "user_email", autocomplete: "username", autofocus: true, required: "required", class: "form-control", class_for_error: "is-invalid" %> 19 | <%= f.error_message :email, class: "invalid-feedback" %> 20 |
    21 | 22 |
    23 | <%= f.label :password %> 24 | <%= f.password_field :password, id: "user_password", autocomplete: "current-password", required: "required", class: "form-control", class_for_error: "is-invalid" %> 25 | <%= f.error_message :password, class: "invalid-feedback" %> 26 |
    27 | 28 | <% if devise_mapping.rememberable? %> 29 |
    30 |
    31 | <%= f.check_box :remember_me, class: "form-check-input" %> 32 | <%= f.label :remember_me, class: "form-check-label" %> 33 |
    34 |
    35 | <% end %> 36 | 37 | <%= f.submit t(".submit"), class: "btn btn-block btn-primary" %> 38 | 39 | <%- if devise_mapping.recoverable? %> 40 |
    41 | 42 | <%= link_to t(".forgot_your_password"), new_password_path(resource_name) %> 43 | 44 |
    45 | <% end %> 46 | <% end %> 47 |
    48 |
    49 |
    50 |
    51 | 52 | <%- if devise_mapping.confirmable? %> 53 |
    54 | <%= link_to t(".didn_t_receive_confirmation_instructions"), new_confirmation_path(resource_name) %> 55 |
    56 | <% end -%> 57 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.0].define(version: 2019_03_02_174954) do 14 | # These are extensions that must be enabled in order to support this database 15 | enable_extension "plpgsql" 16 | 17 | create_table "users", force: :cascade do |t| 18 | t.string "email", default: "", null: false 19 | t.string "encrypted_password", default: "", null: false 20 | t.string "reset_password_token" 21 | t.datetime "reset_password_sent_at", precision: nil 22 | t.datetime "remember_created_at", precision: nil 23 | t.integer "sign_in_count", default: 0, null: false 24 | t.datetime "current_sign_in_at", precision: nil 25 | t.datetime "last_sign_in_at", precision: nil 26 | t.string "current_sign_in_ip" 27 | t.string "last_sign_in_ip" 28 | t.string "confirmation_token" 29 | t.datetime "confirmed_at", precision: nil 30 | t.datetime "confirmation_sent_at", precision: nil 31 | t.string "unconfirmed_email" 32 | t.integer "failed_attempts", default: 0, null: false 33 | t.string "unlock_token" 34 | t.datetime "locked_at", precision: nil 35 | t.string "invitation_token" 36 | t.datetime "invitation_created_at", precision: nil 37 | t.datetime "invitation_sent_at", precision: nil 38 | t.datetime "invitation_accepted_at", precision: nil 39 | t.integer "invitation_limit" 40 | t.string "invited_by_type" 41 | t.bigint "invited_by_id" 42 | t.integer "invitations_count", default: 0 43 | t.datetime "created_at", null: false 44 | t.datetime "updated_at", null: false 45 | t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true 46 | t.index ["email"], name: "index_users_on_email", unique: true 47 | t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true 48 | t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by_type_and_invited_by_id" 49 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 50 | t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Ruby CircleCI 2.0 configuration file 3 | # 4 | # Check https://circleci.com/docs/2.0/language-ruby/ for more details 5 | # 6 | version: 2 7 | jobs: 8 | build: 9 | docker: 10 | # specify the version you desire here 11 | - image: circleci/ruby:2.7.5-node-browsers 12 | environment: 13 | PG_HOST: 127.0.0.1 14 | PG_USERNAME: cybros_test 15 | RAILS_ENV: test 16 | 17 | - image: circleci/postgres:latest 18 | environment: 19 | POSTGRES_USER: cybros_test 20 | POSTGRES_DB: cybros_test 21 | POSTGRES_PASSWORD: "cybros_test" 22 | 23 | working_directory: ~/repo 24 | 25 | steps: 26 | - checkout 27 | 28 | # Use latest Bundler 29 | - run: 30 | name: Update RubyGem & Bundler 31 | command: | 32 | sudo gem update --system 33 | gem install bundler 34 | 35 | # Download and cache dependencies 36 | - restore_cache: 37 | keys: 38 | - v2-dependencies-{{ checksum "Gemfile.lock" }} 39 | # fallback to using the latest cache if no exact match is found 40 | - v2-dependencies- 41 | 42 | - run: 43 | name: Install dependencies 44 | command: | 45 | bundle install --jobs=4 --retry=3 --path vendor/bundle --with test circle_ci 46 | 47 | - save_cache: 48 | key: v2-dependencies-{{ checksum "Gemfile.lock" }} 49 | paths: 50 | - vendor/bundle 51 | 52 | - restore_cache: 53 | keys: 54 | - v2-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "yarn.lock" }} 55 | # fallback to using the latest cache if no exact match is found 56 | - v2-dependencies-{{ checksum "Gemfile.lock" }} 57 | - v2-dependencies- 58 | 59 | - run: 60 | name: Install Yarn dependencies 61 | command: | 62 | bin/yarn install --check-files 63 | 64 | - save_cache: 65 | key: v2-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "yarn.lock" }} 66 | paths: 67 | - node_modules 68 | 69 | - run: 70 | name: Copy configs 71 | command: | 72 | cp .circleci/app_config/database.yml config/database.yml 73 | cp config/mailer.yml.example config/mailer.yml 74 | cp config/credentials.yml.example config/credentials.yml 75 | bin/rails credentials:encrypt 76 | 77 | # Database setup 78 | - run: bin/rails db:setup 79 | 80 | # run tests! 81 | - run: 82 | name: Run tests 83 | command: | 84 | mkdir /tmp/test-results 85 | bin/rails test 86 | 87 | # collect reports 88 | - store_test_results: 89 | path: /tmp/test-results 90 | - store_artifacts: 91 | path: /tmp/test-results 92 | destination: test-results 93 | -------------------------------------------------------------------------------- /app/controllers/admin/users_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Admin::UsersController < Admin::ApplicationController 4 | before_action :set_user, only: %i[show edit update lock unlock resend_confirmation_mail resend_invitation_mail] 5 | # before_action :set_breadcrumbs, only: %i[new edit create update], if: -> { request.format.html? } 6 | 7 | def index 8 | prepare_meta_tags title: t(".title") 9 | 10 | @users = User.all 11 | @users = @users.where("email LIKE ?", "%#{params[:user_email]}%") if params[:user_email].present? 12 | @users = @users.page(params[:page]).per(params[:per_page]) 13 | end 14 | 15 | def show 16 | prepare_meta_tags title: @user.email 17 | end 18 | 19 | def new 20 | prepare_meta_tags title: t(".title") 21 | @user = User.new 22 | end 23 | 24 | def edit 25 | prepare_meta_tags title: t(".title") 26 | end 27 | 28 | def create 29 | @user = User.new(user_params) 30 | 31 | if @user.save 32 | redirect_to admin_user_url(@user), notice: t(".shared.notice.created") 33 | else 34 | prepare_meta_tags title: t("admin.users.new.title") 35 | render :new 36 | end 37 | end 38 | 39 | def update 40 | if @user.update_without_password(user_params) 41 | redirect_to admin_user_url(@user), notice: t(".shared.notice.updated") 42 | else 43 | prepare_meta_tags title: t("admin.users.edit.title") 44 | render :edit 45 | end 46 | end 47 | 48 | def lock 49 | unless @user.access_locked? 50 | @user.lock_access! 51 | end 52 | 53 | redirect_to admin_user_url(@user), notice: t(".shared.notice.locked") 54 | end 55 | 56 | def unlock 57 | if @user.access_locked? 58 | @user.unlock_access! 59 | end 60 | 61 | redirect_to admin_user_url(@user), notice: t(".shared.notice.unlocked") 62 | end 63 | 64 | def resend_confirmation_mail 65 | if !@user.confirmed? || @user.pending_reconfirmation? 66 | @user.resend_confirmation_instructions 67 | end 68 | 69 | redirect_to admin_user_url(@user), notice: t(".shared.notice.sent_confirmation_mail") 70 | end 71 | 72 | def resend_invitation_mail 73 | if @user.created_by_invite? && !@user.invitation_accepted? 74 | @user.deliver_invitation 75 | end 76 | 77 | redirect_to admin_user_url(@user), notice: t(".shared.notice.sent_invitation_mail") 78 | end 79 | 80 | private 81 | 82 | # Use callbacks to share common setup or constraints between actions. 83 | def set_user 84 | @user = User.find(params[:id]) 85 | end 86 | 87 | def set_breadcrumbs 88 | @_breadcrumbs = [{ 89 | text: t("layouts.sidebar.admin.users"), 90 | link: admin_users_path 91 | }] 92 | end 93 | 94 | # Never trust parameters from the scary internet, only allow the white list through. 95 | def user_params 96 | params.fetch(:user, {}).permit(:email, :password, :password_confirmation) 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /app/views/admin/users/index.html.erb: -------------------------------------------------------------------------------- 1 | <%- content_for :action_bar do %> 2 |
      3 |
    1. 4 | <%= link_to t(".actions.new"), new_admin_user_path, class: "btn text-primary" %> 5 | <%= link_to t(".actions.invite"), new_admin_invitation_path, class: "btn text-primary" %> 6 |
    2. 7 |
    8 | <% end %> 9 | 10 |
    11 |
    12 |
    13 |

    14 | <%= t(".title") %> 15 |

    16 |

    17 | <%= form_tag admin_users_path, method: :get, class: 'form-inline' do -%> 18 |

    19 | 20 | <%= text_field_tag 'user_email', params[:user_email], class: 'form-control mr-2', id: 'text-user-name' %> 21 |
    22 |
    <%= submit_tag t(".actions.search"), class: 'btn btn-primary' %>
    23 | <% end -%> 24 |

    25 |
    26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | <% @users.each do |user| %> 39 | 40 | 41 | 42 | 43 | 44 | 71 | 74 | 75 | <% end %> 76 | 77 |
    <%= t(".table.id") %><%= t(".table.email") %><%= t(".table.created_at") %><%= t(".table.current_sign_in_at") %><%= t(".table.status") %>
    <%= user.id %><%= user.email %><%= time_tag user.created_at %><%= time_tag user.current_sign_in_at if user.current_sign_in_at %> 45 | <% if user.admin? %> 46 | 47 | <%= t("admin.users.shared.status.admin") %> 48 | 49 | <% end %> 50 | <% unless user.confirmed? %> 51 | 52 | <%= t("admin.users.shared.status.pending_confirmation") %> 53 | 54 | <% end %> 55 | <% if user.pending_reconfirmation? %> 56 | 57 | <%= t("admin.users.shared.status.pending_reconfirmation") %> 58 | 59 | <% end %> 60 | <% if user.created_by_invite? && !user.invitation_accepted? %> 61 | 62 | <%= t("admin.users.shared.status.inviting") %> 63 | 64 | <% end %> 65 | <% if user.access_locked? %> 66 | 67 | <%= t("admin.users.shared.status.locked") %> 68 | 69 | <% end %> 70 | 72 | <%= link_to t(".table.actions.show"), admin_user_path(user) %> 73 |
    78 |
    79 | 80 | <%= paginate @users %> 81 |
    82 |
    83 |
    84 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | def body_class(extra = nil) 5 | [ 6 | extra, 7 | content_for(:body_class), 8 | "#{controller_path.tr('/', '-')}-#{action_name}", 9 | user_signed_in? ? "signed-in" : "guest" 10 | ].reject(&:blank?).join(" ") 11 | end 12 | 13 | def html_class(extra = nil) 14 | classes = [extra, content_for(:html_class), *browser.meta] 15 | classes << "default" unless browser.device.mobile? || browser.device.tablet? 16 | 17 | classes.reject(&:blank?).join(" ") 18 | end 19 | 20 | def options_for_enum_select(klass, attribute, selected = nil) 21 | container = klass.public_send(attribute.to_s.pluralize).map do |k, v| 22 | v ||= k 23 | [klass.human_enum_value(attribute, k), v] 24 | end 25 | 26 | options_for_select(container, selected) 27 | end 28 | 29 | # See https://docs.gitlab.com/ee/development/ee_features.html#code-in-app-views 30 | def render_if_exists(partial, locals = {}) 31 | render(partial, locals) if partial_exists?(partial) 32 | end 33 | 34 | def partial_exists?(partial) 35 | lookup_context.exists?(partial, [], true) 36 | end 37 | 38 | def template_exists?(template) 39 | lookup_context.exists?(template, [], false) 40 | end 41 | 42 | # Check if a particular controller is the current one 43 | # 44 | # args - One or more controller names to check (using path notation when inside namespaces) 45 | # 46 | # Examples 47 | # 48 | # # On TreeController 49 | # current_controller?(:tree) # => true 50 | # current_controller?(:commits) # => false 51 | # current_controller?(:commits, :tree) # => true 52 | # 53 | # # On Admin::ApplicationController 54 | # current_controller?(:application) # => true 55 | # current_controller?('admin/application') # => true 56 | # current_controller?('gitlab/application') # => false 57 | def current_controller?(*args) 58 | args.any? do |v| 59 | v.to_s.downcase == controller.controller_name || v.to_s.downcase == controller.controller_path 60 | end 61 | end 62 | 63 | # Check if current controller is under the given namespace 64 | # 65 | # namespace - One or more controller names to check (using path notation when inside namespaces) 66 | # 67 | # Examples 68 | # 69 | # # On Admin::ApplicationController 70 | # current_namespace?(:application) # => false 71 | # current_namespace?('admin/application') # => true 72 | # current_namespace?('gitlab/application') # => false 73 | def current_namespace?(namespace) 74 | controller.controller_path.start_with? namespace.to_s.downcase 75 | end 76 | 77 | # Check if a particular action is the current one 78 | # 79 | # args - One or more action names to check 80 | # 81 | # Examples 82 | # 83 | # # On Projects#new 84 | # current_action?(:new) # => true 85 | # current_action?(:create) # => false 86 | # current_action?(:new, :create) # => true 87 | def current_action?(*args) 88 | args.any? { |v| v.to_s.downcase == action_name } 89 | end 90 | 91 | # Returns active css class when condition returns true 92 | # otherwise returns nil. 93 | # 94 | # Example: 95 | # %li{ class: active_when(params[:filter] == '1') } 96 | def active_when(condition) 97 | "active" if condition 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /config/locales/devise.zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | devise: 3 | confirmations: 4 | confirmed: "恭喜您,注册成功,现在可以登录了" 5 | send_instructions: "几分钟后,您将收到确认帐号的电子邮件." 6 | send_paranoid_instructions: "您将收到一封确认账号的邮件,请注意查收" 7 | didn_t_receive_confirmation_instructions: "没有收到确认邮件?" 8 | failure: 9 | already_authenticated: "登录成功" 10 | inactive: "您的账号还未激活" 11 | invalid: "账号或密码错误" 12 | last_attempt: "你还有一次尝试正确的密码,过后你的帐户会被锁住" 13 | locked: "你的账号已被禁用,请联系管理员." 14 | not_found_in_database: "邮箱或密码错误" 15 | timeout: "登录超时,请重新登录." 16 | unauthenticated: "继续操作前请确保您已登录." 17 | unconfirmed: "继续操作前请先确认您的帐号." 18 | invited: '你有一个待处理的邀请,接受它以完成注册。' 19 | mailer: 20 | confirmation_instructions: 21 | action: "确认我的帐户" 22 | greeting: "欢迎 %{recipient}!" 23 | instruction: "您可以通过下面的链接确认您的帐户的电子邮件:" 24 | subject: "确认信息" 25 | password_change: 26 | greeting: 27 | message: 28 | subject: 29 | reset_password_instructions: 30 | action: "更改我的密码" 31 | greeting: "你好 %{recipient}!" 32 | instruction: "有人要求更改密码的链接,您可以通过下面的链接更改密码:" 33 | instruction_2: "如果您没有要求请求更改密码,请忽略此电子邮件。" 34 | instruction_3: "如果你没有访问上面的链接并更改密码,你的密码就不会被改变。" 35 | subject: "重置密码信息" 36 | unlock_instructions: 37 | action: "帐户解锁" 38 | greeting: "你好 %{recipient}!" 39 | instruction: "点击下面的链接到您的帐户解锁:" 40 | message: "由于多次的不成功的登入尝试,您的帐户已被锁定。" 41 | subject: "解锁信息" 42 | invitation_instructions: 43 | subject: '注册邀请' 44 | hello: '你好 %{email}' 45 | someone_invited_you: '有人邀请你加入 %{url},你可以访问下方链接接受。' 46 | accept: '接受邀请' 47 | accept_until: '这封邀请有效期至 %{due_date}。' 48 | ignore: '如果你不想接受邀请,请忽略这封邮件。
    \n除非你访问了链接并且设置了密码,否则你的创建账号不会被创建。' 49 | omniauth_callbacks: 50 | failure: "因为%{reason},所以您无法从%{kind}获得授权." 51 | success: "成功地从%{kind}获得授权." 52 | passwords: 53 | no_token: "您暂时不能访问此页面。您需要通过密码重置邮件中的重置链接来访问此页面,如果您正是通过重置链接访问,请确定链接的正确性。" 54 | send_instructions: "几分钟后,您将收到重置密码的电子邮件." 55 | send_paranoid_instructions: "如果您的邮箱存在于我们的数据库中,您将收到一封找回密码的邮件." 56 | updated: "您的密码已修改成功,您现在已登录." 57 | updated_not_active: "密码修改成功." 58 | registrations: 59 | destroyed: "再见!您的帐户已成功注销。我们希望很快可以再见到您." 60 | signed_up: "欢迎您!您已注册成功." 61 | signed_up_but_inactive: "谢谢您!然而您的账号还未被激活,在这之前无法登录。" 62 | signed_up_but_locked: "谢谢您!然而您的账号已被锁定,无法登录。" 63 | signed_up_but_unconfirmed: "谢谢您!一封确认邮件已经发至您的邮箱,请点击其中的链接激活您的账号。" 64 | update_needs_confirmation: "新的账号信息已成功提交,一封确认邮件已经发至您的邮箱,请点击其中的链接以使您的新E-mail地址生效。" 65 | updated: "帐号资料更新成功." 66 | sessions: 67 | already_signed_out: "成功的登出" 68 | signed_in: "登录成功." 69 | signed_out: "退出成功." 70 | unlocks: 71 | send_instructions: "几分钟后,您将收到一封解锁帐号的邮件." 72 | send_paranoid_instructions: "如果您的邮箱存在于我们的数据库中,您将收到一封解锁账号的邮件." 73 | unlocked: "您的帐号已成功解锁,您现在已登录." 74 | invitations: 75 | send_instructions: '一封邀请邮件已经发送至 %{email}。' 76 | invitation_token_invalid: '邀请令牌已经失效!' 77 | updated: '你的密码已经设置成功,现在会将你登录。' 78 | updated_not_active: '你的密码已经设置过了。' 79 | no_invitations_remaining: '没有有效的邀请。' 80 | invitation_removed: '你的邀请已被移除。' 81 | errors: 82 | messages: 83 | already_confirmed: "已经确认,请重新登录." 84 | confirmation_period_expired: "必须在 %{period} 以内确认,请重新申请" 85 | expired: "您已过期,请重新申请" 86 | not_found: "没有找到" 87 | not_locked: "未锁定" 88 | not_saved: 89 | one: "因为1个错误导致此%{resource}保存失败:" 90 | other: "因为%{count}个错误导致此%{resource}保存失败:" 91 | time: 92 | formats: 93 | devise: 94 | mailer: 95 | invitation_instructions: 96 | accept_until_format: '%Y年%b%d日 %H:%M' 97 | -------------------------------------------------------------------------------- /app/overrides/action_view/helpers/form_builder_override.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActionView::Helpers::FormBuilder.class_eval do 4 | def error_message(method, tag: :div, ref_method: nil, escape: true, **options, &block) 5 | return if object.errors.empty? 6 | 7 | error = object.errors[method]&.first 8 | error ||= object.errors[ref_method]&.first if ref_method 9 | return unless error 10 | 11 | if block_given? 12 | @template.content_tag(tag, options, nil, escape, &block) 13 | else 14 | @template.content_tag(tag, error, options, escape) 15 | end 16 | end 17 | 18 | [:text_field, :password_field, :file_field, :text_area, 19 | :color_field, :search_field, :telephone_field, 20 | :phone_field, :date_field, :time_field, :datetime_field, 21 | :datetime_local_field, :month_field, :week_field, :url_field, 22 | :email_field, :number_field, :range_field].each do |selector| 23 | alias_method :"_#{selector}", selector unless instance_methods(false).include?(:"_#{selector}") 24 | class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 25 | def #{selector}(method, **options) 26 | _#{selector} method, normalize_html_options(method, **options) 27 | end 28 | RUBY_EVAL 29 | end 30 | 31 | alias_method :_check_box, :check_box unless instance_methods(false).include?(:_check_box) 32 | def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") 33 | _check_box method, normalize_html_options(method, **options), checked_value, unchecked_value 34 | end 35 | 36 | alias_method :_radio_button, :radio_button unless instance_methods(false).include?(:_radio_button) 37 | def radio_button(method, tag_value, options = {}) 38 | _radio_button method, tag_value, normalize_html_options(method, **options) 39 | end 40 | 41 | alias_method :_select, :select unless instance_methods(false).include?(:_select) 42 | def select(method, choices = nil, options = {}, html_options = {}, &block) 43 | _select method, choices, options, normalize_html_options(method, **html_options), &block 44 | end 45 | 46 | alias_method :_collection_select, :collection_select unless instance_methods(false).include?(:_collection_select) 47 | def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) 48 | _collection_select method, collection, value_method, text_method, options, normalize_html_options(method, **html_options) 49 | end 50 | 51 | alias_method :_grouped_collection_select, :grouped_collection_select unless instance_methods(false).include?(:_grouped_collection_select) 52 | def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) 53 | _grouped_collection_select method, collection, group_method, group_label_method, option_key_method, option_value_method, options, normalize_html_options(method, **html_options) 54 | end 55 | 56 | alias_method :_time_zone_select, :time_zone_select unless instance_methods(false).include?(:_time_zone_select) 57 | def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) 58 | _time_zone_select method, priority_zones, options, normalize_html_options(method, **html_options) 59 | end 60 | 61 | # TODO: collection_check_boxes, collection_radio_buttons 62 | 63 | private 64 | 65 | def normalize_html_options(method, class_for_error: nil, ref_method: nil, **options) 66 | if @object&.errors&.any? && class_for_error.present? 67 | errors = @object.errors 68 | if errors.include?(method) || (ref_method.present? && errors.include?(ref_method.to_sym)) 69 | return options.merge class: [options[:class], class_for_error].join(" ") 70 | end 71 | end 72 | 73 | options 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /config/database.yml.example: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.3 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On macOS with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On macOS with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see Rails configuration guide 21 | # https://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | # number of seconds to wait for connection to succeed 25 | #connect_timeout: 2 26 | 27 | # number of seconds to wait for a connection to become available before giving up and raising a timeout error 28 | #checkout_timeout: 5 29 | 30 | #variables: 31 | # number of seconds to wait for query to returned 32 | #statement_timeout: 5000 # ms 33 | 34 | development: 35 | <<: *default 36 | database: cybros_development 37 | 38 | # The specified database role being used to connect to postgres. 39 | # To create additional roles in postgres see `$ createuser --help`. 40 | # When left blank, postgres will use the default role. This is 41 | # the same name as the operating system user that initialized the database. 42 | #username: cybros 43 | 44 | # The password associated with the postgres role (username). 45 | #password: 46 | 47 | # Connect on a TCP socket. Omitted by default since the client uses a 48 | # domain socket that doesn't need configuration. Windows does not have 49 | # domain sockets, so uncomment these lines. 50 | #host: localhost 51 | 52 | # The TCP port the server listens on. Defaults to 5432. 53 | # If your server runs on a different port number, change accordingly. 54 | #port: 5432 55 | 56 | # Schema search path. The server defaults to $user,public 57 | #schema_search_path: myapp,sharedapp,public 58 | 59 | # Minimum log levels, in increasing order: 60 | # debug5, debug4, debug3, debug2, debug1, 61 | # log, notice, warning, error, fatal, and panic 62 | # Defaults to warning. 63 | #min_messages: notice 64 | 65 | # Warning: The database defined as "test" will be erased and 66 | # re-generated from your development database when you run "rake". 67 | # Do not set this db to the same as development or production. 68 | test: 69 | <<: *default 70 | host: <%= ENV.fetch("POSTGRES_HOST", nil) %> 71 | database: <%= ENV.fetch("POSTGRES_DB", 'cybros_test') %> 72 | username: <%= ENV.fetch("POSTGRES_USER", nil) %> 73 | password: <%= ENV.fetch("POSTGRES_PASSWORD", nil) %> 74 | 75 | # As with config/credentials.yml, you never want to store sensitive information, 76 | # like your database password, in your source code. If your source code is 77 | # ever seen by anyone, they now have access to your database. 78 | # 79 | # Instead, provide the password as a unix environment variable when you boot 80 | # the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database 81 | # for a full rundown on how to provide these environment variables in a 82 | # production deployment. 83 | # 84 | # On Heroku and other platform providers, you may have a full connection URL 85 | # available as an environment variable. For example: 86 | # 87 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 88 | # 89 | # You can use this database configuration with: 90 | # 91 | # production: 92 | # url: <%= ENV['DATABASE_URL'] %> 93 | # 94 | production: 95 | <<: *default 96 | database: cybros_production 97 | username: cybros 98 | password: <%= ENV['CYBROS_DATABASE_PASSWORD'] %> 99 | -------------------------------------------------------------------------------- /app/views/admin/users/show.html.erb: -------------------------------------------------------------------------------- 1 | <%- content_for :action_bar do %> 2 |
      3 |
    1. 4 | <%= link_to t("shared.actions.back"), admin_users_path, class: "btn text-primary" %> 5 |
    2. 6 |
    3. 7 | <%= link_to t("shared.actions.edit"), edit_admin_user_path(@user), class: "btn text-primary" %> 8 |
    4. 9 | <% if @user.created_by_invite? && !@user.invitation_accepted? %> 10 |
    5. 11 | <%= link_to t(".actions.resend_invitation_mail"), resend_invitation_mail_admin_user_path(@user), method: :patch, class: "btn text-primary" %> 12 |
    6. 13 | <% elsif !@user.confirmed? || @user.pending_reconfirmation? %> 14 |
    7. 15 | <%= link_to t(".actions.resend_confirmation_mail"), resend_confirmation_mail_admin_user_path(@user), method: :patch, class: "btn text-primary" %> 16 |
    8. 17 | <% end %> 18 | <% unless @user == current_user %> 19 |
    9. 20 | <% if @user.access_locked? %> 21 | <%= link_to t(".actions.unlock"), unlock_admin_user_path(@user), method: :patch, class: "btn text-danger", data: { confirm: t("admin.users.shared.confirmation.unlock") } %> 22 | <% else %> 23 | <%= link_to t(".actions.lock"), lock_admin_user_path(@user), method: :patch, class: "btn text-danger", data: { confirm: t("admin.users.shared.confirmation.lock") } %> 24 | <% end %> 25 |
    10. 26 | <% end %> 27 |
    28 | <% end %> 29 | 30 |
    31 |
    32 |
    33 |

    34 | <%= @user.class.model_name.human %> #<%= @user.id %> 35 |

    36 | 37 |
    38 |
    39 | <%= t(".email") %> 40 |
    41 |
    42 | <%= @user.unconfirmed_email || @user.email %> 43 | <% if @user.unconfirmed_email %> 44 | <%= @user.email %> 45 | <% end %> 46 |
    47 | 48 |
    49 | <%= t(".status") %> 50 |
    51 |
    52 | <% if @user.admin? %> 53 | 54 | <%= t("admin.users.shared.status.admin") %> 55 | 56 | <% end %> 57 | <% unless @user.confirmed? %> 58 | 59 | <%= t("admin.users.shared.status.pending_confirmation") %> 60 | 61 | <% end %> 62 | <% if @user.pending_reconfirmation? %> 63 | 64 | <%= t("admin.users.shared.status.pending_reconfirmation") %> 65 | 66 | <% end %> 67 | <% if @user.created_by_invite? && !@user.invitation_accepted? %> 68 | 69 | <%= t("admin.users.shared.status.inviting") %> 70 | 71 | <% end %> 72 | <% if @user.access_locked? %> 73 | 74 | <%= t("admin.users.shared.status.locked") %> 75 | 76 | <% end %> 77 |   78 |
    79 | 80 |
    81 | <%= t(".created_at") %> 82 |
    83 |
    84 | <%= time_tag @user.created_at %> 85 |
    86 | 87 |
    88 | <%= t(".current_sign_in_at") %> 89 |
    90 |
    91 | <% if @user.current_sign_in_at %> 92 | <%= time_tag @user.current_sign_in_at %> 93 | <% end %> 94 |
    95 |
    96 |
    97 |
    98 |
    99 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_support/core_ext/integer/time" 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # Code is not reloaded between requests. 9 | config.cache_classes = true 10 | 11 | # Eager load code on boot. This eager loads most of Rails and 12 | # your application in memory, allowing both threaded web servers 13 | # and those relying on copy on write to perform better. 14 | # Rake tasks automatically ignore this option for performance. 15 | config.eager_load = true 16 | 17 | # Full error reports are disabled and caching is turned on. 18 | config.consider_all_requests_local = false 19 | config.action_controller.perform_caching = true 20 | 21 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 22 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 23 | # config.require_master_key = true 24 | 25 | # Disable serving static files from the `/public` folder by default since 26 | # Apache or NGINX already handles this. 27 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 28 | 29 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 30 | # config.asset_host = "http://assets.example.com" 31 | 32 | # Specifies the header that your server uses for sending files. 33 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 34 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 35 | 36 | # Mount Action Cable outside main process or domain. 37 | # config.action_cable.mount_path = nil 38 | # config.action_cable.url = "wss://example.com/cable" 39 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | # config.force_ssl = true 43 | 44 | # Include generic and useful information about system operation, but avoid logging too much 45 | # information to avoid inadvertent exposure of personally identifiable information (PII). 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | config.log_tags = [ :request_id ] 50 | 51 | # Use a different cache store in production. 52 | # config.cache_store = :mem_cache_store 53 | 54 | # Use a real queuing backend for Active Job (and separate queues per environment). 55 | # config.active_job.queue_adapter = :resque 56 | # config.active_job.queue_name_prefix = "cybros_core_production" 57 | 58 | config.action_mailer.perform_caching = false 59 | 60 | # Ignore bad email addresses and do not raise email delivery errors. 61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 62 | # config.action_mailer.raise_delivery_errors = false 63 | 64 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 65 | # the I18n.default_locale when a translation cannot be found). 66 | config.i18n.fallbacks = true 67 | 68 | # Don't log any deprecations. 69 | config.active_support.report_deprecations = false 70 | 71 | # Use default logging formatter so that PID and timestamp are not suppressed. 72 | config.log_formatter = ::Logger::Formatter.new 73 | 74 | # Use a different logger for distributed setups. 75 | # require "syslog/logger" 76 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") 77 | 78 | if ENV["RAILS_LOG_TO_STDOUT"].present? 79 | logger = ActiveSupport::Logger.new(STDOUT) 80 | logger.formatter = config.log_formatter 81 | config.logger = ActiveSupport::TaggedLogging.new(logger) 82 | end 83 | 84 | # Do not dump schema after migrations. 85 | config.active_record.dump_schema_after_migration = false 86 | end 87 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "boot" 4 | 5 | require "rails" 6 | # Pick the frameworks you want: 7 | require "active_model/railtie" 8 | require "active_job/railtie" 9 | require "active_record/railtie" 10 | # require "active_storage/engine" 11 | require "action_controller/railtie" 12 | require "action_mailer/railtie" 13 | # require "action_mailbox/engine" 14 | # require "action_text/engine" 15 | require "action_view/railtie" 16 | require "action_cable/engine" 17 | require "rails/test_unit/railtie" 18 | 19 | # Require the gems listed in Gemfile, including any gems 20 | # you've limited to :test, :development, or :production. 21 | Bundler.require(*Rails.groups) 22 | 23 | # Require monkey patches 24 | Dir[Pathname.new(File.dirname(__FILE__)).realpath.parent.join("lib", "monkey_patches", "*.rb")].map do |file| 25 | require file 26 | end 27 | 28 | module CybrosCore 29 | class Application < Rails::Application 30 | # Configuration for the application, engines, and railties goes here. 31 | # 32 | # Initialize configuration defaults for originally generated Rails version. 33 | config.load_defaults 7.0 34 | 35 | config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}")] 36 | 37 | config.generators do |g| 38 | g.helper false 39 | g.assets false 40 | g.test_framework nil 41 | end 42 | 43 | # Store uploaded files on the local file system (see config/storage.yml for options). 44 | # config.active_storage.service = Rails.env.to_sym 45 | 46 | overrides = "#{Rails.root}/app/overrides" 47 | Rails.autoloaders.main.ignore(overrides) 48 | config.to_prepare do 49 | Dir.glob("#{overrides}/**/*_override.rb").each do |override| 50 | load override 51 | end 52 | end 53 | 54 | # Read ActionMailer config from config/mailer.yml 55 | initializer "action_mailer.set_configs.set_yaml_configs", before: "action_mailer.set_configs" do |app| 56 | next unless File.exist?(Rails.root.join("config", "mailer.yml")) 57 | 58 | configure = app.config_for("mailer").deep_symbolize_keys 59 | configure.each do |key, value| 60 | setter = "#{key}=" 61 | unless app.config.action_mailer.respond_to? setter 62 | raise "Can't set option `#{key}` to ActionMailer, make sure that options in config/mailer.yml are valid." 63 | end 64 | 65 | app.config.action_mailer.send(setter, value) 66 | end 67 | end 68 | 69 | # Separate ActiveStorage key base 70 | # initializer "app.active_storage.verifier", after: "active_storage.verifier" do 71 | # config.after_initialize do |app| 72 | # storage_key_base = 73 | # if Rails.env.development? || Rails.env.test? 74 | # app.secrets.secret_key_base 75 | # else 76 | # validate_secret_key_base( 77 | # ENV["STORAGE_KEY_BASE"] || app.credentials.storage_key_base || app.secrets.storage_key_base 78 | # ) 79 | # end 80 | # key_generator = ActiveSupport::KeyGenerator.new(storage_key_base, iterations: 1000) 81 | # secret = key_generator.generate_key("ActiveStorage") 82 | # ActiveStorage.verifier = ActiveSupport::MessageVerifier.new(secret) 83 | # end 84 | # end 85 | 86 | # Settings in config/environments/* take precedence over those specified here. 87 | # Application configuration can go into files in config/initializers 88 | # -- all .rb files in that directory are automatically loaded after loading 89 | # the framework and any gems in your application. 90 | 91 | # http://lulalala.logdown.com/posts/5835445-rails-many-default-url-options 92 | # if Settings.url_options&.respond_to?(:to_h) 93 | # Rails.application.routes.default_url_options = Settings.url_options.to_h 94 | # config.default_url_options = Settings.url_options.to_h 95 | # config.action_controller.default_url_options = Settings.url_options.to_h 96 | # config.action_mailer.default_url_options = Settings.url_options.to_h 97 | # end 98 | 99 | # These settings can be overridden in specific environments using the files 100 | # in config/environments, which are processed later. 101 | # 102 | # config.time_zone = "Asia/Shanghai" 103 | # config.i18n.default_locale = "zh-CN" 104 | # config.eager_load_paths << Rails.root.join("extras") 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /app/helpers/navigation_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NavigationHelper 4 | # Navigation helper 5 | # 6 | # Returns an `li` element with an 'active' class if the supplied 7 | # controller(s) and/or action(s) are currently active. The content of the 8 | # element is the value passed to the block. 9 | # 10 | # options - The options hash used to determine if the element is "active" (default: {}) 11 | # :controller - One or more controller names to check, use path notation when namespaced (optional). 12 | # :action - One or more action names to check (optional). 13 | # :path - A shorthand path, such as 'dashboard#index', to check (optional). 14 | # :html_options - Extra options to be passed to the list element (optional). 15 | # block - An optional block that will become the contents of the returned 16 | # `li` element. 17 | # 18 | # When both :controller and :action are specified, BOTH must match in order 19 | # to be marked as active. When only one is given, either can match. 20 | # 21 | # Examples 22 | # 23 | # # Assuming we're on TreeController#show 24 | # 25 | # # Controller matches, but action doesn't 26 | # nav_link(controller: [:tree, :refs], action: :edit) { "Hello" } 27 | # # => '
  • Hello
  • ' 28 | # 29 | # # Controller matches 30 | # nav_link(controller: [:tree, :refs]) { "Hello" } 31 | # # => '
  • Hello
  • ' 32 | # 33 | # # Several paths 34 | # nav_link(path: ['tree#show', 'profile#show']) { "Hello" } 35 | # # => '
  • Hello
  • ' 36 | # 37 | # # Shorthand path 38 | # nav_link(path: 'tree#show') { "Hello" } 39 | # # => '
  • Hello
  • ' 40 | # 41 | # # Supplying custom options for the list element 42 | # nav_link(controller: :tree, html_options: {class: 'home'}) { "Hello" } 43 | # # => '
  • Hello
  • ' 44 | # 45 | # # For namespaced controllers like Admin::AppearancesController#show 46 | # 47 | # # Controller and namespace matches 48 | # nav_link(controller: 'admin/appearances') { "Hello" } 49 | # # => '
  • Hello
  • ' 50 | # 51 | # # Controller and namespace matches but action doesn't 52 | # nav_link(controller: 'admin/appearances', action: :edit) { "Hello" } 53 | # # => '
  • Hello
  • ' 54 | # 55 | # # Shorthand path with namespace 56 | # nav_link(path: 'admin/appearances#show') { "Hello" } 57 | # # => '
  • Hello
  • ' 58 | # 59 | # Returns a list item element String 60 | def nav_item(options = {}, &block) 61 | klass = active_nav_item?(options) ? "active" : "" 62 | 63 | # Add our custom class into the html_options, which may or may not exist 64 | # and which may or may not already have a :class key 65 | o = options.delete(:html_options) || {} 66 | o[:class] = [*o[:class], klass].join(" ").strip 67 | 68 | if block_given? 69 | content_tag(:li, capture(&block), o) 70 | else 71 | content_tag(:li, nil, o) 72 | end 73 | end 74 | 75 | def nav_link_to(path = {}, name = nil, options = nil, html_options = nil, &block) 76 | klass = active_nav_item?(path) ? "active" : "" 77 | 78 | # Add our custom class into the html_options, which may or may not exist 79 | # and which may or may not already have a :class key 80 | html_options ||= {} 81 | html_options[:class] = [*html_options[:class], klass].join(" ").strip 82 | 83 | link_to name, options, html_options, &block 84 | end 85 | 86 | def active_nav_item?(options) 87 | if path = options.delete(:path) 88 | unless path.respond_to?(:each) 89 | path = [path] 90 | end 91 | 92 | path.any? do |single_path| 93 | current_path?(single_path) 94 | end 95 | elsif page = options.delete(:page) 96 | unless page.respond_to?(:each) 97 | page = [page] 98 | end 99 | 100 | page.any? do |single_page| 101 | current_page?(single_page) 102 | end 103 | elsif namespace = options.delete(:namespace) 104 | current_namespace?(namespace) 105 | else 106 | c = options.delete(:controller) 107 | a = options.delete(:action) 108 | 109 | if c && a 110 | # When given both options, make sure BOTH are true 111 | current_controller?(*c) && current_action?(*a) 112 | else 113 | # Otherwise check EITHER option 114 | current_controller?(*c) || current_action?(*a) 115 | end 116 | end 117 | end 118 | 119 | def current_path?(path) 120 | c, a, _ = path.split("#") 121 | current_controller?(c) && current_action?(a) 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_custom.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | @import "variables"; 3 | 4 | // Custom styles for this template 5 | 6 | a.card-link, 7 | a.card-link:hover { 8 | color: inherit; 9 | text-decoration: none; 10 | 11 | &:hover { 12 | background-color: darken($card-bg, 10%); 13 | } 14 | } 15 | 16 | .card .nav.nav-tabs { 17 | .nav-link { 18 | margin-left: -1px; 19 | margin-right: -1px; 20 | 21 | font-size: 1.25em; 22 | 23 | &.active { 24 | font-weight: bold; 25 | } 26 | 27 | &:not(.active) { 28 | margin-top: -1px; 29 | border-top: 2px solid $border-color; 30 | background: $gray-100; 31 | 32 | &:hover { 33 | color: $gray-800; 34 | border-color: $border-color; 35 | } 36 | } 37 | } 38 | } 39 | 40 | .nav-tabs .nav-item { 41 | z-index: $zindex-nav-tab; 42 | } 43 | 44 | .sidebar { 45 | .sidebar-header { 46 | margin: 0; 47 | padding: 0; 48 | 49 | text-align: left; 50 | 51 | background: $sidebar-bg; 52 | @include borders($sidebar-header-borders); 53 | } 54 | 55 | .nav { 56 | .nav-item.active .nav-link { 57 | box-shadow: inset 4px 0 0 $sidebar-nav-link-active-box-color; 58 | color: $sidebar-nav-link-active-color; 59 | background: $sidebar-nav-link-active-bg; 60 | @include borders($sidebar-nav-link-active-borders); 61 | 62 | .nav-icon { 63 | color: $sidebar-nav-link-active-icon-color; 64 | } 65 | } 66 | 67 | //.nav-item .nav-link { 68 | // .nav-icon { 69 | // font-size: 18px; 70 | // } 71 | //} 72 | } 73 | } 74 | 75 | body:not(.sidebar-minimized) { 76 | .sidebar { 77 | .nav { 78 | .nav-link { 79 | i { 80 | width: 50px; 81 | height: 45px; 82 | margin-top: -($sidebar-nav-link-padding-y + 0.25rem) !important; 83 | margin-right: math.div($sidebar-nav-link-padding-x, 2); 84 | margin-bottom: -$sidebar-nav-link-padding-y; 85 | margin-left: -$sidebar-nav-link-padding-x; 86 | line-height: 45px; 87 | color: $sidebar-nav-link-icon-color; 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | .sidebar-minimized { 95 | .sidebar .nav .nav-dropdown.open { 96 | background: transparent; 97 | } 98 | } 99 | 100 | .main-header-wrapper { 101 | margin-bottom: $main-header-margin-bottom; 102 | 103 | .alert { 104 | padding: $main-header-padding-y $main-header-padding-x; 105 | } 106 | 107 | .action_bar { 108 | padding: $action_bar-padding-y $action_bar-padding-x; 109 | margin-bottom: 0; 110 | background-color: $action_bar-bg; 111 | @include border-radius($action_bar-border-radius); 112 | @include borders($action_bar-borders); 113 | 114 | ol, ul { 115 | position: relative; 116 | display: flex; 117 | flex-wrap: wrap; 118 | list-style: none; 119 | 120 | margin: 0; 121 | padding: 0; 122 | } 123 | 124 | .btn-group { 125 | vertical-align: top; 126 | } 127 | 128 | .btn { 129 | padding: 0 $input-btn-padding-x; 130 | vertical-align: top; 131 | border: 0; 132 | 133 | &:hover, 134 | &.active { 135 | color: $body-color; 136 | background: transparent; 137 | } 138 | } 139 | 140 | .action_bar-item { 141 | // The separator between breadcrumbs (by default, a forward-slash: "/") 142 | + .action_bar-item { 143 | padding-left: $action_bar-item-padding; 144 | 145 | &::before { 146 | display: inline-block; // Suppress underlining of the separator in modern browsers 147 | padding-right: $action_bar-item-padding; 148 | color: $action_bar-divider-color; 149 | content: $action_bar-divider; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | .aside-menu { 157 | .close { 158 | position: absolute; 159 | top: 0; 160 | right: 0; 161 | 162 | padding: $aside-menu-nav-padding-y $aside-menu-nav-padding-x; 163 | color: $body-color; 164 | border-top: 0; 165 | @include border-radius(0); 166 | 167 | font-size: $font-size-base; 168 | font-weight: $font-weight-base; 169 | line-height: $line-height-base; 170 | margin-bottom: -1px; 171 | } 172 | } 173 | 174 | .app-header { 175 | padding: $navbar-padding-y $navbar-padding-x; 176 | 177 | .dropdown-menu { 178 | margin-top: $navbar-dropdown-margin-top; 179 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 180 | } 181 | 182 | .avatar { 183 | img { 184 | width: 34px; 185 | border-radius: 0 !important; 186 | } 187 | } 188 | 189 | @include media-breakpoint-down(md) { 190 | padding-left: 0; 191 | 192 | .navbar-brand { 193 | position: relative; 194 | left: auto; 195 | top: auto; 196 | } 197 | } 198 | } 199 | 200 | .aside-menu .nav.nav-tabs { 201 | background-color: $gray-200; 202 | } 203 | 204 | // Here you can add other styles 205 | -------------------------------------------------------------------------------- /app/models/concerns/acts_as_default_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # https://github.com/FooBarWidget/default_value_for 4 | # 5 | # Copyright (c) 2008-2012 Phusion 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | module ActsAsDefaultValue 26 | extend ActiveSupport::Concern 27 | 28 | class NormalValueContainer 29 | def initialize(value) 30 | @value = value 31 | end 32 | 33 | def evaluate(_instance) 34 | if @value.duplicable? 35 | @value.dup 36 | else 37 | @value 38 | end 39 | end 40 | end 41 | 42 | class BlockValueContainer 43 | def initialize(block) 44 | @block = block 45 | end 46 | 47 | def evaluate(instance) 48 | if @block.arity.zero? 49 | @block.call 50 | else 51 | @block.call(instance) 52 | end 53 | end 54 | end 55 | 56 | included do 57 | after_initialize :set_default_values 58 | end 59 | 60 | def initialize(attributes = nil) 61 | @initialization_attributes = attributes.is_a?(Hash) ? attributes.stringify_keys : {} 62 | super 63 | end 64 | 65 | def set_default_values 66 | self.class._all_default_attribute_values.each do |attribute, container| 67 | next unless new_record? || self.class._all_default_attribute_values_not_allowing_nil.include?(attribute) 68 | 69 | connection_default_value_defined = new_record? && respond_to?("#{attribute}_changed?") && !send("#{attribute}_changed?") 70 | 71 | column = self.class.columns.detect { |c| c.name == attribute } 72 | attribute_blank = 73 | if column && column.type == :boolean 74 | send(attribute).nil? 75 | else 76 | send(attribute).blank? 77 | end 78 | next unless connection_default_value_defined || attribute_blank 79 | 80 | # allow explicitly setting nil through allow nil option 81 | next if @initialization_attributes.is_a?(Hash) && 82 | ( 83 | @initialization_attributes.key?(attribute) || 84 | ( 85 | @initialization_attributes.key?("#{attribute}_attributes") && 86 | nested_attributes_options.stringify_keys[attribute] 87 | ) 88 | ) && 89 | !self.class._all_default_attribute_values_not_allowing_nil.include?(attribute) 90 | 91 | send("#{attribute}=", container.evaluate(self)) 92 | 93 | clear_attribute_changes [attribute] if has_attribute?(attribute) 94 | end 95 | end 96 | 97 | def attributes_for_create(attribute_names) 98 | attribute_names += self.class._all_default_attribute_values.keys.map(&:to_s).find_all do |name| 99 | self.class.columns_hash.key?(name) 100 | end 101 | 102 | super 103 | end 104 | 105 | module ClassMethods 106 | def _default_attribute_values # :nodoc: 107 | @default_attribute_values ||= {} 108 | end 109 | 110 | def _default_attribute_values_not_allowing_nil # :nodoc: 111 | @default_attribute_values_not_allowing_nil ||= Set.new 112 | end 113 | 114 | def _all_default_attribute_values # :nodoc: 115 | if superclass.respond_to?(:_default_attribute_values) 116 | superclass._all_default_attribute_values.merge(_default_attribute_values) 117 | else 118 | _default_attribute_values 119 | end 120 | end 121 | 122 | def _all_default_attribute_values_not_allowing_nil # :nodoc: 123 | if superclass.respond_to?(:_default_attribute_values_not_allowing_nil) 124 | superclass._all_default_attribute_values_not_allowing_nil + _default_attribute_values_not_allowing_nil 125 | else 126 | _default_attribute_values_not_allowing_nil 127 | end 128 | end 129 | 130 | # Declares a default value for the given attribute. 131 | # 132 | # Sets the default value to the given options parameter 133 | # 134 | # The options can be used to specify the following things: 135 | # * allow_nil (default: true) - Sets explicitly passed nil values if option is set to true. 136 | def default_value_for(attribute, value, **options) 137 | allow_nil = options.fetch(:allow_nil, true) 138 | 139 | container = 140 | if value.is_a? Proc 141 | BlockValueContainer.new(value) 142 | else 143 | NormalValueContainer.new(value) 144 | end 145 | 146 | _default_attribute_values[attribute.to_s] = container 147 | _default_attribute_values_not_allowing_nil << attribute.to_s unless allow_nil 148 | 149 | attribute 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - rubocop-performance 3 | - rubocop-rails 4 | 5 | AllCops: 6 | TargetRubyVersion: 2.6 7 | # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop 8 | # to ignore them, so only the ones explicitly set in this file are enabled. 9 | DisabledByDefault: true 10 | Exclude: 11 | - 'node_modules/**/*' 12 | - 'db/**/*' 13 | - 'bin/**/*' 14 | 15 | #Metrics/AbcSize: 16 | # Max: 30 17 | 18 | # Prefer assert_not over assert ! 19 | Rails/AssertNot: 20 | Include: 21 | - 'test/**/*' 22 | 23 | # Prefer assert_not_x over refute_x 24 | Rails/RefuteMethods: 25 | Include: 26 | - 'test/**/*' 27 | 28 | Rails/IndexBy: 29 | Enabled: true 30 | 31 | Rails/IndexWith: 32 | Enabled: true 33 | 34 | # Prefer &&/|| over and/or. 35 | Style/AndOr: 36 | Enabled: true 37 | 38 | # Align `when` with `case`. 39 | Layout/CaseIndentation: 40 | Enabled: true 41 | 42 | Layout/ClosingHeredocIndentation: 43 | Enabled: true 44 | 45 | # Align comments with method definitions. 46 | Layout/CommentIndentation: 47 | Enabled: true 48 | 49 | Layout/ElseAlignment: 50 | Enabled: true 51 | 52 | # Align `end` with the matching keyword or starting expression except for 53 | # assignments, where it should be aligned with the LHS. 54 | Layout/EndAlignment: 55 | Enabled: true 56 | EnforcedStyleAlignWith: variable 57 | AutoCorrect: true 58 | 59 | Layout/EmptyLineAfterMagicComment: 60 | Enabled: true 61 | 62 | Layout/EmptyLinesAroundAccessModifier: 63 | Enabled: true 64 | 65 | Layout/EmptyLinesAroundBlockBody: 66 | Enabled: true 67 | 68 | # In a regular class definition, no empty lines around the body. 69 | Layout/EmptyLinesAroundClassBody: 70 | Enabled: true 71 | 72 | # In a regular method definition, no empty lines around the body. 73 | Layout/EmptyLinesAroundMethodBody: 74 | Enabled: true 75 | 76 | # In a regular module definition, no empty lines around the body. 77 | Layout/EmptyLinesAroundModuleBody: 78 | Enabled: true 79 | 80 | # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. 81 | Style/HashSyntax: 82 | Enabled: true 83 | 84 | Layout/FirstArgumentIndentation: 85 | Enabled: true 86 | 87 | # Method definitions after `private` or `protected` isolated calls need one 88 | # extra level of indentation. 89 | Layout/IndentationConsistency: 90 | Enabled: true 91 | EnforcedStyle: indented_internal_methods 92 | 93 | # Two spaces, no tabs (for indentation). 94 | Layout/IndentationWidth: 95 | Enabled: true 96 | 97 | Layout/LeadingCommentSpace: 98 | Enabled: true 99 | 100 | Layout/SpaceAfterColon: 101 | Enabled: true 102 | 103 | Layout/SpaceAfterComma: 104 | Enabled: true 105 | 106 | Layout/SpaceAfterSemicolon: 107 | Enabled: true 108 | 109 | Layout/SpaceAroundEqualsInParameterDefault: 110 | Enabled: true 111 | 112 | Layout/SpaceAroundKeyword: 113 | Enabled: true 114 | 115 | Layout/SpaceBeforeComma: 116 | Enabled: true 117 | 118 | Layout/SpaceBeforeComment: 119 | Enabled: true 120 | 121 | Layout/SpaceBeforeFirstArg: 122 | Enabled: true 123 | 124 | Style/DefWithParentheses: 125 | Enabled: true 126 | 127 | # Defining a method with parameters needs parentheses. 128 | Style/MethodDefParentheses: 129 | Enabled: true 130 | 131 | Style/FrozenStringLiteralComment: 132 | Enabled: true 133 | EnforcedStyle: always 134 | 135 | Style/RedundantFreeze: 136 | Enabled: true 137 | 138 | # Use `foo {}` not `foo{}`. 139 | Layout/SpaceBeforeBlockBraces: 140 | Enabled: true 141 | 142 | # Use `foo { bar }` not `foo {bar}`. 143 | Layout/SpaceInsideBlockBraces: 144 | Enabled: true 145 | EnforcedStyleForEmptyBraces: space 146 | 147 | # Use `{ a: 1 }` not `{a:1}`. 148 | Layout/SpaceInsideHashLiteralBraces: 149 | Enabled: true 150 | 151 | Layout/SpaceInsideParens: 152 | Enabled: true 153 | 154 | # Check quotes usage according to lint rule below. 155 | Style/StringLiterals: 156 | Enabled: true 157 | EnforcedStyle: double_quotes 158 | 159 | # Detect hard tabs, no hard tabs. 160 | Layout/IndentationStyle: 161 | Enabled: true 162 | 163 | # Empty lines should not have any spaces. 164 | Layout/TrailingEmptyLines: 165 | Enabled: true 166 | 167 | # No trailing whitespace. 168 | Layout/TrailingWhitespace: 169 | Enabled: true 170 | 171 | # Use quotes for string literals when they are enough. 172 | Style/RedundantPercentQ: 173 | Enabled: true 174 | 175 | Lint/AmbiguousOperator: 176 | Enabled: true 177 | 178 | Lint/AmbiguousRegexpLiteral: 179 | Enabled: true 180 | 181 | Lint/ErbNewArguments: 182 | Enabled: true 183 | 184 | # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. 185 | Lint/RequireParentheses: 186 | Enabled: true 187 | 188 | Lint/ShadowingOuterLocalVariable: 189 | Enabled: true 190 | 191 | Lint/RedundantStringCoercion: 192 | Enabled: true 193 | 194 | Lint/UriEscapeUnescape: 195 | Enabled: true 196 | 197 | Lint/UselessAssignment: 198 | Enabled: true 199 | 200 | Lint/DeprecatedClassMethods: 201 | Enabled: true 202 | 203 | Lint/DeprecatedOpenSSLConstant: 204 | Enabled: true 205 | 206 | Style/ParenthesesAroundCondition: 207 | Enabled: true 208 | 209 | Style/HashTransformKeys: 210 | Enabled: true 211 | 212 | Style/HashTransformValues: 213 | Enabled: true 214 | 215 | Style/RedundantBegin: 216 | Enabled: true 217 | 218 | Style/RedundantReturn: 219 | Enabled: true 220 | AllowMultipleReturnValues: true 221 | 222 | Style/Semicolon: 223 | Enabled: true 224 | AllowAsExpressionSeparator: true 225 | 226 | # Prefer Foo.method over Foo::method 227 | Style/ColonMethodCall: 228 | Enabled: true 229 | 230 | Style/TrivialAccessors: 231 | Enabled: true 232 | 233 | Style/SlicingWithRange: 234 | Enabled: true 235 | 236 | Style/RedundantRegexpEscape: 237 | Enabled: true 238 | 239 | Performance/FlatMap: 240 | Enabled: true 241 | 242 | Performance/RedundantMerge: 243 | Enabled: true 244 | 245 | Performance/StartWith: 246 | Enabled: true 247 | 248 | Performance/EndWith: 249 | Enabled: true 250 | 251 | Performance/RegexpMatch: 252 | Enabled: true 253 | 254 | Performance/ReverseEach: 255 | Enabled: true 256 | 257 | Performance/UnfreezeString: 258 | Enabled: true 259 | 260 | Performance/DeletePrefix: 261 | Enabled: true 262 | 263 | Performance/DeleteSuffix: 264 | Enabled: true 265 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cybros Core 2 | ==== 3 | 4 | [![CircleCI](https://circleci.com/gh/jasl/cybros_core.svg?style=svg)](https://circleci.com/gh/jasl/cybros_core) 5 | 6 | This is a barebone Rails 6.0 app to show some basic configurations. 7 | 8 | I'm used to maintaining a barebone app that helps me build new project quickly, 9 | and this is extracted from my side project initially for sharing my ideas to friends, 10 | but if this is valuable to you, use it freely. 11 | 12 | ## Goal 13 | 14 | I hope this could be a template for new apps, it should be production-ready, 15 | so I'll keep polishing the codebase, follow best practice, keep dependencies up to date. 16 | 17 | I don't wanna add too much features especially business-specific, 18 | but I'd like to perfection User system (based on Devise) because most apps need this, 19 | and keep improving UI/UX relates works. 20 | 21 | BTW: I'm really hoping someone could extract GitLab's user system. 22 | 23 | I list some helps wanted, see below. 24 | 25 | ## Features 26 | 27 | ### Classic front-end 28 | 29 | Personally, I'm not skilled at front-end and I still prefer classic Rails server-side rendering, 30 | and partially introduce React or Vue for complex pages. 31 | 32 | A good example is Gitlab, I also cheat some useful helpers to this app. 33 | 34 | #### Webpacker 5 without Sprockets 35 | 36 | Webpacker can do all the jobs that Sprockets does, 37 | and has full support of front-end community, 38 | So I remove Sprockets and tune Webpacker allows Assets Pipeline experience. 39 | 40 | I do these: 41 | 42 | - Remove gems related to Sprockets 43 | - Search and remove `assets` related configs 44 | - `resolved_paths: ['app/assets']` in `config/webpacker.yml` 45 | - `app/javascript/packs/application.js` require all static assets (images, webfonts, etc.) 46 | 47 | #### CoreUI with Bootstrap, FontAwesome 48 | 49 | See `app/assets/stylesheets/application.scss` 50 | 51 | ### Application configuration 52 | 53 | #### A hack about Rails Credentials 54 | 55 | Rails Credentials is a useful feature to store security-sensitive configs. 56 | 57 | But we can't bundle `master.key`, and `credentials.yml.enc` isn't readable, 58 | so it's difficult to redistribute the app, 59 | I gave a [PR to Rails](https://github.com/rails/rails/pull/34777) but no respond, 60 | I consistantly think it's useful so I integrate it as a hack, see `bin/rails`. 61 | 62 | So you can copy `config/credentials.yml.example` as `config/credentials.yml`, 63 | edit it, then run `rails credentials:encrypt` that will generate `config/credentials.yml.enc` and `config/master.key` for you. 64 | 65 | #### A hack about ActionMailer configuration 66 | 67 | Unlike `database.yml`, ActionMailer's config separates in many files, 68 | I do a hack that you can config ActionMailer in one place. 69 | 70 | See `config/mailer.yml` 71 | 72 | Codes in `config/application.rb` 73 | 74 | ### Implemented a full-feature layouts & views 75 | 76 | I don't have art skill but ... at least it works! 77 | 78 | #### Overrides Form Helpers to enhance them to support Bootstrap form validation style 79 | 80 | The technique is in 81 | 82 | See `app/overrides/action_view/helpers/form_builder_override.rb` 83 | 84 | In addition, see `config/application.rb` for how to require overrides. 85 | 86 | #### Don't render ActionView's default error field wrapper 87 | 88 | That will break many CSS frameworks. 89 | 90 | See `config/initializers/action_view.rb` 91 | 92 | #### Default value for model fields 93 | 94 | See `app/models/concerns/acts_as_default_value.rb` 95 | 96 | Default value of column can only be a static value, 97 | Active Record's `attribute` DSL can set default for field but doesn't have entity context, 98 | Using hooks (such as `after_initilize`) to set default values has edge cases, 99 | you can use `default_value_for` to set default value. 100 | 101 | Here's a complex example: 102 | 103 | ```ruby 104 | default_value_for :role_id, 105 | -> (member) { 106 | if member.has_attribute?(:tenant_id) || member.tenant 107 | member&.tenant&.member_role&.id 108 | end 109 | }, allow_nil: false 110 | ``` 111 | 112 | #### I18n for `enum` 113 | 114 | See `app/models/concerns/enum_attribute_localizable.rb` 115 | 116 | Rails doesn't have best practice for `enum` I18n, 117 | I integrate my personal practice. 118 | 119 | For example, I have a model `Post` with `status` column for `enum` 120 | 121 | ```ruby 122 | class Post < ApplicationRecord 123 | enum status: %i[draft published archived] 124 | end 125 | ``` 126 | 127 | The locale `post.en.yml` looks like 128 | 129 | ```yaml 130 | en: 131 | activerecord: 132 | models: 133 | post: Post 134 | attributes: 135 | post: 136 | status: Status 137 | statuses: 138 | draft: Draft 139 | published: Published 140 | archived: Archived 141 | ``` 142 | 143 | To render human readable post's status, you can do like this: 144 | 145 | ```ruby 146 | Post.human_enum_value(:status, @post) 147 | ``` 148 | 149 | ### Undocumented yet 150 | 151 | TODO: 152 | 153 | ## Run the app 154 | 155 | - Clone it 156 | - `bundle` 157 | - `yarn` 158 | - `cp config/database.yml.example config/database.yml` 159 | - `cp config/credentials.yml.example config/credentials.yml` & `rails credentials:encrypt` 160 | - `cp config/mailer.yml.example config/mailer.yml` 161 | - `rails db:migrate` 162 | - `rails s` 163 | 164 | ### Receive Devise confirmation mail 165 | 166 | In development, I use `mailcatcher` to receive mails, 167 | run `gem install mailcatcher` to install it. 168 | 169 | Open a new terminal, run `mailcatcher`, then follow the instructions 170 | 171 | ### Set user as admin 172 | 173 | - `cp config/settings.yml config/settings.local.yml` 174 | - Put your email into `admin.emails` 175 | - In user menu (right-top of pages), you should see `Administration` 176 | 177 | ## Troubleshooting 178 | 179 | Make sure run `gem update --system` to use latest Rubygem 180 | 181 | ## Help wanted 182 | 183 | - UI/UX design & SCSS & HTML improvement 184 | - Layout for mails 185 | - Coding style & structural improvement 186 | - Try support uploading user avatar using ActiveStorage 187 | - Find bugs 188 | - Docker for deployment, including stages to compiling assets & copy `yml`s, easy to migrate to k8s 189 | 190 | ## Screenshots 191 | 192 | ![Sign in page](_screenshots/sign_in_page.png) 193 | ![Admin user page](_screenshots/admin_user_page.png) 194 | 195 | ## License 196 | 197 | [MIT License](https://opensource.org/licenses/MIT). 198 | --------------------------------------------------------------------------------