Multi-environment and multi-user construction
This commit is contained in:
@@ -45,6 +45,17 @@ export class UpdateOrgCurrenciesDto {
|
||||
rates?: ExchangeRateInputDto[];
|
||||
}
|
||||
|
||||
export class CreateOrganizationDto {
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@MaxLength(120)
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([...BUSINESS_FIELDS])
|
||||
businessField?: (typeof BUSINESS_FIELDS)[number];
|
||||
}
|
||||
|
||||
export class UpdateOrganizationDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
import { Body, Controller, Get, Patch, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Patch, Post, UseGuards } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { RolesGuard } from '../auth/roles.guard';
|
||||
import { Roles } from '../auth/roles.decorator';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import { UpdateOrgCurrenciesDto, UpdateOrganizationDto } from './dto';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import {
|
||||
CreateOrganizationDto,
|
||||
UpdateOrgCurrenciesDto,
|
||||
UpdateOrganizationDto,
|
||||
} from './dto';
|
||||
import { OrganizationsService } from './organizations.service';
|
||||
|
||||
@Controller('organizations')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class OrganizationsController {
|
||||
constructor(private readonly organizationsService: OrganizationsService) {}
|
||||
constructor(
|
||||
private readonly organizationsService: OrganizationsService,
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@CurrentUser() user: { userId: string },
|
||||
@Body() dto: CreateOrganizationDto,
|
||||
) {
|
||||
const org = await this.organizationsService.createForUser(user.userId, dto);
|
||||
return this.authService.switchOrganization(user.userId, org.id);
|
||||
}
|
||||
|
||||
@Get('current')
|
||||
current(@CurrentUser() user: { organizationId: string }) {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { MembershipsModule } from '../memberships/memberships.module';
|
||||
import { OrgRolesModule } from '../org-roles/org-roles.module';
|
||||
import { PipelinesModule } from '../pipelines/pipelines.module';
|
||||
import { OrganizationsController } from './organizations.controller';
|
||||
import { OrganizationsService } from './organizations.service';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, MembershipsModule, OrgRolesModule, PipelinesModule],
|
||||
controllers: [OrganizationsController],
|
||||
providers: [OrganizationsService],
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import type {
|
||||
OrganizationDto,
|
||||
@@ -11,10 +12,74 @@ import { CURRENCY_CATALOG, isBusinessField } from '@mento/shared';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { isKnownCurrency } from '../common/currency';
|
||||
import { MembershipsService } from '../memberships/memberships.service';
|
||||
import { OrgRolesService } from '../org-roles/org-roles.service';
|
||||
import { PipelinesService } from '../pipelines/pipelines.service';
|
||||
|
||||
function slugify(input: string): string {
|
||||
return (
|
||||
input
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\u0600-\u06FF]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, 48) || `org-${Date.now()}`
|
||||
);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly memberships: MembershipsService,
|
||||
private readonly orgRoles: OrgRolesService,
|
||||
private readonly pipelines: PipelinesService,
|
||||
) {}
|
||||
|
||||
async createForUser(
|
||||
userId: string,
|
||||
dto: { name: string; businessField?: string },
|
||||
): Promise<{ id: string }> {
|
||||
const name = dto.name.trim();
|
||||
if (name.length < 2) {
|
||||
throw new BadRequestException('نام محیط معتبر نیست');
|
||||
}
|
||||
const businessField =
|
||||
dto.businessField && isBusinessField(dto.businessField)
|
||||
? dto.businessField
|
||||
: 'personal';
|
||||
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { id: true, isActive: true },
|
||||
});
|
||||
if (!user || user.isActive === false) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
const baseSlug = slugify(name);
|
||||
let slug = baseSlug;
|
||||
let i = 1;
|
||||
while (await this.prisma.organization.findUnique({ where: { slug } })) {
|
||||
slug = `${baseSlug}-${i++}`;
|
||||
}
|
||||
|
||||
const organization = await this.prisma.organization.create({
|
||||
data: {
|
||||
name,
|
||||
slug,
|
||||
businessField,
|
||||
},
|
||||
});
|
||||
await this.memberships.createMembership({
|
||||
userId,
|
||||
organizationId: organization.id,
|
||||
role: 'owner',
|
||||
});
|
||||
await this.orgRoles.ensureDefaultRoles(organization.id);
|
||||
await this.pipelines.ensureDefaultPipeline(organization.id);
|
||||
return { id: organization.id };
|
||||
}
|
||||
|
||||
async getCurrent(organizationId: string): Promise<OrganizationDto> {
|
||||
const org = await this.prisma.organization.findUnique({
|
||||
|
||||
Reference in New Issue
Block a user