@venturialstd/organization 0.0.1 โ†’ 0.0.2

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.
package/README.md CHANGED
@@ -1,46 +1,46 @@
1
1
  # @venturialstd/organization
2
2
 
3
- Organization Management Module for Venturial - A NestJS module for managing organizations and organization members with role-based access control.
3
+ A comprehensive NestJS module for managing organizations with multi-tenant capabilities, role-based access control, and isolated per-organization settings.
4
4
 
5
5
  ## ๐Ÿ“‹ Table of Contents
6
6
 
7
7
  - [Features](#features)
8
8
  - [Installation](#installation)
9
- - [Module Structure](#module-structure)
10
9
  - [Quick Start](#quick-start)
11
- - [Configuration](#configuration)
12
- - [Entities](#entities)
10
+ - [Core Concepts](#core-concepts)
11
+ - [Architecture](#architecture)
13
12
  - [API Reference](#api-reference)
14
- - [Enums & Constants](#enums--constants)
15
- - [Guards & Decorators](#guards--decorators)
13
+ - [Organization Settings System](#organization-settings-system)
16
14
  - [Test Server](#test-server)
17
- - [NPM Scripts](#npm-scripts)
18
- - [Usage Examples](#usage-examples)
15
+ - [Migration Guide](#migration-guide)
16
+ - [Examples](#examples)
17
+ - [Best Practices](#best-practices)
19
18
 
20
19
  ---
21
20
 
22
21
  ## โœจ Features
23
22
 
24
- - ๐Ÿข **Organization Management**: Complete CRUD operations
25
- - ๐Ÿ‘ฅ **Member Management**: Add, invite, remove organization members
26
- - ๐Ÿ” **Role-Based Access**: Owner, Admin, Member, Viewer roles
27
- - โš™๏ธ **Settings System**: Configurable organization settings
23
+ - ๐Ÿข **Organization Management**: Complete CRUD operations for organizations
24
+ - ๐Ÿ‘ฅ **Member Management**: Add, invite, and manage organization members
25
+ - ๐Ÿ” **Role-Based Access Control**: Owner, Admin, Member, and Viewer roles
26
+ - โš™๏ธ **Isolated Settings System**: Per-organization configuration for modules
27
+ - ๐Ÿ”’ **Complete Isolation**: No fallback between organizations
28
28
  - ๐Ÿ›ก๏ธ **Guards & Decorators**: Request-level access control
29
29
  - ๐Ÿ”„ **TypeORM Integration**: Full database support with migrations
30
30
  - ๐Ÿ“ฆ **Fully Typed**: Complete TypeScript support
31
- - ๐Ÿงช **Test Infrastructure**: Standalone test server on port 3002
31
+ - ๐Ÿงช **Test Infrastructure**: Standalone test server
32
32
 
33
33
  ---
34
34
 
35
35
  ## ๐Ÿ“ฆ Installation
36
36
 
37
- ```bash
37
+ \`\`\`bash
38
38
  npm install @venturialstd/organization
39
- ```
39
+ \`\`\`
40
40
 
41
41
  ### Peer Dependencies
42
42
 
43
- ```json
43
+ \`\`\`json
44
44
  {
45
45
  "@nestjs/common": "^11.0.11",
46
46
  "@nestjs/core": "^11.0.5",
@@ -50,47 +50,7 @@ npm install @venturialstd/organization
50
50
  "class-validator": "^0.14.1",
51
51
  "typeorm": "^0.3.20"
52
52
  }
53
- ```
54
-
55
- ---
56
-
57
- ## ๐Ÿ“ Module Structure
58
-
59
- ```
60
- src/organization/
61
- โ”œโ”€โ”€ package.json # Module configuration
62
- โ”œโ”€โ”€ tsconfig.json # TypeScript configuration
63
- โ”œโ”€โ”€ README.md # This file
64
- โ”œโ”€โ”€ .npmignore # NPM publish exclusions
65
- โ”œโ”€โ”€ src/ # Source code
66
- โ”‚ โ”œโ”€โ”€ constants/
67
- โ”‚ โ”‚ โ”œโ”€โ”€ organization.constant.ts # Enums (roles, status)
68
- โ”‚ โ”‚ โ””โ”€โ”€ organization.settings.constant.ts # Settings keys
69
- โ”‚ โ”œโ”€โ”€ decorators/
70
- โ”‚ โ”‚ โ”œโ”€โ”€ organization.decorator.ts # @OrganizationId(), @UserId(), etc.
71
- โ”‚ โ”‚ โ””โ”€โ”€ roles.decorator.ts # @Roles() decorator
72
- โ”‚ โ”œโ”€โ”€ entities/
73
- โ”‚ โ”‚ โ”œโ”€โ”€ organization.entity.ts # Organization entity
74
- โ”‚ โ”‚ โ””โ”€โ”€ organization-user.entity.ts # OrganizationUser entity
75
- โ”‚ โ”œโ”€โ”€ guards/
76
- โ”‚ โ”‚ โ””โ”€โ”€ organization.guard.ts # OrganizationGuard, OrganizationRoleGuard
77
- โ”‚ โ”œโ”€โ”€ services/
78
- โ”‚ โ”‚ โ”œโ”€โ”€ organization.service.ts # Organization CRUD service
79
- โ”‚ โ”‚ โ””โ”€โ”€ organization-user.service.ts # User-Organization service
80
- โ”‚ โ”œโ”€โ”€ settings/
81
- โ”‚ โ”‚ โ””โ”€โ”€ organization.settings.ts # Module settings definition
82
- โ”‚ โ”œโ”€โ”€ organization.module.ts # NestJS module
83
- โ”‚ โ””โ”€โ”€ index.ts # Public exports
84
- โ””โ”€โ”€ test/ # Test infrastructure
85
- โ”œโ”€โ”€ controllers/
86
- โ”‚ โ”œโ”€โ”€ organization-test.controller.ts
87
- โ”‚ โ””โ”€โ”€ organization-user-test.controller.ts
88
- โ”œโ”€โ”€ migrations/ # Database migrations
89
- โ”œโ”€โ”€ .env.example # Environment template
90
- โ”œโ”€โ”€ data-source.ts # TypeORM DataSource
91
- โ”œโ”€โ”€ test-app.module.ts # Test app module
92
- โ””โ”€โ”€ main.ts # Test server bootstrap
93
- ```
53
+ \`\`\`
94
54
 
95
55
  ---
96
56
 
@@ -98,7 +58,7 @@ src/organization/
98
58
 
99
59
  ### 1. Import the Module
100
60
 
101
- ```typescript
61
+ \`\`\`typescript
102
62
  import { Module } from '@nestjs/common';
103
63
  import { OrganizationModule } from '@venturialstd/organization';
104
64
 
@@ -106,13 +66,25 @@ import { OrganizationModule } from '@venturialstd/organization';
106
66
  imports: [OrganizationModule],
107
67
  })
108
68
  export class AppModule {}
109
- ```
69
+ \`\`\`
110
70
 
111
- ### 2. Use the Services
71
+ ### 2. Run Migrations
72
+
73
+ \`\`\`bash
74
+ # Create and run migrations
75
+ npm run migration:generate -- CreateOrganizations
76
+ npm run migration:run
77
+ \`\`\`
112
78
 
113
- ```typescript
79
+ ### 3. Use the Services
80
+
81
+ \`\`\`typescript
114
82
  import { Injectable } from '@nestjs/common';
115
- import { OrganizationService, OrganizationUserService } from '@venturialstd/organization';
83
+ import {
84
+ OrganizationService,
85
+ OrganizationUserService,
86
+ ORGANIZATION_USER_ROLE
87
+ } from '@venturialstd/organization';
116
88
 
117
89
  @Injectable()
118
90
  export class YourService {
@@ -121,558 +93,818 @@ export class YourService {
121
93
  private readonly organizationUserService: OrganizationUserService,
122
94
  ) {}
123
95
 
124
- async createOrganization() {
125
- const organization = await this.organizationService.createOrganization(
96
+ async createOrganization(userId: string) {
97
+ return this.organizationService.createOrganization(
126
98
  'My Organization',
127
99
  'my-org',
128
100
  'example.com',
129
- 'Organization description',
130
- 'user-id-123'
101
+ 'Description',
102
+ userId
131
103
  );
132
-
133
- return organization;
134
104
  }
135
105
 
136
- async addMember() {
106
+ async addMember(organizationId: string, userId: string) {
137
107
  return this.organizationUserService.addUserToOrganization(
138
- 'org-id',
139
- 'user-id',
108
+ organizationId,
109
+ userId,
140
110
  ORGANIZATION_USER_ROLE.MEMBER
141
111
  );
142
112
  }
143
113
  }
144
- ```
114
+ \`\`\`
145
115
 
146
116
  ---
147
117
 
148
- ## โš™๏ธ Configuration
118
+ ## ๐Ÿ’ก Core Concepts
149
119
 
150
- ### Development Setup
120
+ ### Organizations
151
121
 
152
- ```bash
153
- # Navigate to module directory
154
- cd src/organization
122
+ An organization represents a tenant in your multi-tenant application. Each organization:
123
+ - Has a unique name and slug
124
+ - Can have a custom domain
125
+ - Has its own settings and configuration
126
+ - Contains multiple members with different roles
127
+ - Is completely isolated from other organizations
155
128
 
156
- # Install dependencies
157
- npm install --legacy-peer-deps
129
+ ### Organization Members
158
130
 
159
- # Set up environment
160
- cp test/.env.example test/.env
161
- # Edit test/.env with your database credentials
131
+ Users can belong to multiple organizations with different roles:
132
+ - **Owner**: Full control, can delete organization
133
+ - **Admin**: Can manage members and settings
134
+ - **Member**: Standard access to organization resources
135
+ - **Viewer**: Read-only access
162
136
 
163
- # Generate migration
164
- npm run migration:generate --name=InitialSchema
137
+ ### Organization Settings
165
138
 
166
- # Run migrations
167
- npm run migration:run
139
+ Each organization can have isolated settings for any module in your application:
140
+ - **Complete Isolation**: No sharing between organizations
141
+ - **No Fallback**: Each organization must configure its own settings
142
+ - **Type-Safe**: Fully typed with TypeScript
143
+ - **Module-Agnostic**: Works with any module (GitLab, Jira, Slack, etc.)
168
144
 
169
- # Start test server
170
- npm run test:dev
171
- ```
172
-
173
- ### Environment Variables (test/.env)
145
+ ---
174
146
 
175
- ```bash
176
- APP_PORT=3002
177
- DB_HOST=localhost
178
- DB_PORT=5433
179
- DB_NAME=organization_test
180
- DB_USER=root
181
- DB_PASS=example
182
- ```
147
+ ## ๐Ÿ—๏ธ Architecture
148
+
149
+ ### Module Structure
150
+
151
+ \`\`\`
152
+ OrganizationModule
153
+ โ”œโ”€โ”€ Entities
154
+ โ”‚ โ”œโ”€โ”€ Organization # Organization entity
155
+ โ”‚ โ”œโ”€โ”€ OrganizationUser # User-Organization relationship
156
+ โ”‚ โ””โ”€โ”€ OrganizationSettings # Per-organization settings
157
+ โ”œโ”€โ”€ Services
158
+ โ”‚ โ”œโ”€โ”€ OrganizationService # CRUD operations
159
+ โ”‚ โ”œโ”€โ”€ OrganizationUserService # Member management
160
+ โ”‚ โ””โ”€โ”€ OrganizationSettingsService # Settings management
161
+ โ”œโ”€โ”€ Guards
162
+ โ”‚ โ”œโ”€โ”€ OrganizationGuard # Check user belongs to org
163
+ โ”‚ โ””โ”€โ”€ OrganizationRoleGuard # Check user role
164
+ โ””โ”€โ”€ Decorators
165
+ โ”œโ”€โ”€ @OrganizationId() # Extract org ID from request
166
+ โ”œโ”€โ”€ @UserId() # Extract user ID from request
167
+ โ”œโ”€โ”€ @Roles() # Define required roles
168
+ โ””โ”€โ”€ @OrganizationContext() # Extract full context
169
+ \`\`\`
170
+
171
+ ### Settings Isolation
172
+
173
+ \`\`\`
174
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
175
+ โ”‚ Application โ”‚
176
+ โ”‚ โ”‚
177
+ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
178
+ โ”‚ โ”‚ Organization A โ”‚ โ”‚
179
+ โ”‚ โ”‚ โ€ข GitLab: gitlab-a.com + token-a โ”‚ โ”‚
180
+ โ”‚ โ”‚ โ€ข Jira: jira-a.com + credentials-a โ”‚ โ”‚
181
+ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
182
+ โ”‚ โ”‚
183
+ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
184
+ โ”‚ โ”‚ Organization B โ”‚ โ”‚
185
+ โ”‚ โ”‚ โ€ข GitLab: gitlab.com + token-b โ”‚ โ”‚
186
+ โ”‚ โ”‚ โ€ข Jira: [Not configured] โ”‚ โ”‚
187
+ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
188
+ โ”‚ โ”‚
189
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
190
+ \`\`\`
183
191
 
184
192
  ---
185
193
 
186
- ## ๐Ÿ—„๏ธ Entities
187
-
188
- ### Organization
189
-
190
- ```typescript
191
- @Entity('organization')
192
- export class Organization {
193
- id: string; // UUID primary key
194
- name: string; // Unique organization name
195
- slug: string; // Unique URL-friendly slug
196
- domain?: string; // Optional custom domain
197
- description?: string; // Organization description
198
- isActive: boolean; // Active/inactive status
199
- settings: Record<string, unknown>; // JSONB settings
200
- ownerId?: string; // Owner user ID
201
- createdAt: Date; // Creation timestamp
202
- updatedAt: Date; // Update timestamp
203
- }
204
- ```
205
-
206
- **Key Features:**
207
- - Unique `name` and `slug` constraints
208
- - JSONB `settings` for flexible configuration
209
- - Soft activation with `isActive` flag
210
- - Owner tracking via `ownerId`
211
-
212
- ### OrganizationUser
213
-
214
- ```typescript
215
- @Entity('organization_user')
216
- export class OrganizationUser {
217
- id: string; // UUID primary key
218
- organizationId: string; // Organization reference (indexed)
219
- userId: string; // User reference (indexed)
220
- role: ORGANIZATION_USER_ROLE; // User role in organization
221
- status: ORGANIZATION_USER_STATUS; // Member status
222
- isPrimary: boolean; // Primary owner flag
223
- invitedBy?: string; // Inviter user ID
224
- invitedAt?: Date; // Invitation timestamp
225
- joinedAt?: Date; // Join timestamp
226
- permissions: Record<string, unknown>; // JSONB custom permissions
227
- createdAt: Date; // Creation timestamp
228
- updatedAt: Date; // Update timestamp
229
- }
230
- ```
194
+ ## ๐Ÿ“š API Reference
231
195
 
232
- **Key Features:**
233
- - Unique index on `(organizationId, userId)`
234
- - Separate indexes on `organizationId` and `userId`
235
- - Invitation system support
236
- - Primary owner designation
237
- - Custom permissions via JSONB
196
+ ### Services
238
197
 
239
- ---
198
+ #### OrganizationService
240
199
 
241
- ## ๐Ÿ“– API Reference
242
-
243
- ### OrganizationService
244
-
245
- #### Create Organization
246
- ```typescript
247
- createOrganization(
248
- name: string,
249
- slug: string,
250
- domain?: string,
251
- description?: string,
252
- ownerId?: string
253
- ): Promise<Organization>
254
- ```
255
-
256
- #### Find Methods
257
- ```typescript
258
- getOrganizationBySlug(slug: string): Promise<Organization>
259
- getOrganizationByDomain(domain: string): Promise<Organization>
260
- getActiveOrganizations(): Promise<Organization[]>
261
- getOrganizationsByOwner(ownerId: string): Promise<Organization[]>
262
- ```
263
-
264
- #### Update Methods
265
- ```typescript
266
- updateOrganizationSettings(
267
- organizationId: string,
268
- settings: Record<string, unknown>
269
- ): Promise<Organization>
270
-
271
- setOrganizationStatus(
272
- organizationId: string,
273
- isActive: boolean
274
- ): Promise<Organization>
275
- ```
276
-
277
- ### OrganizationUserService
278
-
279
- #### Member Management
280
- ```typescript
281
- addUserToOrganization(
282
- organizationId: string,
283
- userId: string,
284
- role?: ORGANIZATION_USER_ROLE,
285
- invitedBy?: string
286
- ): Promise<OrganizationUser>
287
-
288
- inviteUserToOrganization(
289
- organizationId: string,
290
- userId: string,
291
- role: ORGANIZATION_USER_ROLE,
292
- invitedBy: string
293
- ): Promise<OrganizationUser>
294
-
295
- acceptInvitation(
296
- organizationId: string,
297
- userId: string
298
- ): Promise<OrganizationUser>
299
-
300
- removeUserFromOrganization(
301
- organizationId: string,
302
- userId: string
303
- ): Promise<void>
304
- ```
305
-
306
- #### Role Management
307
- ```typescript
308
- updateUserRole(
309
- organizationId: string,
310
- userId: string,
311
- newRole: ORGANIZATION_USER_ROLE
312
- ): Promise<OrganizationUser>
313
-
314
- setPrimaryOwner(
315
- organizationId: string,
316
- userId: string
317
- ): Promise<OrganizationUser>
318
-
319
- transferOwnership(
320
- organizationId: string,
321
- fromUserId: string,
322
- toUserId: string
323
- ): Promise<void>
324
- ```
325
-
326
- #### Query Methods
327
- ```typescript
328
- getUserOrganizations(userId: string): Promise<OrganizationUser[]>
329
- getOrganizationUsers(organizationId: string): Promise<OrganizationUser[]>
330
- getActiveOrganizationUsers(organizationId: string): Promise<OrganizationUser[]>
331
- getUserInvitations(userId: string): Promise<OrganizationUser[]>
332
- hasAccess(organizationId: string, userId: string): Promise<boolean>
333
- getUserRole(organizationId: string, userId: string): Promise<ORGANIZATION_USER_ROLE | null>
334
- ```
335
-
336
- #### Status Management
337
- ```typescript
338
- suspendUser(organizationId: string, userId: string): Promise<OrganizationUser>
339
- reactivateUser(organizationId: string, userId: string): Promise<OrganizationUser>
340
- ```
200
+ \`\`\`typescript
201
+ class OrganizationService {
202
+ // Create a new organization
203
+ createOrganization(
204
+ name: string,
205
+ slug: string,
206
+ domain: string,
207
+ description: string,
208
+ ownerId: string
209
+ ): Promise<Organization>
341
210
 
342
- ---
211
+ // Get organization by ID
212
+ getOrganizationById(id: string): Promise<Organization>
343
213
 
344
- ## ๐ŸŽญ Enums & Constants
214
+ // Update organization
215
+ updateOrganization(id: string, data: Partial<Organization>): Promise<Organization>
345
216
 
346
- ### ORGANIZATION_USER_ROLE
217
+ // Delete organization
218
+ deleteOrganization(id: string): Promise<void>
347
219
 
348
- ```typescript
349
- enum ORGANIZATION_USER_ROLE {
350
- OWNER = 'owner', // Full control over organization
351
- ADMIN = 'admin', // Administrative access
352
- MEMBER = 'member', // Standard member access
353
- VIEWER = 'viewer' // Read-only access
220
+ // Get user's organizations
221
+ getUserOrganizations(userId: string): Promise<Organization[]>
354
222
  }
355
- ```
356
-
357
- ### ORGANIZATION_USER_STATUS
358
-
359
- ```typescript
360
- enum ORGANIZATION_USER_STATUS {
361
- ACTIVE = 'active', // Active member
362
- INVITED = 'invited', // Pending invitation
363
- SUSPENDED = 'suspended' // Suspended member
223
+ \`\`\`
224
+
225
+ #### OrganizationUserService
226
+
227
+ \`\`\`typescript
228
+ class OrganizationUserService {
229
+ // Add user to organization
230
+ addUserToOrganization(
231
+ organizationId: string,
232
+ userId: string,
233
+ role: ORGANIZATION_USER_ROLE
234
+ ): Promise<OrganizationUser>
235
+
236
+ // Remove user from organization
237
+ removeUserFromOrganization(
238
+ organizationId: string,
239
+ userId: string
240
+ ): Promise<void>
241
+
242
+ // Update user role
243
+ updateUserRole(
244
+ organizationId: string,
245
+ userId: string,
246
+ role: ORGANIZATION_USER_ROLE
247
+ ): Promise<OrganizationUser>
248
+
249
+ // Get organization members
250
+ getOrganizationMembers(organizationId: string): Promise<OrganizationUser[]>
251
+
252
+ // Check user role
253
+ getUserRole(
254
+ organizationId: string,
255
+ userId: string
256
+ ): Promise<ORGANIZATION_USER_ROLE>
364
257
  }
365
- ```
366
-
367
- ### Settings Keys
368
-
369
- ```typescript
370
- ORGANIZATION_SETTING_KEYS = {
371
- GENERAL_MAX_ORGANIZATIONS: 'organization.general.max_organizations',
372
- GENERAL_MAX_MEMBERS: 'organization.general.max_members',
373
- FEATURES_CUSTOM_DOMAIN: 'organization.features.custom_domain',
374
- FEATURES_API_ACCESS: 'organization.features.api_access',
375
- FEATURES_SSO: 'organization.features.sso',
376
- LIMITS_STORAGE: 'organization.limits.storage_gb',
377
- LIMITS_BANDWIDTH: 'organization.limits.bandwidth_gb',
378
- LIMITS_PROJECTS: 'organization.limits.projects',
258
+ \`\`\`
259
+
260
+ #### OrganizationSettingsService
261
+
262
+ \`\`\`typescript
263
+ class OrganizationSettingsService {
264
+ // Get a single setting
265
+ get(organizationId: string, key: string): Promise<string | undefined>
266
+
267
+ // Get multiple settings
268
+ getManySettings(
269
+ organizationId: string,
270
+ keys: string[]
271
+ ): Promise<Record<string, string>>
272
+
273
+ // Get all settings for organization
274
+ getAllForOrganization(
275
+ organizationId: string,
276
+ module?: string
277
+ ): Promise<OrganizationSettings[]>
278
+
279
+ // Set or update a setting
280
+ setOrganizationSetting(
281
+ organizationId: string,
282
+ dto: Partial<OrganizationSettings>
283
+ ): Promise<OrganizationSettings>
284
+
285
+ // Delete a setting
286
+ deleteOrganizationSetting(
287
+ organizationId: string,
288
+ module: string,
289
+ section: string,
290
+ key: string
291
+ ): Promise<void>
292
+
293
+ // Reset all settings for a module
294
+ resetModuleSettings(
295
+ organizationId: string,
296
+ module: string
297
+ ): Promise<void>
298
+
299
+ // Bulk update settings
300
+ bulkUpdateSettings(
301
+ organizationId: string,
302
+ settings: Partial<OrganizationSettings>[]
303
+ ): Promise<OrganizationSettings[]>
379
304
  }
380
- ```
381
-
382
- ---
383
-
384
- ## ๐Ÿ›ก๏ธ Guards & Decorators
305
+ \`\`\`
306
+
307
+ ### Entities
308
+
309
+ #### Organization
310
+
311
+ \`\`\`typescript
312
+ class Organization {
313
+ id: string;
314
+ name: string;
315
+ slug: string;
316
+ domain: string;
317
+ description: string;
318
+ isActive: boolean;
319
+ settings: Record<string, unknown>;
320
+ ownerId: string;
321
+ createdAt: Date;
322
+ updatedAt: Date;
323
+ }
324
+ \`\`\`
325
+
326
+ #### OrganizationUser
327
+
328
+ \`\`\`typescript
329
+ class OrganizationUser {
330
+ id: string;
331
+ organizationId: string;
332
+ userId: string;
333
+ role: ORGANIZATION_USER_ROLE;
334
+ isActive: boolean;
335
+ createdAt: Date;
336
+ updatedAt: Date;
337
+ }
338
+ \`\`\`
339
+
340
+ #### OrganizationSettings
341
+
342
+ \`\`\`typescript
343
+ class OrganizationSettings {
344
+ id: string;
345
+ organizationId: string;
346
+ module: string; // e.g., 'GITLAB', 'JIRA'
347
+ section: string; // e.g., 'CREDENTIALS', 'OAUTH'
348
+ key: string; // e.g., 'TOKEN', 'API_KEY'
349
+ value: string;
350
+ type: SETTINGS_TYPE;
351
+ label: string;
352
+ description: string;
353
+ options: object | null;
354
+ sortOrder: number;
355
+ createdAt: Date;
356
+ updatedAt: Date;
357
+ }
358
+ \`\`\`
385
359
 
386
360
  ### Decorators
387
361
 
388
- #### @OrganizationId()
389
- Extract organization ID from request:
390
- ```typescript
362
+ \`\`\`typescript
363
+ // Extract organization ID from request
391
364
  @Get()
392
- async getData(@OrganizationId() organizationId: string) {
393
- return this.service.getData(organizationId);
394
- }
395
- ```
365
+ getData(@OrganizationId() organizationId: string) { }
396
366
 
397
- #### @UserId()
398
- Extract user ID from request:
399
- ```typescript
367
+ // Extract user ID from request
400
368
  @Get()
401
- async getData(@UserId() userId: string) {
402
- return this.service.getUserData(userId);
403
- }
404
- ```
369
+ getData(@UserId() userId: string) { }
405
370
 
406
- #### @OrganizationContext()
407
- Extract full context:
408
- ```typescript
371
+ // Extract full context
409
372
  @Get()
410
- async getData(@OrganizationContext() context) {
411
- // context = { organizationId, userId, role, user }
412
- return this.service.getData(context);
413
- }
414
- ```
373
+ getData(@OrganizationContext() context: OrganizationContextType) { }
415
374
 
416
- #### @Roles()
417
- Specify required roles:
418
- ```typescript
419
- @Roles(ORGANIZATION_USER_ROLE.OWNER, ORGANIZATION_USER_ROLE.ADMIN)
420
- @UseGuards(OrganizationGuard, OrganizationRoleGuard)
421
- @Delete(':id')
422
- async delete() { ... }
423
- ```
375
+ // Define required roles
376
+ @Roles(ORGANIZATION_USER_ROLE.ADMIN, ORGANIZATION_USER_ROLE.OWNER)
377
+ @UseGuards(OrganizationRoleGuard)
378
+ adminEndpoint() { }
379
+ \`\`\`
424
380
 
425
381
  ### Guards
426
382
 
427
- #### OrganizationGuard
428
- Ensures user has access to the organization:
429
- ```typescript
383
+ \`\`\`typescript
384
+ // Check user belongs to organization
430
385
  @UseGuards(OrganizationGuard)
431
- @Get()
432
- async getData(@OrganizationId() organizationId: string) {
433
- return this.service.getData(organizationId);
386
+ @Get('organizations/:organizationId/data')
387
+ getData() { }
388
+
389
+ // Check user has required role
390
+ @Roles(ORGANIZATION_USER_ROLE.ADMIN)
391
+ @UseGuards(OrganizationRoleGuard)
392
+ adminEndpoint() { }
393
+ \`\`\`
394
+
395
+ ### Constants
396
+
397
+ \`\`\`typescript
398
+ // User roles
399
+ enum ORGANIZATION_USER_ROLE {
400
+ OWNER = 'owner',
401
+ ADMIN = 'admin',
402
+ MEMBER = 'member',
403
+ VIEWER = 'viewer',
434
404
  }
435
- ```
436
405
 
437
- #### OrganizationRoleGuard
438
- Enforces role-based access control:
439
- ```typescript
440
- @Roles(ORGANIZATION_USER_ROLE.OWNER, ORGANIZATION_USER_ROLE.ADMIN)
441
- @UseGuards(OrganizationGuard, OrganizationRoleGuard)
442
- @Delete(':id')
443
- async delete() { ... }
444
- ```
406
+ // Settings types
407
+ enum SETTINGS_TYPE {
408
+ TEXT = 'text',
409
+ TEXTAREA = 'textarea',
410
+ NUMBER = 'number',
411
+ BOOLEAN = 'boolean',
412
+ SELECT = 'select',
413
+ MULTISELECT = 'multiselect',
414
+ JSON = 'json',
415
+ DATE = 'date',
416
+ SECRET = 'secret',
417
+ }
418
+ \`\`\`
445
419
 
446
420
  ---
447
421
 
448
- ## ๐Ÿงช Test Server
422
+ ## โš™๏ธ Organization Settings System
423
+
424
+ ### Complete Isolation
425
+
426
+ Organization settings are **completely isolated** with no fallback mechanism:
427
+
428
+ \`\`\`typescript
429
+ // Organization A
430
+ const tokenA = await orgSettings.get('org-a-id', 'GITLAB:CREDENTIALS:TOKEN');
431
+ // Returns: 'token-a'
432
+
433
+ // Organization B (not configured)
434
+ const tokenB = await orgSettings.get('org-b-id', 'GITLAB:CREDENTIALS:TOKEN');
435
+ // Returns: undefined (NO FALLBACK)
436
+ \`\`\`
437
+
438
+ ### Settings Types
439
+
440
+ Use \`SETTINGS_TYPE\` enum for type-safe settings:
441
+
442
+ \`\`\`typescript
443
+ import { SETTINGS_TYPE } from '@venturialstd/organization';
444
+
445
+ const settings = [
446
+ {
447
+ module: 'GITLAB',
448
+ section: 'CREDENTIALS',
449
+ key: 'TOKEN',
450
+ type: SETTINGS_TYPE.SECRET, // Hides value in UI
451
+ // ...
452
+ },
453
+ {
454
+ module: 'GITLAB',
455
+ section: 'PREFERENCES',
456
+ key: 'AUTO_MERGE',
457
+ type: SETTINGS_TYPE.BOOLEAN, // true/false toggle
458
+ // ...
459
+ },
460
+ ];
461
+ \`\`\`
462
+
463
+ ### Using Settings in Your Module
464
+
465
+ #### 1. Define Settings for Your Module
466
+
467
+ \`\`\`typescript
468
+ // src/your-module/settings/your-module.settings.ts
469
+ import { SETTINGS_TYPE } from '@venturialstd/organization';
470
+
471
+ export const SETTINGS = [
472
+ {
473
+ scope: 'ORGANIZATION',
474
+ module: 'YOUR_MODULE',
475
+ section: 'CREDENTIALS',
476
+ key: 'API_KEY',
477
+ label: 'API Key',
478
+ description: 'Your module API key',
479
+ type: SETTINGS_TYPE.SECRET,
480
+ value: '',
481
+ options: {
482
+ placeholder: 'Enter your API key',
483
+ help: 'Get your API key from your module dashboard',
484
+ },
485
+ sortOrder: 10,
486
+ },
487
+ {
488
+ scope: 'ORGANIZATION',
489
+ module: 'YOUR_MODULE',
490
+ section: 'CREDENTIALS',
491
+ key: 'API_SECRET',
492
+ label: 'API Secret',
493
+ description: 'Your module API secret',
494
+ type: SETTINGS_TYPE.SECRET,
495
+ value: '',
496
+ options: null,
497
+ sortOrder: 11,
498
+ },
499
+ ];
500
+ \`\`\`
501
+
502
+ #### 2. Use Settings in Your Service
503
+
504
+ \`\`\`typescript
505
+ import { Injectable } from '@nestjs/common';
506
+ import { OrganizationSettingsService } from '@venturialstd/organization';
449
507
 
450
- Start the test server on port **3002**:
451
-
452
- ```bash
453
- npm run test:dev
454
- ```
455
-
456
- ### Available Endpoints
457
-
458
- #### Organization Management
459
- ```
460
- POST /organizations - Create organization
461
- GET /organizations - Get all organizations
462
- GET /organizations/active - Get active organizations
463
- GET /organizations/owner/:ownerId - Get by owner
464
- GET /organizations/slug/:slug - Get by slug
465
- GET /organizations/domain/:domain - Get by domain
466
- GET /organizations/:id - Get by ID
467
- PUT /organizations/:id/settings - Update settings
468
- PUT /organizations/:id/status - Update status
469
- DELETE /organizations/:id - Delete organization
470
- ```
471
-
472
- #### User-Organization Management
473
- ```
474
- POST /organization-users/add - Add user
475
- POST /organization-users/invite - Invite user
476
- POST /organization-users/accept-invitation - Accept invite
477
- GET /organization-users/organization/:organizationId - Get members
478
- GET /organization-users/organization/:organizationId/active - Get active members
479
- GET /organization-users/user/:userId - Get user's organizations
480
- GET /organization-users/user/:userId/invitations - Get invitations
481
- GET /organization-users/check-access/:orgId/:userId - Check access
482
- GET /organization-users/role/:orgId/:userId - Get user role
483
- PUT /organization-users/role - Update role
484
- PUT /organization-users/primary-owner - Set primary owner
485
- POST /organization-users/transfer-ownership - Transfer ownership
486
- PUT /organization-users/suspend - Suspend user
487
- PUT /organization-users/reactivate - Reactivate user
488
- DELETE /organization-users/remove - Remove user
489
- ```
508
+ @Injectable()
509
+ export class YourModuleService {
510
+ constructor(
511
+ private readonly orgSettings: OrganizationSettingsService
512
+ ) {}
490
513
 
491
- ---
514
+ async connect(organizationId: string) {
515
+ // Get organization-specific credentials
516
+ const apiKey = await this.orgSettings.get(
517
+ organizationId,
518
+ 'YOUR_MODULE:CREDENTIALS:API_KEY'
519
+ );
492
520
 
493
- ## ๐Ÿ“ NPM Scripts
521
+ const apiSecret = await this.orgSettings.get(
522
+ organizationId,
523
+ 'YOUR_MODULE:CREDENTIALS:API_SECRET'
524
+ );
525
+
526
+ // Check if configured
527
+ if (!apiKey || !apiSecret) {
528
+ throw new Error('Module not configured for this organization');
529
+ }
530
+
531
+ // Use organization-specific credentials
532
+ return this.client.connect({ apiKey, apiSecret });
533
+ }
494
534
 
495
- ```bash
496
- # Build
497
- npm run build # Build TypeScript to dist/
498
- npm run prepublishOnly # Auto-build before publish
535
+ async getMultipleSettings(organizationId: string) {
536
+ // Get multiple settings at once
537
+ const settings = await this.orgSettings.getManySettings(
538
+ organizationId,
539
+ [
540
+ 'YOUR_MODULE:CREDENTIALS:API_KEY',
541
+ 'YOUR_MODULE:CREDENTIALS:API_SECRET',
542
+ 'YOUR_MODULE:PREFERENCES:TIMEOUT',
543
+ ]
544
+ );
545
+
546
+ return settings;
547
+ }
548
+ }
549
+ \`\`\`
499
550
 
500
- # Publishing
501
- npm run release:patch # Build, bump version, publish
551
+ #### 3. Import OrganizationModule
502
552
 
503
- # Development
504
- npm run test:dev # Start test server
505
- npm run test:watch # Watch mode for development
553
+ \`\`\`typescript
554
+ import { Module } from '@nestjs/common';
555
+ import { OrganizationModule } from '@venturialstd/organization';
506
556
 
507
- # Migrations
508
- npm run migration:generate --name=Name # Generate migration
509
- npm run migration:run # Run pending migrations
510
- npm run migration:revert # Revert last migration
511
- ```
557
+ @Module({
558
+ imports: [OrganizationModule],
559
+ providers: [YourModuleService],
560
+ })
561
+ export class YourModule {}
562
+ \`\`\`
563
+
564
+ ### Database Schema
565
+
566
+ \`\`\`sql
567
+ CREATE TABLE organization_settings (
568
+ id UUID PRIMARY KEY,
569
+ organizationId UUID REFERENCES organization(id) ON DELETE CASCADE,
570
+ module VARCHAR NOT NULL, -- 'GITLAB', 'JIRA', 'YOUR_MODULE'
571
+ section VARCHAR NOT NULL, -- 'CREDENTIALS', 'OAUTH', 'PREFERENCES'
572
+ key VARCHAR NOT NULL, -- 'TOKEN', 'API_KEY', 'HOST'
573
+ value VARCHAR NOT NULL, -- Actual value
574
+ type settings_type_enum NOT NULL,
575
+ label VARCHAR NOT NULL,
576
+ description VARCHAR,
577
+ options JSONB,
578
+ sortOrder INTEGER DEFAULT 0,
579
+ createdAt TIMESTAMPTZ NOT NULL DEFAULT NOW(),
580
+ updatedAt TIMESTAMPTZ NOT NULL DEFAULT NOW(),
581
+ UNIQUE(organizationId, module, section, key)
582
+ );
583
+ \`\`\`
512
584
 
513
585
  ---
514
586
 
515
- ## ๐Ÿ’ก Usage Examples
587
+ ## ๐Ÿงช Test Server
516
588
 
517
- ### Basic Organization Creation
589
+ The module includes a standalone test server for development and testing.
518
590
 
519
- ```typescript
520
- import { OrganizationService } from '@venturialstd/organization';
591
+ ### Start the Test Server
521
592
 
522
- @Injectable()
523
- export class MyService {
524
- constructor(private orgService: OrganizationService) {}
525
-
526
- async createOrg() {
527
- return this.orgService.createOrganization(
528
- 'Acme Corp',
529
- 'acme-corp',
530
- 'acme.com',
531
- 'A leading company',
532
- 'owner-user-id-123'
533
- );
534
- }
593
+ \`\`\`bash
594
+ cd src/organization
595
+ npm install
596
+ npm run start:dev
597
+ \`\`\`
598
+
599
+ The server runs on **http://localhost:3002**
600
+
601
+ ### Test Endpoints
602
+
603
+ \`\`\`bash
604
+ # Get all organizations
605
+ GET http://localhost:3002/test/organizations
606
+
607
+ # Get organization by ID
608
+ GET http://localhost:3002/test/organizations/:id
609
+
610
+ # Get organization settings
611
+ GET http://localhost:3002/test/organizations/:id/settings
612
+
613
+ # Get settings for specific module
614
+ GET http://localhost:3002/test/organizations/:id/settings?module=GITLAB
615
+
616
+ # Create/update setting
617
+ PUT http://localhost:3002/test/organizations/:id/settings
618
+ Content-Type: application/json
619
+
620
+ {
621
+ "module": "GITLAB",
622
+ "section": "CREDENTIALS",
623
+ "key": "TOKEN",
624
+ "value": "your-token",
625
+ "label": "GitLab Token",
626
+ "type": "secret"
535
627
  }
536
- ```
628
+ \`\`\`
537
629
 
538
- ### Adding Members with Roles
630
+ ### Test Controller
539
631
 
540
- ```typescript
541
- import {
542
- OrganizationUserService,
543
- ORGANIZATION_USER_ROLE
544
- } from '@venturialstd/organization';
632
+ The test controller is for **reference and development only**. In production, implement your own controllers with:
633
+ - Custom authentication
634
+ - Custom authorization
635
+ - Custom validation
636
+ - Custom error handling
637
+
638
+ **Location:** \`test/controllers/organization-settings-test.controller.ts\`
639
+
640
+ ---
641
+
642
+ ## ๐Ÿ“– Migration Guide
643
+
644
+ ### Running Migrations
645
+
646
+ \`\`\`bash
647
+ # Generate migration
648
+ npm run migration:generate -- YourMigrationName
649
+
650
+ # Run migrations
651
+ npm run migration:run
652
+
653
+ # Revert migration
654
+ npm run migration:revert
655
+ \`\`\`
656
+
657
+ ### Required Migrations
658
+
659
+ The module requires these migrations:
660
+ 1. Create organizations table
661
+ 2. Create organization_users table
662
+ 3. Create organization_settings table
663
+
664
+ Migrations are located in: \`database/migrations/\`
545
665
 
666
+ ---
667
+
668
+ ## ๐Ÿ’ป Examples
669
+
670
+ ### Example 1: Create Organization with Owner
671
+
672
+ \`\`\`typescript
546
673
  @Injectable()
547
- export class MemberService {
548
- constructor(private orgUserService: OrganizationUserService) {}
674
+ export class OnboardingService {
675
+ constructor(
676
+ private readonly orgService: OrganizationService,
677
+ private readonly orgUserService: OrganizationUserService,
678
+ ) {}
549
679
 
550
- async addAdmin(orgId: string, userId: string) {
551
- return this.orgUserService.addUserToOrganization(
552
- orgId,
553
- userId,
554
- ORGANIZATION_USER_ROLE.ADMIN,
555
- 'inviter-user-id'
680
+ async onboardUser(userId: string, companyName: string) {
681
+ // Create organization
682
+ const org = await this.orgService.createOrganization(
683
+ companyName,
684
+ this.generateSlug(companyName),
685
+ \`\${this.generateSlug(companyName)}.example.com\`,
686
+ \`\${companyName} organization\`,
687
+ userId
556
688
  );
689
+
690
+ // Owner is automatically added by createOrganization
691
+ return org;
692
+ }
693
+
694
+ private generateSlug(name: string): string {
695
+ return name.toLowerCase().replace(/\\s+/g, '-');
557
696
  }
558
697
  }
559
- ```
698
+ \`\`\`
560
699
 
561
- ### Protected Endpoints
700
+ ### Example 2: Check User Permission
562
701
 
563
- ```typescript
564
- import {
565
- Controller,
566
- Get,
567
- UseGuards
568
- } from '@nestjs/common';
569
- import {
570
- OrganizationGuard,
571
- OrganizationRoleGuard,
572
- OrganizationId,
573
- Roles,
574
- ORGANIZATION_USER_ROLE
575
- } from '@venturialstd/organization';
702
+ \`\`\`typescript
703
+ @Controller('organizations/:organizationId/data')
704
+ export class DataController {
705
+ constructor(
706
+ private readonly orgUserService: OrganizationUserService,
707
+ ) {}
576
708
 
577
- @Controller('projects')
578
- @UseGuards(OrganizationGuard)
579
- export class ProjectController {
580
-
581
- // Any member can view
582
709
  @Get()
583
- async list(@OrganizationId() orgId: string) {
584
- return this.service.getProjects(orgId);
585
- }
710
+ @UseGuards(OrganizationGuard)
711
+ async getData(
712
+ @OrganizationId() organizationId: string,
713
+ @UserId() userId: string,
714
+ ) {
715
+ const role = await this.orgUserService.getUserRole(
716
+ organizationId,
717
+ userId
718
+ );
586
719
 
587
- // Only owners and admins can delete
588
- @Delete(':id')
589
- @UseGuards(OrganizationRoleGuard)
590
- @Roles(ORGANIZATION_USER_ROLE.OWNER, ORGANIZATION_USER_ROLE.ADMIN)
591
- async delete(@OrganizationId() orgId: string, @Param('id') id: string) {
592
- return this.service.deleteProject(orgId, id);
720
+ // Check if user has required role
721
+ if (![ORGANIZATION_USER_ROLE.ADMIN, ORGANIZATION_USER_ROLE.OWNER].includes(role)) {
722
+ throw new ForbiddenException('Insufficient permissions');
723
+ }
724
+
725
+ // Return data
593
726
  }
594
727
  }
595
- ```
728
+ \`\`\`
596
729
 
597
- ### Invitation Flow
730
+ ### Example 3: Module with Organization Settings
598
731
 
599
- ```typescript
732
+ \`\`\`typescript
733
+ // GitLab Service
600
734
  @Injectable()
601
- export class InvitationService {
602
- constructor(private orgUserService: OrganizationUserService) {}
603
-
604
- // Step 1: Send invitation
605
- async inviteMember(orgId: string, email: string, inviterId: string) {
606
- const userId = await this.userService.findByEmail(email);
607
-
608
- return this.orgUserService.inviteUserToOrganization(
609
- orgId,
610
- userId,
611
- ORGANIZATION_USER_ROLE.MEMBER,
612
- inviterId
613
- );
614
- }
735
+ export class GitLabService {
736
+ constructor(
737
+ private readonly orgSettings: OrganizationSettingsService
738
+ ) {}
739
+
740
+ async getClient(organizationId: string) {
741
+ // Get org-specific GitLab credentials
742
+ const [host, token] = await Promise.all([
743
+ this.orgSettings.get(organizationId, 'GITLAB:CREDENTIALS:HOST'),
744
+ this.orgSettings.get(organizationId, 'GITLAB:CREDENTIALS:TOKEN'),
745
+ ]);
746
+
747
+ // Validate configuration
748
+ if (!host || !token) {
749
+ throw new Error(
750
+ 'GitLab not configured for this organization. ' +
751
+ 'Please configure it in organization settings.'
752
+ );
753
+ }
615
754
 
616
- // Step 2: Get user's pending invitations
617
- async getPendingInvites(userId: string) {
618
- return this.orgUserService.getUserInvitations(userId);
755
+ // Return organization-specific client
756
+ return new GitLab({ host, token });
619
757
  }
620
758
 
621
- // Step 3: Accept invitation
622
- async acceptInvite(orgId: string, userId: string) {
623
- return this.orgUserService.acceptInvitation(orgId, userId);
759
+ async getRepositories(organizationId: string) {
760
+ const client = await this.getClient(organizationId);
761
+ return client.Projects.all();
624
762
  }
625
763
  }
626
- ```
764
+ \`\`\`
627
765
 
628
- ### Ownership Transfer
766
+ ### Example 4: Bulk Configure Organization
629
767
 
630
- ```typescript
768
+ \`\`\`typescript
631
769
  @Injectable()
632
- export class OwnershipService {
633
- constructor(private orgUserService: OrganizationUserService) {}
770
+ export class OrganizationSetupService {
771
+ constructor(
772
+ private readonly orgSettings: OrganizationSettingsService,
773
+ ) {}
634
774
 
635
- async transferOwnership(
636
- orgId: string,
637
- currentOwnerId: string,
638
- newOwnerId: string
775
+ async configureGitLab(
776
+ organizationId: string,
777
+ config: { host: string; token: string }
639
778
  ) {
640
- // Verify current owner
641
- const isPrimaryOwner = await this.orgUserService
642
- .getUserRole(orgId, currentOwnerId);
643
-
644
- if (isPrimaryOwner !== ORGANIZATION_USER_ROLE.OWNER) {
645
- throw new ForbiddenException('Only owner can transfer');
646
- }
779
+ return this.orgSettings.bulkUpdateSettings(organizationId, [
780
+ {
781
+ module: 'GITLAB',
782
+ section: 'CREDENTIALS',
783
+ key: 'HOST',
784
+ value: config.host,
785
+ label: 'GitLab Host',
786
+ type: SETTINGS_TYPE.TEXT,
787
+ },
788
+ {
789
+ module: 'GITLAB',
790
+ section: 'CREDENTIALS',
791
+ key: 'TOKEN',
792
+ value: config.token,
793
+ label: 'GitLab Token',
794
+ type: SETTINGS_TYPE.SECRET,
795
+ },
796
+ ]);
797
+ }
798
+ }
799
+ \`\`\`
647
800
 
648
- // Transfer
649
- await this.orgUserService.transferOwnership(
650
- orgId,
651
- currentOwnerId,
652
- newOwnerId
801
+ ### Example 5: Settings Resolution Pattern
802
+
803
+ \`\`\`typescript
804
+ @Injectable()
805
+ export class IntegrationService {
806
+ constructor(
807
+ private readonly orgSettings: OrganizationSettingsService,
808
+ ) {}
809
+
810
+ async getIntegrationConfig(organizationId: string, integration: string) {
811
+ // Get all settings for this integration
812
+ const settings = await this.orgSettings.getAllForOrganization(
813
+ organizationId,
814
+ integration
653
815
  );
816
+
817
+ // Check if configured
818
+ if (settings.length === 0) {
819
+ return {
820
+ configured: false,
821
+ message: \`\${integration} is not configured for this organization\`,
822
+ };
823
+ }
824
+
825
+ // Convert to key-value object
826
+ const config = settings.reduce((acc, setting) => {
827
+ acc[\`\${setting.section}:\${setting.key}\`] = setting.value;
828
+ return acc;
829
+ }, {});
830
+
831
+ return {
832
+ configured: true,
833
+ config,
834
+ };
654
835
  }
655
836
  }
656
- ```
837
+ \`\`\`
657
838
 
658
839
  ---
659
840
 
660
- ## ๐Ÿ“„ License
841
+ ## ๐Ÿ“ Best Practices
661
842
 
662
- MIT
843
+ ### 1. Always Check Configuration
663
844
 
664
- ---
845
+ \`\`\`typescript
846
+ // โŒ Don't assume settings exist
847
+ const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN');
848
+ await client.connect(token); // Could be undefined!
665
849
 
666
- ## ๐Ÿค Contributing
850
+ // โœ… Always validate
851
+ const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN');
852
+ if (!token) {
853
+ throw new Error('Module not configured');
854
+ }
855
+ await client.connect(token);
856
+ \`\`\`
857
+
858
+ ### 2. Use Bulk Operations
859
+
860
+ \`\`\`typescript
861
+ // โŒ Multiple single calls
862
+ const host = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:HOST');
863
+ const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN');
864
+ const secret = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:SECRET');
865
+
866
+ // โœ… Single bulk call
867
+ const settings = await orgSettings.getManySettings(orgId, [
868
+ 'MODULE:CREDENTIALS:HOST',
869
+ 'MODULE:CREDENTIALS:TOKEN',
870
+ 'MODULE:CREDENTIALS:SECRET',
871
+ ]);
872
+ \`\`\`
873
+
874
+ ### 3. Implement Your Own Controllers
875
+
876
+ \`\`\`typescript
877
+ // โŒ Don't use test controller in production
878
+ import { OrganizationSettingsTestController } from '@venturialstd/organization';
879
+
880
+ // โœ… Implement your own
881
+ @Controller('api/organizations/:organizationId/settings')
882
+ @UseGuards(JwtAuthGuard, OrganizationGuard)
883
+ export class OrganizationSettingsController {
884
+ // Your implementation with proper auth
885
+ }
886
+ \`\`\`
887
+
888
+ ### 4. Use Type-Safe Settings Types
889
+
890
+ \`\`\`typescript
891
+ // โœ… Import and use the enum
892
+ import { SETTINGS_TYPE } from '@venturialstd/organization';
667
893
 
668
- Contributions are welcome! Please feel free to submit a Pull Request.
894
+ const setting = {
895
+ type: SETTINGS_TYPE.SECRET, // Type-safe
896
+ // ...
897
+ };
898
+ \`\`\`
669
899
 
670
900
  ---
671
901
 
672
- ## ๐Ÿ“ง Support
902
+ ## ๐Ÿ“„ License
673
903
 
674
- For issues and questions, please create an issue in the repository.
904
+ MIT
675
905
 
676
906
  ---
677
907
 
678
- **Built with โค๏ธ using NestJS and TypeORM**
908
+ ## ๐Ÿค Contributing
909
+
910
+ Contributions welcome! Please read the contributing guidelines before submitting PRs.