teamable 0.0.1.alpha → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 %>