groovestack-identities 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9a8a5d3695a440756d99cc17cb5289b06f16fe4fbf4fbfd74d059ecd1ad5f8a1
4
+ data.tar.gz: 7b103475d07e14d60524d1c5e5f3205eca6d76dd5372c5bea58df1cc6b95ae0b
5
+ SHA512:
6
+ metadata.gz: fe0299f5a20f516358c7d145e3bb4d662233aad3766eeaad11a51b218fb1e1570118e096663121df41eec9a3a3840e1c69c5beb3e342a65f3ef3e871ad2b50a1
7
+ data.tar.gz: 5f3389beedc3cab5f2cfa605c932fd75f54f47b6ff4de465557d6556f1724d53050c4f0c3c84289bd23822df896e7f14542b7f5324351e77dff256bfab6cdd9a
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,46 @@
1
+ plugins:
2
+ - rubocop-graphql
3
+
4
+ AllCops:
5
+ NewCops: enable
6
+ TargetRubyVersion: 3.1.3
7
+
8
+ GraphQL/ArgumentDescription:
9
+ Enabled: false
10
+
11
+ GraphQL/FieldDescription:
12
+ Enabled: false
13
+
14
+ GraphQL/ObjectDescription:
15
+ Enabled: false
16
+
17
+ Layout/LineLength:
18
+ Exclude:
19
+ - spec/**/*
20
+
21
+ Metrics/BlockLength:
22
+ Exclude:
23
+ - spec/**/*
24
+ - '*.gemspec'
25
+
26
+ Metrics/MethodLength:
27
+ Max: 20
28
+
29
+ Style/Documentation:
30
+ Enabled: false
31
+
32
+ Style/RedundantConstantBase:
33
+ Enabled: false
34
+
35
+
36
+
37
+ # Style/StringLiterals:
38
+ # Enabled: true
39
+ # EnforcedStyle: double_quotes
40
+
41
+ # Style/StringLiteralsInInterpolation:
42
+ # Enabled: true
43
+ # EnforcedStyle: double_quotes
44
+
45
+ # Layout/LineLength:
46
+ # Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-04-23
4
+
5
+ - Initial release
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # Groovestack::Identities
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rubocop/rake_task'
6
+
7
+ RuboCop::RakeTask.new
8
+
9
+ task default: %i[rubocop]
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateUsers < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :users, id: :uuid do |t|
6
+ t.string :name
7
+ t.string :email
8
+ t.jsonb :roles, null: false, default: []
9
+ t.string :language
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :users, :email, unique: true
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateIdentities < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :identities, id: :uuid do |t|
6
+ t.string :provider
7
+ t.string :uid
8
+
9
+ t.references :user, foreign_key: true, index: false, null: false, type: :uuid
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :identities, %i[provider uid], unique: true
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Identity
5
+ class Filter < ::Groovestack::Base::GraphQL::Base::InputObject
6
+ description 'Identity filter props'
7
+
8
+ argument :ids, [ID], required: false
9
+
10
+ argument :user_id, ID, required: false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Identity
5
+ module Mutations
6
+ class Delete < ::Groovestack::Base::GraphQL::Base::Mutation
7
+ argument :id, ID, required: true
8
+
9
+ type ::GraphQL::Identity::Type
10
+
11
+ def perform(id:)
12
+ identity = ::Identity.find(id)
13
+
14
+ identity.destroy!
15
+
16
+ identity
17
+ end
18
+ end
19
+
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ field :delete_identity, mutation: ::GraphQL::Identity::Mutations::Delete, description: 'delete identity'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Identity
5
+ module Queries
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include ::Groovestack::Base::GraphQL::Providers::ReactAdmin::Resource
10
+
11
+ react_admin_resource :identities, graphql_path: 'GraphQL'
12
+ end
13
+
14
+ def identities_scope(base_scope:, sort_field: nil, sort_order: nil, filter: {})
15
+ scope = base_scope
16
+ scope = scope.where(id: filter.ids) if filter.ids.present?
17
+ scope = scope.where(user_id: filter.user_id) if filter.user_id.present?
18
+
19
+ return scope if sort_field.blank?
20
+
21
+ scope.order({ sort_field.underscore => sort_order || 'desc' })
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Identity
5
+ class Type < ::Groovestack::Base::GraphQL::Base::Object
6
+ description 'An identity'
7
+
8
+ graphql_name 'Identity'
9
+
10
+ field :created_at, ::GraphQL::Types::ISO8601DateTime, null: false, description: 'created at'
11
+ field :id, ID, null: false, description: 'id'
12
+ field :uid, String, null: false, description: 'uid'
13
+ field :updated_at, ::GraphQL::Types::ISO8601DateTime, null: false, description: 'updated at'
14
+
15
+ field :provider, String, null: true, description: 'provider'
16
+
17
+ # associations
18
+
19
+ field :user_id, ID, null: false, description: 'user id of the user the identity is associated with'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module User
5
+ class Filter < ::Groovestack::Base::GraphQL::Base::InputObject
6
+ description 'User filter props'
7
+
8
+ argument :ids, [ID], required: false
9
+ argument :q, String, required: false
10
+
11
+ argument :name, String, required: false
12
+ argument :roles, [String], required: false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module User
5
+ module Mutations
6
+ class Update < ::Groovestack::Base::GraphQL::Base::Mutation
7
+ argument :email, String, required: false
8
+ argument :id, ID, required: true
9
+ argument :language, String, required: false
10
+ argument :name, String, required: false
11
+ argument :roles, [String], required: false
12
+
13
+ type ::GraphQL::User::Type
14
+
15
+ def current_user
16
+ context[:current_resource]
17
+ end
18
+
19
+ def perform(id:, **attrs)
20
+ obj = id == current_user&.id ? current_user : ::User.find(id)
21
+
22
+ if !current_user.admin? && attrs[:roles].present?
23
+ raise GraphQL::ExecutionError,
24
+ 'Validation Failed: only admins can update user roles'
25
+ end
26
+
27
+ obj.update!(attrs)
28
+
29
+ obj
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module User
5
+ module Queries
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include ::Groovestack::Base::GraphQL::Providers::ReactAdmin::Resource
10
+
11
+ react_admin_resource :users, graphql_path: 'GraphQL'
12
+
13
+ def User(id:) # rubocop:disable Naming/MethodName
14
+ id == 'me' ? current_user : ::User.find(id)
15
+ end
16
+ end
17
+
18
+ def current_user
19
+ context[:current_resource]
20
+ end
21
+
22
+ def users_scope(base_scope:, sort_field: nil, sort_order: nil, filter: {}) # rubocop:disable Metrics/AbcSize
23
+ scope = base_scope
24
+ scope = scope.where(id: filter.ids) if filter.ids.present?
25
+
26
+ if filter.q.present?
27
+ scope = scope.where(id: scope.fuzzysearch(filter.q)).or(scope.where(id: scope.emailsearch(filter.q)))
28
+ end
29
+ scope = scope.fuzzysearch(filter.name) if filter.name.present?
30
+ scope = scope.with_roles(filter.roles) if filter.roles.present?
31
+
32
+ return scope if sort_field.blank?
33
+
34
+ scope.order({ sort_field.underscore => sort_order || 'desc' })
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module User
5
+ class Type < ::Groovestack::Base::GraphQL::Base::Object
6
+ include ::Groovestack::Base::GraphQL::Types::TypeResolver
7
+
8
+ description 'A user'
9
+
10
+ field :email, String, null: true, description: 'email'
11
+ field :id, ID, null: false, description: 'id'
12
+ field :language, String, null: true, description: 'user language'
13
+ field :name, String, null: true, description: 'name'
14
+ field :roles, [String], null: true, description: 'roles'
15
+
16
+ field :created_at, ::GraphQL::Types::ISO8601DateTime, null: false, description: 'created at'
17
+ field :updated_at, ::GraphQL::Types::ISO8601DateTime, null: false, description: 'updated at'
18
+
19
+ # associations
20
+
21
+ field :identities, [::GraphQL::Identity::Type], null: false, description: 'identities'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Rails)
4
+ module Groovestack
5
+ module Identities
6
+ class Railtie < ::Rails::Engine
7
+ include ::Groovestack::Base::CoreRailtie
8
+
9
+ def dx_validations
10
+ []
11
+ end
12
+
13
+ initializer :append_migrations do |app|
14
+ append_migrations app
15
+ end
16
+
17
+ initializer :append_initializers do |app|
18
+ append_initializers app
19
+ end
20
+
21
+ config.after_initialize do
22
+ after_init
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Groovestack
4
+ module Identities
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'identities/version'
4
+
5
+ require 'groovestack/base'
6
+ require 'users/roles'
7
+ require 'user'
8
+ require 'identity'
9
+
10
+ require 'groovestack/identities/railtie' if defined?(Rails::Railtie)
11
+
12
+ # Load fabricators if Fabrication and Faker are available
13
+ if defined?(Fabrication) && defined?(Faker)
14
+ fabricator_path = File.expand_path('../../spec/fabricators/user_fabricator', __dir__)
15
+ require fabricator_path
16
+ end
17
+
18
+ module GraphQL
19
+ module Identity
20
+ autoload :Type, 'graphql/identity/type'
21
+ autoload :Filter, 'graphql/identity/filter'
22
+ autoload :Queries, 'graphql/identity/queries'
23
+ autoload :Mutations, 'graphql/identity/mutations'
24
+ end
25
+
26
+ module User
27
+ autoload :Filter, 'graphql/user/filter'
28
+ autoload :Type, 'graphql/user/type'
29
+ autoload :Queries, 'graphql/user/queries'
30
+ autoload :Mutations, 'graphql/user/mutations'
31
+ end
32
+ end
33
+
34
+ module Groovestack
35
+ module Identities
36
+ class Error < StandardError; end
37
+ # Your code goes here...
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Identity < ActiveRecord::Base
4
+ belongs_to :user
5
+ end
data/models/user.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class User < ActiveRecord::Base
4
+ include Users::Roles
5
+
6
+ has_many :identities, dependent: :destroy
7
+
8
+ scope :fuzzysearch, ->(q) { where('name::text ilike ?', "%#{q}%".gsub(/\s/, '%').squeeze('%')) }
9
+ scope :emailsearch, ->(qemail) { where('email::text ilike ?', "#{qemail}%") }
10
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Lint/ConstantDefinitionInBlock
4
+
5
+ module Users
6
+ module Roles
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ scope :with_roles, ->(roles) {
11
+ roles_array = Array(roles).map(&:to_s)
12
+ where("roles ?| array[:roles]", roles: roles_array)
13
+ }
14
+ scope :admins, -> { with_roles(Role::ADMIN) }
15
+
16
+ module Role
17
+ ADMIN = 'admin'
18
+ end
19
+
20
+ ROLES = [
21
+ Role::ADMIN
22
+ ].freeze
23
+
24
+ def add_roles(roles)
25
+ self.roles |= roles
26
+ end
27
+
28
+ def add_role(role)
29
+ add_roles([role])
30
+ end
31
+
32
+ def admin!
33
+ add_role(User::Role::ADMIN)
34
+ save!
35
+ end
36
+
37
+ def admin?
38
+ roles.include? User::Role::ADMIN
39
+ end
40
+
41
+ def role?(role)
42
+ roles.include? role.to_s
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ # rubocop:enable Lint/ConstantDefinitionInBlock
@@ -0,0 +1,6 @@
1
+ module Groovestack
2
+ module Identities
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: groovestack-identities
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Max Schridde
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: groovestack-base
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.10
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '0.1'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.1.10
32
+ description: Groovestack::Identities provides a flexible user and identity data model
33
+ for the CORE Platform.
34
+ email:
35
+ - maxjschridde@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - ".rspec"
41
+ - ".rubocop.yml"
42
+ - CHANGELOG.md
43
+ - README.md
44
+ - Rakefile
45
+ - db/migrate/20231103172517_create_users.rb
46
+ - db/migrate/20231103174037_create_identities.rb
47
+ - graphql/identity/filter.rb
48
+ - graphql/identity/mutations.rb
49
+ - graphql/identity/queries.rb
50
+ - graphql/identity/type.rb
51
+ - graphql/user/filter.rb
52
+ - graphql/user/mutations.rb
53
+ - graphql/user/queries.rb
54
+ - graphql/user/type.rb
55
+ - lib/groovestack/identities.rb
56
+ - lib/groovestack/identities/railtie.rb
57
+ - lib/groovestack/identities/version.rb
58
+ - models/identity.rb
59
+ - models/user.rb
60
+ - models/users/roles.rb
61
+ - sig/groovestack/identities.rbs
62
+ homepage: https://talysto.com/tech/groovestack/
63
+ licenses: []
64
+ metadata:
65
+ allowed_push_host: https://rubygems.org
66
+ homepage_uri: https://talysto.com/tech/groovestack/
67
+ source_code_uri: https://github.com/talysto/groovestack-core/
68
+ changelog_uri: https://github.com/talysto/groovestack-core/
69
+ rubygems_mfa_required: 'true'
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ - models
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 3.1.0
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubygems_version: 3.6.7
86
+ specification_version: 4
87
+ summary: User and Identity Management for the CORE Platform
88
+ test_files: []