teamable 0.0.1.alpha → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92e14701652541974d41ad1b2f37bb99ae1ba17e2932da58ff40a60164e40be7
4
- data.tar.gz: cac90b085b07a4b5b616168a6d4a33a60cf904f357a563c96fe8fbd699dfda1f
3
+ metadata.gz: 1860e39d3cd85c51f07d0a6eafdb2b8fb54fd6857b31b828803ebf96ed83779c
4
+ data.tar.gz: 4aa1de393a617deaaea7508d7236b60eb6617091766fc654973b4be6b8365462
5
5
  SHA512:
6
- metadata.gz: a640b4d0df029a0cba8044a7ff47fea7bc56ab9538ae19ffd93a853b56a6b7bdd2001ccca4ca0c0028bd21199c71484ad3b7623b8105f1a3dcd92b2d85cee84e
7
- data.tar.gz: 6ab50670c95b7451d6c35394302c812307f52f0678638eeced97527c6f8ae175c4e407a53fae297dee8135b7f0091c78cf5c3d55004811504b3f01399c8a3bf5
6
+ metadata.gz: 1ee01575799e26c74d465da1c28fe90c0e5598c91b24732f17419e2332c27b8f5f12834affedbbe8c505b65e8b97d7ba8690d8454462638e52702bbe4558b629
7
+ data.tar.gz: fb3521f7e93c51498120d6c17d3a58295dee54d66dd488d2d804023d6822043c7a6083c4ff4f98e6b44eee119e597d54b27fdf0a77da7334fe12d10c88925322
data/README.md CHANGED
@@ -7,7 +7,7 @@ Teamable
7
7
 
8
8
  Teamable enables role based multi-user accounts (teams/organizations). Teams are represented by the `Account` model. An account can have multiple users associated with it and a user can have multiple accounts. You can think of accounts as a common name for **teams** or **organizations**.
9
9
 
10
- Every user can have one personal account they're admin of, which allows our code to work the same way whether we're adding resources privately for a user or for a team/organization. When a user signs in for the first time, they will be redirected to `new_account_path` and prompted to create their first personal or team account.
10
+ Every user can have one personal account they're admin of, which allows our code to work the same way whether we're adding resources privately for a user or for a team/organization. When a user signs in for the first time, they will be redirected to `setup_personal_account_path` and prompted to create their personal account.
11
11
 
12
12
  ### Team resources
13
13
 
@@ -44,7 +44,7 @@ Installation
44
44
  Add the following line to Gemfile:
45
45
 
46
46
  ```ruby
47
- gem "teamable", "~> 1.0"
47
+ gem "teamable", github: "kiqr/teamable" # Use Github repo to use alpha release.
48
48
  ```
49
49
 
50
50
  and run `bundle install` from your terminal to install it.
@@ -71,7 +71,39 @@ foo@bar:~$ rails g teamable:install
71
71
  create app/models/member.rb
72
72
  create db/migrate/20211010111722_teamable_create_accounts.rb
73
73
  create db/migrate/20211010111723_teamable_create_members.rb
