@rhino-dev/rhino-nestjs 0.2.5

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 (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +427 -0
  3. package/dist/blueprint/blueprint-parser.d.ts +130 -0
  4. package/dist/blueprint/blueprint-parser.js +276 -0
  5. package/dist/blueprint/blueprint-parser.js.map +1 -0
  6. package/dist/blueprint/blueprint-runner.d.ts +51 -0
  7. package/dist/blueprint/blueprint-runner.js +238 -0
  8. package/dist/blueprint/blueprint-runner.js.map +1 -0
  9. package/dist/blueprint/blueprint-validator.d.ts +19 -0
  10. package/dist/blueprint/blueprint-validator.js +163 -0
  11. package/dist/blueprint/blueprint-validator.js.map +1 -0
  12. package/dist/blueprint/generators/policy-generator.d.ts +40 -0
  13. package/dist/blueprint/generators/policy-generator.js +157 -0
  14. package/dist/blueprint/generators/policy-generator.js.map +1 -0
  15. package/dist/blueprint/generators/prisma-schema-generator.d.ts +43 -0
  16. package/dist/blueprint/generators/prisma-schema-generator.js +197 -0
  17. package/dist/blueprint/generators/prisma-schema-generator.js.map +1 -0
  18. package/dist/blueprint/generators/resource-definition-generator.d.ts +52 -0
  19. package/dist/blueprint/generators/resource-definition-generator.js +229 -0
  20. package/dist/blueprint/generators/resource-definition-generator.js.map +1 -0
  21. package/dist/blueprint/generators/seeder-generator.d.ts +21 -0
  22. package/dist/blueprint/generators/seeder-generator.js +135 -0
  23. package/dist/blueprint/generators/seeder-generator.js.map +1 -0
  24. package/dist/blueprint/generators/test-generator.d.ts +28 -0
  25. package/dist/blueprint/generators/test-generator.js +275 -0
  26. package/dist/blueprint/generators/test-generator.js.map +1 -0
  27. package/dist/blueprint/manifest-manager.d.ts +58 -0
  28. package/dist/blueprint/manifest-manager.js +137 -0
  29. package/dist/blueprint/manifest-manager.js.map +1 -0
  30. package/dist/cli/commands/blueprint.command.d.ts +16 -0
  31. package/dist/cli/commands/blueprint.command.js +35 -0
  32. package/dist/cli/commands/blueprint.command.js.map +1 -0
  33. package/dist/cli/commands/export-postman.command.d.ts +5 -0
  34. package/dist/cli/commands/export-postman.command.js +39 -0
  35. package/dist/cli/commands/export-postman.command.js.map +1 -0
  36. package/dist/cli/commands/export-types.command.d.ts +4 -0
  37. package/dist/cli/commands/export-types.command.js +36 -0
  38. package/dist/cli/commands/export-types.command.js.map +1 -0
  39. package/dist/cli/commands/generate.command.d.ts +6 -0
  40. package/dist/cli/commands/generate.command.js +212 -0
  41. package/dist/cli/commands/generate.command.js.map +1 -0
  42. package/dist/cli/commands/install.command.d.ts +8 -0
  43. package/dist/cli/commands/install.command.js +205 -0
  44. package/dist/cli/commands/install.command.js.map +1 -0
  45. package/dist/cli/index.d.ts +17 -0
  46. package/dist/cli/index.js +123 -0
  47. package/dist/cli/index.js.map +1 -0
  48. package/dist/cli/utils/io.d.ts +12 -0
  49. package/dist/cli/utils/io.js +74 -0
  50. package/dist/cli/utils/io.js.map +1 -0
  51. package/dist/cli/utils/prompt.d.ts +12 -0
  52. package/dist/cli/utils/prompt.js +78 -0
  53. package/dist/cli/utils/prompt.js.map +1 -0
  54. package/dist/constants/tokens.d.ts +3 -0
  55. package/dist/constants/tokens.js +7 -0
  56. package/dist/constants/tokens.js.map +1 -0
  57. package/dist/controllers/auth.controller.d.ts +59 -0
  58. package/dist/controllers/auth.controller.js +121 -0
  59. package/dist/controllers/auth.controller.js.map +1 -0
  60. package/dist/controllers/global.controller.d.ts +44 -0
  61. package/dist/controllers/global.controller.js +357 -0
  62. package/dist/controllers/global.controller.js.map +1 -0
  63. package/dist/controllers/invitation.controller.d.ts +30 -0
  64. package/dist/controllers/invitation.controller.js +111 -0
  65. package/dist/controllers/invitation.controller.js.map +1 -0
  66. package/dist/controllers/nested.controller.d.ts +12 -0
  67. package/dist/controllers/nested.controller.js +69 -0
  68. package/dist/controllers/nested.controller.js.map +1 -0
  69. package/dist/decorators/index.d.ts +33 -0
  70. package/dist/decorators/index.js +79 -0
  71. package/dist/decorators/index.js.map +1 -0
  72. package/dist/errors/rhino-exception.d.ts +24 -0
  73. package/dist/errors/rhino-exception.js +47 -0
  74. package/dist/errors/rhino-exception.js.map +1 -0
  75. package/dist/exporters/postman-exporter.d.ts +21 -0
  76. package/dist/exporters/postman-exporter.js +396 -0
  77. package/dist/exporters/postman-exporter.js.map +1 -0
  78. package/dist/exporters/typescript-exporter.d.ts +23 -0
  79. package/dist/exporters/typescript-exporter.js +129 -0
  80. package/dist/exporters/typescript-exporter.js.map +1 -0
  81. package/dist/guards/jwt-auth.guard.d.ts +16 -0
  82. package/dist/guards/jwt-auth.guard.js +61 -0
  83. package/dist/guards/jwt-auth.guard.js.map +1 -0
  84. package/dist/guards/resource-policy.guard.d.ts +12 -0
  85. package/dist/guards/resource-policy.guard.js +111 -0
  86. package/dist/guards/resource-policy.guard.js.map +1 -0
  87. package/dist/index.d.ts +60 -0
  88. package/dist/index.js +171 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/interceptors/hidden-columns.interceptor.d.ts +18 -0
  91. package/dist/interceptors/hidden-columns.interceptor.js +66 -0
  92. package/dist/interceptors/hidden-columns.interceptor.js.map +1 -0
  93. package/dist/interceptors/response.interceptor.d.ts +19 -0
  94. package/dist/interceptors/response.interceptor.js +47 -0
  95. package/dist/interceptors/response.interceptor.js.map +1 -0
  96. package/dist/interfaces/rhino-config.interface.d.ts +103 -0
  97. package/dist/interfaces/rhino-config.interface.js +3 -0
  98. package/dist/interfaces/rhino-config.interface.js.map +1 -0
  99. package/dist/interfaces/rhino-request.interface.d.ts +12 -0
  100. package/dist/interfaces/rhino-request.interface.js +3 -0
  101. package/dist/interfaces/rhino-request.interface.js.map +1 -0
  102. package/dist/middleware/resolve-organization.middleware.d.ts +15 -0
  103. package/dist/middleware/resolve-organization.middleware.js +38 -0
  104. package/dist/middleware/resolve-organization.middleware.js.map +1 -0
  105. package/dist/middleware/route-group.middleware.d.ts +17 -0
  106. package/dist/middleware/route-group.middleware.js +60 -0
  107. package/dist/middleware/route-group.middleware.js.map +1 -0
  108. package/dist/middleware/tenant-route-rewrite.d.ts +76 -0
  109. package/dist/middleware/tenant-route-rewrite.js +124 -0
  110. package/dist/middleware/tenant-route-rewrite.js.map +1 -0
  111. package/dist/policies/resource-policy.d.ts +25 -0
  112. package/dist/policies/resource-policy.js +77 -0
  113. package/dist/policies/resource-policy.js.map +1 -0
  114. package/dist/prisma/prisma-soft-delete.extension.d.ts +16 -0
  115. package/dist/prisma/prisma-soft-delete.extension.js +57 -0
  116. package/dist/prisma/prisma-soft-delete.extension.js.map +1 -0
  117. package/dist/prisma/prisma-uuid.extension.d.ts +9 -0
  118. package/dist/prisma/prisma-uuid.extension.js +28 -0
  119. package/dist/prisma/prisma-uuid.extension.js.map +1 -0
  120. package/dist/prisma/prisma.service.d.ts +38 -0
  121. package/dist/prisma/prisma.service.js +100 -0
  122. package/dist/prisma/prisma.service.js.map +1 -0
  123. package/dist/rhino.config.d.ts +43 -0
  124. package/dist/rhino.config.js +130 -0
  125. package/dist/rhino.config.js.map +1 -0
  126. package/dist/rhino.module.d.ts +61 -0
  127. package/dist/rhino.module.js +268 -0
  128. package/dist/rhino.module.js.map +1 -0
  129. package/dist/services/audit.service.d.ts +35 -0
  130. package/dist/services/audit.service.js +92 -0
  131. package/dist/services/audit.service.js.map +1 -0
  132. package/dist/services/auth.service.d.ts +33 -0
  133. package/dist/services/auth.service.js +157 -0
  134. package/dist/services/auth.service.js.map +1 -0
  135. package/dist/services/invitation.service.d.ts +37 -0
  136. package/dist/services/invitation.service.js +143 -0
  137. package/dist/services/invitation.service.js.map +1 -0
  138. package/dist/services/nested.service.d.ts +34 -0
  139. package/dist/services/nested.service.js +163 -0
  140. package/dist/services/nested.service.js.map +1 -0
  141. package/dist/services/organization.service.d.ts +13 -0
  142. package/dist/services/organization.service.js +60 -0
  143. package/dist/services/organization.service.js.map +1 -0
  144. package/dist/services/query-builder.service.d.ts +31 -0
  145. package/dist/services/query-builder.service.js +151 -0
  146. package/dist/services/query-builder.service.js.map +1 -0
  147. package/dist/services/resource.service.d.ts +38 -0
  148. package/dist/services/resource.service.js +230 -0
  149. package/dist/services/resource.service.js.map +1 -0
  150. package/dist/services/route-registration.service.d.ts +40 -0
  151. package/dist/services/route-registration.service.js +68 -0
  152. package/dist/services/route-registration.service.js.map +1 -0
  153. package/dist/services/scope.service.d.ts +16 -0
  154. package/dist/services/scope.service.js +32 -0
  155. package/dist/services/scope.service.js.map +1 -0
  156. package/dist/services/serializer.service.d.ts +36 -0
  157. package/dist/services/serializer.service.js +90 -0
  158. package/dist/services/serializer.service.js.map +1 -0
  159. package/dist/services/validation.service.d.ts +50 -0
  160. package/dist/services/validation.service.js +203 -0
  161. package/dist/services/validation.service.js.map +1 -0
  162. package/dist/tsconfig.build.tsbuildinfo +1 -0
  163. package/dist/utils/fk-chain-walker.d.ts +30 -0
  164. package/dist/utils/fk-chain-walker.js +23 -0
  165. package/dist/utils/fk-chain-walker.js.map +1 -0
  166. package/dist/utils/format.d.ts +7 -0
  167. package/dist/utils/format.js +28 -0
  168. package/dist/utils/format.js.map +1 -0
  169. package/dist/utils/model-builder.d.ts +64 -0
  170. package/dist/utils/model-builder.js +67 -0
  171. package/dist/utils/model-builder.js.map +1 -0
  172. package/dist/utils/permission-matcher.d.ts +46 -0
  173. package/dist/utils/permission-matcher.js +120 -0
  174. package/dist/utils/permission-matcher.js.map +1 -0
  175. package/dist/utils/scope-discovery.d.ts +34 -0
  176. package/dist/utils/scope-discovery.js +102 -0
  177. package/dist/utils/scope-discovery.js.map +1 -0
  178. package/package.json +64 -0
  179. package/stubs/skills/rhino-audit.md +184 -0
  180. package/stubs/skills/rhino-bugfix.md +117 -0
  181. package/stubs/skills/rhino-deploy.md +5 -0
  182. package/stubs/skills/rhino-docs.md +147 -0
  183. package/stubs/skills/rhino-feature.md +146 -0
  184. package/stubs/skills/rhino-migrate.md +181 -0
  185. package/stubs/skills/rhino-model.md +180 -0
  186. package/stubs/skills/rhino-plan.md +5 -0
  187. package/stubs/skills/rhino-policy.md +183 -0
  188. package/stubs/skills/rhino-refactor.md +147 -0
  189. package/stubs/skills/rhino-review.md +113 -0
  190. package/stubs/skills/rhino-scope.md +140 -0
  191. package/stubs/skills/rhino-test.md +233 -0
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: rhino-model
3
+ description: Create a new Rhino model definition — Prisma schema, registration, Zod validation, and policy skeleton.
4
+ ---
5
+
6
+ You are creating a new model in an Rhino NestJS application. Follow these steps in order.
7
+
8
+ ## Step 1: Read Context
9
+
10
+ Before creating anything, read:
11
+ - `prisma/schema.prisma` — existing models and conventions.
12
+ - `src/app.module.ts` — how models are currently registered.
13
+ - `CLAUDE.md` — project conventions.
14
+
15
+ Ask the user for:
16
+ - Model name (PascalCase singular, e.g., `BlogPost`)
17
+ - Fields and their types
18
+ - Relationships to existing models
19
+ - Whether it needs multi-tenancy (`belongsToOrganization`)
20
+ - Whether it needs soft deletes, audit trail, or UUID primary key
21
+ - Which roles should have access and what they can do
22
+
23
+ ## Step 2: Prisma Schema
24
+
25
+ Add the model to `prisma/schema.prisma`. Follow existing casing conventions (camelCase fields):
26
+
27
+ ```prisma
28
+ model BlogPost {
29
+ id Int @id @default(autoincrement())
30
+ title String
31
+ content String?
32
+ status String @default("draft")
33
+ organizationId Int?
34
+ userId Int
35
+ deletedAt DateTime?
36
+ createdAt DateTime @default(now())
37
+ updatedAt DateTime @updatedAt
38
+
39
+ organization Organization? @relation(fields: [organizationId], references: [id])
40
+ user User @relation(fields: [userId], references: [id])
41
+ }
42
+ ```
43
+
44
+ Run migration:
45
+ ```bash
46
+ npx prisma migrate dev --name create_blog_posts
47
+ npx prisma generate
48
+ ```
49
+
50
+ ## Step 3: Zod Validation Schema
51
+
52
+ Create `src/schemas/blog-post.schema.ts`:
53
+
54
+ ```typescript
55
+ import { z } from 'zod';
56
+
57
+ export const BlogPostSchema = z.object({
58
+ title: z.string().min(1).max(255),
59
+ content: z.string().optional(),
60
+ status: z.enum(['draft', 'published', 'archived']).optional(),
61
+ userId: z.number().int().positive(),
62
+ });
63
+
64
+ // Update allows all fields optional (partial)
65
+ export const BlogPostUpdateSchema = BlogPostSchema.partial();
66
+
67
+ // Role-keyed schemas (optional — use when roles need different required fields)
68
+ export const BlogPostStoreByRole = {
69
+ admin: BlogPostSchema,
70
+ '*': BlogPostSchema.omit({ status: true }),
71
+ };
72
+ ```
73
+
74
+ ## Step 4: Policy
75
+
76
+ Create `src/policies/blog-post.policy.ts`:
77
+
78
+ ```typescript
79
+ import { ResourcePolicy } from '@rhino-dev/rhino-nestjs';
80
+
81
+ export class BlogPostPolicy extends ResourcePolicy {
82
+ // Control which fields each role can write
83
+ permittedAttributesForCreate(user: any): string[] {
84
+ if (this.hasRole(user, 'admin')) return ['*'];
85
+ if (this.hasRole(user, 'editor')) return ['title', 'content'];
86
+ return [];
87
+ }
88
+
89
+ permittedAttributesForUpdate(user: any): string[] {
90
+ return this.permittedAttributesForCreate(user);
91
+ }
92
+
93
+ // Control which fields each role can read in responses
94
+ permittedAttributesForShow(user: any): string[] {
95
+ return ['*'];
96
+ }
97
+
98
+ hiddenAttributesForShow(user: any): string[] {
99
+ if (this.hasRole(user, 'admin')) return [];
100
+ return ['internalNotes'];
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Step 5: Register in AppModule
106
+
107
+ Add to `RhinoModule.forRoot({ models: { ... } })`:
108
+
109
+ ```typescript
110
+ 'blog-posts': {
111
+ model: 'blogPost', // matches Prisma client key
112
+ policy: BlogPostPolicy,
113
+ validation: BlogPostSchema,
114
+ validationUpdate: BlogPostUpdateSchema,
115
+
116
+ // Query capabilities
117
+ allowedFilters: ['status', 'userId'],
118
+ allowedSorts: ['createdAt', 'title', 'updatedAt'],
119
+ defaultSort: '-createdAt',
120
+ allowedIncludes: ['user'],
121
+ allowedSearch: ['title', 'content'],
122
+
123
+ // Features
124
+ softDeletes: true,
125
+ belongsToOrganization: true,
126
+ hasAuditTrail: true,
127
+ paginationEnabled: true,
128
+ perPage: 25,
129
+ }
130
+ ```
131
+
132
+ ## Step 6: Write Tests
133
+
134
+ Create `src/services/blog-post.spec.ts` (or `test/e2e/blog-post.e2e-spec.ts`):
135
+
136
+ ```typescript
137
+ describe('BlogPost endpoints', () => {
138
+ it('returns 201 when admin creates a blog post', async () => { /* ... */ });
139
+ it('returns 403 when viewer tries to create a blog post', async () => { /* ... */ });
140
+ it('returns 422 when title is missing', async () => { /* ... */ });
141
+ it('scopes blog posts to the current organization', async () => { /* ... */ });
142
+ it('soft deletes the blog post on DELETE', async () => { /* ... */ });
143
+ });
144
+ ```
145
+
146
+ Run:
147
+ ```bash
148
+ npm test
149
+ ```
150
+
151
+ ## Step 7: Verify Route Table
152
+
153
+ Check that the model is generating the expected routes:
154
+
155
+ ```typescript
156
+ import { describeRoutes } from '@rhino-dev/rhino-nestjs';
157
+ // In a test or startup log:
158
+ console.log(describeRoutes(configService));
159
+ ```
160
+
161
+ Expected routes for `blog-posts`:
162
+ ```
163
+ GET /api/:org/blog-posts
164
+ POST /api/:org/blog-posts
165
+ GET /api/:org/blog-posts/:id
166
+ PUT /api/:org/blog-posts/:id
167
+ DELETE /api/:org/blog-posts/:id
168
+ GET /api/:org/blog-posts/trashed
169
+ POST /api/:org/blog-posts/:id/restore
170
+ DELETE /api/:org/blog-posts/:id/force-delete
171
+ ```
172
+
173
+ ## Checklist
174
+
175
+ - [ ] Prisma schema added and migrated
176
+ - [ ] Zod schema created (store + update variants)
177
+ - [ ] Policy created with `permittedAttributesForCreate/Update/Show`
178
+ - [ ] Model registered in `RhinoModule.forRoot()`
179
+ - [ ] Tests written and passing
180
+ - [ ] `CLAUDE.md` features table updated if this adds a new capability
@@ -0,0 +1,5 @@
1
+ # /rhino-plan — Plan Before Coding
2
+
3
+ Guide for planning a new Rhino NestJS feature before writing code: identify affected models,
4
+ policies, routes, and Prisma schema changes; list edge cases and multi-tenant implications;
5
+ draft the blueprint YAML; outline required tests; then get approval before implementing.
@@ -0,0 +1,183 @@
1
+ ---
2
+ name: rhino-policy
3
+ description: Create or update an Rhino ResourcePolicy for a NestJS model — action permissions, attribute permissions, and role-based logic.
4
+ ---
5
+
6
+ You are creating or updating an authorization policy for an Rhino NestJS model.
7
+
8
+ ## Step 1: Understand the Permission Model
9
+
10
+ Rhino policies check `{slug}.{action}` permissions. The base `ResourcePolicy` class handles this automatically. You only need to subclass it to:
11
+
12
+ 1. Restrict which fields users can **write** (`permittedAttributesForCreate` / `permittedAttributesForUpdate`)
13
+ 2. Restrict which fields users can **read** (`permittedAttributesForShow` / `hiddenAttributesForShow`)
14
+ 3. Add custom authorization logic beyond permission checks (e.g., "only the author can edit")
15
+
16
+ Read the following before writing any code:
17
+ - `src/policies/resource-policy.ts` — the base class
18
+ - `src/utils/permission-matcher.ts` — how `userHasPermission` and `resolveUserRoleSlug` work
19
+ - `prisma/schema.prisma` — understand the model's fields
20
+ - Existing policy files in `src/policies/`
21
+
22
+ ## Step 2: Identify the Permission Matrix
23
+
24
+ Ask the user (or read the blueprint YAML) to define:
25
+
26
+ | Role | index | show | store | update | destroy | Hidden fields | Create fields | Update fields |
27
+ |------|-------|------|-------|--------|---------|---------------|---------------|---------------|
28
+ | admin | ✓ | ✓ | ✓ | ✓ | ✓ | none | `*` | `*` |
29
+ | editor | ✓ | ✓ | ✓ | ✓ | — | none | `[title, content]` | `[title, content]` |
30
+ | viewer | ✓ | ✓ | — | — | — | `[cost]` | — | — |
31
+
32
+ ## Step 3: Create the Policy File
33
+
34
+ Create `src/policies/[model].policy.ts`:
35
+
36
+ ```typescript
37
+ import { ResourcePolicy } from '@rhino-dev/rhino-nestjs';
38
+
39
+ export class PostPolicy extends ResourcePolicy {
40
+ // ── Action Authorization ──────────────────────────────────────
41
+ // Only needed if you want custom logic BEYOND the {slug}.{action} check.
42
+ // The base class already handles standard permission checks.
43
+
44
+ // Example: restrict update to the record's owner (plus admin)
45
+ update(user: any, model: any, organization?: any): boolean {
46
+ if (!super.update(user, model, organization)) return false;
47
+ if (this.hasRole(user, 'admin', organization)) return true;
48
+ return model?.userId === user?.id;
49
+ }
50
+
51
+ // ── Field-Level Write Permissions ─────────────────────────────
52
+ permittedAttributesForCreate(user: any): string[] {
53
+ if (this.hasRole(user, 'admin')) return ['*'];
54
+ if (this.hasRole(user, 'editor')) return ['title', 'content', 'categoryId'];
55
+ return [];
56
+ }
57
+
58
+ permittedAttributesForUpdate(user: any): string[] {
59
+ if (this.hasRole(user, 'admin')) return ['*'];
60
+ if (this.hasRole(user, 'editor')) return ['title', 'content'];
61
+ return [];
62
+ }
63
+
64
+ // ── Field-Level Read Permissions ──────────────────────────────
65
+ // Return ['*'] to allow all fields (default).
66
+ // Return a specific list to whitelist.
67
+ permittedAttributesForShow(user: any): string[] {
68
+ return ['*'];
69
+ }
70
+
71
+ // Return fields to ALWAYS hide for non-admin users.
72
+ hiddenAttributesForShow(user: any): string[] {
73
+ if (this.hasRole(user, 'admin')) return [];
74
+ return ['costPrice', 'internalNotes'];
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## Step 4: Register the Policy
80
+
81
+ In `app.module.ts`, add `policy: PostPolicy` to the model registration:
82
+
83
+ ```typescript
84
+ posts: {
85
+ model: 'post',
86
+ policy: PostPolicy, // ← Add this
87
+ // ... rest of config
88
+ }
89
+ ```
90
+
91
+ ## Step 5: Seed Permissions
92
+
93
+ Permissions are stored as JSON on `users.permissions` (non-tenant) or `user_roles.permissions` (tenant).
94
+
95
+ ```typescript
96
+ // Non-tenant: assign directly on user
97
+ await prisma.user.update({
98
+ where: { id: adminUser.id },
99
+ data: { permissions: ['*'] },
100
+ });
101
+
102
+ // Tenant: assign via user_roles
103
+ await prisma.userRole.create({
104
+ data: {
105
+ userId: editorUser.id,
106
+ organizationId: org.id,
107
+ roleSlug: 'editor',
108
+ permissions: ['posts.index', 'posts.show', 'posts.store', 'posts.update'],
109
+ },
110
+ });
111
+ ```
112
+
113
+ ## Step 6: Write Policy Tests
114
+
115
+ Create a unit test file `src/policies/post.policy.spec.ts`:
116
+
117
+ ```typescript
118
+ describe('PostPolicy', () => {
119
+ let policy: PostPolicy;
120
+
121
+ beforeEach(() => {
122
+ policy = new PostPolicy();
123
+ policy.resourceSlug = 'posts';
124
+ });
125
+
126
+ describe('permittedAttributesForCreate', () => {
127
+ it('returns * for admin', () => {
128
+ const user = makeUserWithRole('admin', org.id);
129
+ expect(policy.permittedAttributesForCreate(user)).toEqual(['*']);
130
+ });
131
+
132
+ it('returns restricted fields for editor', () => {
133
+ const user = makeUserWithRole('editor', org.id);
134
+ expect(policy.permittedAttributesForCreate(user)).toEqual(
135
+ expect.arrayContaining(['title', 'content'])
136
+ );
137
+ expect(policy.permittedAttributesForCreate(user)).not.toContain('status');
138
+ });
139
+
140
+ it('returns empty array for viewer', () => {
141
+ const user = makeUserWithRole('viewer', org.id);
142
+ expect(policy.permittedAttributesForCreate(user)).toEqual([]);
143
+ });
144
+ });
145
+
146
+ describe('hiddenAttributesForShow', () => {
147
+ it('hides costPrice for non-admin', () => {
148
+ const user = makeUserWithRole('viewer', org.id);
149
+ expect(policy.hiddenAttributesForShow(user)).toContain('costPrice');
150
+ });
151
+
152
+ it('reveals all fields for admin', () => {
153
+ const user = makeUserWithRole('admin', org.id);
154
+ expect(policy.hiddenAttributesForShow(user)).toEqual([]);
155
+ });
156
+ });
157
+ });
158
+ ```
159
+
160
+ Run:
161
+ ```bash
162
+ npm test -- --testPathPattern=post.policy
163
+ ```
164
+
165
+ ## Common Mistakes
166
+
167
+ - Do not forget to call `super.update(...)` etc. in overridden action methods — skipping it bypasses the permission check entirely.
168
+ - `hasRole(user, 'admin')` checks the role in the current organization context stored on the user object. If organization context is not set, it returns false.
169
+ - Return `['*']` (not `[]`) from `permittedAttributesForShow` when all fields should be visible — `[]` means nothing is visible.
170
+ - The `resourceSlug` on the policy must match the key in `RhinoModule.forRoot({ models: { [slug]: ... } })`.
171
+
172
+ ## Permission Reference
173
+
174
+ | Action | Permission checked | Policy method |
175
+ |--------|-------------------|---------------|
176
+ | GET (list) | `{slug}.index` | `viewAny` |
177
+ | GET (single) | `{slug}.show` | `view` |
178
+ | POST | `{slug}.store` | `create` |
179
+ | PUT/PATCH | `{slug}.update` | `update` |
180
+ | DELETE | `{slug}.destroy` | `delete` |
181
+ | GET trashed | `{slug}.trashed` | `viewTrashed` |
182
+ | POST restore | `{slug}.restore` | `restore` |
183
+ | DELETE force | `{slug}.forceDelete` | `forceDelete` |
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: rhino-refactor
3
+ description: Safely refactor Rhino NestJS code — establish baseline, apply incremental changes, verify tests pass at each step.
4
+ ---
5
+
6
+ You are refactoring code in an Rhino NestJS project. The goal is to improve structure without changing observable behavior.
7
+
8
+ ## Step 1: Establish Baseline
9
+
10
+ Before touching any code:
11
+
12
+ ```bash
13
+ npm test
14
+ ```
15
+
16
+ Note the exact test count and which tests pass. This is your baseline. Every test that currently passes must still pass after the refactor.
17
+
18
+ Read the code to be refactored and identify:
19
+ - What is the current structure?
20
+ - What specific problem does the refactor solve? (readability, performance, duplication, etc.)
21
+ - What is the target structure?
22
+
23
+ ## Step 2: Identify the Scope
24
+
25
+ Clarify with the user what is in scope:
26
+ - Which files are affected?
27
+ - Are public interfaces (API config shape, service method signatures) changing?
28
+ - Does this touch any of these critical paths?
29
+ - `resource.service.ts` — any change here affects all CRUD operations
30
+ - `resource-policy.guard.ts` — any change here affects authorization
31
+ - `validation.service.ts` — any change here affects input validation
32
+ - `query-builder.service.ts` — any change here affects all queries
33
+
34
+ If public interfaces are changing, plan a migration path before starting.
35
+
36
+ ## Step 3: Refactor Incrementally
37
+
38
+ Work in small steps. After each step, run the tests:
39
+
40
+ ```bash
41
+ npm test
42
+ ```
43
+
44
+ Common refactor patterns in Rhino NestJS:
45
+
46
+ ### Extract a service method
47
+
48
+ If controller code is doing logic that belongs in a service:
49
+
50
+ ```typescript
51
+ // Before: logic in controller
52
+ @Post(':modelSlug')
53
+ async store(...) {
54
+ const orgFilter = ...; // logic here
55
+ const record = await ...;
56
+ }
57
+
58
+ // After: logic in ResourceService
59
+ async create(modelSlug: string, data: any, ctx: ResourceContext) {
60
+ // moved logic
61
+ }
62
+ ```
63
+
64
+ ### Consolidate repeated Zod schemas
65
+
66
+ ```typescript
67
+ // Before: duplicated schema fragments
68
+ const StoreSchema = z.object({ title: z.string(), status: z.enum([...]) });
69
+ const UpdateSchema = z.object({ title: z.string().optional(), status: z.enum([...]).optional() });
70
+
71
+ // After: derived from base
72
+ const BaseSchema = z.object({ title: z.string(), status: z.enum(['draft', 'published']) });
73
+ const StoreSchema = BaseSchema;
74
+ const UpdateSchema = BaseSchema.partial();
75
+ ```
76
+
77
+ ### Extract repeated policy logic
78
+
79
+ ```typescript
80
+ // Before: repeated hasRole checks
81
+ permittedAttributesForCreate(user: any) {
82
+ if (this.hasRole(user, 'admin') || this.hasRole(user, 'owner')) return ['*'];
83
+ // ...
84
+ }
85
+ permittedAttributesForUpdate(user: any) {
86
+ if (this.hasRole(user, 'admin') || this.hasRole(user, 'owner')) return ['*'];
87
+ // ...
88
+ }
89
+
90
+ // After: extracted helper
91
+ private isPrivileged(user: any) {
92
+ return this.hasRole(user, 'admin') || this.hasRole(user, 'owner');
93
+ }
94
+ ```
95
+
96
+ ### Consolidate model registration config
97
+
98
+ ```typescript
99
+ // Before: scattered constants
100
+ const POST_FILTERS = ['status', 'userId'];
101
+ const POST_SORTS = ['createdAt', 'title'];
102
+
103
+ // After: co-located config object
104
+ export const PostModelConfig: ModelRegistration = {
105
+ model: 'post',
106
+ allowedFilters: ['status', 'userId'],
107
+ allowedSorts: ['createdAt', 'title'],
108
+ // ...
109
+ };
110
+ ```
111
+
112
+ ## Step 4: Update Types
113
+
114
+ If method signatures changed, update:
115
+ - All call sites
116
+ - TypeScript interfaces in `src/interfaces/`
117
+ - Type exports in `src/index.ts`
118
+
119
+ Run:
120
+ ```bash
121
+ npx tsc --noEmit
122
+ ```
123
+
124
+ Fix all type errors before running tests.
125
+
126
+ ## Step 5: Verify
127
+
128
+ ```bash
129
+ npm test # Full suite — must match baseline exactly
130
+ npx tsc --noEmit # No type errors
131
+ ```
132
+
133
+ If any test fails that was passing before, fix the code (not the test). Only modify tests if the test was genuinely wrong.
134
+
135
+ ## Step 6: Document Changes
136
+
137
+ If the refactor changed any public API:
138
+ - Update `CLAUDE.md`
139
+ - Update `README.md` if the config shape changed
140
+ - Update the relevant Docusaurus doc page
141
+
142
+ ## Rules
143
+
144
+ - Never comment out a passing test to make a refactor work.
145
+ - Never change behavior while refactoring — separate refactor commits from behavior changes.
146
+ - If you discover a bug during refactoring, note it but do not fix it in the same change. Fix it separately with a test first.
147
+ - Keep commits small. One logical change per commit.
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: rhino-review
3
+ description: Review Rhino NestJS code for correctness, security, and alignment with conventions. Produces a structured findings report.
4
+ ---
5
+
6
+ You are reviewing code in an Rhino NestJS project. Produce a structured report with findings grouped by severity.
7
+
8
+ ## Step 1: Gather Context
9
+
10
+ Read the following before reviewing:
11
+ - `CLAUDE.md` — development rules and conventions
12
+ - `src/app.module.ts` — model registrations
13
+ - `prisma/schema.prisma` — data model
14
+ - The specific file(s) the user asked you to review
15
+
16
+ ## Step 2: Run the Tests First
17
+
18
+ ```bash
19
+ npm test
20
+ ```
21
+
22
+ Note any failing tests. Do not proceed with fixes until you have the test baseline.
23
+
24
+ ## Step 3: Review Checklist
25
+
26
+ Work through each category and note findings. Severity: **Critical** (security / data loss), **Major** (bug / missing behavior), **Minor** (convention / style).
27
+
28
+ ### Security
29
+
30
+ - [ ] No `organizationId` accepted from request body — `ValidationService` must strip it.
31
+ - [ ] Permissions checked before data access — `ResourcePolicyGuard` applied.
32
+ - [ ] Cross-tenant isolation — `belongsToOrganization: true` set on models that need it.
33
+ - [ ] No raw SQL queries that could be injected.
34
+ - [ ] JWT secret comes from env (`process.env.JWT_SECRET`), not hardcoded.
35
+ - [ ] Sensitive fields (`password`, `rememberToken`) in `auditExclude` and in `additionalHiddenColumns`.
36
+
37
+ ### Model Registration
38
+
39
+ - [ ] Every field that should be filterable is in `allowedFilters`.
40
+ - [ ] Every field that should be sortable is in `allowedSorts`.
41
+ - [ ] `defaultSort` is set (avoid undefined ordering).
42
+ - [ ] `allowedIncludes` lists only relationships that exist on the Prisma model.
43
+ - [ ] `allowedSearch` fields are string columns (not IDs or JSON).
44
+ - [ ] `softDeletes: true` is set if the Prisma model has a `deletedAt` field.
45
+ - [ ] `hasAuditTrail: true` if audit logging is required.
46
+ - [ ] `paginationEnabled` explicitly set if the model returns large datasets.
47
+
48
+ ### Policy
49
+
50
+ - [ ] `permittedAttributesForCreate` and `permittedAttributesForUpdate` implemented.
51
+ - [ ] `permittedAttributesForShow` and `hiddenAttributesForShow` implemented.
52
+ - [ ] Every role in the permission matrix is covered (no missing `else` branches).
53
+ - [ ] `hasRole(user, role)` is used correctly — it checks role within org context.
54
+ - [ ] Overridden action methods (`update`, `delete`, etc.) call `super.method()`.
55
+ - [ ] `resourceSlug` is set correctly (must match the model key in `RhinoModule.forRoot`).
56
+
57
+ ### Zod Validation
58
+
59
+ - [ ] `validationStore` schema marks required fields as required.
60
+ - [ ] `validationUpdate` schema makes all fields optional (`.partial()` or equivalent).
61
+ - [ ] Enum values in the Zod schema match the valid values in Prisma schema / docs.
62
+ - [ ] FK fields validated as positive integers (not strings).
63
+ - [ ] No `organizationId` / `organization_id` in the Zod schema.
64
+ - [ ] Role-keyed schemas (`Record<string, ZodSchema>`) have a `'*'` fallback.
65
+
66
+ ### Tests
67
+
68
+ - [ ] Happy path (200/201) tests exist for each CRUD action.
69
+ - [ ] 403 tests for each role that should be denied.
70
+ - [ ] 422 test for missing required field on store.
71
+ - [ ] 404 test for non-existent ID.
72
+ - [ ] Multi-tenant isolation test (org A data not visible to org B).
73
+ - [ ] Soft delete tests (trashed, restore, force-delete) if `softDeletes: true`.
74
+ - [ ] Audit log created for creates, updates, and deletes if `hasAuditTrail: true`.
75
+
76
+ ### Code Style
77
+
78
+ - [ ] No `class-validator` decorators — Zod only.
79
+ - [ ] No per-model controllers — all CRUD through `GlobalController`.
80
+ - [ ] Business logic in services, not controllers.
81
+ - [ ] No direct `prisma.client.[model]` calls in controllers — use `ResourceService`.
82
+ - [ ] `@Injectable()` decorator on all services.
83
+ - [ ] All method parameters and return types explicitly typed.
84
+
85
+ ## Step 4: Produce Report
86
+
87
+ Format your findings as:
88
+
89
+ ```
90
+ ## Code Review Report — [filename or feature]
91
+
92
+ ### Critical
93
+ 1. [Finding]: [file:line] — [explanation and fix]
94
+
95
+ ### Major
96
+ 1. [Finding]: [file:line] — [explanation and fix]
97
+
98
+ ### Minor
99
+ 1. [Finding]: [file:line] — [explanation and fix]
100
+
101
+ ### Passed Checks
102
+ - Security: organizationId stripped ✓
103
+ - Tests: multi-tenant isolation ✓
104
+ - ...
105
+ ```
106
+
107
+ ## Step 5: Apply Fixes (if requested)
108
+
109
+ If the user asks you to fix the findings:
110
+ 1. Fix Critical issues first.
111
+ 2. Write regression tests for any bugs found.
112
+ 3. Run `npm test` after each fix to confirm nothing breaks.
113
+ 4. Summarize what was changed.