├── 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 |
4 | ×
5 |
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 |
2 | <% breadcrumbs.each do |breadcrumb| %>
3 |
4 | <% if breadcrumb[:link].present? %>
5 | <%= link_to breadcrumb[:text], breadcrumb[:link] %>
6 | <% else %>
7 | <%= breadcrumb[:text] %>
8 | <% end %>
9 |
10 | <% end %>
11 |
12 |
15 |
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 |
4 | <%= link_to t("shared.actions.back"), admin_users_path, class: "btn text-primary" %>
5 |
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 |
4 | <%= link_to t("shared.actions.back"), admin_user_path(@user), class: "btn text-primary" %>
5 |
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 |
4 | <%= link_to t("shared.actions.back"), admin_users_path, class: "btn text-primary" %>
5 |
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 |
7 | ×
8 |
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 |
13 |
source code
14 |
15 |
16 | <% end %>
17 |
18 | <%- content_for :action_bar do %>
19 |
20 |
21 | <%= link_to "Home", root_path %>
22 |
23 |
24 | <%= link_to "Home", root_path %>
25 |
26 |
27 | <%= link_to "Home", root_path %>
28 |
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 |
13 |
26 |
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 |
5 | <%- if devise_mapping.database_authenticatable? %>
6 |
7 | <%= link_to t("users.sessions.new.title"), new_session_path(resource_name), class: "nav-link" %>
8 |
9 | <% end %>
10 |
11 | <%= link_to t("users.registrations.new.title"), new_registration_path(resource_name), class: "nav-link active" %>
12 |
13 |
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 |
5 |
6 | <%= link_to t("users.sessions.new.title"), new_session_path(resource_name), class: "nav-link active" %>
7 |
8 | <%- if devise_mapping.registerable? %>
9 |
10 | <%= link_to t("users.registrations.new.title"), new_registration_path(resource_name), class: "nav-link" %>
11 |
12 | <% end %>
13 |
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 |
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 |
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 |
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 | <%= t(".table.email") -%>
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 | <%= t(".table.id") %>
30 | <%= t(".table.email") %>
31 | <%= t(".table.created_at") %>
32 | <%= t(".table.current_sign_in_at") %>
33 | <%= t(".table.status") %>
34 |
35 |
36 |
37 |
38 | <% @users.each do |user| %>
39 |
40 | <%= user.id %>
41 | <%= user.email %>
42 | <%= time_tag user.created_at %>
43 | <%= time_tag user.current_sign_in_at if user.current_sign_in_at %>
44 |
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 |
71 |
72 | <%= link_to t(".table.actions.show"), admin_user_path(user) %>
73 |
74 |
75 | <% end %>
76 |
77 |
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 |
4 | <%= link_to t("shared.actions.back"), admin_users_path, class: "btn text-primary" %>
5 |
6 |
7 | <%= link_to t("shared.actions.edit"), edit_admin_user_path(@user), class: "btn text-primary" %>
8 |
9 | <% if @user.created_by_invite? && !@user.invitation_accepted? %>
10 |
11 | <%= link_to t(".actions.resend_invitation_mail"), resend_invitation_mail_admin_user_path(@user), method: :patch, class: "btn text-primary" %>
12 |
13 | <% elsif !@user.confirmed? || @user.pending_reconfirmation? %>
14 |
15 | <%= link_to t(".actions.resend_confirmation_mail"), resend_confirmation_mail_admin_user_path(@user), method: :patch, class: "btn text-primary" %>
16 |
17 | <% end %>
18 | <% unless @user == current_user %>
19 |
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 |
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 | [](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 | 
193 | 
194 |
195 | ## License
196 |
197 | [MIT License](https://opensource.org/licenses/MIT).
198 |
--------------------------------------------------------------------------------