@venturialstd/organization 0.0.1 โ†’ 0.0.3

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 (36) hide show
  1. package/README.md +910 -678
  2. package/dist/constants/repository-provider.constant.d.ts +6 -0
  3. package/dist/constants/repository-provider.constant.d.ts.map +1 -0
  4. package/dist/constants/repository-provider.constant.js +10 -0
  5. package/dist/constants/repository-provider.constant.js.map +1 -0
  6. package/dist/constants/settings-type.constant.d.ts +12 -0
  7. package/dist/constants/settings-type.constant.d.ts.map +1 -0
  8. package/dist/constants/settings-type.constant.js +16 -0
  9. package/dist/constants/settings-type.constant.js.map +1 -0
  10. package/dist/controllers/organization-repository.controller.d.ts +16 -0
  11. package/dist/controllers/organization-repository.controller.d.ts.map +1 -0
  12. package/dist/controllers/organization-repository.controller.js +84 -0
  13. package/dist/controllers/organization-repository.controller.js.map +1 -0
  14. package/dist/decorators/organization.decorator.d.ts.map +1 -1
  15. package/dist/decorators/organization.decorator.js +6 -2
  16. package/dist/decorators/organization.decorator.js.map +1 -1
  17. package/dist/dtos/repository-auth.dto.d.ts +6 -0
  18. package/dist/dtos/repository-auth.dto.d.ts.map +1 -0
  19. package/dist/dtos/repository-auth.dto.js +28 -0
  20. package/dist/dtos/repository-auth.dto.js.map +1 -0
  21. package/dist/entities/organization-settings.entity.d.ts +19 -0
  22. package/dist/entities/organization-settings.entity.d.ts.map +1 -0
  23. package/dist/entities/organization-settings.entity.js +124 -0
  24. package/dist/entities/organization-settings.entity.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +3 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/organization.module.d.ts.map +1 -1
  30. package/dist/organization.module.js +11 -4
  31. package/dist/organization.module.js.map +1 -1
  32. package/dist/services/organization-settings.service.d.ts +15 -0
  33. package/dist/services/organization-settings.service.d.ts.map +1 -0
  34. package/dist/services/organization-settings.service.js +114 -0
  35. package/dist/services/organization-settings.service.js.map +1 -0
  36. package/package.json +42 -42
