punk 0.1.4 → 0.2.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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/workflows/test.yml +26 -1
  4. data/.rdoc_options +23 -0
  5. data/.rgignore +1 -0
  6. data/.rspec +2 -0
  7. data/Gemfile +5 -6
  8. data/Gemfile.lock +16 -29
  9. data/README.md +1 -1
  10. data/VERSION +1 -1
  11. data/app/migrations/001_lets_punk.rb +3 -0
  12. data/app/routes/hello.rb +4 -0
  13. data/env/.gitignore +3 -0
  14. data/env/spec/test.sh +3 -0
  15. data/lib/punk/actions/.keep +0 -0
  16. data/lib/punk/actions/groups/list.rb +24 -0
  17. data/lib/punk/actions/sessions/clear.rb +21 -0
  18. data/lib/punk/actions/sessions/create.rb +64 -0
  19. data/lib/punk/actions/sessions/list.rb +18 -0
  20. data/lib/punk/actions/sessions/verify.rb +24 -0
  21. data/lib/punk/actions/tenants/list.rb +18 -0
  22. data/lib/punk/actions/users/list_group.rb +18 -0
  23. data/lib/punk/actions/users/list_tenant.rb +18 -0
  24. data/lib/punk/actions/users/show.rb +18 -0
  25. data/lib/punk/commands/list.rb +12 -6
  26. data/lib/punk/config/defaults.json +3 -0
  27. data/lib/punk/config/schema.json +3 -0
  28. data/lib/punk/core/app.rb +4 -6
  29. data/lib/punk/core/commander.rb +7 -4
  30. data/lib/punk/core/exec.rb +2 -0
  31. data/lib/punk/core/load.rb +0 -1
  32. data/lib/punk/framework/command.rb +5 -1
  33. data/lib/punk/framework/plugins/validation.rb +0 -14
  34. data/lib/punk/helpers/loggable.rb +1 -1
  35. data/lib/punk/migrations/001_punk.rb +103 -0
  36. data/lib/punk/models/.keep +0 -0
  37. data/lib/punk/models/group.rb +20 -0
  38. data/lib/punk/models/group_user_metadata.rb +17 -0
  39. data/lib/punk/models/identity.rb +29 -0
  40. data/lib/punk/models/session.rb +89 -0
  41. data/lib/punk/models/tenant.rb +19 -0
  42. data/lib/punk/models/tenant_user_metadata.rb +17 -0
  43. data/lib/punk/models/user.rb +31 -0
  44. data/lib/punk/routes/groups.rb +31 -0
  45. data/lib/punk/routes/plivo.rb +4 -0
  46. data/lib/punk/routes/sessions.rb +108 -0
  47. data/lib/punk/routes/swagger.rb +9 -0
  48. data/lib/punk/routes/tenants.rb +29 -0
  49. data/lib/punk/routes/users.rb +36 -0
  50. data/lib/punk/services/.keep +0 -0
  51. data/lib/punk/services/challenge_claim.rb +46 -0
  52. data/lib/punk/services/create_identities.rb +25 -0
  53. data/lib/punk/services/generate_swagger.rb +25 -0
  54. data/lib/punk/services/prove_claim.rb +29 -0
  55. data/lib/punk/services/secret.rb +9 -0
  56. data/lib/punk/templates/groups/list.jbuilder +7 -0
  57. data/lib/punk/templates/plivo.slim +16 -0
  58. data/lib/punk/templates/sessions/list.jbuilder +6 -0
  59. data/lib/punk/templates/sessions/pending.jbuilder +4 -0
  60. data/lib/punk/templates/tenants/list.jbuilder +7 -0
  61. data/lib/punk/templates/tenants/list.slim +8 -0
  62. data/lib/punk/templates/users/list.jbuilder +7 -0
  63. data/lib/punk/templates/users/list.rcsv +4 -0
  64. data/lib/punk/templates/users/show.jbuilder +5 -0
  65. data/lib/punk/views/groups/list.rb +22 -0
  66. data/lib/punk/views/plivo_store.rb +15 -0
  67. data/lib/punk/views/sessions/list.rb +22 -0
  68. data/lib/punk/views/sessions/pending.rb +28 -0
  69. data/lib/punk/views/tenants/list.rb +22 -0
  70. data/lib/punk/views/users/list.rb +22 -0
  71. data/lib/punk/views/users/show.rb +22 -0
  72. data/lib/punk/workers/.keep +0 -0
  73. data/lib/punk/workers/expire_sessions.rb +9 -0
  74. data/lib/punk/workers/geocode_session_worker.rb +48 -0
  75. data/lib/punk/workers/identify_session_worker.rb +45 -0
  76. data/lib/punk/workers/secret.rb +18 -0
  77. data/lib/punk/workers/send_email_worker.rb +51 -0
  78. data/lib/punk/workers/send_sms_worker.rb +40 -0
  79. data/punk.gemspec +140 -14
  80. data/schema.psql +345 -0
  81. data/spec/actions/groups/punk/list_groups_action_spec.rb +36 -0
  82. data/spec/actions/sessions/punk/clear_session_action_spec.rb +29 -0
  83. data/spec/actions/sessions/punk/create_session_action_spec.rb +33 -0
  84. data/spec/actions/sessions/punk/list_sessions_action_spec.rb +26 -0
  85. data/spec/actions/sessions/punk/verify_session_action_spec.rb +59 -0
  86. data/spec/actions/tenants/punk/list_tenants_action_spec.rb +25 -0
  87. data/spec/actions/users/punk/list_group_users_action_spec.rb +26 -0
  88. data/spec/actions/users/punk/list_tenant_users_action_spec.rb +26 -0
  89. data/spec/factories/group.rb +12 -0
  90. data/spec/factories/group_user_metadata.rb +10 -0
  91. data/spec/factories/identity.rb +19 -0
  92. data/spec/factories/session.rb +12 -0
  93. data/spec/factories/tenant.rb +10 -0
  94. data/spec/factories/tenant_user_metadata.rb +10 -0
  95. data/spec/factories/user.rb +12 -0
  96. data/spec/lib/commands/auth_spec.rb +11 -0
  97. data/spec/lib/commands/generate_spec.rb +7 -0
  98. data/spec/lib/commands/http_spec.rb +23 -0
  99. data/spec/lib/commands/list_spec.rb +7 -0
  100. data/spec/lib/commands/swagger_spec.rb +7 -0
  101. data/spec/lib/engine/punk_env_spec.rb +13 -0
  102. data/spec/lib/engine/punk_exec_spec.rb +9 -0
  103. data/spec/lib/engine/punk_init_spec.rb +9 -0
  104. data/spec/lib/engine/punk_store_spec.rb +10 -0
  105. data/spec/lib/punk.env +7 -0
  106. data/spec/models/punk/group_spec.rb +50 -0
  107. data/spec/models/punk/group_user_metadata_spec.rb +61 -0
  108. data/spec/models/punk/identity_spec.rb +61 -0
  109. data/spec/models/punk/session_spec.rb +156 -0
  110. data/spec/models/punk/tenant_spec.rb +51 -0
  111. data/spec/models/punk/tenant_user_metadata_spec.rb +61 -0
  112. data/spec/models/punk/user_spec.rb +115 -0
  113. data/spec/routes/groups/get_groups_spec.rb +33 -0
  114. data/spec/routes/plivo/get_plivo_spec.rb +11 -0
  115. data/spec/routes/sessions/delete_session_spec.rb +11 -0
  116. data/spec/routes/sessions/get_sessions_spec.rb +30 -0
  117. data/spec/routes/sessions/patch_session_spec.rb +11 -0
  118. data/spec/routes/sessions/post_session_spec.rb +11 -0
  119. data/spec/routes/swagger/get_swagger_spec.rb +12 -0
  120. data/spec/routes/tenants/get_tenants_spec.rb +31 -0
  121. data/spec/routes/users/get_users_spec.rb +60 -0
  122. data/spec/services/punk/challenge_claim_service_spec.rb +7 -0
  123. data/spec/services/punk/create_identities_service_spec.rb +14 -0
  124. data/spec/services/punk/generate_swagger_service_spec.rb +7 -0
  125. data/spec/services/punk/prove_claim_service_spec.rb +7 -0
  126. data/spec/services/punk/secret_service_spec.rb +7 -0
  127. data/spec/spec_helper.rb +122 -0
  128. data/spec/vcr_cassettes/PUNK_GeocodeSessionWorker/updates_the_session_data.yml +57 -0
  129. data/spec/vcr_cassettes/PUNK_IdentifySessionWorker/updates_the_session_data.yml +112 -0
  130. data/spec/views/punk/plivo_store_spec.rb +7 -0
  131. data/spec/views/sessions/punk/list_sessions_view_spec.rb +7 -0
  132. data/spec/views/sessions/punk/pending_session_view_spec.rb +7 -0
  133. data/spec/views/tenants/punk/list_tenants_view_spec.rb +7 -0
  134. data/spec/views/users/punk/list_groups_view_spec.rb +7 -0
  135. data/spec/views/users/punk/list_users_view_spec.rb +7 -0
  136. data/spec/workers/punk/expire_sessions_worker_spec.rb +31 -0
  137. data/spec/workers/punk/geocode_session_worker_spec.rb +14 -0
  138. data/spec/workers/punk/identify_session_worker_spec.rb +15 -0
  139. data/spec/workers/punk/secret_worker_spec.rb +20 -0
  140. data/spec/workers/punk/send_email_worker_spec.rb +46 -0
  141. data/spec/workers/punk/send_sms_worker_spec.rb +33 -0
  142. metadata +148 -11
  143. data/lib/punk/views/all.rb +0 -4
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class VerifySessionAction < Action
5
+ args :session, :secret
6
+
7
+ def validate
8
+ validates_not_null :session
9
+ validates_not_empty :session
10
+ return if session.blank?
11
+ validates_not_null :secret
12
+ return if secret.blank?
13
+ validates_type Session, :session
14
+ validates_state :session, :pending
15
+ validates_event :session, :verify
16
+ end
17
+
18
+ def process
19
+ verify = ProveClaimService.run(session: session, secret: secret)
20
+ raise BadRequest, 'Secret is incorrect' unless verify.result == true
21
+ present Info, message: 'We have succesfully verified your identity. Welcome to GroupFire!'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ListTenantsAction < Action
5
+ args :user
6
+
7
+ def validate
8
+ validates_not_null :user
9
+ validates_not_empty :user
10
+ return if user.blank?
11
+ validates_type User, :user
12
+ end
13
+
14
+ def process
15
+ present ListTenantsView, tenants: user.tenants_dataset.order(:name).all
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ListGroupUsersAction < Action
5
+ args :group
6
+
7
+ def validate
8
+ validates_not_null :group
9
+ validates_not_empty :group
10
+ return if group.blank?
11
+ validates_type Group, :group
12
+ end
13
+
14
+ def process
15
+ present ListUsersView, users: group.users
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ListTenantUsersAction < Action
5
+ args :tenant
6
+
7
+ def validate
8
+ validates_not_null :tenant
9
+ validates_not_empty :tenant
10
+ return if tenant.blank?
11
+ validates_type Tenant, :tenant
12
+ end
13
+
14
+ def process
15
+ present ListUsersView, users: tenant.users
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ShowUserAction < Action
5
+ args :user
6
+
7
+ def validate
8
+ validates_not_null :user
9
+ validates_not_empty :user
10
+ return if user.blank?
11
+ validates_type User, :user
12
+ end
13
+
14
+ def process
15
+ present ShowUserView, user: user
16
+ end
17
+ end
18
+ end
@@ -4,25 +4,31 @@ PUNK::Command.create "list" do
4
4
  shortcut 'l'
