Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/area/area.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
7 changes: 7 additions & 0 deletions src/area/domain/enums/area-jobs.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const enum AreaQueues {
AREA_WORKSPACE = 'AREA_WORKSPACE_QUEUE',
}

export const enum AreaWorkspaceJobs {
CREATE_AREA = 'AREA_CREATE_AREA',
}
1 change: 1 addition & 0 deletions src/area/domain/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AreaQueues, AreaWorkspaceJobs } from './area-jobs.enum';
6 changes: 6 additions & 0 deletions src/area/domain/events/area-workspace.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class AreaCreateEvent {
constructor(
readonly userId: string,
readonly projectSlug: string,
) {}
}
1 change: 1 addition & 0 deletions src/area/domain/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './area-workspace.event';
58 changes: 58 additions & 0 deletions src/area/infrastructure/workers/area.processor.ts
Original file line number Diff line number Diff line change
@@ -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<AreaCreateEvent>): Promise<void> {
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<AreaCreateEvent>) => {
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);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/auth/application/use-cases/oauth/exchange.use-case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
15 changes: 8 additions & 7 deletions src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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: [
Expand Down
5 changes: 1 addition & 4 deletions src/auth/domain/events/create-user-workspace.event.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
export class CreateUserWorkspaceEvent {
constructor(
public readonly userId: string,
public readonly username: string,
) {}
constructor(public readonly userId: string) {}
}
51 changes: 20 additions & 31 deletions src/auth/infrastructure/workers/user.processor.ts
Original file line number Diff line number Diff line change
@@ -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();
}
Expand All @@ -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:
Expand All @@ -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<CreateUserWorkspaceEvent>) => {
const { userId, username } = job.data;
private readonly handleCreateWorkspace = async (job: Job<CreateUserWorkspaceEvent>) => {
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);
};
}
17 changes: 15 additions & 2 deletions src/project/application/controllers/projects/controller.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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()
Expand Down Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions src/project/domain/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ProjectQueues, ProjectWorkspaceJobs } from './project-jobs.enum';
7 changes: 7 additions & 0 deletions src/project/domain/enums/project-jobs.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const enum ProjectQueues {
PROJECT_WORKSPACE = 'PROJECT_WORKSPACE_QUEUE',
}

export const enum ProjectWorkspaceJobs {
CREATE_PROJECT = 'PROJECT_CREATE_PROJECT',
}
6 changes: 6 additions & 0 deletions src/project/domain/events/create-project.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class ProjectCreateEvent {
constructor(
readonly userId: string,
readonly teamId: string,
) {}
}
1 change: 1 addition & 0 deletions src/project/domain/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './create-project.event';
68 changes: 68 additions & 0 deletions src/project/infrastructure/workers/project.processor.ts
Original file line number Diff line number Diff line change
@@ -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<ProjectCreateEvent>): Promise<void> {
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<ProjectCreateEvent>) => {
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}`,
);
};
}
15 changes: 13 additions & 2 deletions src/project/project.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
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';
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 {}
Loading
Loading