package/README.md CHANGED
@@ -1,678 +1,910 @@
1
- # @venturialstd/organization
2
-
3
- Organization Management Module for Venturial - A NestJS module for managing organizations and organization members with role-based access control.
4
-
5
- ## ๐Ÿ“‹ Table of Contents
6
-
7
- - [Features](#features)
8
- - [Installation](#installation)
9
- - [Module Structure](#module-structure)
10
- - [Quick Start](#quick-start)
11
- - [Configuration](#configuration)
12
- - [Entities](#entities)
13
- - [API Reference](#api-reference)
14
- - [Enums & Constants](#enums--constants)
15
- - [Guards & Decorators](#guards--decorators)
16
- - [Test Server](#test-server)
17
- - [NPM Scripts](#npm-scripts)
18
- - [Usage Examples](#usage-examples)
19
-
20
- ---
21
-
22
- ## โœจ Features
23
-
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
28
- - ๐Ÿ›ก๏ธ **Guards & Decorators**: Request-level access control
29
- - ๐Ÿ”„ **TypeORM Integration**: Full database support with migrations
30
- - ๐Ÿ“ฆ **Fully Typed**: Complete TypeScript support
31
- - ๐Ÿงช **Test Infrastructure**: Standalone test server on port 3002
32
-
33
- ---
34
-
35
- ## ๐Ÿ“ฆ Installation
36
-
37
- ```bash
38
- npm install @venturialstd/organization
39
- ```
40
-
41
- ### Peer Dependencies
42
-
43
- ```json
44
- {
45
- "@nestjs/common": "^11.0.11",
46
- "@nestjs/core": "^11.0.5",
47
- "@nestjs/typeorm": "^10.0.0",
48
- "@venturialstd/core": "^1.0.16",
49
- "class-transformer": "^0.5.1",
50
- "class-validator": "^0.14.1",
51
- "typeorm": "^0.3.20"
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
- ```
94
-
95
- ---
96
-
97
- ## ๐Ÿš€ Quick Start
98
-
99
- ### 1. Import the Module
100
-
101
- ```typescript
102
- import { Module } from '@nestjs/common';
103
- import { OrganizationModule } from '@venturialstd/organization';
104
-
105
- @Module({
106
- imports: [OrganizationModule],
107
- })
108
- export class AppModule {}
109
- ```
110
-
111
- ### 2. Use the Services
112
-
113
- ```typescript
114
- import { Injectable } from '@nestjs/common';
115
- import { OrganizationService, OrganizationUserService } from '@venturialstd/organization';
116
-
117
- @Injectable()
118
- export class YourService {
119
- constructor(
120
- private readonly organizationService: OrganizationService,
121
- private readonly organizationUserService: OrganizationUserService,
122
- ) {}
123
-
124
- async createOrganization() {
125
- const organization = await this.organizationService.createOrganization(
126
- 'My Organization',
127
- 'my-org',
128
- 'example.com',
129
- 'Organization description',
130
- 'user-id-123'
131
- );
132
-
133
- return organization;
134
- }
135
-
136
- async addMember() {
137
- return this.organizationUserService.addUserToOrganization(
138
- 'org-id',
139
- 'user-id',
140
- ORGANIZATION_USER_ROLE.MEMBER
141
- );
142
- }
143
- }
144
- ```
145
-
146
- ---
147
-
148
- ## โš™๏ธ Configuration
149
-
150
- ### Development Setup
151
-
152
- ```bash
153
- # Navigate to module directory
154
- cd src/organization
155
-
156
- # Install dependencies
157
- npm install --legacy-peer-deps
158
-
159
- # Set up environment
160
- cp test/.env.example test/.env
161
- # Edit test/.env with your database credentials
162
-
163
- # Generate migration
164
- npm run migration:generate --name=InitialSchema
165
-
166
- # Run migrations
167
- npm run migration:run
168
-
169
- # Start test server
170
- npm run test:dev
171
- ```
172
-
173
- ### Environment Variables (test/.env)
174
-
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
- ```
183
-
184
- ---
185
-
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
- ```
231
-
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
238
-
239
- ---
240
-
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
- ```
341
-
342
- ---
343
-
344
- ## ๐ŸŽญ Enums & Constants
345
-
346
- ### ORGANIZATION_USER_ROLE
347
-
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
354
- }
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
364
- }
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',
379
- }
380
- ```
381
-
382
- ---
383
-
384
- ## ๐Ÿ›ก๏ธ Guards & Decorators
385
-
386
- ### Decorators
387
-
388
- #### @OrganizationId()
389
- Extract organization ID from request:
390
- ```typescript
391
- @Get()
392
- async getData(@OrganizationId() organizationId: string) {
393
- return this.service.getData(organizationId);
394
- }
395
- ```
396
-
397
- #### @UserId()
398
- Extract user ID from request:
399
- ```typescript
400
- @Get()
401
- async getData(@UserId() userId: string) {
402
- return this.service.getUserData(userId);
403
- }
404
- ```
405
-
406
- #### @OrganizationContext()
407
- Extract full context:
408
- ```typescript
409
- @Get()
410
- async getData(@OrganizationContext() context) {
411
- // context = { organizationId, userId, role, user }
412
- return this.service.getData(context);
413
- }
414
- ```
415
-
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
- ```
424
-
425
- ### Guards
426
-
427
- #### OrganizationGuard
428
- Ensures user has access to the organization:
429
- ```typescript
430
- @UseGuards(OrganizationGuard)
431
- @Get()
432
- async getData(@OrganizationId() organizationId: string) {
433
- return this.service.getData(organizationId);
434
- }
435
- ```
436
-
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
- ```
445
-
446
- ---
447
-
448
- ## ๐Ÿงช Test Server
449
-
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
- ```
490
-
491
- ---
492
-
493
- ## ๐Ÿ“ NPM Scripts
494
-
495
- ```bash
496
- # Build
497
- npm run build # Build TypeScript to dist/
498
- npm run prepublishOnly # Auto-build before publish
499
-
500
- # Publishing
501
- npm run release:patch # Build, bump version, publish
502
-
503
- # Development
504
- npm run test:dev # Start test server
505
- npm run test:watch # Watch mode for development
506
-
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
- ```
512
-
513
- ---
514
-
515
- ## ๐Ÿ’ก Usage Examples
516
-
517
- ### Basic Organization Creation
518
-
519
- ```typescript
520
- import { OrganizationService } from '@venturialstd/organization';
521
-
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
- }
535
- }
536
- ```
537
-
538
- ### Adding Members with Roles
539
-
540
- ```typescript
541
- import {
542
- OrganizationUserService,
543
- ORGANIZATION_USER_ROLE
544
- } from '@venturialstd/organization';
545
-
546
- @Injectable()
547
- export class MemberService {
548
- constructor(private orgUserService: OrganizationUserService) {}
549
-
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'
556
- );
557
- }
558
- }
559
- ```
560
-
561
- ### Protected Endpoints
562
-
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';
576
-
577
- @Controller('projects')
578
- @UseGuards(OrganizationGuard)
579
- export class ProjectController {
580
-
581
- // Any member can view
582
- @Get()
583
- async list(@OrganizationId() orgId: string) {
584
- return this.service.getProjects(orgId);
585
- }
586
-
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);
593
- }
594
- }
595
- ```
596
-
597
- ### Invitation Flow
598
-
599
- ```typescript
600
- @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
- }
615
-
616
- // Step 2: Get user's pending invitations
617
- async getPendingInvites(userId: string) {
618
- return this.orgUserService.getUserInvitations(userId);
619
- }
620
-
621
- // Step 3: Accept invitation
622
- async acceptInvite(orgId: string, userId: string) {
623
- return this.orgUserService.acceptInvitation(orgId, userId);
624
- }
625
- }
626
- ```
627
-
628
- ### Ownership Transfer
629
-
630
- ```typescript
631
- @Injectable()
632
- export class OwnershipService {
633
- constructor(private orgUserService: OrganizationUserService) {}
634
-
635
- async transferOwnership(
636
- orgId: string,
637
- currentOwnerId: string,
638
- newOwnerId: string
639
- ) {
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
- }
647
-
648
- // Transfer
649
- await this.orgUserService.transferOwnership(
650
- orgId,
651
- currentOwnerId,
652
- newOwnerId
653
- );
654
- }
655
- }
656
- ```
657
-
658
- ---
659
-
660
- ## ๐Ÿ“„ License
661
-
662
- MIT
663
-
664
- ---
665
-
666
- ## ๐Ÿค Contributing
667
-
668
- Contributions are welcome! Please feel free to submit a Pull Request.
669
-
670
- ---
671
-
672
- ## ๐Ÿ“ง Support
673
-
674
- For issues and questions, please create an issue in the repository.
675
-
676
- ---
677
-
678
- **Built with โค๏ธ using NestJS and TypeORM**
1
+ # @venturialstd/organization
2
+
3
+ A comprehensive NestJS module for managing organizations with multi-tenant capabilities, role-based access control, and isolated per-organization settings.
4
+
5
+ ## ๐Ÿ“‹ Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [Core Concepts](#core-concepts)
11
+ - [Architecture](#architecture)
12
+ - [API Reference](#api-reference)
13
+ - [Organization Settings System](#organization-settings-system)
14
+ - [Test Server](#test-server)
15
+ - [Migration Guide](#migration-guide)
16
+ - [Examples](#examples)
17
+ - [Best Practices](#best-practices)
18
+
19
+ ---
20
+
21
+ ## โœจ Features
22
+
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
+ - ๐Ÿ›ก๏ธ **Guards & Decorators**: Request-level access control
29
+ - ๐Ÿ”„ **TypeORM Integration**: Full database support with migrations
30
+ - ๐Ÿ“ฆ **Fully Typed**: Complete TypeScript support
31
+ - ๐Ÿงช **Test Infrastructure**: Standalone test server
32
+
33
+ ---
34
+
35
+ ## ๐Ÿ“ฆ Installation
36
+
37
+ \`\`\`bash
38
+ npm install @venturialstd/organization
39
+ \`\`\`
40
+
41
+ ### Peer Dependencies
42
+
43
+ \`\`\`json
44
+ {
45
+ "@nestjs/common": "^11.0.11",
46
+ "@nestjs/core": "^11.0.5",
47
+ "@nestjs/typeorm": "^10.0.0",
48
+ "@venturialstd/core": "^1.0.16",
49
+ "class-transformer": "^0.5.1",
50
+ "class-validator": "^0.14.1",
51
+ "typeorm": "^0.3.20"
52
+ }
53
+ \`\`\`
54
+
55
+ ---
56
+
57
+ ## ๐Ÿš€ Quick Start
58
+
59
+ ### 1. Import the Module
60
+
61
+ \`\`\`typescript
62
+ import { Module } from '@nestjs/common';
63
+ import { OrganizationModule } from '@venturialstd/organization';
64
+
65
+ @Module({
66
+ imports: [OrganizationModule],
67
+ })
68
+ export class AppModule {}
69
+ \`\`\`
70
+
71
+ ### 2. Run Migrations
72
+
73
+ \`\`\`bash
74
+ # Create and run migrations
75
+ npm run migration:generate -- CreateOrganizations
76
+ npm run migration:run
77
+ \`\`\`
78
+
79
+ ### 3. Use the Services
80
+
81
+ \`\`\`typescript
82
+ import { Injectable } from '@nestjs/common';
83
+ import {
84
+ OrganizationService,
85
+ OrganizationUserService,
86
+ ORGANIZATION_USER_ROLE
87
+ } from '@venturialstd/organization';
88
+
89
+ @Injectable()
90
+ export class YourService {
91
+ constructor(
92
+ private readonly organizationService: OrganizationService,
93
+ private readonly organizationUserService: OrganizationUserService,
94
+ ) {}
95
+
96
+ async createOrganization(userId: string) {
97
+ return this.organizationService.createOrganization(
98
+ 'My Organization',
99
+ 'my-org',
100
+ 'example.com',
101
+ 'Description',
102
+ userId
103
+ );
104
+ }
105
+
106
+ async addMember(organizationId: string, userId: string) {
107
+ return this.organizationUserService.addUserToOrganization(
108
+ organizationId,
109
+ userId,
110
+ ORGANIZATION_USER_ROLE.MEMBER
111
+ );
112
+ }
113
+ }
114
+ \`\`\`
115
+
116
+ ---
117
+
118
+ ## ๐Ÿ’ก Core Concepts
119
+
120
+ ### Organizations
121
+
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
128
+
129
+ ### Organization Members
130
+
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
136
+
137
+ ### Organization Settings
138
+
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.)
144
+
145
+ ---
146
+
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
+ \`\`\`
191
+
192
+ ---
193
+
194
+ ## ๐Ÿ“š API Reference
195
+
196
+ ### Services
197
+
198
+ #### OrganizationService
199
+
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>
210
+
211
+ // Get organization by ID
212
+ getOrganizationById(id: string): Promise<Organization>
213
+
214
+ // Update organization
215
+ updateOrganization(id: string, data: Partial<Organization>): Promise<Organization>
216
+
217
+ // Delete organization
218
+ deleteOrganization(id: string): Promise<void>
219
+
220
+ // Get user's organizations
221
+ getUserOrganizations(userId: string): Promise<Organization[]>
222
+ }
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>
257
+ }
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[]>
304
+ }
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
+ \`\`\`
359
+
360
+ ### Decorators
361
+
362
+ \`\`\`typescript
363
+ // Extract organization ID from request
364
+ @Get()
365
+ getData(@OrganizationId() organizationId: string) { }
366
+
367
+ // Extract user ID from request
368
+ @Get()
369
+ getData(@UserId() userId: string) { }
370
+
371
+ // Extract full context
372
+ @Get()
373
+ getData(@OrganizationContext() context: OrganizationContextType) { }
374
+
375
+ // Define required roles
376
+ @Roles(ORGANIZATION_USER_ROLE.ADMIN, ORGANIZATION_USER_ROLE.OWNER)
377
+ @UseGuards(OrganizationRoleGuard)
378
+ adminEndpoint() { }
379
+ \`\`\`
380
+
381
+ ### Guards
382
+
383
+ \`\`\`typescript
384
+ // Check user belongs to organization
385
+ @UseGuards(OrganizationGuard)
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',
404
+ }
405
+
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
+ \`\`\`
419
+
420
+ ---
421
+
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';
507
+
508
+ @Injectable()
509
+ export class YourModuleService {
510
+ constructor(
511
+ private readonly orgSettings: OrganizationSettingsService
512
+ ) {}
513
+
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
+ );
520
+
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
+ }
534
+
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
+ \`\`\`
550
+
551
+ #### 3. Import OrganizationModule
552
+
553
+ \`\`\`typescript
554
+ import { Module } from '@nestjs/common';
555
+ import { OrganizationModule } from '@venturialstd/organization';
556
+
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
+ \`\`\`
584
+
585
+ ---
586
+
587
+ ## ๐Ÿงช Test Server
588
+
589
+ The module includes a standalone test server for development and testing.
590
+
591
+ ### Start the Test Server
592
+
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"
627
+ }
628
+ \`\`\`
629
+
630
+ ### Test Controller
631
+
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/\`
665
+
666
+ ---
667
+
668
+ ## ๐Ÿ’ป Examples
669
+
670
+ ### Example 1: Create Organization with Owner
671
+
672
+ \`\`\`typescript
673
+ @Injectable()
674
+ export class OnboardingService {
675
+ constructor(
676
+ private readonly orgService: OrganizationService,
677
+ private readonly orgUserService: OrganizationUserService,
678
+ ) {}
679
+
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
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, '-');
696
+ }
697
+ }
698
+ \`\`\`
699
+
700
+ ### Example 2: Check User Permission
701
+
702
+ \`\`\`typescript
703
+ @Controller('organizations/:organizationId/data')
704
+ export class DataController {
705
+ constructor(
706
+ private readonly orgUserService: OrganizationUserService,
707
+ ) {}
708
+
709
+ @Get()
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
+ );
719
+
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
726
+ }
727
+ }
728
+ \`\`\`
729
+
730
+ ### Example 3: Module with Organization Settings
731
+
732
+ \`\`\`typescript
733
+ // GitLab Service
734
+ @Injectable()
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
+ }
754
+
755
+ // Return organization-specific client
756
+ return new GitLab({ host, token });
757
+ }
758
+
759
+ async getRepositories(organizationId: string) {
760
+ const client = await this.getClient(organizationId);
761
+ return client.Projects.all();
762
+ }
763
+ }
764
+ \`\`\`
765
+
766
+ ### Example 4: Bulk Configure Organization
767
+
768
+ \`\`\`typescript
769
+ @Injectable()
770
+ export class OrganizationSetupService {
771
+ constructor(
772
+ private readonly orgSettings: OrganizationSettingsService,
773
+ ) {}
774
+
775
+ async configureGitLab(
776
+ organizationId: string,
777
+ config: { host: string; token: string }
778
+ ) {
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
+ \`\`\`
800
+
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
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
+ };
835
+ }
836
+ }
837
+ \`\`\`
838
+
839
+ ---
840
+
841
+ ## ๐Ÿ“ Best Practices
842
+
843
+ ### 1. Always Check Configuration
844
+
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!
849
+
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';
893
+
894
+ const setting = {
895
+ type: SETTINGS_TYPE.SECRET, // Type-safe
896
+ // ...
897
+ };
898
+ \`\`\`
899
+
900
+ ---
901
+
902
+ ## ๐Ÿ“„ License
903
+
904
+ MIT
905
+
906
+ ---
907
+
908
+ ## ๐Ÿค Contributing
909
+
910
+ Contributions welcome! Please read the contributing guidelines before submitting PRs.