5
5
  description "List routes, actions, models, views, services or workers"
6
6
 
7
+ option shortcut: :a, name: :all, description: "Include Punk!", type: nil
8
+
7
9
  def process
8
10
  case args.join(' ')
9
11
  when 'routes'
10
12
  PUNK.app
11
- PUNK::App.route_list
13
+ PUNK::App.route_list # TODO: exclude PUNK routes by default
12
14
  when 'actions'
13
- ObjectSpace.each_object(PUNK::Action.singleton_class).map(&:name).reject { |name| name.nil? || name =~ /^PUNK/ }
15
+ ObjectSpace.each_object(PUNK::Action.singleton_class).map(&:name).reject { |name| _hide?(name) }
14
16
  when 'models'
15
- ObjectSpace.each_object(PUNK::Model.singleton_class).map(&:name).reject { |name| name.nil? || name =~ /^PUNK/ }
17
+ ObjectSpace.each_object(PUNK::Model.singleton_class).map(&:name).reject { |name| _hide?(name) }
16
18
  when 'views'
17
- ObjectSpace.each_object(PUNK::View.singleton_class).map(&:name).reject { |name| name.nil? || name =~ /^PUNK/ }
19
+ ObjectSpace.each_object(PUNK::View.singleton_class).map(&:name).reject { |name| _hide?(name) }
18
20
  when 'services'