74
- route teamable "organizations"
74
+ route teamable "account"
75
+ ```
76
+
77
+ #### Team authentication
78
+
79
+ To set up a controller with team authentication, just add `before_action :authenticate_account!` to your controllers. If you're using Devise as your authentication gem, you should add `unless: :devise_controller?` to prevent redirects from Devise routes (for example password settings or sign out).
80
+
81
+ ```ruby
82
+ class ApplicationController < ActionController::Base
83
+ before_action :authenticate_user!
84
+ before_action :authenticate_account!, unless: :devise_controller? # Add this row after authenticate_user!
85
+ end
86
+ ```
87
+
88
+
89
+ Routes
90
+ ------
91
+
92
+ #### Scoped/prefixed routes
93
+ Teamable finds the current account by parsing the `account_id` param. If none is set it will use the personal account. Define the routes for your application inside the `teamable` block and they will automatically be prefixed with `/team/<team_id>` if the user is signed in to a team account.
94
+
95
+ ```ruby
96
+ teamable "account" do
97
+ root "dashboard#show"
98
+ end
99
+ ```
100
+
101
+ #### Custom path scope
102
+ Teamable ships with some default routes. If you want to change the path name for teamable routes, you can do it by changing the first argument passed to `teamable` in your `config/routes.rb`, like this:
103
+
104
+ ```ruby
105
+ # config/routes.rb
106
+ teamable "organizations" # Scope teamable urls by "/organizations" instead of "/account".
75
107
  ```
76
108
 
77
109
  Contributing
@@ -15,24 +15,22 @@ module Teamable
15
15
  @account.members.build(user: current_user)
16
16
 
17
17
  if @account.save
18
- update_teamable_session_id!(@account.id)
19
- redirect_to root_path, notice: "success"
18
+ redirect_to root_path(account_id: @account)
20
19
  else
21
20
  render :new, status: :unprocessable_entity
22
21
  end
23
22
  end
24
23
 
25
24
  def switch
26
- account = current_user.accounts.find(params[:id])
27
- update_teamable_session_id!(account.id)
28
- redirect_to after_account_switched_path
25
+ account = current_user.accounts.find_puid!(params[:account_id])
26
+ redirect_to after_account_switched_path(account)
29
27
  end
30
28
 
31
29
  private
32
30
 
33
31
  # Only allow a list of trusted parameters through.
34
32
  def account_params
35
- params.require(:account).permit(:name, :billing_email)
33
+ params.require(:account).permit(:name)
36
34
  end
37
35
  end
38
36
  end
@@ -4,17 +4,20 @@ module Teamable
4
4
  class SetupController < TeamableController
5
5
  # GET /account/setup
6
6
  def new
7
- @account = current_user.accounts.new
7
+ @account = current_user.build_personal_account(
8
+ personal_account: true
9
+ )
8
10
  end
9
11
 
10
12
  # POST /account/setup
11
13
  def create
12
- @account = current_user.accounts.build(account_params)
14
+ @account = current_user.build_personal_account(account_params)
15
+ @account.personal_account = true
13
16
  @account.members.build(user: current_user)
14
17
 
15
18
  if @account.save
16
- update_teamable_session_id!(@account.id)
17
- redirect_to root_path, notice: "success"
19
+ current_user.update(personal_account: @account)
20
+ redirect_to root_path, notice: "Your account was setup successfully!"
18
21
  else
19
22
  render :new, status: :unprocessable_entity
20
23
  end
@@ -24,7 +27,7 @@ module Teamable
24
27
 
25
28
  # Only allow a list of trusted parameters through.
26
29
  def account_params
27
- params.require(:account).permit(:name, :billing_email)
30
+ params.require(:account).permit(:name)
28
31
  end
29
32
  end
30
33
  end
@@ -3,4 +3,4 @@
3
3
 
4
4
  <%= render "teamable/shared/flash_messages" %>
5
5
 
6
- <%= render "form" %>
6
+ <%= render "teamable/shared/form" %>
@@ -3,4 +3,4 @@
3
3
 
4
4
  <%= render "teamable/shared/flash_messages" %>
5
5
 
6
- <%= render "form" %>
6
+ <%= render "teamable/shared/form" %>
@@ -0,0 +1,5 @@
1
+ <%= form_for @account, url: @account.personal_account ? setup_personal_account_path : account_path, method: :post do |f| %>
2
+ <%= render 'teamable/shared/errors', resource: @account %>
3
+ <%= f.text_field :name, placeholder: @account.personal_account ? "Your name" : "Account name" %><br>
4
+ <%= f.submit "Create account" %>
5
+ <% end %>
@@ -33,6 +33,7 @@ module Teamable
33
33
  def generate_migrations
34
34
  migration_template "migrations/account.tt", "#{db_migrate_path}/teamable_create_accounts.rb"
35
35
  migration_template "migrations/member.tt", "#{db_migrate_path}/teamable_create_members.rb"
36
+ migration_template "migrations/user.tt", "#{db_migrate_path}/teamable_add_personal_account_to_users.rb"
36
37
  end
37
38
 
38
39
  def add_route
@@ -4,8 +4,11 @@ class TeamableCreateAccounts < ActiveRecord::Migration[5.2]
4
4
  def change
5
5
  create_table :accounts do |t|
6
6
  t.string :name, null: false
7
- t.string :billing_email, null: false
7
+ t.string :public_uid, null: false
8
+ t.boolean :personal_account, default: false
8
9
  t.timestamps
9
10
  end
11
+
12
+ add_index :accounts, :public_uid, unique: true
10
13
  end
11
14
  end
@@ -5,7 +5,6 @@ class TeamableCreateMembers < ActiveRecord::Migration[5.2]
5
5
  create_table :members do |t|
6
6
  t.references :user, foreign_key: { to_table: :users }
7
7
  t.references :account, foreign_key: { to_table: :accounts }
8
- t.string :invitee_email
9
8
  t.string :role, nullable: false
10
9
  t.timestamps
11
10
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TeamableAddPersonalAccountToUsers < ActiveRecord::Migration[5.2]
4
+ def change
5
+ add_reference :users, :personal_account, foreign_key: { to_table: :accounts }, null: true
6
+ end
7
+ end
@@ -3,6 +3,6 @@
3
3
  class Member < ApplicationRecord
4
4
  include Teamable::Models::Member
5
5
 
6
- AVAILABLE_ROLES = %w[member admin].freeze
7
- FIRST_USER_ROLE = "admin" # Default role for the user who created the account.
6
+ AVAILABLE_ROLES = %w[owner admin member].freeze
7
+ FIRST_USER_ROLE = "owner" # Default role for the user who created the account.
8
8
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+ require "active_support/core_ext/string/inflections"
5
+
6
+ module Teamable
7
+ module Generators
8
+ class ViewsGenerator < Rails::Generators::Base
9
+ desc "Copy view files to app/views"
10
+ source_root Teamable::Engine.root
11
+
12
+ def copy_views
13
+ directory "app/views/teamable", "app/views/teamable"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -16,7 +16,7 @@ module Teamable
16
16
  end
17
17
 
18
18
  rescue_from Teamable::MissingAccountError do
19
- redirect_to setup_account_path
19
+ redirect_to setup_personal_account_path
20
20
  end
21
21
  end
22
22
 
@@ -38,13 +38,6 @@ module Teamable
38
38
  !!current_account
39
39
  end
40
40
 
41
- # Store an account id in a session variable.
42
- # Warning: this does not validate that the current_user
43
- # actually is a member of the specified account.
44
- def update_teamable_session_id!(account_id)
45
- session[:teamable_account_id] = account_id
46
- end
47
-
48
41
  private
49
42
 
50
43
  # Load current account from Account model and store it
@@ -52,20 +45,20 @@ module Teamable
52
45
  def load_current_account
53
46
  return unless user_signed_in?
54
47
 
55
- Teamable::Current.account ||= account_from_session || fallback_account || nil
48
+ Teamable::Current.account ||= account_from_params || current_user.personal_account || nil
56
49
  end
57
50
 
58
51
  # Try to load current account using session[:teamable_account_id]
59
- def account_from_session
60
- return if session[:teamable_account_id].blank?
52
+ def account_from_params
53
+ return if params[:account_id].blank?
61
54
 
62
- current_user.accounts.find_by(id: session[:teamable_account_id])
55
+ current_user.accounts.find_puid(params[:account_id])
63
56
  end
64
57
 
65
58
  # Finds last joined account if the user have any associated accounts.
66
59
  def fallback_account
67
60
  memberships = current_user.members
68
- return nil if memberships.length.zero?
61
+ return nil if memberships.empty?
69
62
 
70
63
  memberships.last.account # Return last joined account.
71
64
  end
@@ -10,8 +10,10 @@ module Teamable
10
10
  # Method references in Teamable::AccountsController#switch to redirect a user
11
11
  # after they've switched to another account. You can override it in your
12
12
  # ApplicationController to provide a custom url.
13
- def after_account_switched_path
14
- root_path
13
+ def after_account_switched_path(account = nil)
14
+ return root_path(account_id: nil) if defined?(current_user) && current_user&.personal_account == account
15
+
16
+ root_path(account_id: account)
15
17
  end
16
18
  end
17
19
  end
@@ -3,10 +3,13 @@
3
3
  module Teamable
4
4
  # Makes Teamable available to Rails as an Engine.
5
5
  class Engine < ::Rails::Engine
6
+ config.autoload_paths << Teamable::Engine.root.join("app")
7
+ config.autoload_paths << Teamable::Engine.root.join("lib")
8
+
6
9
  initializer "teamable.setup" do
7
10
  # Make teamable helpers available in controllers.
8
- ActionController::Base.include Teamable::Controllers::CurrentAccountHelper
9
- ActionController::Base.include Teamable::Controllers::UrlHelpers
11
+ ActiveSupport.on_load(:action_controller) { include Teamable::Controllers::CurrentAccountHelper }
12
+ ActiveSupport.on_load(:action_controller) { include Teamable::Controllers::UrlHelpers }
10
13
  end
11
14
  end
12
15
  end
@@ -4,14 +4,13 @@ module Teamable
4
4
  module Models
5
5
  module Account
6
6
  extend ActiveSupport::Concern
7
+ include PublicUid::ModelConcern
7
8
 
8
9
  included do
9
10
  has_many :members
10
11
  has_many :users, through: :members
11
12
 
12
- validates :name, presence: true
13
- validates :billing_email, presence: true, uniqueness: true
14
- validates_format_of :billing_email, with: Teamable.config.email_regexp
13
+ validates :name, presence: true, length: { minimum: 4, maximum: 255 }
15
14
  end
16
15
  end
17
16
  end
@@ -8,6 +8,13 @@ module Teamable
8
8
  included do
9
9
  has_many :members
10
10
  has_many :accounts, through: :members
11
+
12
+ has_many :teams, -> { where(personal_account: false) }, through: :account_users, source: :account
13
+ belongs_to :personal_account, class_name: "Account", optional: true
14
+ end
15
+
16
+ def personal_account?
17
+ !!personal_account
11
18
  end
12
19
  end
13
20
  end
@@ -3,27 +3,34 @@
3
3
  module ActionDispatch
4
4
  module Routing
5
5
  class Mapper
6
- def teamable(team_label, options = {}, &block)
7
- options[:path] ||= team_label.to_s
6
+ def teamable(teamable_path = nil, options = {}, &block)
7
+ options = default_options(teamable_path, options)
8
8
 
9
- teamable_scope options do
10
- teamable_accounts(options)
9
+ resource :personal_account, only: %i[], path: "onboarding" do
10
+ get :setup, controller: options[:setup_controller], action: "new"
11
+ post :setup, controller: options[:setup_controller], action: "create"
12
+ end
13
+
14
+ teamable_settings(options)
15
+
16
+ scope "(/#{options[:path]}/:account_id)", account_id: %r{[^/]+} do
11
17
  yield block if block_given?
12
18
  end
13
19
  end
14
20
 
15
- protected
21
+ private
16
22
 
17
- def teamable_scope(options, &block)
18
- scope options[:path], module: "teamable", &block
23
+ def teamable_settings(options)
24
+ resource :account, only: %i[new create], controller: options[:accounts_controller], path: options[:path].to_s do
25
+ patch ":account_id/switch", action: "switch", as: :switch
26
+ end
19
27
  end
20
28
 
21
- def teamable_accounts(_options)
22
- resource :account, only: %i[new create], path: "" do
23
- get :setup, to: "setup#new"
24
- post :setup, to: "setup#create"
25
- patch ":id/switch", to: "accounts#switch", as: :switch
26
- end
29
+ def default_options(teamable_path, options)
30
+ options[:path] = teamable_path || "team"
31
+ options[:setup_controller] ||= "teamable/setup"
32
+ options[:accounts_controller] ||= "teamable/accounts"
33
+ options
27
34
  end
28
35
  end
29
36
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Teamable
4
- VERSION = "0.0.1.alpha"
2
+ VERSION = "0.1.0"
5
3
  end
data/lib/teamable.rb CHANGED
@@ -7,6 +7,7 @@ require "teamable/version"
7
7
  require "teamable/engine"
8
8
 
9
9
  require "dry-configurable"
10
+ require "public_uid"
10
11
 
11
12
  # Enable role based multi-user accounts (teams/organizations) in Rails.
12
13
  module Teamable
@@ -27,10 +28,10 @@ module Teamable
27
28
 
28
29
  # The controller class that all Teamable controllers will inherit from.
29
30
  # Defaults to `ApplicationController`.
30
- setting :parent_controller, "ApplicationController"
31
+ setting :parent_controller, default: "ApplicationController"
31
32
 
32
33
  # Email regex used to validate email formats. It simply asserts that
33
34
  # one (and only one) @ exists in the given string. This is mainly
34
35
  # to give user feedback and not to assert the e-mail validity.
35
- setting :email_regexp, /\A[^@\s]+@[^@\s]+\z/
36
+ setting :email_regexp, default: /\A[^@\s]+@[^@\s]+\z/
36
37
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teamable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.alpha
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rasmus Kjellberg
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-10-17 00:00:00.000000000 Z
12
+ date: 2024-03-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-configurable
@@ -17,14 +17,28 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 0.11.0
20
+ version: 1.1.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: 0.11.0
27
+ version: 1.1.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: public_uid
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 2.2.0
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 2.2.0
28
42
  description:
29
43
  email: hello@kiqr.dev
30
44
  executables: []
@@ -36,17 +50,18 @@ files:
36
50
  - app/controllers/teamable/accounts_controller.rb
37
51
  - app/controllers/teamable/setup_controller.rb
38
52
  - app/controllers/teamable_controller.rb
39
- - app/views/teamable/accounts/_form.html.erb
40
53
  - app/views/teamable/accounts/new.html.erb
41
- - app/views/teamable/setup/_form.html.erb
42
54
  - app/views/teamable/setup/new.html.erb
43
55
  - app/views/teamable/shared/_errors.html.erb
44
56
  - app/views/teamable/shared/_flash_messages.html.erb
57
+ - app/views/teamable/shared/_form.html.erb
45
58
  - lib/generators/teamable/install_generator.rb
46
59
  - lib/generators/teamable/templates/migrations/account.tt
47
60
  - lib/generators/teamable/templates/migrations/member.tt
61
+ - lib/generators/teamable/templates/migrations/user.tt
48
62
  - lib/generators/teamable/templates/models/account.tt
49
63
  - lib/generators/teamable/templates/models/member.tt
64
+ - lib/generators/teamable/views_generator.rb
50
65
  - lib/teamable.rb
51
66
  - lib/teamable/controllers/current_account_helper.rb
52
67
  - lib/teamable/controllers/url_helpers.rb
@@ -65,6 +80,7 @@ metadata:
65
80
  bug_tracker_uri: https://github.com/kiqr/teamable/issues
66
81
  documentation_uri: https://github.com/kiqr/teamable/issues
67
82
  source_code_uri: https://github.com/kiqr/teamable
83
+ rubygems_mfa_required: 'true'
68
84
  post_install_message:
69
85
  rdoc_options: []
70
86
  require_paths:
@@ -73,14 +89,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
89
  requirements:
74
90
  - - ">="
75
91
  - !ruby/object:Gem::Version
76
- version: '2.6'
92
+ version: '3.2'
77
93
  required_rubygems_version: !ruby/object:Gem::Requirement
78
94
  requirements:
79
- - - ">"
95
+ - - ">="
80
96
  - !ruby/object:Gem::Version
81
- version: 1.3.1
97
+ version: '0'
82
98
  requirements: []
83
- rubygems_version: 3.2.26
99
+ rubygems_version: 3.5.6
84
100
  signing_key:
85
101
  specification_version: 4
86
102
  summary: Extension to enable teams for Authenticatable
@@ -1,7 +0,0 @@
1
- <%= form_for @account, url: account_path, method: :post do |f| %>
2
- <%= render 'teamable/shared/errors', resource: @account %>
3
-
4
- <%= f.text_field :name, placeholder: "Account name" %><br>
5
- <%= f.text_field :billing_email, placeholder: "Billing email" %><br>
6
- <%= f.submit "Create account" %>
7
- <% end %>
@@ -1,7 +0,0 @@
1
- <%= form_for @account, url: setup_account_path, method: :post do |f| %>
2
- <%= render 'teamable/shared/errors', resource: @account %>
3
-
4
- <%= f.text_field :name, placeholder: "Account name" %><br>
5
- <%= f.text_field :billing_email, placeholder: "Billing email" %><br>
6
- <%= f.submit "Create account" %>
7
- <% end %>