dscf-core 0.2.8 → 0.2.9

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/dscf/core/auditable_controller.rb +17 -17
  3. data/app/controllers/concerns/dscf/core/authenticatable.rb +3 -8
  4. data/app/controllers/concerns/dscf/core/authorizable.rb +66 -0
  5. data/app/controllers/concerns/dscf/core/common.rb +49 -19
  6. data/app/controllers/concerns/dscf/core/file_uploadable.rb +261 -0
  7. data/app/controllers/concerns/dscf/core/reviewable_controller.rb +31 -1
  8. data/app/controllers/dscf/core/addresses_controller.rb +0 -5
  9. data/app/controllers/dscf/core/application_controller.rb +3 -5
  10. data/app/controllers/dscf/core/auth_controller.rb +16 -11
  11. data/app/controllers/dscf/core/businesses_controller.rb +0 -5
  12. data/app/controllers/dscf/core/files_controller.rb +38 -0
  13. data/app/controllers/dscf/core/permissions_controller.rb +20 -0
  14. data/app/controllers/dscf/core/role_permissions_controller.rb +54 -0
  15. data/app/controllers/dscf/core/roles_controller.rb +30 -0
  16. data/app/controllers/dscf/core/user_roles_controller.rb +56 -0
  17. data/app/errors/dscf/core/file_upload_error.rb +9 -0
  18. data/app/jobs/dscf/core/audit_logger_job.rb +1 -3
  19. data/app/models/concerns/dscf/core/attachable.rb +403 -0
  20. data/app/models/dscf/core/file_attachment.rb +205 -0
  21. data/app/models/dscf/core/permission.rb +27 -0
  22. data/app/models/dscf/core/role.rb +8 -3
  23. data/app/models/dscf/core/role_permission.rb +18 -0
  24. data/app/models/dscf/core/user.rb +56 -0
  25. data/app/models/dscf/core/user_role.rb +23 -3
  26. data/app/policies/dscf/core/application_policy.rb +62 -0
  27. data/app/policies/dscf/core/business_policy.rb +29 -0
  28. data/app/policies/dscf/core/business_type_policy.rb +6 -0
  29. data/app/policies/dscf/core/permission_policy.rb +6 -0
  30. data/app/policies/dscf/core/role_policy.rb +25 -0
  31. data/app/serializers/dscf/core/attachment_serializer.rb +30 -0
  32. data/app/serializers/dscf/core/permission_serializer.rb +7 -0
  33. data/app/serializers/dscf/core/role_light_serializer.rb +7 -0
  34. data/app/serializers/dscf/core/role_permission_serializer.rb +9 -0
  35. data/app/serializers/dscf/core/role_serializer.rb +2 -2
  36. data/app/serializers/dscf/core/user_auth_serializer.rb +6 -2
  37. data/app/serializers/dscf/core/user_role_serializer.rb +2 -3
  38. data/app/services/dscf/core/file_storage/client.rb +210 -0
  39. data/app/services/dscf/core/file_storage/uploader.rb +127 -0
  40. data/app/services/dscf/core/file_storage.rb +44 -0
  41. data/app/services/dscf/core/token_service.rb +2 -1
  42. data/config/locales/en.yml +35 -2
  43. data/config/routes.rb +15 -0
  44. data/db/migrate/20250821185708_create_dscf_core_roles.rb +3 -1
  45. data/db/migrate/20250822054547_create_dscf_core_user_roles.rb +3 -1
  46. data/db/migrate/20260128000000_create_dscf_core_file_attachments.rb +48 -0
  47. data/db/migrate/20260304000001_create_dscf_core_permissions.rb +19 -0
  48. data/db/migrate/20260304000002_create_dscf_core_role_permissions.rb +11 -0
  49. data/lib/dscf/core/permission_registry.rb +58 -0
  50. data/lib/dscf/core/version.rb +1 -1
  51. data/lib/dscf/core.rb +12 -1
  52. data/spec/factories/dscf/core/permissions.rb +14 -0
  53. data/spec/factories/dscf/core/reviews.rb +0 -1
  54. data/spec/factories/dscf/core/role_permissions.rb +6 -0
  55. data/spec/factories/dscf/core/roles.rb +42 -2
  56. data/spec/factories/dscf/core/user_roles.rb +4 -2
  57. metadata +33 -3
@@ -4,22 +4,26 @@ module Dscf
4
4
  skip_before_action :authenticate_user, only: %i[login signup refresh]
5
5
  skip_before_action :validate_token_expiry, only: %i[login signup refresh]
6
6
  skip_before_action :validate_device_consistency, only: %i[login signup refresh]
7
+ skip_before_action :authorize_action!, only: %i[login signup refresh]
7
8
 
8
9
  def login
