@venturialstd/tenant 0.0.1

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 (50) hide show
  1. package/README.md +974 -0
  2. package/dist/constants/tenant.constant.d.ts +28 -0
  3. package/dist/constants/tenant.constant.d.ts.map +1 -0
  4. package/dist/constants/tenant.constant.js +35 -0
  5. package/dist/constants/tenant.constant.js.map +1 -0
  6. package/dist/constants/tenant.settings.constant.d.ts +9 -0
  7. package/dist/constants/tenant.settings.constant.d.ts.map +1 -0
  8. package/dist/constants/tenant.settings.constant.js +12 -0
  9. package/dist/constants/tenant.settings.constant.js.map +1 -0
  10. package/dist/decorators/roles.decorator.d.ts +3 -0
  11. package/dist/decorators/roles.decorator.d.ts.map +1 -0
  12. package/dist/decorators/roles.decorator.js +7 -0
  13. package/dist/decorators/roles.decorator.js.map +1 -0
  14. package/dist/decorators/tenant.decorator.d.ts +4 -0
  15. package/dist/decorators/tenant.decorator.d.ts.map +1 -0
  16. package/dist/decorators/tenant.decorator.js +22 -0
  17. package/dist/decorators/tenant.decorator.js.map +1 -0
  18. package/dist/entities/tenant-user.entity.d.ts +16 -0
  19. package/dist/entities/tenant-user.entity.d.ts.map +1 -0
  20. package/dist/entities/tenant-user.entity.js +120 -0
  21. package/dist/entities/tenant-user.entity.js.map +1 -0
  22. package/dist/entities/tenant.entity.d.ts +16 -0
  23. package/dist/entities/tenant.entity.d.ts.map +1 -0
  24. package/dist/entities/tenant.entity.js +115 -0
  25. package/dist/entities/tenant.entity.js.map +1 -0
  26. package/dist/guards/tenant.guard.d.ts +16 -0
  27. package/dist/guards/tenant.guard.d.ts.map +1 -0
  28. package/dist/guards/tenant.guard.js +90 -0
  29. package/dist/guards/tenant.guard.js.map +1 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +28 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/services/tenant-user.service.d.ts +30 -0
  35. package/dist/services/tenant-user.service.d.ts.map +1 -0
  36. package/dist/services/tenant-user.service.js +245 -0
  37. package/dist/services/tenant-user.service.js.map +1 -0
  38. package/dist/services/tenant.service.d.ts +21 -0
  39. package/dist/services/tenant.service.d.ts.map +1 -0
  40. package/dist/services/tenant.service.js +157 -0
  41. package/dist/services/tenant.service.js.map +1 -0
  42. package/dist/settings/tenant.settings.d.ts +3 -0
  43. package/dist/settings/tenant.settings.d.ts.map +1 -0
  44. package/dist/settings/tenant.settings.js +85 -0
  45. package/dist/settings/tenant.settings.js.map +1 -0
  46. package/dist/tenant.module.d.ts +3 -0
  47. package/dist/tenant.module.d.ts.map +1 -0
  48. package/dist/tenant.module.js +28 -0
  49. package/dist/tenant.module.js.map +1 -0
  50. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,974 @@
