From c261f17090da2290c653ee77c1c3dc578adb85c6 Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 26 Jun 2026 19:34:05 +0300 Subject: [PATCH] feat(workspace): implement multi-level workspace creation flow with team, project, and area --- src/area/area.module.ts | 10 ++- src/area/domain/enums/area-jobs.enum.ts | 7 ++ src/area/domain/enums/index.ts | 1 + .../domain/events/area-workspace.event.ts | 6 ++ src/area/domain/events/index.ts | 1 + .../infrastructure/workers/area.processor.ts | 58 ++++++++++++++++ .../use-cases/auth/sign-up-verify.use-case.ts | 2 +- .../use-cases/oauth/exchange.use-case.ts | 2 +- src/auth/auth.module.ts | 15 ++-- .../events/create-user-workspace.event.ts | 5 +- .../infrastructure/workers/user.processor.ts | 51 ++++++-------- .../controllers/projects/controller.ts | 17 ++++- src/project/domain/enums/index.ts | 1 + src/project/domain/enums/project-jobs.enum.ts | 7 ++ .../domain/events/create-project.event.ts | 6 ++ src/project/domain/events/index.ts | 1 + .../workers/project.processor.ts | 68 +++++++++++++++++++ src/project/project.module.ts | 15 +++- .../controllers/team/controller.ts | 22 ++++-- src/team/application/use-cases/index.ts | 1 - src/team/domain/enums/index.ts | 2 +- .../{mail-jobs.enum.ts => team-jobs.enum.ts} | 5 ++ src/team/domain/events/create-team.event.ts | 3 + src/team/infrastructure/workers/index.ts | 1 + .../infrastructure/workers/team.processor.ts | 64 +++++++++++++++++ src/team/team.module.ts | 21 +++--- 26 files changed, 325 insertions(+), 67 deletions(-) create mode 100644 src/area/domain/enums/area-jobs.enum.ts create mode 100644 src/area/domain/enums/index.ts create mode 100644 src/area/domain/events/area-workspace.event.ts create mode 100644 src/area/domain/events/index.ts create mode 100644 src/area/infrastructure/workers/area.processor.ts create mode 100644 src/project/domain/enums/index.ts create mode 100644 src/project/domain/enums/project-jobs.enum.ts create mode 100644 src/project/domain/events/create-project.event.ts create mode 100644 src/project/domain/events/index.ts create mode 100644 src/project/infrastructure/workers/project.processor.ts rename src/team/domain/enums/{mail-jobs.enum.ts => team-jobs.enum.ts} (56%) create mode 100644 src/team/domain/events/create-team.event.ts create mode 100644 src/team/infrastructure/workers/team.processor.ts diff --git a/src/area/area.module.ts b/src/area/area.module.ts index 9ffe21e6..cf1a10a5 100644 --- a/src/area/area.module.ts +++ b/src/area/area.module.ts @@ -1,15 +1,21 @@ import { ProjectModule } from '@core/project'; +import { BullModule } from '@nestjs/bullmq'; import { forwardRef, Module } from '@nestjs/common'; import { AreaFacade } from './application/area.facade'; import { CONTROLLERS } from './application/controllers'; import { GetAreaQuery, GetStateQuery, USE_CASES } from './application/use-cases'; +import { AreaQueues } from './domain/enums/area-jobs.enum'; import { REPOSITORIES } from './infrastructure/persistence/repositories'; +import { AreaProcessor } from './infrastructure/workers/area.processor'; @Module({ - imports: [forwardRef(() => ProjectModule)], + imports: [ + BullModule.registerQueue({ name: AreaQueues.AREA_WORKSPACE }), + forwardRef(() => ProjectModule), + ], controllers: [...CONTROLLERS], - providers: [...REPOSITORIES, ...USE_CASES, AreaFacade], + providers: [...REPOSITORIES, ...USE_CASES, AreaFacade, AreaProcessor], exports: [GetAreaQuery, GetStateQuery], }) export class AreaModule {} diff --git a/src/area/domain/enums/area-jobs.enum.ts b/src/area/domain/enums/area-jobs.enum.ts new file mode 100644 index 00000000..cff3840c --- /dev/null +++ b/src/area/domain/enums/area-jobs.enum.ts @@ -0,0 +1,7 @@ +export const enum AreaQueues { + AREA_WORKSPACE = 'AREA_WORKSPACE_QUEUE', +} + +export const enum AreaWorkspaceJobs { + CREATE_AREA = 'AREA_CREATE_AREA', +} diff --git a/src/area/domain/enums/index.ts b/src/area/domain/enums/index.ts new file mode 100644 index 00000000..e4c04b81 --- /dev/null +++ b/src/area/domain/enums/index.ts @@ -0,0 +1 @@ +export { AreaQueues, AreaWorkspaceJobs } from './area-jobs.enum'; diff --git a/src/area/domain/events/area-workspace.event.ts b/src/area/domain/events/area-workspace.event.ts new file mode 100644 index 00000000..c212c864 --- /dev/null +++ b/src/area/domain/events/area-workspace.event.ts @@ -0,0 +1,6 @@ +export class AreaCreateEvent { + constructor( + readonly userId: string, + readonly projectSlug: string, + ) {} +} diff --git a/src/area/domain/events/index.ts b/src/area/domain/events/index.ts new file mode 100644 index 00000000..c52aeabd --- /dev/null +++ b/src/area/domain/events/index.ts @@ -0,0 +1 @@ +export * from './area-workspace.event'; diff --git a/src/area/infrastructure/workers/area.processor.ts b/src/area/infrastructure/workers/area.processor.ts new file mode 100644 index 00000000..6c941fe3 --- /dev/null +++ b/src/area/infrastructure/workers/area.processor.ts @@ -0,0 +1,58 @@ +import { CreateAreaUseCase } from '@core/area/application/use-cases'; +import { AreaQueues, AreaWorkspaceJobs } from '@core/area/domain/enums'; +import { AreaCreateEvent } from '@core/area/domain/events'; +import { Processor, WorkerHost } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job } from 'bullmq'; + +@Injectable() +@Processor(AreaQueues.AREA_WORKSPACE) +export class AreaProcessor extends WorkerHost { + constructor(private readonly createAreaUseCase: CreateAreaUseCase) { + super(); + } + + async process(job: Job): Promise { + await job.log(`[START] Job ID: ${job.id} | Type: ${job.name}`); + + try { + switch (job.name) { + case AreaWorkspaceJobs.CREATE_AREA: + await this.handleProjectCreated(job); + break; + + default: + await job.log(`[WRN] No handler for job: ${job.name}`); + await job.updateProgress(100); + } + + await job.log(`[DONE] Job ${job.id} processed`); + } catch (error) { + await job.log(String(error)); + throw error; + } + } + + private readonly handleProjectCreated = async (job: Job) => { + const { userId, projectSlug } = job.data; + + await job.log(`Start creating default area for project with slug "${projectSlug}"`); + await job.updateProgress(20); + + const timestampSuffix = Date.now().toString(36); + + await this.createAreaUseCase.execute( + projectSlug, + { + title: `Моя доска`, + description: `Доска по умолчанию`, + slug: `my-area-${timestampSuffix}`, + isLocked: false, + }, + userId, + ); + + await job.log(`Area created successfully for project ${projectSlug}`); + await job.updateProgress(100); + }; +} diff --git a/src/auth/application/use-cases/auth/sign-up-verify.use-case.ts b/src/auth/application/use-cases/auth/sign-up-verify.use-case.ts index bd6860d7..fe59df7f 100644 --- a/src/auth/application/use-cases/auth/sign-up-verify.use-case.ts +++ b/src/auth/application/use-cases/auth/sign-up-verify.use-case.ts @@ -113,7 +113,7 @@ export class SignUpVerifyUseCase { await this.cacheService.removeOne(SIGNUP_CACHE_KEY(dto.email)); - const event = new CreateUserWorkspaceEvent(user.id, user.firstName); + const event = new CreateUserWorkspaceEvent(user.id); await this.queue.add(AuthUserJobs.CREATE_WORKSPACE, event); return { diff --git a/src/auth/application/use-cases/oauth/exchange.use-case.ts b/src/auth/application/use-cases/oauth/exchange.use-case.ts index 06f7633b..67cb24da 100644 --- a/src/auth/application/use-cases/oauth/exchange.use-case.ts +++ b/src/auth/application/use-cases/oauth/exchange.use-case.ts @@ -40,7 +40,7 @@ export class ExchangeUseCase { const tokens = await this.createSession(user.id, user.email, meta); if (isNewUser) { - const event = new CreateUserWorkspaceEvent(user.id, user.firstName); + const event = new CreateUserWorkspaceEvent(user.id); await this.queue.add(AuthUserJobs.CREATE_WORKSPACE, event); } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index c75e30a6..e003019d 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,8 +1,7 @@ -import { ProjectModule } from '@core/project'; -import { TeamModule } from '@core/team'; +import { TeamQueues } from '@core/team/domain/enums'; import { UserModule } from '@core/user'; import { BullModule } from '@nestjs/bullmq'; -import { forwardRef, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtModule } from '@nestjs/jwt'; import { MailAdapter } from '@shared/adapters/mail'; @@ -41,10 +40,12 @@ const WORKERS = [MailProcessor, UserProcessor]; }, }), }), - BullModule.registerQueue({ name: AuthQueues.AUTH_MAIL }, { name: AuthQueues.AUTH_USER }), - forwardRef(() => UserModule), - TeamModule, - ProjectModule, + BullModule.registerQueue( + { name: AuthQueues.AUTH_MAIL }, + { name: AuthQueues.AUTH_USER }, + { name: TeamQueues.TEAM_WORKSPACE }, + ), + UserModule, ], controllers: CONTROLLERS, providers: [ diff --git a/src/auth/domain/events/create-user-workspace.event.ts b/src/auth/domain/events/create-user-workspace.event.ts index 2b3b6bb7..29330aba 100644 --- a/src/auth/domain/events/create-user-workspace.event.ts +++ b/src/auth/domain/events/create-user-workspace.event.ts @@ -1,6 +1,3 @@ export class CreateUserWorkspaceEvent { - constructor( - public readonly userId: string, - public readonly username: string, - ) {} + constructor(public readonly userId: string) {} } diff --git a/src/auth/infrastructure/workers/user.processor.ts b/src/auth/infrastructure/workers/user.processor.ts index 5907863c..faf7a6c6 100644 --- a/src/auth/infrastructure/workers/user.processor.ts +++ b/src/auth/infrastructure/workers/user.processor.ts @@ -1,17 +1,18 @@ -import { AuthQueues } from '@core/auth/domain/enums'; -import { AuthUserJobs } from '@core/auth/domain/enums/auth-jobs.enum'; -import { CreateUserWorkspaceEvent } from '@core/auth/domain/events'; -import { CreateProjectUseCase } from '@core/project/application/use-cases'; -import { CreateTeamUseCase } from '@core/team/application/use-cases'; -import { Processor, WorkerHost } from '@nestjs/bullmq'; -import { Job } from 'bullmq'; -import slugify from 'slugify'; +import { TeamQueues, TeamWorkspaceJobs } from '@core/team/domain/enums'; +import { CreateTeamEvent } from '@core/team/domain/events/create-team.event'; +import { Processor, InjectQueue, WorkerHost } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; +import { AuthQueues, AuthUserJobs } from '../../domain/enums'; +import { CreateUserWorkspaceEvent } from '../../domain/events'; + +@Injectable() @Processor(AuthQueues.AUTH_USER) export class UserProcessor extends WorkerHost { constructor( - private readonly createTeamUseCase: CreateTeamUseCase, - private readonly createProjectUseCase: CreateProjectUseCase, + @InjectQueue(TeamQueues.TEAM_WORKSPACE) + private readonly teamQueue: Queue, ) { super(); } @@ -22,7 +23,7 @@ export class UserProcessor extends WorkerHost { try { switch (job.name) { case AuthUserJobs.CREATE_WORKSPACE: - await this.createWorkspace(job); + await this.handleCreateWorkspace(job); break; default: @@ -33,34 +34,22 @@ export class UserProcessor extends WorkerHost { await job.log(`[DONE] Job ${job.id} processed`); } catch (error) { await job.log(String(error)); - throw error; } } - private readonly createWorkspace = async (job: Job) => { - const { userId, username } = job.data; + private readonly handleCreateWorkspace = async (job: Job) => { + const { userId } = job.data; - await job.log(`Start creating a workspace for ${username}`); + await job.log(`Start workspace creation flow for user with ID${userId}`); await job.updateProgress(20); - const team = await this.createTeamUseCase.execute(userId, { - name: username, - description: `Personal team for ${username}`, - }); - - await this.createProjectUseCase.execute(userId, team.teamId, { - name: `${username}'s Project`, - description: `Personal project for ${username}`, - slug: slugify(username.slice(0, 10), { - lower: true, - strict: true, - }), - status: 'active', - visibility: 'private', - }); + const event = new CreateTeamEvent(userId); + await this.teamQueue.add(TeamWorkspaceJobs.CREATE_TEAM, event); - await job.log(`Successfully created a workspace for ${username}`); + await job.log( + `Event ${TeamWorkspaceJobs.CREATE_TEAM} sent to team queue for user ${userId}`, + ); await job.updateProgress(100); }; } diff --git a/src/project/application/controllers/projects/controller.ts b/src/project/application/controllers/projects/controller.ts index d7a8eb57..418e4f01 100644 --- a/src/project/application/controllers/projects/controller.ts +++ b/src/project/application/controllers/projects/controller.ts @@ -1,5 +1,9 @@ +import { AreaQueues, AreaWorkspaceJobs } from '@core/area/domain/enums'; +import { AreaCreateEvent } from '@core/area/domain/events'; +import { InjectQueue } from '@nestjs/bullmq'; import { Body, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; import { ApiBaseController, GetUserId, Public } from '@shared/decorators'; +import { Queue } from 'bullmq'; import { CreateProjectDto, CreateShareTokenDto, ProjectQuery, UpdateProjectDto } from '../../dtos'; import { ProjectFacade } from '../../project.facade'; @@ -17,7 +21,11 @@ import { @ApiBaseController('teams/:teamId/projects', 'Projects', true) export class ProjectsController { - constructor(private readonly facade: ProjectFacade) {} + constructor( + private readonly facade: ProjectFacade, + @InjectQueue(AreaQueues.AREA_WORKSPACE) + private readonly areaQueue: Queue, + ) {} @Get() @FindAllProjectsSwagger() @@ -75,7 +83,12 @@ export class ProjectsController { @GetUserId() userId: string, @Body() dto: CreateProjectDto, ) { - return this.facade.create(userId, teamId, dto); + const result = await this.facade.create(userId, teamId, dto); + + const event = new AreaCreateEvent(userId, result.slug); + await this.areaQueue.add(AreaWorkspaceJobs.CREATE_AREA, event); + + return result; } @Patch(':slug') diff --git a/src/project/domain/enums/index.ts b/src/project/domain/enums/index.ts new file mode 100644 index 00000000..b6abcfbf --- /dev/null +++ b/src/project/domain/enums/index.ts @@ -0,0 +1 @@ +export { ProjectQueues, ProjectWorkspaceJobs } from './project-jobs.enum'; diff --git a/src/project/domain/enums/project-jobs.enum.ts b/src/project/domain/enums/project-jobs.enum.ts new file mode 100644 index 00000000..03c3b1f8 --- /dev/null +++ b/src/project/domain/enums/project-jobs.enum.ts @@ -0,0 +1,7 @@ +export const enum ProjectQueues { + PROJECT_WORKSPACE = 'PROJECT_WORKSPACE_QUEUE', +} + +export const enum ProjectWorkspaceJobs { + CREATE_PROJECT = 'PROJECT_CREATE_PROJECT', +} diff --git a/src/project/domain/events/create-project.event.ts b/src/project/domain/events/create-project.event.ts new file mode 100644 index 00000000..006a0a13 --- /dev/null +++ b/src/project/domain/events/create-project.event.ts @@ -0,0 +1,6 @@ +export class ProjectCreateEvent { + constructor( + readonly userId: string, + readonly teamId: string, + ) {} +} diff --git a/src/project/domain/events/index.ts b/src/project/domain/events/index.ts new file mode 100644 index 00000000..e9cd3198 --- /dev/null +++ b/src/project/domain/events/index.ts @@ -0,0 +1 @@ +export * from './create-project.event'; diff --git a/src/project/infrastructure/workers/project.processor.ts b/src/project/infrastructure/workers/project.processor.ts new file mode 100644 index 00000000..165a3de3 --- /dev/null +++ b/src/project/infrastructure/workers/project.processor.ts @@ -0,0 +1,68 @@ +import { AreaQueues, AreaWorkspaceJobs } from '@core/area/domain/enums'; +import { AreaCreateEvent } from '@core/area/domain/events'; +import { CreateProjectUseCase } from '@core/project/application/use-cases'; +import { ProjectQueues, ProjectWorkspaceJobs } from '@core/project/domain/enums'; +import { ProjectCreateEvent } from '@core/project/domain/events'; +import { Processor, WorkerHost, InjectQueue } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; + +@Injectable() +@Processor(ProjectQueues.PROJECT_WORKSPACE) +export class ProjectProcessor extends WorkerHost { + constructor( + private readonly createProjectUseCase: CreateProjectUseCase, + @InjectQueue(AreaQueues.AREA_WORKSPACE) + private readonly areaQueue: Queue, + ) { + super(); + } + + async process(job: Job): Promise { + await job.log(`[START] Job ID: ${job.id} | Type: ${job.name}`); + + try { + switch (job.name) { + case ProjectWorkspaceJobs.CREATE_PROJECT: + await this.handleTeamCreated(job); + break; + + default: + await job.log(`[WRN] No handler for job: ${job.name}`); + await job.updateProgress(100); + } + + await job.log(`[DONE] Job ${job.id} processed`); + } catch (error) { + await job.log(String(error)); + throw error; + } + } + + private readonly handleTeamCreated = async (job: Job) => { + const { teamId, userId } = job.data; + + await job.log(`Start creating default project for team with ID "${teamId}"`); + await job.updateProgress(20); + + const timestampSuffix = Date.now().toString(36); + + const project = await this.createProjectUseCase.execute(userId, teamId, { + name: `Мой проект`, + description: `Проект по умолчанию`, + slug: `my-project-${timestampSuffix}`, + status: 'active', + visibility: 'private', + }); + + await job.log(`Project created: ${project.slug}`); + await job.updateProgress(100); + + const event = new AreaCreateEvent(userId, project.slug); + await this.areaQueue.add(AreaWorkspaceJobs.CREATE_AREA, event); + + await job.log( + `Event ${AreaWorkspaceJobs.CREATE_AREA} sent to area queue for project with slug ${project.slug}`, + ); + }; +} diff --git a/src/project/project.module.ts b/src/project/project.module.ts index c01f1544..7b66c41e 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -1,5 +1,8 @@ +import { AreaQueues } from '@core/area/domain/enums'; +import { ProjectQueues } from '@core/project/domain/enums'; import { TeamModule } from '@core/team'; import { UserModule } from '@core/user'; +import { BullModule } from '@nestjs/bullmq'; import { forwardRef, Module } from '@nestjs/common'; import { CONTROLLERS } from './application/controllers'; @@ -7,11 +10,19 @@ import { ProjectFacade } from './application/project.facade'; import { USE_CASES, EXPORT_USE_CASES } from './application/use-cases'; import { POLICIES } from './domain/policy'; import { REPOSITORIES } from './infrastructure/persistence/repositories'; +import { ProjectProcessor } from './infrastructure/workers/project.processor'; @Module({ - imports: [UserModule, forwardRef(() => TeamModule)], + imports: [ + BullModule.registerQueue( + { name: ProjectQueues.PROJECT_WORKSPACE }, + { name: AreaQueues.AREA_WORKSPACE }, + ), + UserModule, + forwardRef(() => TeamModule), + ], controllers: CONTROLLERS, - providers: [...REPOSITORIES, ...POLICIES, ...USE_CASES, ProjectFacade], + providers: [...REPOSITORIES, ...POLICIES, ...USE_CASES, ProjectFacade, ProjectProcessor], exports: [...EXPORT_USE_CASES, ...POLICIES], }) export class ProjectModule {} diff --git a/src/team/application/controllers/team/controller.ts b/src/team/application/controllers/team/controller.ts index 2011b261..d5f054a1 100644 --- a/src/team/application/controllers/team/controller.ts +++ b/src/team/application/controllers/team/controller.ts @@ -1,7 +1,12 @@ -import { CreateTeamDto, UpdateTeamDto } from '@core/team/application/dtos'; -import { TeamFacade } from '@core/team/application/team.facade'; +import { ProjectQueues, ProjectWorkspaceJobs } from '@core/project/domain/enums'; +import { ProjectCreateEvent } from '@core/project/domain/events'; +import { InjectQueue } from '@nestjs/bullmq'; import { Body, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post } from '@nestjs/common'; import { ApiBaseController, GetUserId } from '@shared/decorators'; +import { Queue } from 'bullmq'; + +import { CreateTeamDto, UpdateTeamDto } from '../../dtos'; +import { TeamFacade } from '../../team.facade'; import { CreateTeamSwagger, @@ -12,12 +17,21 @@ import { @ApiBaseController('teams', 'Teams', true) export class TeamController { - constructor(private readonly facade: TeamFacade) {} + constructor( + private readonly facade: TeamFacade, + @InjectQueue(ProjectQueues.PROJECT_WORKSPACE) + private readonly projectQueue: Queue, + ) {} @Post() @CreateTeamSwagger() async create(@GetUserId() userId: string, @Body() dto: CreateTeamDto) { - return this.facade.createTeam(userId, dto); + const result = await this.facade.createTeam(userId, dto); + + const event = new ProjectCreateEvent(userId, result.teamId); + await this.projectQueue.add(ProjectWorkspaceJobs.CREATE_PROJECT, event); + + return result; } @Get(':id') diff --git a/src/team/application/use-cases/index.ts b/src/team/application/use-cases/index.ts index 0cf70ae1..5bae3ea3 100644 --- a/src/team/application/use-cases/index.ts +++ b/src/team/application/use-cases/index.ts @@ -38,7 +38,6 @@ export const TeamUseCases = [ ]; export const TEAM_EXTERNAL_QUERIES = [FindTeamQuery, FindTeamMemberQuery]; -export const TEAM_EXTERNAL_COMMANDS = [CreateTeamUseCase]; export { FindTeamQuery } from './base/find-team.query'; export { FindTeamMemberQuery } from './members/find-team-member.query'; diff --git a/src/team/domain/enums/index.ts b/src/team/domain/enums/index.ts index 4a727802..2fdccd70 100644 --- a/src/team/domain/enums/index.ts +++ b/src/team/domain/enums/index.ts @@ -1 +1 @@ -export { TeamMailJobs, TeamQueues } from './mail-jobs.enum'; +export { TeamQueues, TeamMailJobs, TeamWorkspaceJobs } from './team-jobs.enum'; diff --git a/src/team/domain/enums/mail-jobs.enum.ts b/src/team/domain/enums/team-jobs.enum.ts similarity index 56% rename from src/team/domain/enums/mail-jobs.enum.ts rename to src/team/domain/enums/team-jobs.enum.ts index da9a52a8..596e83db 100644 --- a/src/team/domain/enums/mail-jobs.enum.ts +++ b/src/team/domain/enums/team-jobs.enum.ts @@ -1,7 +1,12 @@ export const enum TeamQueues { TEAM_MAIL = 'TEAM_MAIL_QUEUE', + TEAM_WORKSPACE = 'TEAM_WORKSPACE_QUEUE', } export const enum TeamMailJobs { SEND_TEAM_INVITATION = 'TEAM_SEND_TEAM_INVITATION', } + +export const enum TeamWorkspaceJobs { + CREATE_TEAM = 'TEAM_CREATE_TEAM', +} diff --git a/src/team/domain/events/create-team.event.ts b/src/team/domain/events/create-team.event.ts new file mode 100644 index 00000000..fc6a08b5 --- /dev/null +++ b/src/team/domain/events/create-team.event.ts @@ -0,0 +1,3 @@ +export class CreateTeamEvent { + constructor(public readonly userId: string) {} +} diff --git a/src/team/infrastructure/workers/index.ts b/src/team/infrastructure/workers/index.ts index d20e25dd..b9170a61 100644 --- a/src/team/infrastructure/workers/index.ts +++ b/src/team/infrastructure/workers/index.ts @@ -1 +1,2 @@ export { MailProcessor } from './mail.processor'; +export { TeamProcessor } from './team.processor'; diff --git a/src/team/infrastructure/workers/team.processor.ts b/src/team/infrastructure/workers/team.processor.ts new file mode 100644 index 00000000..6337c21e --- /dev/null +++ b/src/team/infrastructure/workers/team.processor.ts @@ -0,0 +1,64 @@ +import { ProjectQueues, ProjectWorkspaceJobs } from '@core/project/domain/enums'; +import { ProjectCreateEvent } from '@core/project/domain/events/create-project.event'; +import { CreateTeamUseCase } from '@core/team/application/use-cases'; +import { TeamQueues, TeamWorkspaceJobs } from '@core/team/domain/enums'; +import { Processor, WorkerHost, InjectQueue } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; + +import { CreateTeamEvent } from '../../domain/events/create-team.event'; + +@Injectable() +@Processor(TeamQueues.TEAM_WORKSPACE) +export class TeamProcessor extends WorkerHost { + constructor( + private readonly createTeamUseCase: CreateTeamUseCase, + @InjectQueue(ProjectQueues.PROJECT_WORKSPACE) + private readonly projectQueue: Queue, + ) { + super(); + } + + async process(job: Job): Promise { + await job.log(`[START] Job ID: ${job.id} | Type: ${job.name}`); + + try { + switch (job.name) { + case TeamWorkspaceJobs.CREATE_TEAM: + await this.handleCreateTeam(job); + break; + + default: + await job.log(`[WRN] No handler for job: ${job.name}`); + await job.updateProgress(100); + } + + await job.log(`[DONE] Job ${job.id} processed`); + } catch (error) { + await job.log(String(error)); + throw error; + } + } + + private readonly handleCreateTeam = async (job: Job) => { + const { userId } = job.data; + + await job.log(`Start creating default team for user with ID${userId}`); + await job.updateProgress(20); + + const team = await this.createTeamUseCase.execute(userId, { + name: `Моя команда`, + description: `Команда по умолчанию`, + }); + + await job.log( + `Default team with ID "${team.teamId}" successfully created for user with ID "${userId}"`, + ); + await job.updateProgress(100); + + const event = new ProjectCreateEvent(userId, team.teamId); + await this.projectQueue.add(ProjectWorkspaceJobs.CREATE_PROJECT, event); + + await job.log(`Event team.created sent to project queue for team ${team.teamId}`); + }; +} diff --git a/src/team/team.module.ts b/src/team/team.module.ts index 24a18c59..0a7c46cd 100644 --- a/src/team/team.module.ts +++ b/src/team/team.module.ts @@ -1,3 +1,4 @@ +import { ProjectQueues } from '@core/project/domain/enums'; import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; @@ -8,25 +9,22 @@ import { MeController, } from './application/controllers'; import { TeamFacade } from './application/team.facade'; -import { - TeamQueries, - TeamUseCases, - TEAM_EXTERNAL_QUERIES, - TEAM_EXTERNAL_COMMANDS, -} from './application/use-cases'; +import { TeamQueries, TeamUseCases, TEAM_EXTERNAL_QUERIES } from './application/use-cases'; import { TeamQueues } from './domain/enums'; import { TeamMemberPolicy } from './domain/policy'; import { LISTENERS } from './infrastructure/listeners'; import { TeamRepository } from './infrastructure/persistence/repositories'; -import { MailProcessor } from './infrastructure/workers'; +import { MailProcessor, TeamProcessor } from './infrastructure/workers'; const REPOSITORY = { provide: 'ITeamRepository', useClass: TeamRepository }; @Module({ imports: [ - BullModule.registerQueue({ - name: TeamQueues.TEAM_MAIL, - }), + BullModule.registerQueue( + { name: TeamQueues.TEAM_MAIL }, + { name: TeamQueues.TEAM_WORKSPACE }, + { name: ProjectQueues.PROJECT_WORKSPACE }, + ), ], controllers: [TeamInvitationsController, TeamMembersController, TeamController, MeController], providers: [ @@ -37,7 +35,8 @@ const REPOSITORY = { provide: 'ITeamRepository', useClass: TeamRepository }; ...TeamQueries, TeamFacade, MailProcessor, + TeamProcessor, ], - exports: [...TEAM_EXTERNAL_QUERIES, ...TEAM_EXTERNAL_COMMANDS], + exports: [...TEAM_EXTERNAL_QUERIES], }) export class TeamModule {}