19
- ObjectSpace.each_object(PUNK::Service.singleton_class).select { |klass| klass.superclass == PUNK::Service }.map(&:name).reject { |name| name.nil? || name =~ /^PUNK/ }
21
+ ObjectSpace.each_object(PUNK::Service.singleton_class).select { |klass| klass.superclass == PUNK::Service }.map(&:name).reject { |name| _hide?(name) }
20
22
  when 'workers'
21
- ObjectSpace.each_object(PUNK::Worker.singleton_class).select { |klass| klass.superclass == PUNK::Worker }.map(&:name).reject { |name| name.nil? || name =~ /^PUNK/ }
23
+ ObjectSpace.each_object(PUNK::Worker.singleton_class).select { |klass| klass.superclass == PUNK::Worker }.map(&:name).reject { |name| _hide?(name) }
22
24
  when '', 'help'
23
25
  "? specify one of: routes, actions, models, views, services, workers"
24
26
  else
25
27
  "? unkown arguments: #{args.join(',')}"
26
28
  end
27
29
  end
30
+
31
+ def _hide?(name)
32
+ name.nil? || (name =~ /^PUNK/) && !opts[:all]
33
+ end
28
34
  end
@@ -30,6 +30,9 @@
30
30
  "api_key": "$USERSTACK_API_KEY",