10
+ skip_authorization
9
11
  user = AuthService.authenticate_user(params[:email_or_phone], params[:password])
10
12
 
11
13
  if user&.valid_for_authentication?
12
14
  tokens = sign_in(user, request)
15
+ user_with_includes = User.includes(roles: :permissions, user_profile: {}).find(user.id)
13
16
  render_success(
14
17
  "auth.success.login",
15
18
  data: {
16
- user: user,
19
+ user: user_with_includes,
17
20
  access_token: tokens[:access_token],
18
21
  refresh_token: tokens[:refresh_token].refresh_token
19
22
  },
20
23
  serializer_options: {
21
24
  user: {
22
- serializer: Dscf::Core::UserAuthSerializer
25
+ serializer: Dscf::Core::UserAuthSerializer,
26
+ include: [:user_profile, roles: [:permissions]]
23
27
  }
24
28
  }
25
29
  )
@@ -29,6 +33,7 @@ module Dscf
29
33
  end
30
34
 
31
35
  def signup
36
+ skip_authorization
32
37
  user = User.new(user_params)
33
38
 
34
39
  return render_error("auth.errors.missing_email_or_phone") unless user.email.present? || user.phone.present?
@@ -36,15 +41,17 @@ module Dscf
36
41
  ActiveRecord::Base.transaction do
37
42
  if user.save
38
43
  assign_default_role(user)
44
+ user_with_includes = User.includes(roles: :permissions, user_profile: {}).find(user.id)
39
45
  render_success(
40
46
  "auth.success.signup",
41
47
  data: {
42
- user: user
48
+ user: user_with_includes
43
49
  },
44
50
  status: :created,
45
51
  serializer_options: {
46
52
  user: {
47
- serializer: Dscf::Core::UserAuthSerializer
53
+ serializer: Dscf::Core::UserAuthSerializer,
54
+ include: [:user_profile, roles: [:permissions]]
48
55
  }
49
56
  }
50
57
  )
@@ -64,20 +71,23 @@ module Dscf
64
71
  end
65
72
 
66
73
  def me
74
+ user = User.includes(roles: :permissions, user_profile: {}).find(current_user.id)
67
75
  render_success(
68
76
  "auth.success.me",
69
77
  data: {
70
- user: current_user
78
+ user: user
71
79
  },
72
80
  serializer_options: {
73
81
  user: {
74
- serializer: Dscf::Core::UserAuthSerializer
82
+ serializer: Dscf::Core::UserAuthSerializer,
83
+ include: [:user_profile, roles: [:permissions]]
75
84
  }
76
85
  }
77
86
  )
78
87
  end
79
88
 
80
89
  def refresh
90
+ skip_authorization
81
91
  new_tokens = refresh_token
82
92
  if new_tokens