1
+ # @venturialstd/tenant
2
+
3
+ A comprehensive **Multi-Tenant SaaS Platform Package**, developed by **Venturial**, that provides complete tenant management capabilities with user isolation for building scalable SaaS applications. This package follows the Venturial architecture pattern and integrates seamlessly with TypeORM and NestJS.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **Multi-Tenant Management**: Create and manage multiple tenants with isolated data
10
+ - **User-Tenant Relationships**: Complete user isolation per tenant with role-based access control
11
+ - **Role-Based Access Control (RBAC)**: Owner, Admin, Member, and Viewer roles
12
+ - **Subscription Plans**: Support for Free, Starter, Professional, and Enterprise plans
13
+ - **Trial Period**: Configurable trial periods for new tenants
14
+ - **Custom Domains**: Optional custom domain support for tenants
15
+ - **Invitation System**: Invite users to tenants with pending invitation management
16
+ - **Ownership Transfer**: Transfer tenant ownership between users
17
+ - **User Suspension**: Suspend and reactivate user access to tenants
18
+ - **Guards & Decorators**: Built-in guards and decorators for easy tenant isolation
19
+ - **CRUD Operations**: Full TypeORM CRUD service integration
20
+ - **Settings Management**: Dynamic tenant-level settings with `@venturialstd/core`
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @venturialstd/tenant
28
+ # or
29
+ yarn add @venturialstd/tenant
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Basic Usage
35
+
36
+ ### 1. Import the TenantModule in your application
37
+
38
+ ```ts
39
+ import { Module } from '@nestjs/common';
40
+ import { TenantModule } from '@venturialstd/tenant';
41
+
42
+ @Module({
43
+ imports: [
44
+ TenantModule,
45
+ // ... other modules
46
+ ],
47
+ })
48
+ export class AppModule {}
49
+ ```
50
+
51
+ ### 2. Use the services in your application
52
+
53
+ ```ts
54
+ import { Injectable } from '@nestjs/common';
55
+ import { TenantService, TenantUserService, TenantUserRole } from '@venturialstd/tenant';
56
+
57
+ @Injectable()
58
+ export class YourService {
59
+ constructor(
60
+ private readonly tenantService: TenantService,
61
+ private readonly tenantUserService: TenantUserService,
62
+ ) {}
63
+
64
+ async createNewTenant(userId: string) {
65
+ // Create tenant
66
+ const tenant = await this.tenantService.createTenant(
67
+ 'Acme Corp',
68
+ 'acme-corp',
69
+ 'acme.example.com',
70
+ 'A leading software company',
71
+ userId
72
+ );
73
+
74
+ // Add user as primary owner
75
+ await this.tenantUserService.addUserToTenant(
76
+ tenant.id,
77
+ userId,
78
+ TenantUserRole.OWNER
79
+ );
80
+
81
+ await this.tenantUserService.setPrimaryOwner(tenant.id, userId);
82
+
83
+ return tenant;
84
+ }
85
+
86
+ async inviteUserToTenant(tenantId: string, userId: string, invitedBy: string) {
87
+ return this.tenantUserService.inviteUserToTenant(
88
+ tenantId,
89
+ userId,
90
+ TenantUserRole.MEMBER,
91
+ invitedBy
92
+ );
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### 3. Use Guards and Decorators for tenant isolation
98
+
99
+ ```ts
100
+ import { Controller, Get, UseGuards } from '@nestjs/common';
101
+ import {
102
+ TenantGuard,
103
+ TenantRoleGuard,
104
+ TenantId,
105
+ UserId,
106
+ Roles,
107
+ TenantUserRole
108
+ } from '@venturialstd/tenant';
109
+
110
+ @Controller('data')
111
+ @UseGuards(TenantGuard) // Ensure user has access to tenant
112
+ export class DataController {
113
+ constructor(private readonly dataService: DataService) {}
114
+
115
+ @Get()
116
+ async getData(@TenantId() tenantId: string, @UserId() userId: string) {
117
+ // tenantId and userId are automatically extracted and validated
118
+ return this.dataService.getTenantData(tenantId, userId);
119
+ }
120
+
121
+ @Delete(':id')
122
+ @Roles(TenantUserRole.OWNER, TenantUserRole.ADMIN) // Only owners and admins
123
+ @UseGuards(TenantRoleGuard) // Enforce role-based access
124
+ async deleteData(@TenantId() tenantId: string, @Param('id') id: string) {
125
+ return this.dataService.deleteData(tenantId, id);
126
+ }
127
+ }
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Entity Structures
133
+
134
+ ### Tenant Entity
135
+
136
+ The `Tenant` entity includes the following fields:
137
+
138
+ - **id**: UUID primary key
139
+ - **name**: Tenant name (required, unique)
140
+ - **slug**: URL-friendly identifier (required, unique)
141
+ - **domain**: Custom domain (optional)
142
+ - **description**: Tenant description (optional)
143
+ - **isActive**: Active/inactive status (default: true)
144
+ - **settings**: JSON field for tenant-specific settings
145
+ - **ownerId**: Reference to the owner user
146
+ - **plan**: Subscription plan (free, starter, professional, enterprise)
147
+ - **trialEndsAt**: Trial period expiration date
148
+ - **subscriptionEndsAt**: Subscription expiration date
149
+ - **createdAt**: Creation timestamp
150
+ - **updatedAt**: Last update timestamp
151
+
152
+ ### TenantUser Entity
153
+
154
+ The `TenantUser` entity manages user-tenant relationships:
155
+
156
+ - **id**: UUID primary key
157
+ - **tenantId**: Reference to tenant (indexed)
158
+ - **userId**: Reference to user (indexed)
159
+ - **role**: User role (OWNER, ADMIN, MEMBER, VIEWER)
160
+ - **status**: User status (ACTIVE, INVITED, SUSPENDED)
161
+ - **isPrimary**: Whether user is the primary owner
162
+ - **invitedBy**: User who sent the invitation
163
+ - **invitedAt**: Invitation timestamp
164
+ - **joinedAt**: Join timestamp
165
+ - **permissions**: JSON field for custom permissions
166
+ - **createdAt**: Creation timestamp
167
+ - **updatedAt**: Last update timestamp
168
+
169
+ **Unique constraint**: (tenantId, userId) - A user can only be added once per tenant
170
+
171
+ ---
172
+
173
+ ## Available Methods
174
+
175
+ ### TenantService Methods
176
+
177
+ #### `createTenant(name, slug, domain?, description?, ownerId?)`
178
+ Creates a new tenant with validation and default settings.
179
+
180
+ ```ts
181
+ const tenant = await tenantService.createTenant(
182
+ 'My Company',
183
+ 'my-company',
184
+ 'mycompany.com',
185
+ 'Company description',
186
+ 'owner-uuid'
187
+ );
188
+ ```
189
+
190
+ #### `getTenantBySlug(slug)`
191
+ Retrieve a tenant by its slug.
192
+
193
+ ```ts
194
+ const tenant = await tenantService.getTenantBySlug('my-company');
195
+ ```
196
+
197
+ #### `getTenantByDomain(domain)`
198
+ Retrieve a tenant by its custom domain.
199
+
200
+ ```ts
201
+ const tenant = await tenantService.getTenantByDomain('mycompany.com');
202
+ ```
203
+
204
+ #### `updateTenantSettings(tenantId, settings)`
205
+ Update tenant-specific settings.
206
+
207
+ ```ts
208
+ await tenantService.updateTenantSettings('tenant-uuid', {
209
+ theme: 'dark',
210
+ language: 'en',
211
+ features: { api: true }
212
+ });
213
+ ```
214
+
215
+ #### `setTenantStatus(tenantId, isActive)`
216
+ Activate or deactivate a tenant.
217
+
218
+ ```ts
219
+ await tenantService.setTenantStatus('tenant-uuid', false); // Deactivate
220
+ await tenantService.setTenantStatus('tenant-uuid', true); // Activate
221
+ ```
222
+
223
+ #### `updateTenantPlan(tenantId, plan, subscriptionEndsAt?)`
224
+ Update the tenant's subscription plan.
225
+
226
+ ```ts
227
+ const endDate = new Date();
228
+ endDate.setMonth(endDate.getMonth() + 1);
229
+
230
+ await tenantService.updateTenantPlan(
231
+ 'tenant-uuid',
232
+ 'professional',
233
+ endDate
234
+ );
235
+ ```
236
+
237
+ #### `getActiveTenants()`
238
+ Get all active tenants.
239
+
240
+ ```ts
241
+ const activeTenants = await tenantService.getActiveTenants();
242
+ ```
243
+
244
+ #### `getTenantsByOwner(ownerId)`
245
+ Get all tenants owned by a specific user.
246
+
247
+ ```ts
248
+ const tenants = await tenantService.getTenantsByOwner('owner-uuid');
249
+ ```
250
+
251
+ #### `isInTrialPeriod(tenantId)`
252
+ Check if a tenant is currently in trial period.
253
+
254
+ ```ts
255
+ const inTrial = await tenantService.isInTrialPeriod('tenant-uuid');
256
+ ```
257
+
258
+ #### `hasActiveSubscription(tenantId)`
259
+ Check if a tenant has an active subscription.
260
+
261
+ ```ts
262
+ const isActive = await tenantService.hasActiveSubscription('tenant-uuid');
263
+ ```
264
+
265
+ ### TenantUserService Methods
266
+
267
+ #### `addUserToTenant(tenantId, userId, role, invitedBy?)`
268
+ Add a user to a tenant with a specific role.
269
+
270
+ ```ts
271
+ await tenantUserService.addUserToTenant(
272
+ 'tenant-uuid',
273
+ 'user-uuid',
274
+ TenantUserRole.MEMBER,
275
+ 'inviter-uuid'
276
+ );
277
+ ```
278
+
279
+ #### `inviteUserToTenant(tenantId, userId, role, invitedBy)`
280
+ Invite a user to a tenant (creates pending invitation).
281
+
282
+ ```ts
283
+ await tenantUserService.inviteUserToTenant(
284
+ 'tenant-uuid',
285
+ 'user-uuid',
286
+ TenantUserRole.ADMIN,
287
+ 'inviter-uuid'
288
+ );
289
+ ```
290
+
291
+ #### `acceptInvitation(tenantId, userId)`
292
+ Accept a pending tenant invitation.
293
+
294
+ ```ts
295
+ await tenantUserService.acceptInvitation('tenant-uuid', 'user-uuid');
296
+ ```
297
+
298
+ #### `removeUserFromTenant(tenantId, userId)`
299
+ Remove a user from a tenant (cannot remove primary owner).
300
+
301
+ ```ts
302
+ await tenantUserService.removeUserFromTenant('tenant-uuid', 'user-uuid');
303
+ ```
304
+
305
+ #### `updateUserRole(tenantId, userId, newRole)`
306
+ Update a user's role in a tenant.
307
+
308
+ ```ts
309
+ await tenantUserService.updateUserRole(
310
+ 'tenant-uuid',
311
+ 'user-uuid',
312
+ TenantUserRole.ADMIN
313
+ );
314
+ ```
315
+
316
+ #### `getUserTenants(userId)`
317
+ Get all tenants a user belongs to.
318
+
319
+ ```ts
320
+ const userTenants = await tenantUserService.getUserTenants('user-uuid');
321
+ ```
322
+
323
+ #### `getTenantUsers(tenantId)`
324
+ Get all users in a tenant (including invited).
325
+
326
+ ```ts
327
+ const users = await tenantUserService.getTenantUsers('tenant-uuid');
328
+ ```
329
+
330
+ #### `getActiveTenantUsers(tenantId)`
331
+ Get all active users in a tenant.
332
+
333
+ ```ts
334
+ const activeUsers = await tenantUserService.getActiveTenantUsers('tenant-uuid');
335
+ ```
336
+
337
+ #### `hasAccess(tenantId, userId)`
338
+ Check if a user has access to a tenant.
339
+
340
+ ```ts
341
+ const hasAccess = await tenantUserService.hasAccess('tenant-uuid', 'user-uuid');
342
+ ```
343
+
344
+ #### `hasRole(tenantId, userId, role)`
345
+ Check if a user has a specific role in a tenant.
346
+
347
+ ```ts
348
+ const isAdmin = await tenantUserService.hasRole(
349
+ 'tenant-uuid',
350
+ 'user-uuid',
351
+ TenantUserRole.ADMIN
352
+ );
353
+ ```
354
+
355
+ #### `isAdminOrOwner(tenantId, userId)`
356
+ Check if a user is an admin or owner in a tenant.
357
+
358
+ ```ts
359
+ const isAdminOrOwner = await tenantUserService.isAdminOrOwner('tenant-uuid', 'user-uuid');
360
+ ```
361
+
362
+ #### `getUserRole(tenantId, userId)`
363
+ Get a user's role in a tenant.
364
+
365
+ ```ts
366
+ const role = await tenantUserService.getUserRole('tenant-uuid', 'user-uuid');
367
+ ```
368
+
369
+ #### `setPrimaryOwner(tenantId, userId)`
370
+ Set a user as the primary owner of a tenant.
371
+
372
+ ```ts
373
+ await tenantUserService.setPrimaryOwner('tenant-uuid', 'user-uuid');
374
+ ```
375
+
376
+ #### `transferOwnership(tenantId, fromUserId, toUserId)`
377
+ Transfer tenant ownership between users.
378
+
379
+ ```ts
380
+ await tenantUserService.transferOwnership(
381
+ 'tenant-uuid',
382
+ 'current-owner-uuid',
383
+ 'new-owner-uuid'
384
+ );
385
+ ```
386
+
387
+ #### `getUserInvitations(userId)`
388
+ Get all pending invitations for a user.
389
+
390
+ ```ts
391
+ const invitations = await tenantUserService.getUserInvitations('user-uuid');
392
+ ```
393
+
394
+ #### `suspendUser(tenantId, userId)`
395
+ Suspend a user's access to a tenant.
396
+
397
+ ```ts
398
+ await tenantUserService.suspendUser('tenant-uuid', 'user-uuid');
399
+ ```
400
+
401
+ #### `reactivateUser(tenantId, userId)`
402
+ Reactivate a suspended user.
403
+
404
+ ```ts
405
+ await tenantUserService.reactivateUser('tenant-uuid', 'user-uuid');
406
+ ```
407
+
408
+ ---
409
+
410
+ ## Guards and Decorators
411
+
412
+ ### TenantGuard
413
+
414
+ Enforces tenant access control by validating that the authenticated user has access to the requested tenant.
415
+
416
+ ```ts
417
+ @Controller('api')
418
+ @UseGuards(TenantGuard)
419
+ export class ApiController {
420
+ @Get('data')
421
+ async getData(@TenantId() tenantId: string) {
422
+ // User access to tenant is already validated
423
+ return this.service.getData(tenantId);
424
+ }
425
+ }
426
+ ```
427
+
428
+ ### TenantRoleGuard
429
+
430
+ Enforces role-based access control within a tenant. Use with `@Roles()` decorator.
431
+
432
+ ```ts
433
+ @Controller('admin')
434
+ @UseGuards(TenantGuard, TenantRoleGuard)
435
+ export class AdminController {
436
+ @Delete('user/:userId')
437
+ @Roles(TenantUserRole.OWNER, TenantUserRole.ADMIN)
438
+ async removeUser(@TenantId() tenantId: string, @Param('userId') userId: string) {
439
+ // Only owners and admins can access this endpoint
440
+ return this.service.removeUser(tenantId, userId);
441
+ }
442
+ }
443
+ ```
444
+
445
+ ### @TenantId()
446
+
447
+ Extracts the tenant ID from the request. Checks in order:
448
+ 1. `request.tenantId`
449
+ 2. `request.headers['x-tenant-id']`
450
+ 3. `request.params.tenantId`
451
+ 4. `request.query.tenantId`
452
+
453
+ ```ts
454
+ @Get()
455
+ async getData(@TenantId() tenantId: string) {
456
+ return this.service.getData(tenantId);
457
+ }
458
+ ```
459
+
460
+ ### @UserId()
461
+
462
+ Extracts the user ID from the authenticated request.
463
+
464
+ ```ts
465
+ @Get('profile')
466
+ async getProfile(@UserId() userId: string, @TenantId() tenantId: string) {
467
+ return this.service.getUserProfile(tenantId, userId);
468
+ }
469
+ ```
470
+
471
+ ### @TenantContext()
472
+
473
+ Extracts the complete tenant context including tenantId, userId, role, and user object.
474
+
475
+ ```ts
476
+ @Get()
477
+ async getData(@TenantContext() context: { tenantId: string, userId: string, role: TenantUserRole, user: any }) {
478
+ return this.service.getData(context);
479
+ }
480
+ ```
481
+
482
+ ### @Roles(...roles)
483
+
484
+ Specifies required roles for accessing an endpoint. Must be used with `TenantRoleGuard`.
485
+
486
+ ```ts
487
+ @Post('invite')
488
+ @Roles(TenantUserRole.OWNER, TenantUserRole.ADMIN)
489
+ @UseGuards(TenantGuard, TenantRoleGuard)
490
+ async inviteUser(@TenantId() tenantId: string, @Body() dto: InviteUserDto) {
491
+ return this.service.inviteUser(tenantId, dto);
492
+ }
493
+ ```
494
+
495
+ ---
496
+
497
+ ## Configuration Settings
498
+
499
+ The module provides the following configurable settings through `SettingsService`:
500
+
501
+ ### General Settings
502
+ - **ENABLED**: Enable/disable the tenant module (boolean)
503
+ - **MAX_TENANTS**: Maximum number of tenants allowed (0 = unlimited)
504
+ - **DEFAULT_PLAN**: Default subscription plan for new tenants
505
+ - **TRIAL_DAYS**: Number of trial days for new tenants (default: 14)
506
+
507
+ ### Feature Settings
508
+ - **CUSTOM_DOMAIN**: Allow custom domains for tenants
509
+ - **API_ACCESS**: Allow API access for tenants
510
+
511
+ ---
512
+
513
+ ## Enums and Types
514
+
515
+ ### TenantUserRole
516
+ ```ts
517
+ enum TenantUserRole {
518
+ OWNER = 'owner',
519
+ ADMIN = 'admin',
520
+ MEMBER = 'member',
521
+ VIEWER = 'viewer',
522
+ }
523
+ ```
524
+
525
+ ### TenantUserStatus
526
+ ```ts
527
+ enum TenantUserStatus {
528
+ ACTIVE = 'active',
529
+ INVITED = 'invited',
530
+ SUSPENDED = 'suspended',
531
+ }
532
+ ```
533
+
534
+ ### TENANT_PLAN
535
+ ```ts
536
+ enum TENANT_PLAN {
537
+ FREE = 'free',
538
+ STARTER = 'starter',
539
+ PROFESSIONAL = 'professional',
540
+ ENTERPRISE = 'enterprise',
541
+ }
542
+ ```
543
+
544
+ ### TENANT_STATUS
545
+ ```ts
546
+ enum TENANT_STATUS {
547
+ ACTIVE = 'active',
548
+ INACTIVE = 'inactive',
549
+ SUSPENDED = 'suspended',
550
+ TRIAL = 'trial',
551
+ }
552
+ ```
553
+
554
+ ---
555
+
556
+ ## Integration with @venturialstd/core
557
+
558
+ This package uses `@venturialstd/core` for:
559
+ - **SharedModule**: Core functionality and configuration
560
+ - **SettingsService**: Dynamic settings management
561
+ - **AppLogger**: Structured logging
562
+
563
+ ---
564
+
565
+ ## TypeORM Integration
566
+
567
+ The package extends `TypeOrmCrudService` from `@dataui/crud-typeorm`, providing:
568
+ - Standard CRUD operations
569
+ - Query builders
570
+ - Pagination support
571
+ - Filtering and sorting capabilities
572
+
573
+ ---
574
+
575
+ ## Example: Complete SaaS Implementation with User Isolation
576
+
577
+ ```ts
578
+ import { Module } from '@nestjs/common';
579
+ import { TypeOrmModule } from '@nestjs/typeorm';
580
+ import { TenantModule, TenantService, TenantUserService } from '@venturialstd/tenant';
581
+
582
+ @Module({
583
+ imports: [
584
+ TypeOrmModule.forRoot({
585
+ // Your database configuration
586
+ }),
587
+ TenantModule,
588
+ ],
589
+ })
590
+ export class AppModule {}
591
+
592
+ // Service for onboarding new tenants
593
+ @Injectable()
594
+ export class SaasService {
595
+ constructor(
596
+ private readonly tenantService: TenantService,
597
+ private readonly tenantUserService: TenantUserService,
598
+ ) {}
599
+
600
+ async onboardNewTenant(data: any, ownerId: string) {
601
+ // Create tenant
602
+ const tenant = await this.tenantService.createTenant(
603
+ data.companyName,
604
+ data.slug,
605
+ data.domain,
606
+ data.description,
607
+ ownerId
608
+ );
609
+
610
+ // Add owner to tenant
611
+ await this.tenantUserService.addUserToTenant(
612
+ tenant.id,
613
+ ownerId,
614
+ TenantUserRole.OWNER
615
+ );
616
+
617
+ // Set as primary owner
618
+ await this.tenantUserService.setPrimaryOwner(tenant.id, ownerId);
619
+
620
+ // Configure tenant settings
621
+ await this.tenantService.updateTenantSettings(tenant.id, {
622
+ theme: data.preferences?.theme || 'light',
623
+ locale: data.preferences?.locale || 'en',
624
+ notifications: true,
625
+ });
626
+
627
+ return tenant;
628
+ }
629
+
630
+ async inviteTeamMember(
631
+ tenantId: string,
632
+ userId: string,
633
+ invitedBy: string,
634
+ role: TenantUserRole = TenantUserRole.MEMBER
635
+ ) {
636
+ // Verify inviter is admin or owner
637
+ const isAdmin = await this.tenantUserService.isAdminOrOwner(tenantId, invitedBy);
638
+ if (!isAdmin) {
639
+ throw new ForbiddenException('Only admins can invite team members');
640
+ }
641
+
642
+ // Invite user
643
+ await this.tenantUserService.inviteUserToTenant(
644
+ tenantId,
645
+ userId,
646
+ role,
647
+ invitedBy
648
+ );
649
+
650
+ // Send invitation email (implement your email service)
651
+ // await this.emailService.sendInvitation(userId, tenantId);
652
+ }
653
+
654
+ async checkTenantAccess(tenantId: string, userId: string) {
655
+ const tenant = await this.tenantService.repo.findOne({
656
+ where: { id: tenantId }
657
+ });
658
+
659
+ if (!tenant.isActive) {
660
+ throw new UnauthorizedException('Tenant is inactive');
661
+ }
662
+
663
+ const hasAccess = await this.tenantUserService.hasAccess(tenantId, userId);
664
+ if (!hasAccess) {
665
+ throw new UnauthorizedException('No access to this tenant');
666
+ }
667
+
668
+ const hasSubscription = await this.tenantService.hasActiveSubscription(tenantId);
669
+ if (!hasSubscription) {
670
+ throw new UnauthorizedException('Subscription expired');
671
+ }
672
+
673
+ return true;
674
+ }
675
+ }
676
+
677
+ // Controller with tenant isolation
678
+ @Controller('projects')
679
+ @UseGuards(TenantGuard)
680
+ export class ProjectsController {
681
+ constructor(private readonly projectsService: ProjectsService) {}
682
+
683
+ @Get()
684
+ async list(@TenantId() tenantId: string, @UserId() userId: string) {
685
+ // Automatically scoped to tenant
686
+ return this.projectsService.findByTenant(tenantId);
687
+ }
688
+
689
+ @Post()
690
+ async create(
691
+ @TenantId() tenantId: string,
692
+ @UserId() userId: string,
693
+ @Body() dto: CreateProjectDto
694
+ ) {
695
+ return this.projectsService.create(tenantId, userId, dto);
696
+ }
697
+
698
+ @Delete(':id')
699
+ @Roles(TenantUserRole.OWNER, TenantUserRole.ADMIN)
700
+ @UseGuards(TenantRoleGuard)
701
+ async delete(@TenantId() tenantId: string, @Param('id') id: string) {
702
+ // Only owners and admins can delete
703
+ return this.projectsService.delete(tenantId, id);
704
+ }
705
+ }
706
+ ```
707
+
708
+ ---
709
+
710
+ ## Database Migrations
711
+
712
+ The package requires two database tables: `tenant` and `tenant_user`. Create migrations for both:
713
+
714
+ ### Tenant Table Migration
715
+
716
+ ```bash
717
+ npm run typeorm migration:create -- -n CreateTenant
718
+ ```
719
+
720
+ ```ts
721
+ import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
722
+
723
+ export class CreateTenant1234567890123 implements MigrationInterface {
724
+ public async up(queryRunner: QueryRunner): Promise<void> {
725
+ await queryRunner.createTable(
726
+ new Table({
727
+ name: 'tenant',
728
+ columns: [
729
+ { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
730
+ { name: 'name', type: 'varchar', isUnique: true },
731
+ { name: 'slug', type: 'varchar', isUnique: true },
732
+ { name: 'domain', type: 'varchar', isNullable: true },
733
+ { name: 'description', type: 'text', isNullable: true },
734
+ { name: 'isActive', type: 'boolean', default: true },
735
+ { name: 'settings', type: 'jsonb', isNullable: true },
736
+ { name: 'ownerId', type: 'varchar', isNullable: true },
737
+ { name: 'plan', type: 'varchar', isNullable: true },
738
+ { name: 'trialEndsAt', type: 'timestamptz', isNullable: true },
739
+ { name: 'subscriptionEndsAt', type: 'timestamptz', isNullable: true },
740
+ { name: 'createdAt', type: 'timestamptz', default: 'CURRENT_TIMESTAMP' },
741
+ { name: 'updatedAt', type: 'timestamptz', default: 'CURRENT_TIMESTAMP' },
742
+ ],
743
+ }),
744
+ true,
745
+ );
746
+
747
+ await queryRunner.createIndex('tenant', new TableIndex({ name: 'IDX_tenant_slug', columnNames: ['slug'] }));
748
+ await queryRunner.createIndex('tenant', new TableIndex({ name: 'IDX_tenant_domain', columnNames: ['domain'] }));
749
+ }
750
+
751
+ public async down(queryRunner: QueryRunner): Promise<void> {
752
+ await queryRunner.dropTable('tenant');
753
+ }
754
+ }
755
+ ```
756
+
757
+ ### TenantUser Table Migration
758
+
759
+ ```bash
760
+ npm run typeorm migration:create -- -n CreateTenantUser
761
+ ```
762
+
763
+ ```ts
764
+ import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
765
+
766
+ export class CreateTenantUser1234567890124 implements MigrationInterface {
767
+ public async up(queryRunner: QueryRunner): Promise<void> {
768
+ await queryRunner.createTable(
769
+ new Table({
770
+ name: 'tenant_user',
771
+ columns: [
772
+ { name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'uuid_generate_v4()' },
773
+ { name: 'tenantId', type: 'uuid' },
774
+ { name: 'userId', type: 'uuid' },
775
+ { name: 'role', type: 'enum', enum: ['owner', 'admin', 'member', 'viewer'], default: "'member'" },
776
+ { name: 'status', type: 'enum', enum: ['active', 'invited', 'suspended'], default: "'active'" },
777
+ { name: 'isPrimary', type: 'boolean', default: false },
778
+ { name: 'invitedBy', type: 'uuid', isNullable: true },
779
+ { name: 'invitedAt', type: 'timestamptz', isNullable: true },
780
+ { name: 'joinedAt', type: 'timestamptz', isNullable: true },
781
+ { name: 'permissions', type: 'jsonb', isNullable: true },
782
+ { name: 'createdAt', type: 'timestamptz', default: 'CURRENT_TIMESTAMP' },
783
+ { name: 'updatedAt', type: 'timestamptz', default: 'CURRENT_TIMESTAMP' },
784
+ ],
785
+ }),
786
+ true,
787
+ );
788
+
789
+ await queryRunner.createIndex('tenant_user', new TableIndex({ name: 'IDX_tenant_user_tenantId', columnNames: ['tenantId'] }));
790
+ await queryRunner.createIndex('tenant_user', new TableIndex({ name: 'IDX_tenant_user_userId', columnNames: ['userId'] }));
791
+ await queryRunner.createIndex('tenant_user', new TableIndex({ name: 'IDX_tenant_user_tenant_user', columnNames: ['tenantId', 'userId'], isUnique: true }));
792
+ }
793
+
794
+ public async down(queryRunner: QueryRunner): Promise<void> {
795
+ await queryRunner.dropTable('tenant_user');
796
+ }
797
+ }
798
+ ```
799
+
800
+ Run migrations:
801
+
802
+ ```bash
803
+ npm run typeorm migration:run
804
+ ```
805
+
806
+ ---
807
+
808
+ ## Testing
809
+
810
+ The module includes an isolated NestJS test environment for testing all functionality without integrating it into your main application.
811
+
812
+ ### Setup Test Environment
813
+
814
+ 1. **Copy environment configuration**:
815
+ ```bash
816
+ cd test
817
+ cp .env.example .env
818
+ ```
819
+
820
+ 2. **Configure your database** in `test/.env`:
821
+ ```env
822
+ DB_HOST=localhost
823
+ DB_PORT=5432
824
+ DB_USERNAME=postgres
825
+ DB_PASSWORD=postgres
826
+ DB_DATABASE=tenant_test
827
+ TEST_PORT=3001
828
+ ```
829
+
830
+ 3. **Install dependencies** (from the module root):
831
+ ```bash
832
+ npm install
833
+ ```
834
+
835
+ 4. **Create test database**:
836
+ ```bash
837
+ createdb tenant_test
838
+ # or using psql:
839
+ psql -U postgres -c "CREATE DATABASE tenant_test;"
840
+ ```
841
+
842
+ ### Run Test Server
843
+
844
+ Start the test server (runs on port 3001 by default):
845
+
846
+ ```bash
847
+ npm run test:dev
848
+ ```
849
+
850
+ Or with auto-reload on file changes:
851
+
852
+ ```bash
853
+ npm run test:watch
854
+ ```
855
+
856
+ The server will display all available endpoints:
857
+
858
+ ```
859
+ 🚀 Tenant Module Test Server running on: http://localhost:3001
860
+
861
+ 📋 Available endpoints:
862
+ Tenant Management:
863
+ POST /tenants - Create tenant
864
+ GET /tenants - Get all tenants
865
+ GET /tenants/active - Get active tenants
866
+ GET /tenants/owner/:ownerId - Get tenants by owner
867
+ GET /tenants/slug/:slug - Get tenant by slug
868
+ GET /tenants/domain/:domain - Get tenant by domain
869
+ GET /tenants/:id - Get tenant by ID
870
+ PUT /tenants/:id/settings - Update tenant settings
871
+ PUT /tenants/:id/status - Update tenant status
872
+ PUT /tenants/:id/plan - Update tenant plan
873
+ GET /tenants/:id/trial - Check if trial is active
874
+ GET /tenants/:id/subscription - Check if subscription is active
875
+ DELETE /tenants/:id - Delete tenant
876
+
877
+ User-Tenant Management:
878
+ POST /tenant-users/add - Add user to tenant
879
+ POST /tenant-users/invite - Invite user to tenant
880
+ GET /tenant-users/tenant/:tenantId - Get all users in tenant
881
+ GET /tenant-users/user/:userId - Get user's tenants
882
+ PUT /tenant-users/update-role - Update user role
883
+ POST /tenant-users/transfer-ownership - Transfer ownership
884
+ ... and more
885
+ ```
886
+
887
+ ### Testing with HTTP Requests
888
+
889
+ Use the provided `test/requests.http` file with REST Client (VS Code extension) or Postman:
890
+
891
+ 1. **Create a tenant**:
892
+ ```http
893
+ POST http://localhost:3001/tenants
894
+ Content-Type: application/json
895
+
896
+ {
897
+ "name": "Acme Corporation",
898
+ "slug": "acme-corp",
899
+ "ownerId": "user-123",
900
+ "domain": "acme.example.com"
901
+ }
902
+ ```
903
+
904
+ 2. **Add users to tenant**:
905
+ ```http
906
+ POST http://localhost:3001/tenant-users/add
907
+ Content-Type: application/json
908
+
909
+ {
910
+ "tenantId": "<tenant-id>",
911
+ "userId": "user-456",
912
+ "role": "member"
913
+ }
914
+ ```
915
+
916
+ 3. **Test role-based access**:
917
+ ```http
918
+ GET http://localhost:3001/tenant-users/role/<tenant-id>/user-456
919
+ ```
920
+
921
+ See `test/requests.http` for a complete set of example requests.
922
+
923
+ ### Test Structure
924
+
925
+ ```
926
+ test/
927
+ ├── .env.example # Environment configuration template
928
+ ├── main.ts # Test server bootstrap
929
+ ├── test-app.module.ts # Test NestJS application module
930
+ ├── requests.http # HTTP request examples
931
+ └── controllers/
932
+ ├── tenant-test.controller.ts # Tenant CRUD endpoints
933
+ └── tenant-user-test.controller.ts # User-tenant management endpoints
934
+ ```
935
+
936
+ ---
937
+
938
+ ## Development
939
+
940
+ ### Build the package
941
+
942
+ ```bash
943
+ npm run build
944
+ ```
945
+
946
+ ### Publish the package
947
+
948
+ ```bash
949
+ npm run release:patch
950
+ ```
951
+
952
+ ---
953
+
954
+ ## Dependencies
955
+
956
+ - `@nestjs/common` ^11.0.11
957
+ - `@nestjs/typeorm` ^10.0.0
958
+ - `@venturialstd/core` ^1.0.16
959
+ - `@dataui/crud-typeorm` (for CRUD operations)
960
+ - `typeorm` ^0.3.20
961
+ - `class-validator` ^0.14.1
962
+ - `class-transformer` ^0.5.1
963
+
964
+ ---
965
+
966
+ ## License
967
+
968
+ This package is part of the Venturial ecosystem and follows the organization's licensing.
969
+
970
+ ---
971
+
972
+ ## Support
973
+
974
+ For issues, questions, or contributions, please contact the Venturial development team.