31
31
  "use_ssl": false
32
32
  },
33
+ "ipstack": {
34
+ "api_key": "$IPSTACK_ACCESS_KEY"
35
+ },
33
36
  "db": {
34
37
  "url": "$DATABASE_URL"
35
38
  },
@@ -32,6 +32,9 @@
32
32
  "api_key": "String",
33
33
  "use_ssl": "Flag"
34
34
  },
35
+ "ipstack": {
36
+ "api_key": "String"
37
+ },
35
38
  "log": {
36
39
  "enabled!": "Flag",
37
40
  "type!": "Enum(stdout, stderr, file)",
@@ -23,7 +23,7 @@ module PUNK
23
23
 
24
24
  ROUTES = Tempfile.new("routes.json").path
25
25
  PUNK.profile_info("generate", path: ROUTES) do
26
- system "roda-parse_routes -f #{ROUTES} #{File.expand_path(File.join(PUNK.get.app.path, 'routes', '*'))}"
26
+ system "roda-parse_routes -f #{ROUTES} #{File.expand_path(File.join(PUNK.get.app.path, 'routes', '*'))} #{File.expand_path(File.join(__dir__, '..', 'routes', '*'))}"
27
27
  end
28
28
 
29
29
  class App < Roda
@@ -88,8 +88,7 @@ module PUNK
88
88
 
89
89
  def require_session!
90
90
  begin
91
- # TODO
92
- @_current_session = nil # Session[request.session['session_id']]
91
+ @_current_session = Session[request.session['session_id']]
93
92
  if @_current_session&.active?
94
93
  @_current_session.touch
95
94
  else
@@ -192,9 +191,8 @@ module PUNK
192
191
  name = "#{request.request_method} #{request.path}"
193
192
  logger.info "Started #{name} for #{request.ip}", params.deep_symbolize_keys.sanitize.inspect
194
193
  logger.trace request.env['HTTP_USER_AGENT']
195
- # TODO
196
- # logger.info "Started #{name} for #{request.ip || Session.default_values[:remote_addr].to_s}", params.deep_symbolize_keys.sanitize.inspect
197
- # logger.trace request.env['HTTP_USER_AGENT'] || Session.default_values[:user_agent]
194
+ logger.info "Started #{name} for #{request.ip || Session.default_values[:remote_addr].to_s}", params.deep_symbolize_keys.sanitize.inspect
195
+ logger.trace request.env['HTTP_USER_AGENT'] || Session.default_values[:user_agent]
198
196
  logger.trace request.env['HTTP_COOKIE']
199
197
  logger.push_tags(name)
200
198
  _set_cookie(request.env)
@@ -74,10 +74,13 @@ command 'db migrate' do |c|
74
74
  say('Migrating db...')
75
75
  PUNK.boot
76
76
  Sequel.extension :migration
77
- if options.relative.nil?
78
- Sequel::Migrator.run(PUNK.db, File.join(PUNK.get.app.path, 'migrations'))
79
- else
80
- Sequel::Migrator.run(PUNK.db, File.join(PUNK.get.app.path, 'migrations'), relative: options.relative)
77
+ migrations_path = File.join(PUNK.get.app.path, 'migrations')
78
+ if File.exist?(migrations_path)
79
+ if options.relative.nil?
80
+ Sequel::Migrator.run(PUNK.db, migrations_path)
81
+ else
82
+ Sequel::Migrator.run(PUNK.db, migrations_path, relative: options.relative)
83
+ end
81
84
  end
82
85
  database = File.basename(PUNK.get.db.url)
83
86
  `pg_dump #{database} --schema-only > schema.psql`
@@ -23,6 +23,7 @@ end
23
23
 
24
24
  PUNK::Interface.register(:app) do
25
25
  require_relative 'app'
26
+ PUNK.require_all(File.join(__dir__, '..', 'routes'))
26
27
  PUNK.require_all(File.join(PUNK.get.app.path, 'routes'))
27
28
  retval = PUNK.get.app.reloadable ? PUNK.loader : PUNK::App.freeze.app
28
29
  SemanticLogger.flush
@@ -32,6 +33,7 @@ end
32
33
  PUNK.inject :loader, :app
33
34
 
34
35
  ['actions', 'models', 'views', 'services', 'workers'].each do |dir|
36
+ PUNK.require_all(File.join(__dir__, '..', dir))
35
37
  PUNK.require_all(File.join(PUNK.get.app.path, dir))
36
38
  end
37
39
 
@@ -4,6 +4,5 @@ PUNK.db
4
4
 
5
5
  require_relative '../helpers/all'
6
6
  require_relative '../framework/all'
7
- require_relative '../views/all'
8
7
 
9
8
  PUNK.store[:state] = :loaded
@@ -71,7 +71,11 @@ module PUNK
71
71
  define_method(:options) do |opt|
72
72
  punk_command = PUNK.store.commands[match]
73
73
  punk_command.instance_variable_get(:@options).each_value do |option|
74
- opt.on option[:shortcut], option[:name], option[:description], argument: true, as: option[:type]
74
+ if option[:type].present?
75
+ opt.on option[:shortcut], option[:name], option[:description], argument: true, as: option[:type]
76
+ else
77
+ opt.on option[:shortcut], option[:name], option[:description]
78
+ end
75
79
  end
76
80
  end
77
81
  define_method(:process) do
@@ -28,20 +28,6 @@ module PUNK
28
28
  end
29
29
  end
30
30
 
31
- def validates_parse_id(atts, opts={})
32
- default = { message: "is not a Parse ID" }
33
- validatable_attributes(atts, default.merge(opts)) do |_name, value, message|
34
- message unless /^[[:alnum:]]{10}$/.match(value)
35
- end
36
- end
37
-
38
- def validates_subdomain(atts, opts={})
39
- default = { message: "is not a subdomain" }
40
- validatable_attributes(atts, default.merge(opts)) do |_name, value, message|
41
- message unless /^[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/.match(value)
42
- end
43
- end
44
-
45
31
  def validates_state(name, state)
46
32
  errors.add(name, "is not in #{state} state") unless self[name].send("#{state}?")
47
33
  end
@@ -22,7 +22,7 @@ module PUNK
22
22
 
23
23
  def exception(e, extra={})
24
24
  if ENV.key?('SENTRY_DSN')
25
- ::Raven.capture_exception(
25
+ ::Sentry.capture_exception(
26
26
  e,
27
27
  message: e.message,
28
28
  extra: extra,
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ def _tenants
4
+ create_table :tenants do
5
+ uuid :id, primary_key: true, default: Sequel.function(:gen_random_uuid)
6
+ punk_state :state, null: false, default: 'created'
7
+ String :name, null: false, text: true
8
+ String :icon, text: true
9
+ jsonb :data, default: '{}'
10
+ DateTime :created_at
11
+ DateTime :updated_at
12
+ end
13
+ end
14
+
15
+ def _users
16
+ create_table :users do
17
+ uuid :id, primary_key: true, default: Sequel.function(:gen_random_uuid)
18
+ punk_state :state, null: false, default: 'created'
19
+ String :name, null: false, text: true
20
+ String :icon, text: true
21
+ String :email, text: true, unique: true
22
+ String :phone, text: true, unique: true
23
+ jsonb :data, default: '{}'
24
+ DateTime :created_at
25
+ DateTime :updated_at
26
+ end
27
+ end
28
+
29
+ def _tenants_users
30
+ create_table :tenants_users do
31
+ primary_key [:tenant_id, :user_id]
32
+ foreign_key :tenant_id, :tenants, null: false, type: :uuid
33
+ foreign_key :user_id, :users, null: false, type: :uuid
34
+ index [:tenant_id, :user_id]
35
+ end
36
+ end
37
+
38
+ def _groups
39
+ create_table :groups do
40
+ uuid :id, primary_key: true, default: Sequel.function(:gen_random_uuid)
41
+ punk_state :state, null: false, default: 'created'
42
+ String :name, null: false, text: true
43
+ String :icon, text: true
44
+ jsonb :data, default: '{}'
45
+ DateTime :created_at
46
+ DateTime :updated_at
47
+ foreign_key :tenant_id, :tenants, null: false, type: :uuid
48
+ end
49
+ end
50
+
51
+ def _groups_users
52
+ create_table :groups_users do
53
+ primary_key [:group_id, :user_id]
54
+ foreign_key :group_id, :groups, null: false, type: :uuid
55
+ foreign_key :user_id, :users, null: false, type: :uuid
56
+ index [:group_id, :user_id]
57
+ end
58
+ end
59
+
60
+ def _identities
61
+ create_enum(:claim_type, %w[email phone])
62
+ create_table :identities do
63
+ uuid :id, primary_key: true, default: Sequel.function(:gen_random_uuid)
64
+ punk_state :state, null: false, default: 'created'
65
+ claim_type :claim_type, null: false
66
+ String :claim, text: true, null: false, unique: true
67
+ jsonb :data, default: '{}'
68
+ DateTime :created_at
69
+ DateTime :updated_at
70
+ foreign_key :user_id, :users, null: true, type: :uuid
71
+ end
72
+ end
73
+
74
+ def _sessions
75
+ create_enum(:session_state, %w[pending created active deleted expired])
76
+ create_table :sessions do
77
+ uuid :id, primary_key: true, default: Sequel.function(:gen_random_uuid)
78
+ uuid :slug, default: Sequel.function(:gen_random_uuid)
79
+ session_state :state, null: false, default: 'created'
80
+ File :salt, text: true
81
+ File :hash, text: true
82
+ Integer :attempt_count, null: false, default: 0
83
+ cidr :remote_addr, null: false, default: '127.0.0.1'
84
+ String :user_agent, text: true, null: false, default: 'Mozilla/5.0 (compatible; Punk!; +https://punk.kranzky.com)'
85
+ jsonb :data, default: '{}'
86
+ DateTime :created_at
87
+ DateTime :updated_at
88
+ foreign_key :identity_id, :identities, null: false, type: :uuid
89
+ end
90
+ end
91
+
92
+ PUNK.migration do
93
+ change do
94
+ create_enum(:punk_state, %w[created active deleted])
95
+ _tenants
96
+ _users
97
+ _tenants_users
98
+ _groups
99
+ _groups_users
100
+ _identities
101
+ _sessions
102
+ end
103
+ end
File without changes
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ # @model
5
+ # @property id(required) [string] a unique identifier for the group
6
+ # @property name(required) [string] the name of the group
7
+ # @property icon(required) [string] an image URL
8
+ class Group < PUNK::Model
9
+ alias to_s name
10
+
11
+ many_to_one :tenant
12
+ many_to_many :users
13
+
14
+ def validate
15
+ validates_presence :tenant
16
+ validates_presence :name
17
+ validates_url :icon, allow_blank: true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class GroupUserMetadata < PUNK::Model(:groups_users)
5
+ many_to_one :group
6
+ many_to_one :user
7
+
8
+ def validate
9
+ validates_presence :group
10
+ validates_presence :user
11
+ end
12
+
13
+ def to_s
14
+ "#{group_id}|#{user_id}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class Identity < PUNK::Model
5
+ alias to_s claim
6
+
7
+ many_to_one :user
8
+ one_to_many :sessions
9
+
10
+ symbolize :claim_type
11
+
12
+ def validate
13
+ validates_presence :claim
14
+ validates_presence :claim_type
15
+ validates_unique :claim
16
+ validates_includes [:email, :phone], :claim_type
17
+ validates_email :claim if email?
18
+ validates_phone :claim if phone?
19
+ end
20
+
21
+ def email?
22
+ claim_type == :email
23
+ end
24
+
25
+ def phone?
26
+ claim_type == :phone
27
+ end
28
+ end
29
+ end