diff --git a/.gitignore b/.gitignore index 66be49f..66786ca 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,8 @@ yarn-debug.log* /docker-volumes/* !/docker-volumes/.keep stats.json + +# ignore local settings +config/settings.local.yml +config/settings/*.local.yml +config/environments/*.local.yml diff --git a/Gemfile b/Gemfile index 00a4041..858b9b8 100644 --- a/Gemfile +++ b/Gemfile @@ -110,3 +110,10 @@ gem 'cancancan' # Settings plugin for Rails that makes managing a table of global keys. (https://github.com/huacnlee/rails-settings-cached) gem 'rails-settings-cached', '~> 2.0' + +gem 'omniauth', '~> 1.9' +gem 'omniauth-twitter', '~> 1.4' +gem 'omniauth-github', '~> 1.3' +gem 'omniauth-google-oauth2', '~> 0.8' + +gem 'config' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 3b36038..23e0948 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -85,7 +85,12 @@ GEM chunky_png (1.3.11) coderay (1.1.2) concurrent-ruby (1.1.5) + config (2.0.0) + activesupport (>= 4.2) + deep_merge (~> 1.2, >= 1.2.1) + dry-schema (~> 1.0) crass (1.0.5) + deep_merge (1.2.1) devise (4.7.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -94,10 +99,43 @@ GEM warden (~> 1.2.3) devise-i18n (1.8.2) devise (>= 4.6) + dry-configurable (0.9.0) + concurrent-ruby (~> 1.0) + dry-core (~> 0.4, >= 0.4.7) + dry-container (0.7.2) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (0.4.9) + concurrent-ruby (~> 1.0) + dry-equalizer (0.3.0) + dry-inflector (0.2.0) + dry-initializer (3.0.2) + dry-logic (1.0.5) + concurrent-ruby (~> 1.0) + dry-core (~> 0.2) + dry-equalizer (~> 0.2) + dry-schema (1.4.1) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.8, >= 0.8.3) + dry-core (~> 0.4) + dry-equalizer (~> 0.2) + dry-initializer (~> 3.0) + dry-logic (~> 1.0) + dry-types (~> 1.2) + dry-types (1.2.1) + concurrent-ruby (~> 1.0) + dry-container (~> 0.3) + dry-core (~> 0.4, >= 0.4.4) + dry-equalizer (~> 0.2, >= 0.2.2) + dry-inflector (~> 0.1, >= 0.1.2) + dry-logic (~> 1.0, >= 1.0.2) erubi (1.9.0) + faraday (0.17.1) + multipart-post (>= 1.2, < 3) ffi (1.11.1) globalid (0.4.2) activesupport (>= 4.2.0) + hashie (3.6.0) htmlentities (4.3.4) i18n (1.7.0) concurrent-ruby (~> 1.0) @@ -107,6 +145,7 @@ GEM jaro_winkler (1.5.3) jbuilder (2.9.1) activesupport (>= 4.2.0) + jwt (2.2.1) kramdown (1.17.0) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) @@ -125,9 +164,38 @@ GEM mini_portile2 (2.4.0) minitest (5.12.2) msgpack (1.3.1) + multi_json (1.14.1) + multi_xml (0.6.0) + multipart-post (2.1.1) nio4r (2.5.1) nokogiri (1.10.5) mini_portile2 (~> 2.4.0) + oauth (0.5.4) + oauth2 (1.4.2) + faraday (>= 0.8, < 2.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + omniauth (1.9.0) + hashie (>= 3.4.6, < 3.7.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-google-oauth2 (0.8.0) + jwt (>= 2.0) + omniauth (>= 1.1.1) + omniauth-oauth2 (>= 1.6) + omniauth-oauth (1.1.0) + oauth + omniauth (~> 1.0) + omniauth-oauth2 (1.6.0) + oauth2 (~> 1.1) + omniauth (~> 1.9) + omniauth-twitter (1.4.0) + omniauth-oauth (~> 1.1) + rack orm_adapter (0.5.0) paper_trail (10.3.1) activerecord (>= 4.2) @@ -286,12 +354,17 @@ DEPENDENCIES byebug cancancan capybara (>= 2.15) + config devise devise-i18n identicon initial_avatar jbuilder (~> 2.7) listen (>= 3.0.5, < 3.2) + omniauth (~> 1.9) + omniauth-github (~> 1.3) + omniauth-google-oauth2 (~> 0.8) + omniauth-twitter (~> 1.4) paper_trail (~> 10.3.0) pg (~> 1.1) pry diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb new file mode 100644 index 0000000..cb6ef12 --- /dev/null +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -0,0 +1,25 @@ +# refs: https://qiita.com/mnishiguchi/items/e15bbef61287f84b546e +class OmniauthCallbacksController < Devise::OmniauthCallbacksController + # いくつプロバイダーを利用しようが処理は共通しているので本メソッドをエイリアスとして流用。 + def callback_for_all_providers + unless request.env['omniauth.auth'].present? + flash[:danger] = 'Authentication data was not provided' + redirect_to root_url && return + end + provider = __callee__.to_s + user = OAuthService::GetOAuthUser.call(request.env['omniauth.auth']) + # ユーザーがデータベースに保存されており、且つemailを確認済みであれば、ユーザーをログインする。 + if user.persisted? && user.email_verified? + sign_in_and_redirect user, event: :authentication + set_flash_message(:notice, :success, kind: provider.capitalize) if is_navigational_format? + else + # user.reset_confirmation! + flash[:warning] = t('.need_info_before_signup', default: 'We need your email address before proceeding.') + redirect_to finish_signup_path(user, provider: provider) + end + end + + alias twitter callback_for_all_providers + alias github callback_for_all_providers + alias google_oauth2 callback_for_all_providers +end diff --git a/app/controllers/omniauth_finished_controller.rb b/app/controllers/omniauth_finished_controller.rb new file mode 100644 index 0000000..f226031 --- /dev/null +++ b/app/controllers/omniauth_finished_controller.rb @@ -0,0 +1,25 @@ +# refs: https://qiita.com/mnishiguchi/items/e15bbef61287f84b546e +class OmniauthFinishedController < ApplicationController + skip_authorization_check only: [:finish_signup] + before_action :authenticate_user!, except: :finish_signup + + def finish_signup + @user = User.find(params[:id]) + @provider = params[:provider] + + if (request.post? || request.patch?) && @user.update(user_params) + # @user.send_confirmation_instructions unless @user.confirmed? + sign_in(@user, bypass: true) + redirect_to root_url, notice: t('devise.omniauth_callbacks.success', kind: @provider.capitalize) + end + end + + private + + # user_paramsにアクセスするため。 + def user_params + accessible = [ :username, :email ] + accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank? + params.require(:user).permit(accessible) + end +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb new file mode 100644 index 0000000..6e09245 --- /dev/null +++ b/app/controllers/registrations_controller.rb @@ -0,0 +1,10 @@ +# refs: https://qiita.com/mnishiguchi/items/e15bbef61287f84b546e +class RegistrationsController < Devise::RegistrationsController + + protected + + # Override + def update_resource(resource, params) + resource.update_without_password(params) + end +end diff --git a/app/helpers/o_auth/o_auth_policy.rb b/app/helpers/o_auth/o_auth_policy.rb new file mode 100644 index 0000000..6a2f16d --- /dev/null +++ b/app/helpers/o_auth/o_auth_policy.rb @@ -0,0 +1,55 @@ +# refs: https://qiita.com/mnishiguchi/items/e15bbef61287f84b546e +module OAuthPolicy + class Base + attr_reader :provider, :uid, :name, :nickname, :email, :url, :image_url, + :description, :other, :credentials, :raw_info + end + + class Twitter < OAuthPolicy::Base + def initialize(auth) + @provider = auth['provider'] + @uid = auth['uid'] + @name = auth['info']['name'] + @nickname = auth['info']['nickname'] + @email = '' + @url = auth['info']['urls']['Twitter'] + @image_url = auth['info']['image'] + @description = auth['info']['description'].try(:truncate, 255) + @credentials = auth['credentials'].to_json + @raw_info = auth['extra']['raw_info'].to_json + freeze + end + end + + class Github < OAuthPolicy::Base + def initialize(auth) + @provider = auth['provider'] + @uid = auth['uid'] + @name = auth['info']['name'] + @nickname = '' + @email = '' + @url = 'https://github.com/' + @image_url = auth['info']['image'] + @description = '' + @credentials = auth['credentials'].to_json + @raw_info = auth['extra']['raw_info'].to_json + freeze + end + end + + class GoogleOauth2 < OAuthPolicy::Base + def initialize(auth) + @provider = auth['provider'] + @uid = auth['uid'] + @name = auth['info']['name'] + @nickname = '' + @email = '' + @url = 'https://google.com/' + @image_url = auth['info']['image'] + @description = '' + @credentials = auth['credentials'].to_json + @raw_info = auth['extra']['raw_info'].to_json + freeze + end + end +end diff --git a/app/javascript/stylesheets/sb-admin/_login.scss b/app/javascript/stylesheets/sb-admin/_login.scss index b942f42..b071b3c 100755 --- a/app/javascript/stylesheets/sb-admin/_login.scss +++ b/app/javascript/stylesheets/sb-admin/_login.scss @@ -41,6 +41,22 @@ form.user { } +.omniauth { + .btn-user { + font-size: 0.8rem; + border-radius: 10rem; + padding: 0.75rem 1rem; + } +} + +.btn-twitter { + @include button-variant($brand-twitter, $white); +} + +.btn-github { + @include button-variant($brand-github, $white); +} + .btn-google { @include button-variant($brand-google, $white); } diff --git a/app/javascript/stylesheets/sb-admin/_variables.scss b/app/javascript/stylesheets/sb-admin/_variables.scss index 6474aae..f2ec6fe 100755 --- a/app/javascript/stylesheets/sb-admin/_variables.scss +++ b/app/javascript/stylesheets/sb-admin/_variables.scss @@ -30,6 +30,8 @@ $cyan: #36b9cc !default; // Custom Colors $brand-google: #ea4335; $brand-facebook: #3b5998; +$brand-github: #24292e; +$brand-twitter: rgba(29,161,242,1.00); // Set Contrast Threshold $yiq-contrasted-threshold: 195 !default; diff --git a/app/models/social_profile.rb b/app/models/social_profile.rb new file mode 100644 index 0000000..78de49b --- /dev/null +++ b/app/models/social_profile.rb @@ -0,0 +1,62 @@ +# refs: https://qiita.com/mnishiguchi/items/e15bbef61287f84b546e +# == Schema Information +# +# Table name: social_profiles +# +# id :integer not null, primary key +# user_id :integer +# provider :string +# uid :string +# name :string +# nickname :string +# email :string +# url :string +# image_url :string +# description :string +# others :text +# credentials :text +# raw_info :text +# created_at :datetime not null +# updated_at :datetime not null +# + +class SocialProfile < ApplicationRecord + belongs_to :user + store :others + + validates_uniqueness_of :uid, scope: :provider + + def self.find_for_oauth(auth) + profile = find_or_create_by(uid: auth.uid, provider: auth.provider) + profile.save_oauth_data!(auth) + profile + end + + def save_oauth_data!(auth) + return unless valid_oauth?(auth) + + provider = auth["provider"] + policy = policy(provider, auth) + + self.update_attributes( uid: policy.uid, + name: policy.name, + nickname: policy.nickname, + email: policy.email, + url: policy.url, + image_url: policy.image_url, + description: policy.description, + credentials: policy.credentials, + raw_info: policy.raw_info ) + end + + private + + def policy(provider, auth) + class_name = "#{provider}".classify + "OAuthPolicy::#{class_name}".constantize.new(auth) + end + + def valid_oauth?(auth) + (self.provider.to_s == auth['provider'].to_s) && (self.uid == auth['uid']) + end +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index b4c6e37..52bf6dd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -20,10 +20,13 @@ class User < ApplicationRecord attr_writer :login + TEMP_EMAIL_PREFIX = 'change@me' + TEMP_EMAIL_REGEX = /\Achange@me/ + # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, - :recoverable, :rememberable, :validatable + :recoverable, :rememberable, :validatable, :omniauthable validates :username, presence: true, uniqueness: { case_sensitive: false } validates_format_of :username, with: /^[a-zA-Z0-9_-]*$/, :multiline => true @@ -33,6 +36,10 @@ class User < ApplicationRecord has_many :groups, through: :group_users has_many :tickets, foreign_key: :assignee_id, dependent: :nullify has_one_attached :image + has_many :social_profiles, dependent: :destroy + + # emailの登録状況を判定するカスタムvalidatorを使用するためのおまじない。 + validates :email, presence: true, email: true def validate_username if User.where(email: username).exists? @@ -72,4 +79,33 @@ def user_image_url def assign_default_role self.add_role(:developer) if self.roles.blank? end + + ### + # refs: https://qiita.com/mnishiguchi/items/e15bbef61287f84b546e + ### + def social_profile(provider) + social_profiles.select { |sp| sp.provider == provider.to_s }.first + end + + # 本物の email がセットされているか確認。 + def email_verified? + self.email && self.email !~ TEMP_EMAIL_REGEX + end + + # email 確認がされていない状態にする。 + # def reset_confirmation! + # self.update_column(:confirmed_at, nil) + # end + + # Userモデル経由でcurrent_userを参照できるようにする。 + def self.current_user=(user) + # Set current user in Thread. + Thread.current[:current_user] = user + end + + # Userモデル経由でcurrent_userを参照する。 + def self.current_user + # Get current user from Thread. + Thread.current[:current_user] + end end diff --git a/app/services/o_auth_service.rb b/app/services/o_auth_service.rb new file mode 100644 index 0000000..232a6e6 --- /dev/null +++ b/app/services/o_auth_service.rb @@ -0,0 +1,60 @@ +# refs: https://qiita.com/mnishiguchi/items/e15bbef61287f84b546e +module OAuthService + class GetOAuthUser + def self.call(auth) + # 認証データに対応するSocialProfileが存在するか確認し、なければSocialProfileを新規作成。 + # 認証データをSocialProfileオブジェクトにセットし、データベースに保存。 + profile = SocialProfile.find_for_oauth(auth) + # ユーザーを探す。 + # 第1候補:ログイン中のユーザー、第2候補:SocialProfileオブジェクトに紐付けされているユーザー。 + user = current_or_profile_user(profile) + unless user + # 第3候補:認証データにemailが含まれていればそれを元にユーザーを探す。 + user = User.where(email: email).first if verified_email_from_oauth(auth) + # 見つからなければ、ユーザーを新規作成。 + user ||= find_or_create_new_user(auth) + end + associate_user_with_profile!(user, profile) + user + end + + private + + class << self + def current_or_profile_user(profile) + user = User.current_user.presence || profile.user + end + + # 見つからなければ、ユーザーを新規作成。emailは後に確認するので今は仮のものを入れておく。 + # TEMP_EMAIL_PREFIXを手掛かりに後に仮のものかどうかの判別が可能。 + # OmniAuth認証時はパスワード入力は免除するので、ランダムのパスワードを入れておく。 + def find_or_create_new_user(auth) + # Query for user if verified email is provided + email = verified_email_from_oauth(auth) + user = User.where(email: email).first if email + if user.nil? + temp_email = "#{User::TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com" + user = User.new( + username: auth.extra.raw_info.name, + email: email ? email : temp_email, + password: Devise.friendly_token[0, 20] + ) + # email確認メール送信を延期するために一時的にemail確認済みの状態にする。 + # user.skip_confirmation! + # email仮をデータベースに保存するため、validationを一時的に無効化。 + user.save(validate: false) + user + end + end + + def verified_email_from_oauth(auth) + auth.info.email if auth.info.email && (auth.info.verified || auth.info.verified_email) + end + + # ユーザーとSocialProfileオブジェクトを関連づける。 + def associate_user_with_profile!(user, profile) + profile.update!(user_id: user.id) if profile.user != user + end + end + end +end \ No newline at end of file diff --git a/app/validators/email_validator.rb b/app/validators/email_validator.rb new file mode 100644 index 0000000..7e5ef1d --- /dev/null +++ b/app/validators/email_validator.rb @@ -0,0 +1,18 @@ +require 'mail' + +class EmailValidator < ActiveModel::EachValidator + def validate_each(record,attribute,value) + begin + m = Mail::Address.new(value) + # We must check that value contains a domain, the domain has at least + # one '.' and that value is an email address + r = !m.domain.nil? && m.domain.match('\.') && m.address == value + rescue Exception => e + r = false + end + record.errors[attribute] << (options[:message] || 'is invalid') unless r + + # 仮emailから変更しないとエラーになるようにする。 + record.errors[attribute] << 'must be given. Please give us a real one!!!' unless value !~ User::TEMP_EMAIL_REGEX + end +end diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb index 4b98345..75495d2 100644 --- a/app/views/devise/passwords/new.html.erb +++ b/app/views/devise/passwords/new.html.erb @@ -1,21 +1,31 @@ -
-
-

<%= t('.forgot_your_password') %>

+
+
+
+
+
+
+
+

<%= t('.forgot_your_password', default: 'Forgot your password?') %>

- <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> - <%= devise_error_messages! %> -
-

<%= t('.forgot_your_password_message') %>

-
- <%= f.email_field :email, autofocus: true, placeholder: 'Email Address', class: 'form-control' %> -
+ <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= devise_error_messages! %> +
+

<%= t('.forgot_your_password_message', default: 'Enter the email address used for registration.') %>

+
+ <%= f.email_field :email, autofocus: true, placeholder: 'Email Address', class: 'form-control' %> +
-
- <%= f.submit t('.send_me_reset_password_instructions', default: 'Send me reset password instructions'), class: 'btn btn-primary btn-block btn-lg' %> +
+ <%= f.submit t('.send_me_reset_password_instructions', default: 'Send me reset password instructions'), class: 'btn btn-primary btn-block btn-lg' %> +
+ <% end %> +
+ <%= render "devise/shared/links" %> +
+
+
+
- <% end %> -
- <%= render "devise/shared/links" %>
\ No newline at end of file diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 7cec91e..d6f1043 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -1,6 +1,6 @@
-
+
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 793ee64..e3ea133 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,6 +1,6 @@
-
+
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 208d4cd..04d2bdd 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -1,3 +1,36 @@ +<%- if devise_mapping.omniauthable? && (controller_name == 'sessions' || controller_name == 'registrations') %> +
+ <%- resource_class.omniauth_providers.each do |provider| %> + <% + css_class = '' + fontawesome_class = '' + case provider + when :twitter + css_class = 'btn btn-twitter btn-user btn-block' + fontawesome_class = 'fab fa-twitter fa-fw' + provider_for_view = 'Twitter' + when :github + css_class = 'btn btn-github btn-user btn-block' + fontawesome_class = 'fab fa-github fa-fw' + provider_for_view = 'Github' + when :google_oauth2 + css_class = 'btn btn-google btn-user btn-block' + fontawesome_class = 'fab fa-google fa-fw' + provider_for_view = 'Google' + end + %> + <% + i18n_key = controller_name == 'sessions' ? '.sign_in_with_provider' : '.sign_up_with_provider' + %> + <%= link_to omniauth_authorize_path(resource_name, provider), class: css_class do %> + <%= t(i18n_key, provider: OmniAuth::Utils.camelize(provider_for_view), default: "Sign in with #{OmniAuth::Utils.camelize(provider_for_view)}") %> + <% end %>
+ <% end %> +
+<% end %> + +
+ <%- if controller_name != 'sessions' %> <%= link_to t('.sign_in', default: 'Sign in'), new_session_path(resource_name) %>
<% end %> @@ -16,10 +49,4 @@ <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> - <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
- <% end %> -<% end %> +<% end %> \ No newline at end of file diff --git a/app/views/omniauth_finished/finish_signup.html.erb b/app/views/omniauth_finished/finish_signup.html.erb new file mode 100644 index 0000000..14df4f4 --- /dev/null +++ b/app/views/omniauth_finished/finish_signup.html.erb @@ -0,0 +1,27 @@ +
+
+
+
+
+
+
+
+

<%= t('.sign_up_with_app_name', app_name: 'Re:Backlogs', default: 'Sign up to Re:Backlogs') %>

+
+ <%= simple_form_for(@user, url: finish_signup_path(@user), html: {class: 'user'}) do |f| %> +
+ <%= f.input :username, placeholder: t('activerecord.attributes.user.username', default: 'Username'), autofocus: true, class: 'form-control form-control-user', input_html: { value: '' } %> +
+
+ <%= f.input :email, placeholder: t('activerecord.attributes.user.email', default: 'Email'), autofocus: false, class: 'form-control form-control-user', input_html: { value: '' } %> +
+ <%= f.submit t('.sign_up', default: 'Sign up'), class: "btn btn-primary btn-user btn-block" %> + <%= hidden_field_tag :provider, @provider %> + <% end %> +
+
+
+
+
+
+
\ No newline at end of file diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index b51a5fa..96d92d3 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -16,6 +16,9 @@
+ <% if project.is_public %> + <%= t('.is_public', default: 'Public') %> + <% end %> <%= link_to project.title, project_path(project) %>
<%= project.body %>
diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb index 8ae4d92..418ebae 100644 --- a/app/views/projects/show.html.erb +++ b/app/views/projects/show.html.erb @@ -5,6 +5,11 @@
<%= render 'commons/sidebar', { project: @project } %>
+ <% if @project.is_public %> +
+ <%= t('.public_project_message', default: 'This is a public project. Non-member users are only allowed to comment on tickets.') %> +
+ <% end %>
diff --git a/app/views/sprints/kanban.html.erb b/app/views/sprints/kanban.html.erb index 30ca1f1..81a8942 100644 --- a/app/views/sprints/kanban.html.erb +++ b/app/views/sprints/kanban.html.erb @@ -5,6 +5,11 @@
<%= render 'commons/sidebar', { project: @sprint.project } %>
+ <% if @sprint.project.is_public %> +
+ <%= t('.public_project_message', default: 'This is a public project. Non-member users are only allowed to comment on tickets.') %> +
+ <% end %>
Warden configuration # If you want to use other strategies, that are not supported by Devise, or diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml index 02196a3..6946b50 100644 --- a/config/locales/devise.ja.yml +++ b/config/locales/devise.ja.yml @@ -83,6 +83,7 @@ ja: new_password: 新しいパスワード new: forgot_your_password: パスワードを忘れましたか? + forgot_your_password_message: 登録に利用したメールアドレスを入力してください send_me_reset_password_instructions: パスワードの再設定方法を送信する no_token: このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。 send_instructions: パスワードの再設定について数分以内にメールでご連絡いたします。 @@ -123,8 +124,9 @@ ja: didn_t_receive_unlock_instructions: アカウントの凍結解除方法のメールを受け取っていませんか? forgot_your_password: パスワードを忘れましたか? sign_in: ログイン - sign_in_with_provider: "%{provider}でログイン" + sign_in_with_provider: "%{provider} アカウントでログイン" sign_up: アカウント登録 + sign_up_with_provider: "%{provider} アカウントで新規登録" minimum_password_length: "(%{count}字以上)" unlocks: new: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 749a702..1e932cd 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -38,6 +38,12 @@ ja: projects: index: project_does_not_exists: プロジェクトが作成されていません。「プロジェクトを作成」ボタンからプロジェクトを作成してみましょう。 + is_public: 公開 + show: + public_project_message: これは公開プロジェクトです。メンバーではないユーザはチケットへのコメントのみ許可されています。 + sprints: + kanban: + public_project_message: これは公開プロジェクトです。メンバーではないユーザはチケットへのコメントのみ許可されています。 profiles: index: user_settings: 個人設定 @@ -77,3 +83,9 @@ ja: english: 英語 japanese: 日本語 install: インストールする + omniauth_callbacks: + need_info_before_signup: 登録前にユーザ名とメールアドレスの入力が必要です + omniauth_finished: + finish_signup: + sign_up_with_app_name: "%{app_name} アカウント登録" + sign_up: アカウント登録 diff --git a/config/routes.rb b/config/routes.rb index 3b94d91..d7e1261 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Rails.application.routes.draw do - devise_for :users + devise_for :users, controllers: { omniauth_callbacks: 'omniauth_callbacks' } + # OmniAuth認証後、email入力を求める処理のため。 + match '/users/:id/finish_signup' => 'omniauth_finished#finish_signup', via: [:get, :patch], as: :finish_signup + # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root to: 'projects#index' diff --git a/config/settings.yml b/config/settings.yml new file mode 100644 index 0000000..ebd00d3 --- /dev/null +++ b/config/settings.yml @@ -0,0 +1,10 @@ +omniauth: + google_oauth2: + key: <%= ENV['GOOGLE_OAUTH_KEY'] %> + secret: <%= ENV['GOOGLE_OAUTH_SECRET'] %> + github: + key: <%= ENV['GITHUB_OAUTH_KEY'] %> + secret: <%= ENV['GITHUB_OAUTH_SECRET'] %> + twitter: + key: <%= ENV['TWITTER_OAUTH_KEY'] %> + secret: <%= ENV['TWITTER_OAUTH_SECRET'] %> \ No newline at end of file diff --git a/config/settings/development.yml b/config/settings/development.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/settings/production.yml b/config/settings/production.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/settings/test.yml b/config/settings/test.yml new file mode 100644 index 0000000..e69de29 diff --git a/db/migrate/20191209064135_create_social_profiles.rb b/db/migrate/20191209064135_create_social_profiles.rb new file mode 100644 index 0000000..0ed2f6d --- /dev/null +++ b/db/migrate/20191209064135_create_social_profiles.rb @@ -0,0 +1,22 @@ +class CreateSocialProfiles < ActiveRecord::Migration[6.0] + def change + create_table :social_profiles do |t| + t.references :user, null: false, foreign_key: true + t.string :provider + t.string :uid + t.string :name + t.string :nickname + t.string :email + t.string :url + t.string :image_url + t.string :description + t.text :others + t.text :credentials + t.text :raw_info + + t.timestamps + end + + add_index :social_profiles, [:provider, :uid], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index c4dee64..137434c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_11_15_015542) do +ActiveRecord::Schema.define(version: 2019_12_09_064135) do create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false @@ -134,6 +134,25 @@ t.index ["var"], name: "index_settings_on_var", unique: true end + create_table "social_profiles", force: :cascade do |t| + t.integer "user_id", null: false + t.string "provider" + t.string "uid" + t.string "name" + t.string "nickname" + t.string "email" + t.string "url" + t.string "image_url" + t.string "description" + t.text "others" + t.text "credentials" + t.text "raw_info" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["provider", "uid"], name: "index_social_profiles_on_provider_and_uid", unique: true + t.index ["user_id"], name: "index_social_profiles_on_user_id" + end + create_table "sprints", force: :cascade do |t| t.integer "project_id" t.string "title" @@ -229,6 +248,7 @@ add_foreign_key "group_users", "groups" add_foreign_key "group_users", "users" add_foreign_key "project_ticket_statuses", "projects" + add_foreign_key "social_profiles", "users" add_foreign_key "sprints", "projects" add_foreign_key "tag_tickets", "tags" add_foreign_key "tag_tickets", "tickets" diff --git a/test/fixtures/social_profiles.yml b/test/fixtures/social_profiles.yml new file mode 100644 index 0000000..149816e --- /dev/null +++ b/test/fixtures/social_profiles.yml @@ -0,0 +1,29 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + user: one + provider: MyString + uid: MyString + name: MyString + nickname: MyString + email: MyString + url: MyString + image_url: MyString + description: MyString + others: MyText + credentials: MyText + raw_info: MyText + +two: + user: two + provider: MyString + uid: MyString + name: MyString + nickname: MyString + email: MyString + url: MyString + image_url: MyString + description: MyString + others: MyText + credentials: MyText + raw_info: MyText diff --git a/test/models/social_profile_test.rb b/test/models/social_profile_test.rb new file mode 100644 index 0000000..394dfae --- /dev/null +++ b/test/models/social_profile_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class SocialProfileTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end