@venturialstd/organization 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.
- package/README.md +678 -0
- package/dist/constants/organization.constant.d.ts +12 -0
- package/dist/constants/organization.constant.d.ts.map +1 -0
- package/dist/constants/organization.constant.js +17 -0
- package/dist/constants/organization.constant.js.map +1 -0
- package/dist/constants/organization.settings.constant.d.ts +11 -0
- package/dist/constants/organization.settings.constant.d.ts.map +1 -0
- package/dist/constants/organization.settings.constant.js +14 -0
- package/dist/constants/organization.settings.constant.js.map +1 -0
- package/dist/decorators/organization.decorator.d.ts +4 -0
- package/dist/decorators/organization.decorator.d.ts.map +1 -0
- package/dist/decorators/organization.decorator.js +22 -0
- package/dist/decorators/organization.decorator.js.map +1 -0
- package/dist/decorators/roles.decorator.d.ts +3 -0
- package/dist/decorators/roles.decorator.d.ts.map +1 -0
- package/dist/decorators/roles.decorator.js +7 -0
- package/dist/decorators/roles.decorator.js.map +1 -0
- package/dist/entities/organization-user.entity.d.ts +16 -0
- package/dist/entities/organization-user.entity.d.ts.map +1 -0
- package/dist/entities/organization-user.entity.js +120 -0
- package/dist/entities/organization-user.entity.js.map +1 -0
- package/dist/entities/organization.entity.d.ts +13 -0
- package/dist/entities/organization.entity.d.ts.map +1 -0
- package/dist/entities/organization.entity.js +94 -0
- package/dist/entities/organization.entity.js.map +1 -0
- package/dist/guards/organization.guard.d.ts +16 -0
- package/dist/guards/organization.guard.d.ts.map +1 -0
- package/dist/guards/organization.guard.js +90 -0
- package/dist/guards/organization.guard.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/organization.module.d.ts +3 -0
- package/dist/organization.module.d.ts.map +1 -0
- package/dist/organization.module.js +28 -0
- package/dist/organization.module.js.map +1 -0
- package/dist/services/organization-user.service.d.ts +30 -0
- package/dist/services/organization-user.service.d.ts.map +1 -0
- package/dist/services/organization-user.service.js +246 -0
- package/dist/services/organization-user.service.js.map +1 -0
- package/dist/services/organization.service.d.ts +18 -0
- package/dist/services/organization.service.d.ts.map +1 -0
- package/dist/services/organization.service.js +117 -0
- package/dist/services/organization.service.js.map +1 -0
- package/dist/settings/organization.settings.d.ts +3 -0
- package/dist/settings/organization.settings.d.ts.map +1 -0
- package/dist/settings/organization.settings.js +79 -0
- package/dist/settings/organization.settings.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,678 @@
|
|
|
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**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare enum ORGANIZATION_USER_ROLE {
|
|
2
|
+
OWNER = "owner",
|
|
3
|
+
ADMIN = "admin",
|
|
4
|
+
MEMBER = "member",
|
|
5
|
+
VIEWER = "viewer"
|
|
6
|
+
}
|
|
7
|
+
export declare enum ORGANIZATION_USER_STATUS {
|
|
8
|
+
ACTIVE = "active",
|
|
9
|
+
INVITED = "invited",
|
|
10
|
+
SUSPENDED = "suspended"
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=organization.constant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization.constant.d.ts","sourceRoot":"","sources":["../../src/constants/organization.constant.ts"],"names":[],"mappings":"AAAA,oBAAY,sBAAsB;IAChC,KAAK,UAAU;IACf,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,MAAM,WAAW;CAClB;AAED,oBAAY,wBAAwB;IAClC,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,SAAS,cAAc;CACxB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ORGANIZATION_USER_STATUS = exports.ORGANIZATION_USER_ROLE = void 0;
|
|
4
|
+
var ORGANIZATION_USER_ROLE;
|
|
5
|
+
(function (ORGANIZATION_USER_ROLE) {
|
|
6
|
+
ORGANIZATION_USER_ROLE["OWNER"] = "owner";
|
|
7
|
+
ORGANIZATION_USER_ROLE["ADMIN"] = "admin";
|
|
8
|
+
ORGANIZATION_USER_ROLE["MEMBER"] = "member";
|
|
9
|
+
ORGANIZATION_USER_ROLE["VIEWER"] = "viewer";
|
|
10
|
+
})(ORGANIZATION_USER_ROLE || (exports.ORGANIZATION_USER_ROLE = ORGANIZATION_USER_ROLE = {}));
|
|
11
|
+
var ORGANIZATION_USER_STATUS;
|
|
12
|
+
(function (ORGANIZATION_USER_STATUS) {
|
|
13
|
+
ORGANIZATION_USER_STATUS["ACTIVE"] = "active";
|
|
14
|
+
ORGANIZATION_USER_STATUS["INVITED"] = "invited";
|
|
15
|
+
ORGANIZATION_USER_STATUS["SUSPENDED"] = "suspended";
|
|
16
|
+
})(ORGANIZATION_USER_STATUS || (exports.ORGANIZATION_USER_STATUS = ORGANIZATION_USER_STATUS = {}));
|
|
17
|
+
//# sourceMappingURL=organization.constant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization.constant.js","sourceRoot":"","sources":["../../src/constants/organization.constant.ts"],"names":[],"mappings":";;;AAAA,IAAY,sBAKX;AALD,WAAY,sBAAsB;IAChC,yCAAe,CAAA;IACf,yCAAe,CAAA;IACf,2CAAiB,CAAA;IACjB,2CAAiB,CAAA;AACnB,CAAC,EALW,sBAAsB,sCAAtB,sBAAsB,QAKjC;AAED,IAAY,wBAIX;AAJD,WAAY,wBAAwB;IAClC,6CAAiB,CAAA;IACjB,+CAAmB,CAAA;IACnB,mDAAuB,CAAA;AACzB,CAAC,EAJW,wBAAwB,wCAAxB,wBAAwB,QAInC"}
|