83
93
  render_success(
@@ -115,11 +125,6 @@ module Dscf
115
125
 
116
126
  UserRole.create!(user: user, role: role)
117
127
  end
118
-
119
- def authentication_required?
120
- # Skip authentication for login, signup, and refresh endpoints
121
- %w[login signup refresh].exclude?(action_name)
122
- end
123
128
  end
124
129
  end
125
130
  end
@@ -40,11 +40,6 @@ module Dscf
40
40
 
41
41
  private
42
42
 
43
- # Enable authentication for this controller
44
- def authentication_required?
45
- true
46
- end
47
-
48
43
  def model_params
49
44
  params.require(:business).permit(
50
45
  :name,
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dscf
4
+ module Core
5
+ # Controller for serving file attachments from MinIO storage
6
+ # Provides download endpoints for files uploaded via the Attachable concern
7
+ #
8
+ # @example Download a file
9
+ # GET /dscf/core/files/:file_key
10
+ # GET /dscf/core/files/:file_key?download=true # Force download
11
+ #
12
+ class FilesController < ApplicationController
13
+ include Dscf::Core::FileUploadable
14
+
15
+ # Skip authentication for file downloads (files are accessed by file_key which acts as a token)
16
+ # Override this in your app if you need authenticated file access
17
+ skip_before_action :authenticate_request!, only: [:show], raise: false
18
+
19
+ # GET /dscf/core/files/:file_key
20
+ # Serves a file from MinIO storage
21
+ def show
22
+ attachment = FileAttachment.find_by(file_key: params[:file_key])
23
+
24
+ unless attachment
25
+ render json: {success: false, error: "File not found"}, status: :not_found
26
+ return
27
+ end
28
+
29
+ disposition = params[:download].present? ? "attachment" : "inline"
30
+
31
+ send_attachment(
32
+ attachment,
33
+ disposition: disposition
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ module Dscf
2
+ module Core
3
+ class PermissionsController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ private
7
+
8
+ def allowed_order_columns
9
+ %w[id code resource action engine active created_at updated_at]
10
+ end
11
+
12
+ def default_serializer_includes
13
+ {
14
+ index: [],
15
+ show: []
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,54 @@
1
+ module Dscf
2
+ module Core
3
+ class RolePermissionsController < ApplicationController
4
+ before_action :set_role
5
+ before_action :require_permissions_manage!
6
+ before_action :set_role_permission, only: [:destroy]
7
+
8
+ def index
9
+ @clazz = Dscf::Core::RolePermission
10
+ render_success(data: @role.permissions)
11
+ end
12
+
13
+ def create
14
+ @clazz = Dscf::Core::RolePermission
15
+ role_permission = Dscf::Core::RolePermission.new(
16
+ role: @role,
17
+ permission_id: params[:permission_id]
18
+ )
19
+
20
+ if role_permission.save
21
+ render_success(data: role_permission, status: :created)
22
+ else
23
+ render_error(errors: role_permission.errors.full_messages[0],
24
+ status: :unprocessable_entity)
25
+ end
26
+ end
27
+
28
+ def destroy
29
+ @clazz = Dscf::Core::RolePermission
30
+ @role_permission.destroy
31
+ render_success
32
+ end
33
+
34
+ private
35
+
36
+ def set_role
37
+ @role = Dscf::Core::Role.find(params[:role_id])
38
+ end
39
+
40
+ def set_role_permission
41
+ @role_permission = Dscf::Core::RolePermission.find_by!(
42
+ role: @role,
43
+ permission_id: params[:id]
44
+ )
45
+ end
46
+
47
+ def require_permissions_manage!
48
+ return if current_user.super_admin? || current_user.has_permission?("permissions.manage")
49
+
50
+ render_error("errors.unauthorized", status: :forbidden)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,30 @@
1
+ module Dscf
2
+ module Core
3
+ class RolesController < ApplicationController
4
+ include Dscf::Core::Common
5
+
6
+ private
7
+
8
+ def model_params
9
+ params.require(:role).permit(:code, :name, :description, :active)
10
+ end
11
+
12
+ def eager_loaded_associations
13
+ [:permissions]
14
+ end
15
+
16
+ def allowed_order_columns
17
+ %w[id code name active created_at updated_at]
18
+ end
19
+
20
+ def default_serializer_includes
21
+ {
22
+ index: [:permissions],
23
+ show: [:permissions],
24
+ create: [:permissions],
25
+ update: [:permissions]
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ module Dscf
2
+ module Core
3
+ class UserRolesController < ApplicationController
4
+ before_action :set_target_user
5
+ before_action :require_roles_assign!
6
+ before_action :set_user_role, only: [:destroy]
7
+
8
+ def index
9
+ @clazz = Dscf::Core::UserRole
10
+ user_roles = @target_user.user_roles.includes(:role)
11
+ render_success(data: user_roles)
12
+ end
13
+
14
+ def create
15
+ @clazz = Dscf::Core::UserRole
16
+ user_role = Dscf::Core::UserRole.new(
17
+ user: @target_user,
18
+ role_id: params[:role_id],
19
+ assigned_by: current_user
20
+ )
21
+
22
+ if user_role.save
23
+ render_success(data: user_role, status: :created)
24
+ else
25
+ render_error(errors: user_role.errors.full_messages[0],
26
+ status: :unprocessable_entity)
27
+ end
28
+ end
29
+
30
+ def destroy
31
+ @clazz = Dscf::Core::UserRole
32
+ @user_role.destroy
33
+ render_success
34
+ end
35
+
36
+ private
37
+
38
+ def set_target_user
39
+ @target_user = Dscf::Core::User.find(params[:user_id])
40
+ end
41
+
42
+ def set_user_role
43
+ @user_role = Dscf::Core::UserRole.find_by!(
44
+ user: @target_user,
45
+ role_id: params[:id]
46
+ )
47
+ end
48
+
49
+ def require_roles_assign!
50
+ return if current_user.super_admin? || current_user.has_permission?("roles.assign")
51
+
52
+ render_error("errors.unauthorized", status: :forbidden)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dscf
4
+ module Core
5
+ # Raised when file upload fails in strict mode
6
+ # This triggers a rollback of the entire transaction
7
+ class FileUploadError < StandardError; end
8
+ end
9
+ end
@@ -226,10 +226,8 @@ module Dscf
226
226
  action: "updated",
227
227
  changes: record_changes
228
228
  }
229
- end
230
229
 
231
- # Detect deleted records (in before but not in after)
232
- before_map.each do |id, before_rec|
230
+ # Detect deleted records (in before but not in after)
233
231
  next if after_map[id]
234
232
 
235
233
  # Convert attributes to change format (old: value, new: nil)