Organization Management Module for Venturial
npm install @venturialstd/organization\\bash
npm install @venturialstd/organization
\\\
\\json
{
"@nestjs/common": "^11.0.11",
"@nestjs/core": "^11.0.5",
"@nestjs/typeorm": "^10.0.0",
"@venturialstd/core": "^1.0.16",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"typeorm": "^0.3.20"
}
\\\
\\typescript
import { Module } from '@nestjs/common';
import { OrganizationModule } from '@venturialstd/organization';
@Module({
imports: [OrganizationModule],
})
export class AppModule {}
\\\
\\bash
Create and run migrations
npm run migration:generate -- CreateOrganizations
npm run migration:run
\\\
\\typescript
import { Injectable } from '@nestjs/common';
import {
OrganizationService,
OrganizationUserService,
ORGANIZATION_USER_ROLE
} from '@venturialstd/organization';
@Injectable()
export class YourService {
constructor(
private readonly organizationService: OrganizationService,
private readonly organizationUserService: OrganizationUserService,
) {}
async createOrganization(userId: string) {
return this.organizationService.createOrganization(
'My Organization',
'my-org',
'example.com',
'Description',
userId
);
}
async addMember(organizationId: string, userId: string) {
return this.organizationUserService.addUserToOrganization(
organizationId,
userId,
ORGANIZATION_USER_ROLE.MEMBER
);
}
}
\\\
\\
OrganizationModule
โโโ Entities
โ โโโ Organization # Organization entity
โ โโโ OrganizationUser # User-Organization relationship
โ โโโ OrganizationSettings # Per-organization settings
โโโ Services
โ โโโ OrganizationService # CRUD operations
โ โโโ OrganizationUserService # Member management
โ โโโ OrganizationSettingsService # Settings management
โโโ Guards
โ โโโ OrganizationGuard # Check user belongs to org
โ โโโ OrganizationRoleGuard # Check user role
โโโ Decorators
โโโ @OrganizationId() # Extract org ID from request
โโโ @UserId() # Extract user ID from request
โโโ @Roles() # Define required roles
โโโ @OrganizationContext() # Extract full context
\\\
\\
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Application โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Organization A โ โ
โ โ โข GitLab: gitlab-a.com + token-a โ โ
โ โ โข Jira: jira-a.com + credentials-a โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Organization B โ โ
โ โ โข GitLab: gitlab.com + token-b โ โ
โ โ โข Jira: [Not configured] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
\\\
\\typescript
class OrganizationService {
// Create a new organization
createOrganization(
name: string,
slug: string,
domain: string,
description: string,
ownerId: string
): Promise
// Get organization by ID
getOrganizationById(id: string): Promise
// Update organization
updateOrganization(id: string, data: Partial): Promise
// Delete organization
deleteOrganization(id: string): Promise
// Get user's organizations
getUserOrganizations(userId: string): Promise
}
\ \\
\\typescript
class OrganizationUserService {
// Add user to organization
addUserToOrganization(
organizationId: string,
userId: string,
role: ORGANIZATION_USER_ROLE
): Promise
// Remove user from organization
removeUserFromOrganization(
organizationId: string,
userId: string
): Promise
// Update user role
updateUserRole(
organizationId: string,
userId: string,
role: ORGANIZATION_USER_ROLE
): Promise
// Get organization members
getOrganizationMembers(organizationId: string): Promise
// Check user role
getUserRole(
organizationId: string,
userId: string
): Promise
}
\ \\
\\typescript
class OrganizationSettingsService {
// Get a single setting
get(organizationId: string, key: string): Promise
// Get multiple settings
getManySettings(
organizationId: string,
keys: string[]
): Promise>
// Get all settings for organization
getAllForOrganization(
organizationId: string,
module?: string
): Promise
// Set or update a setting
setOrganizationSetting(
organizationId: string,
dto: Partial
): Promise
// Delete a setting
deleteOrganizationSetting(
organizationId: string,
module: string,
section: string,
key: string
): Promise
// Reset all settings for a module
resetModuleSettings(
organizationId: string,
module: string
): Promise
// Bulk update settings
bulkUpdateSettings(
organizationId: string,
settings: Partial[]
): Promise
}
\ \\
\\typescript
class Organization {
id: string;
name: string;
slug: string;
domain: string;
description: string;
isActive: boolean;
settings: Record;
ownerId: string;
createdAt: Date;
updatedAt: Date;
}
\ \\
\\typescript
class OrganizationUser {
id: string;
organizationId: string;
userId: string;
role: ORGANIZATION_USER_ROLE;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
\\\
\\typescript
class OrganizationSettings {
id: string;
organizationId: string;
module: string; // e.g., 'GITLAB', 'JIRA'
section: string; // e.g., 'CREDENTIALS', 'OAUTH'
key: string; // e.g., 'TOKEN', 'API_KEY'
value: string;
type: SETTINGS_TYPE;
label: string;
description: string;
options: object | null;
sortOrder: number;
createdAt: Date;
updatedAt: Date;
}
\\\
\\typescript
// Extract organization ID from request
@Get()
getData(@OrganizationId() organizationId: string) { }
// Extract user ID from request
@Get()
getData(@UserId() userId: string) { }
// Extract full context
@Get()
getData(@OrganizationContext() context: OrganizationContextType) { }
// Define required roles
@Roles(ORGANIZATION_USER_ROLE.ADMIN, ORGANIZATION_USER_ROLE.OWNER)
@UseGuards(OrganizationRoleGuard)
adminEndpoint() { }
\\\
\\typescript
// Check user belongs to organization
@UseGuards(OrganizationGuard)
@Get('organizations/:organizationId/data')
getData() { }
// Check user has required role
@Roles(ORGANIZATION_USER_ROLE.ADMIN)
@UseGuards(OrganizationRoleGuard)
adminEndpoint() { }
\\\
\\typescript
// User roles
enum ORGANIZATION_USER_ROLE {
OWNER = 'owner',
ADMIN = 'admin',
MEMBER = 'member',
VIEWER = 'viewer',
}
// Settings types
enum SETTINGS_TYPE {
TEXT = 'text',
TEXTAREA = 'textarea',
NUMBER = 'number',
BOOLEAN = 'boolean',
SELECT = 'select',
MULTISELECT = 'multiselect',
JSON = 'json',
DATE = 'date',
SECRET = 'secret',
}
\\\
\\typescript
// Organization A
const tokenA = await orgSettings.get('org-a-id', 'GITLAB:CREDENTIALS:TOKEN');
// Returns: 'token-a'
// Organization B (not configured)
const tokenB = await orgSettings.get('org-b-id', 'GITLAB:CREDENTIALS:TOKEN');
// Returns: undefined (NO FALLBACK)
\\\
SETTINGS_TYPE\ enum for type-safe settings:
\\typescript
import { SETTINGS_TYPE } from '@venturialstd/organization';
const settings = [
{
module: 'GITLAB',
section: 'CREDENTIALS',
key: 'TOKEN',
type: SETTINGS_TYPE.SECRET, // Hides value in UI
// ...
},
{
module: 'GITLAB',
section: 'PREFERENCES',
key: 'AUTO_MERGE',
type: SETTINGS_TYPE.BOOLEAN, // true/false toggle
// ...
},
];
\\\
\\typescript
// src/your-module/settings/your-module.settings.ts
import { SETTINGS_TYPE } from '@venturialstd/organization';
export const SETTINGS = [
{
scope: 'ORGANIZATION',
module: 'YOUR_MODULE',
section: 'CREDENTIALS',
key: 'API_KEY',
label: 'API Key',
description: 'Your module API key',
type: SETTINGS_TYPE.SECRET,
value: '',
options: {
placeholder: 'Enter your API key',
help: 'Get your API key from your module dashboard',
},
sortOrder: 10,
},
{
scope: 'ORGANIZATION',
module: 'YOUR_MODULE',
section: 'CREDENTIALS',
key: 'API_SECRET',
label: 'API Secret',
description: 'Your module API secret',
type: SETTINGS_TYPE.SECRET,
value: '',
options: null,
sortOrder: 11,
},
];
\\\
\\typescript
import { Injectable } from '@nestjs/common';
import { OrganizationSettingsService } from '@venturialstd/organization';
@Injectable()
export class YourModuleService {
constructor(
private readonly orgSettings: OrganizationSettingsService
) {}
async connect(organizationId: string) {
// Get organization-specific credentials
const apiKey = await this.orgSettings.get(
organizationId,
'YOUR_MODULE:CREDENTIALS:API_KEY'
);
const apiSecret = await this.orgSettings.get(
organizationId,
'YOUR_MODULE:CREDENTIALS:API_SECRET'
);
// Check if configured
if (!apiKey || !apiSecret) {
throw new Error('Module not configured for this organization');
}
// Use organization-specific credentials
return this.client.connect({ apiKey, apiSecret });
}
async getMultipleSettings(organizationId: string) {
// Get multiple settings at once
const settings = await this.orgSettings.getManySettings(
organizationId,
[
'YOUR_MODULE:CREDENTIALS:API_KEY',
'YOUR_MODULE:CREDENTIALS:API_SECRET',
'YOUR_MODULE:PREFERENCES:TIMEOUT',
]
);
return settings;
}
}
\\\
\\typescript
import { Module } from '@nestjs/common';
import { OrganizationModule } from '@venturialstd/organization';
@Module({
imports: [OrganizationModule],
providers: [YourModuleService],
})
export class YourModule {}
\\\
\\sql
CREATE TABLE organization_settings (
id UUID PRIMARY KEY,
organizationId UUID REFERENCES organization(id) ON DELETE CASCADE,
module VARCHAR NOT NULL, -- 'GITLAB', 'JIRA', 'YOUR_MODULE'
section VARCHAR NOT NULL, -- 'CREDENTIALS', 'OAUTH', 'PREFERENCES'
key VARCHAR NOT NULL, -- 'TOKEN', 'API_KEY', 'HOST'
value VARCHAR NOT NULL, -- Actual value
type settings_type_enum NOT NULL,
label VARCHAR NOT NULL,
description VARCHAR,
options JSONB,
sortOrder INTEGER DEFAULT 0,
createdAt TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updatedAt TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(organizationId, module, section, key)
);
\\\
\\bash
cd src/organization
npm install
npm run start:dev
\\\
\\bash
Get all organizations
GET http://localhost:3002/test/organizations
Get organization by ID
GET http://localhost:3002/test/organizations/:id
Get organization settings
GET http://localhost:3002/test/organizations/:id/settings
Get settings for specific module
GET http://localhost:3002/test/organizations/:id/settings?module=GITLAB
Create/update setting
PUT http://localhost:3002/test/organizations/:id/settings
Content-Type: application/json
{
"module": "GITLAB",
"section": "CREDENTIALS",
"key": "TOKEN",
"value": "your-token",
"label": "GitLab Token",
"type": "secret"
}
\\\
test/controllers/organization-settings-test.controller.ts\
\\bash
Generate migration
npm run migration:generate -- YourMigrationName
Run migrations
npm run migration:run
Revert migration
npm run migration:revert
\\\
database/migrations/\
\\typescript
@Injectable()
export class OnboardingService {
constructor(
private readonly orgService: OrganizationService,
private readonly orgUserService: OrganizationUserService,
) {}
async onboardUser(userId: string, companyName: string) {
// Create organization
const org = await this.orgService.createOrganization(
companyName,
this.generateSlug(companyName),
\\${this.generateSlug(companyName)}.example.com\,
\\${companyName} organization\,
userId
);
// Owner is automatically added by createOrganization
return org;
}
private generateSlug(name: string): string {
return name.toLowerCase().replace(/\\s+/g, '-');
}
}
\\\
\\typescript
@Controller('organizations/:organizationId/data')
export class DataController {
constructor(
private readonly orgUserService: OrganizationUserService,
) {}
@Get()
@UseGuards(OrganizationGuard)
async getData(
@OrganizationId() organizationId: string,
@UserId() userId: string,
) {
const role = await this.orgUserService.getUserRole(
organizationId,
userId
);
// Check if user has required role
if (![ORGANIZATION_USER_ROLE.ADMIN, ORGANIZATION_USER_ROLE.OWNER].includes(role)) {
throw new ForbiddenException('Insufficient permissions');
}
// Return data
}
}
\\\
\\typescript
// GitLab Service
@Injectable()
export class GitLabService {
constructor(
private readonly orgSettings: OrganizationSettingsService
) {}
async getClient(organizationId: string) {
// Get org-specific GitLab credentials
const [host, token] = await Promise.all([
this.orgSettings.get(organizationId, 'GITLAB:CREDENTIALS:HOST'),
this.orgSettings.get(organizationId, 'GITLAB:CREDENTIALS:TOKEN'),
]);
// Validate configuration
if (!host || !token) {
throw new Error(
'GitLab not configured for this organization. ' +
'Please configure it in organization settings.'
);
}
// Return organization-specific client
return new GitLab({ host, token });
}
async getRepositories(organizationId: string) {
const client = await this.getClient(organizationId);
return client.Projects.all();
}
}
\\\
\\typescript
@Injectable()
export class OrganizationSetupService {
constructor(
private readonly orgSettings: OrganizationSettingsService,
) {}
async configureGitLab(
organizationId: string,
config: { host: string; token: string }
) {
return this.orgSettings.bulkUpdateSettings(organizationId, [
{
module: 'GITLAB',
section: 'CREDENTIALS',
key: 'HOST',
value: config.host,
label: 'GitLab Host',
type: SETTINGS_TYPE.TEXT,
},
{
module: 'GITLAB',
section: 'CREDENTIALS',
key: 'TOKEN',
value: config.token,
label: 'GitLab Token',
type: SETTINGS_TYPE.SECRET,
},
]);
}
}
\\\
\\typescript
@Injectable()
export class IntegrationService {
constructor(
private readonly orgSettings: OrganizationSettingsService,
) {}
async getIntegrationConfig(organizationId: string, integration: string) {
// Get all settings for this integration
const settings = await this.orgSettings.getAllForOrganization(
organizationId,
integration
);
// Check if configured
if (settings.length === 0) {
return {
configured: false,
message: \\${integration} is not configured for this organization\,
};
}
// Convert to key-value object
const config = settings.reduce((acc, setting) => {
acc[\\${setting.section}:\${setting.key}\] = setting.value;
return acc;
}, {});
return {
configured: true,
config,
};
}
}
\\\
\\typescript
// โ Don't assume settings exist
const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN');
await client.connect(token); // Could be undefined!
// โ
Always validate
const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN');
if (!token) {
throw new Error('Module not configured');
}
await client.connect(token);
\\\
\\typescript
// โ Multiple single calls
const host = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:HOST');
const token = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:TOKEN');
const secret = await orgSettings.get(orgId, 'MODULE:CREDENTIALS:SECRET');
// โ
Single bulk call
const settings = await orgSettings.getManySettings(orgId, [
'MODULE:CREDENTIALS:HOST',
'MODULE:CREDENTIALS:TOKEN',
'MODULE:CREDENTIALS:SECRET',
]);
\\\
\\typescript
// โ Don't use test controller in production
import { OrganizationSettingsTestController } from '@venturialstd/organization';
// โ
Implement your own
@Controller('api/organizations/:organizationId/settings')
@UseGuards(JwtAuthGuard, OrganizationGuard)
export class OrganizationSettingsController {
// Your implementation with proper auth
}
\\\
\\typescript
// โ
Import and use the enum
import { SETTINGS_TYPE } from '@venturialstd/organization';
const setting = {
type: SETTINGS_TYPE.SECRET, // Type-safe
// ...
};
\\\