diff --git a/src/app.ts b/src/app.ts index b1260af..eff3621 100644 --- a/src/app.ts +++ b/src/app.ts @@ -71,18 +71,7 @@ spec.paths = Object.keys(spec.paths).reduce( {} as Record, ); -app.use('/docs', ...swaggerUi.serve, (req, res, next) => { - const baseUrl = `${req.protocol}://${req.get('host')}${req.baseUrl}`; - - spec.servers = [ - { - url: `${baseUrl}/v1`, - description: 'Dynamic Server', - }, - ]; - - swaggerUi.setup(spec)(req, res, next); -}); +app.use('/docs', ...swaggerUi.serve, swaggerUi.setup(spec)); useExpressServer(app, { routePrefix: '/v1', diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index c252857..5f41c54 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -168,7 +168,7 @@ export class AuthService { } async getCurrentUser(token: string): Promise { - token = token.slice(7); + // token = token.slice(7); if (token.startsWith('bta_')) { return this.authenticateProjectApiToken(token); } diff --git a/src/modules/project/dto/create-project.dto.ts b/src/modules/project/dto/create-project.dto.ts index dc11c52..21f1ca0 100644 --- a/src/modules/project/dto/create-project.dto.ts +++ b/src/modules/project/dto/create-project.dto.ts @@ -11,6 +11,9 @@ export class CreateProjectDto { @MinLength(3, { message: 'Project name must be more than 3 characters', }) + @MaxLength(20, { + message: 'Project name must be more than 3 characters', + }) name: string; @IsString() @@ -21,7 +24,7 @@ export class CreateProjectDto { message: 'Project slug must start with a small letter and contain only small letters, numbers and dashes', }) - @MaxLength(50, { + @MaxLength(30, { message: 'Project slug must be less than 50 characters', }) slug: string; diff --git a/src/modules/project/dto/generate-project-api-key.dto.ts b/src/modules/project/dto/generate-project-api-key.dto.ts index 9df779c..f4791a5 100644 --- a/src/modules/project/dto/generate-project-api-key.dto.ts +++ b/src/modules/project/dto/generate-project-api-key.dto.ts @@ -12,7 +12,6 @@ export class GenerateProjectApiKeyDto { }) name: string; - @IsString() @IsNotEmpty({ message: 'Api Key expire time not provided', }) diff --git a/src/modules/project/project.controller.ts b/src/modules/project/project.controller.ts index a0a565d..b39dd8a 100644 --- a/src/modules/project/project.controller.ts +++ b/src/modules/project/project.controller.ts @@ -1,5 +1,5 @@ -import { request } from 'express'; -import { Authorized, Body, Delete, Get, JsonController, Post, Put } from 'routing-controllers'; +import { Request } from 'express'; +import { Body, Delete, Get, JsonController, Param, Post, Put, Req } from 'routing-controllers'; import { Inject, Service } from 'typedi'; import { JsonResponse } from '../shared'; @@ -39,15 +39,16 @@ import { ProjectService } from './project.service'; @Service() @JsonController('/projects') -@Authorized() export class ProjectController { constructor(@Inject() private readonly projectService: ProjectService) {} @Post('/') async create( @Body({ validate: true }) payload: CreateProjectDto, + @Req() request: Request, ): Promise> { - const response = await this.projectService.create(payload as CreateProjectRequest); + const authData = request.auth; + const response = await this.projectService.create(payload as CreateProjectRequest, authData); return { data: response, status: 'successful', @@ -56,7 +57,7 @@ export class ProjectController { } @Get('/') - async list(): Promise> { + async list(@Req() request: Request): Promise> { const authData = request.auth; const response = await this.projectService.list(authData); return { @@ -67,7 +68,10 @@ export class ProjectController { } @Get('/:projectId') - async get(projectId: number): Promise> { + async get( + @Param('projectId') projectId: number, + @Req() request: Request, + ): Promise> { const authData = request.auth; const response = await this.projectService.get(projectId, authData); return { @@ -78,7 +82,10 @@ export class ProjectController { } @Delete('/:projectId') - async delete(projectId: number): Promise { + async delete( + @Param('projectId') projectId: number, + @Req() request: Request, + ): Promise { const authData = request.auth; const response = await this.projectService.delete(projectId, authData); return { @@ -90,7 +97,8 @@ export class ProjectController { @Post('/:projectId/api-key') async generateApiKey( @Body({ validate: true }) payload: GenerateProjectApiKeyDto, - projectId: number, + @Req() request: Request, + @Param('projectId') projectId: number, ): Promise> { const authData = request.auth; const response = await this.projectService.generateApiKey( @@ -106,7 +114,10 @@ export class ProjectController { } @Get('/:projectId/api-key') - async listApiKeys(projectId: number): Promise> { + async listApiKeys( + @Param('projectId') projectId: number, + @Req() request: Request, + ): Promise> { const authData = request.auth; const response = await this.projectService.listApiKeys(projectId, authData); return { @@ -117,7 +128,11 @@ export class ProjectController { } @Delete('/:projectId/api-key/:apiKeyId') - async revokeApiKey(projectId: number, apiKeyId: number): Promise { + async revokeApiKey( + @Req() request: Request, + @Param('projectId') projectId: number, + @Param('apiKeyId') apiKeyId: number, + ): Promise { const authData = request.auth; this.projectService.revokeApiKey(projectId, apiKeyId, authData); @@ -129,8 +144,9 @@ export class ProjectController { @Post('/:projectId/collection') async createCollection( + @Req() request: Request, @Body({ validate: true }) payload: CreateProjectCollectionDto, - projectId: number, + @Param('projectId') projectId: number, ): Promise> { const authData = request.auth; const response = await this.projectService.createCollection( @@ -146,7 +162,10 @@ export class ProjectController { } @Get('/:projectId/collection') - async listCollection(projectId: number): Promise> { + async listCollection( + @Req() request: Request, + @Param('projectId') projectId: number, + ): Promise> { const authData = request.auth; const response = await this.projectService.listCollections(projectId, authData); @@ -159,8 +178,9 @@ export class ProjectController { @Get('/:projectId/collection/:collectionId') async getCollection( - projectId: number, - collectionId: number, + @Req() request: Request, + @Param('projectId') projectId: number, + @Param('collectionId') collectionId: number, ): Promise> { const authData = request.auth; const response = await this.projectService.getCollection(projectId, collectionId, authData); @@ -173,8 +193,8 @@ export class ProjectController { @Get('/:projectId/collection/:collectionId/data') async getCollectionData( - projectId: number, - collectionId: number, + @Param('projectId') projectId: number, + @Param('collectionId') collectionId: number, ): Promise> { const response = await this.projectService.getCollectionData(projectId, collectionId); return { @@ -186,8 +206,9 @@ export class ProjectController { @Put('/:projectId/collection/:collectionId') async updateCollection( - projectId: number, - collectionId: number, + @Req() request: Request, + @Param('projectId') projectId: number, + @Param('collectionId') collectionId: number, @Body({ validate: true }) payload: UpdateProjectCollectionDto, ): Promise { const authData = request.auth; @@ -203,12 +224,20 @@ export class ProjectController { }; } - @Post('/:projectId') + @Put('/:projectId/collection/:collectionId/register-device') async registerDevice( - projectId: number, + @Req() request: Request, + @Param('projectId') projectId: number, + @Param('collectionId') collectionId: number, @Body({ validate: true }) payload: RegisterDeviceDto, ): Promise { - const response = await this.registerDevice(projectId, payload); + const authData = request.auth; + const response = await this.projectService.registerDevice( + projectId, + collectionId, + authData, + payload, + ); return { status: response ? 'successful' : 'error', message: response ? 'Device Successfully Registered' : 'Unable to register device', @@ -217,7 +246,8 @@ export class ProjectController { @Post('/:projectId/invitations') async inviteProjectUser( - projectId: number, + @Req() request: Request, + @Param('projectId') projectId: number, @Body({ validate: true }) payload: InviteProjectUserDto, ): Promise { await this.projectService.inviteUser( @@ -233,6 +263,8 @@ export class ProjectController { @Post('/:projectId/invitations/accept') async acceptProjectInvitation( + @Req() request: Request, + @Param('projectId') projectId: number, @Body({ validate: true }) payload: AcceptProjectInvitationDto, ): Promise { @@ -243,11 +275,12 @@ export class ProjectController { }; } - @Delete('/:project/invitations/cancel') + @Delete('/:projectId/invitations/cancel') async cancelProjectInvitation( + @Req() request: Request, @Body({ validate: true }) payload: CancelProjectInvitationDto, - projectId: number, + @Param('projectId') projectId: number, ): Promise { await this.projectService.cancelInvite( projectId, @@ -260,9 +293,10 @@ export class ProjectController { }; } - @Get('/:project/invitations/') + @Get('/:projectId/invitations/') async listProjectInvitations( - projectId: number, + @Req() request: Request, + @Param('projectId') projectId: number, ): Promise> { const response = await this.projectService.listInvites(projectId, request.auth); return { @@ -273,7 +307,10 @@ export class ProjectController { } @Get('/:projectId/users') - async listProjectUser(projectId: number): Promise> { + async listProjectUser( + @Req() request: Request, + @Param('projectId') projectId: number, + ): Promise> { const response = await this.projectService.listUsers(projectId, request.auth); return { status: 'successful', @@ -284,8 +321,9 @@ export class ProjectController { @Delete('/:projectId/users/:userId') async removeProjectUser( - projectId: number, - userId: number, + @Req() request: Request, + @Param('projectId') projectId: number, + @Param('userId') userId: number, ): Promise> { await this.projectService.removeUser(projectId, userId, request.auth); return { @@ -296,8 +334,9 @@ export class ProjectController { @Put('/:projectId/users/:userId') async updateProjectUser( - projectId: number, - userId: number, + @Req() request: Request, + @Param('projectId') projectId: number, + @Param('userId') userId: number, @Body({ validate: true }) payload: UpdateProjectUserPermissionDto, ): Promise> { await this.projectService.updateUserPermission( diff --git a/src/modules/project/project.interface.ts b/src/modules/project/project.interface.ts index 8aae868..834ac5d 100644 --- a/src/modules/project/project.interface.ts +++ b/src/modules/project/project.interface.ts @@ -17,7 +17,6 @@ export interface CreateProjectResponse { export interface CreateProjectRequest { name: string; slug: string; - authData: AuthData; } export type ListProjectResponse = ProjectForUserDTO[]; diff --git a/src/modules/project/project.service.test.ts b/src/modules/project/project.service.test.ts index 66d2150..77418bc 100644 --- a/src/modules/project/project.service.test.ts +++ b/src/modules/project/project.service.test.ts @@ -51,10 +51,11 @@ describe('ProjectService', () => { 'getProjectUsers', 'removeProjectUser', 'updateProjectUserPermissions', + 'getProjectBySlug', ); configService = generateMockObject('getTransformed'); mailSenderService = generateMockObject('sendMail'); - userRepository = generateMockObject('findUserByEmailAddress'); + userRepository = generateMockObject('findUserByEmailAddress', 'isUserBlacklisted'); storage = generateMockObject( 'uploadFile', 'getFilePublicUrl', @@ -76,17 +77,21 @@ describe('ProjectService', () => { it('project should be create successfully', async () => { // arrange projectRepository.createProject.mockResolvedValue(1); + projectRepository.getProjectBySlug.mockResolvedValue(null); + userRepository.isUserBlacklisted.mockResolvedValue(false); // act - const result = await projectService.create({ - authData: { + const result = await projectService.create( + { + name: 'test', + slug: 'test', + }, + { isAuthenticated: true, projectId: 1, userId: 1, }, - name: 'test', - slug: 'test', - }); + ); // asset expect(result).toStrictEqual({ @@ -99,18 +104,22 @@ describe('ProjectService', () => { it('project should be not be created successfully if database crete fails', async () => { // arrange projectRepository.createProject.mockResolvedValue(0); + projectRepository.getProjectBySlug.mockResolvedValue(null); + userRepository.isUserBlacklisted.mockResolvedValue(false); // act try { - await projectService.create({ - authData: { + await projectService.create( + { + name: 'test', + slug: 'test', + }, + { isAuthenticated: true, projectId: 1, userId: 1, }, - name: 'test', - slug: 'test', - }); + ); } catch (error) { // asset expect(error).toBeInstanceOf(ApplicationError); @@ -1540,7 +1549,6 @@ describe('ProjectService', () => { projectId, targetUserId, ['readUsers'], - null, ); }); @@ -1563,7 +1571,6 @@ describe('ProjectService', () => { projectId, targetUserId, ['admin'], - null, ); }); diff --git a/src/modules/project/project.service.ts b/src/modules/project/project.service.ts index 5ab89ab..d814a48 100644 --- a/src/modules/project/project.service.ts +++ b/src/modules/project/project.service.ts @@ -50,15 +50,34 @@ export class ProjectService { @Inject() private configService: ConfigService, ) {} - async create(request: CreateProjectRequest): Promise { - const projectId = await this.projectRepository.createProject( - { - creatorId: request.authData.userId!, - name: request.name, - slug: request.slug, - }, - null, - ); + async create(request: CreateProjectRequest, authData: AuthData): Promise { + // check if the project slug already exists + const projectExists = await this.projectRepository.getProjectBySlug(request.slug); + if (projectExists) { + throw new ApplicationError({ + logger: this.logger, + message: 'Project already exists with slug provided', + errorCode: applicationErrorCodes.invalidRequest, + userFriendlyMessage: 'Project already exists with slug provided', + }); + } + + // check if user is not yet blacklisted + const userIsBlacklisted = await this.userRepository.isUserBlacklisted(authData.userId!); + if (userIsBlacklisted) { + throw new ApplicationError({ + logger: this.logger, + message: 'User is blacklisted', + errorCode: applicationErrorCodes.unauthorized, + userFriendlyMessage: 'You are not authorized to create a project', + }); + } + + const projectId = await this.projectRepository.createProject({ + creatorId: authData.userId!, + name: request.name, + slug: request.slug, + }); if (!projectId) { throw new ApplicationError({ @@ -76,7 +95,7 @@ export class ProjectService { } async list(authData: AuthData): Promise { - const projects = await this.projectRepository.getProjectsByUserId(authData.userId!, null); + const projects = await this.projectRepository.getProjectsByUserId(authData.userId!); if (!projects) { throw new ApplicationError({ @@ -90,7 +109,7 @@ export class ProjectService { } async get(projectId: number, authData: AuthData): Promise { - const projectDetails = await this.projectRepository.getProjectDetails(projectId, null); + const projectDetails = await this.projectRepository.getProjectDetails(projectId); if (!projectDetails) { throw new ApplicationError({ @@ -105,7 +124,6 @@ export class ProjectService { projectId, authData.userId!, 'readUsers', - null, ); if (!canReadUsers) { @@ -120,7 +138,6 @@ export class ProjectService { projectId, authData.userId!, 'admin', - null, ); if (!hasPermission) { @@ -131,7 +148,7 @@ export class ProjectService { }); } - await this.projectRepository.deleteProject(projectId, null); + await this.projectRepository.deleteProject(projectId); return true; } @@ -153,7 +170,6 @@ export class ProjectService { projectId, authData.userId!, 'admin', - null, ); if (!isAdmin) { @@ -175,7 +191,6 @@ export class ProjectService { name: request.name, }, request.force, - null, ); if (!apiKey) { @@ -196,7 +211,6 @@ export class ProjectService { projectId, authData.userId!, 'admin', - null, ); if (!isAdmin) { @@ -207,7 +221,7 @@ export class ProjectService { }); } - const apiKeys = await this.projectRepository.getProjectApiKeys(projectId, null); + const apiKeys = await this.projectRepository.getProjectApiKeys(projectId); return { keys: apiKeys, @@ -219,7 +233,6 @@ export class ProjectService { projectId, authData.userId!, 'admin', - null, ); if (!isAdmin) { @@ -230,7 +243,7 @@ export class ProjectService { }); } - await this.projectRepository.revokeAPIKey(projectId, apiKeyId, null); + await this.projectRepository.revokeAPIKey(projectId, apiKeyId); } async createCollection( @@ -242,7 +255,6 @@ export class ProjectService { projectId, authData.userId!, 'writeCollections', - null, ); if (!canWriteCollections) { @@ -253,7 +265,7 @@ export class ProjectService { }); } - const project = await this.projectRepository.getProjectById(projectId, null); + const project = await this.projectRepository.getProjectById(projectId); if (!project) { throw new ApplicationError({ logger: this.logger, @@ -277,7 +289,6 @@ export class ProjectService { request.name, fileName, request.isPublic, - null, ); return { @@ -293,7 +304,6 @@ export class ProjectService { projectId, authData.userId!, 'readCollections', - null, ); if (!canReadCollections) { @@ -304,7 +314,7 @@ export class ProjectService { }); } - const collections = await this.projectRepository.getProjectCollections(projectId, null); + const collections = await this.projectRepository.getProjectCollections(projectId); return collections; } @@ -318,7 +328,6 @@ export class ProjectService { projectId, authData.userId!, 'writeCollections', - null, ); if (!canWriteCollections) { @@ -332,7 +341,6 @@ export class ProjectService { const collection = await this.projectRepository.getProjectCollectionById( projectId, collectionId, - null, ); if (!collection) { @@ -360,7 +368,6 @@ export class ProjectService { const collection = await this.projectRepository.getProjectCollectionById( projectId, collectionId, - null, ); if (collection == null) { @@ -396,7 +403,6 @@ export class ProjectService { projectId, authData.userId!, 'readCollections', - null, ); if (!canReadCollections) { @@ -432,7 +438,6 @@ export class ProjectService { projectId, authData.userId!, 'writeCollections', - null, ); if (!canReadCollections) { @@ -443,7 +448,7 @@ export class ProjectService { }); } - const project = await this.projectRepository.getProjectById(projectId, null); + const project = await this.projectRepository.getProjectById(projectId); if (!project) { throw new ApplicationError({ @@ -456,7 +461,6 @@ export class ProjectService { const collection = await this.projectRepository.getProjectCollectionById( projectId, collectionId, - null, ); if (!collection) { @@ -476,7 +480,6 @@ export class ProjectService { request.name, collection!.uri, request.isPublic, - null, ); } @@ -489,7 +492,6 @@ export class ProjectService { projectId, authData.userId!, 'writeUsers', - null, ); if (!canWriteUser) { @@ -500,7 +502,7 @@ export class ProjectService { }); } - const project = await this.projectRepository.getProjectById(projectId, null); + const project = await this.projectRepository.getProjectById(projectId); if (!project) { throw new ApplicationError({ message: 'Project not found', @@ -516,14 +518,11 @@ export class ProjectService { projectName: project.name, }); - await this.projectRepository.inviteUserToProject( - { - emailAddress: request.emailAddress, - permissions: request.permissions, - projectId, - }, - null, - ); + await this.projectRepository.inviteUserToProject({ + emailAddress: request.emailAddress, + permissions: request.permissions, + projectId, + }); await this.mailSender.sendMail({ subject: `You have been invited to ${project.name} on Bonadocs`, @@ -556,12 +555,9 @@ export class ProjectService { } const { projectId, projectName, userName, emailAddress } = tokenPayload as TokenPayload; - await this.projectRepository.acceptProjectInvitation(projectId, emailAddress as string, null); + await this.projectRepository.acceptProjectInvitation(projectId, emailAddress as string); - const invitedUser = await this.userRepository.findUserByEmailAddress( - emailAddress as string, - null, - ); + const invitedUser = await this.userRepository.findUserByEmailAddress(emailAddress as string); if (!invitedUser) { throw new ApplicationError({ message: 'User not found, possibly registration not yet complete', @@ -582,7 +578,6 @@ export class ProjectService { const permission = await this.projectRepository.getInvitationPermissionFlags( emailAddress as string, projectId, - null, ); await this.projectRepository.addUserToProject(projectId, authData.userId!, permission); @@ -613,7 +608,6 @@ export class ProjectService { projectId, authData.userId!, 'writeCollections', - null, ); if (!canWriteCollections) { @@ -624,7 +618,7 @@ export class ProjectService { }); } - await this.projectRepository.cancelProjectInvitation(projectId, request.emailAddress, null); + await this.projectRepository.cancelProjectInvitation(projectId, request.emailAddress); } async listInvites(projectId: number, authData: AuthData): Promise { @@ -632,7 +626,6 @@ export class ProjectService { projectId, authData.userId!, 'readUsers', - null, ); if (!canReadUsers) { @@ -643,7 +636,7 @@ export class ProjectService { }); } - return this.projectRepository.listProjectInvitations(projectId, null); + return this.projectRepository.listProjectInvitations(projectId); } async listUsers(projectId: number, authData: AuthData): Promise { @@ -651,7 +644,6 @@ export class ProjectService { projectId, authData.userId!, 'readUsers', - null, ); if (!canReadUsers) { @@ -662,7 +654,7 @@ export class ProjectService { }); } - const data = await this.projectRepository.getProjectUsers(projectId, null); + const data = await this.projectRepository.getProjectUsers(projectId); if (!data) { throw new ApplicationError({ @@ -682,7 +674,6 @@ export class ProjectService { projectId, authData.userId!, 'writeUsers', - null, ); if (!canWriteUsers) { @@ -693,7 +684,7 @@ export class ProjectService { }); } - await this.projectRepository.removeProjectUser(projectId, userId, null); + await this.projectRepository.removeProjectUser(projectId, userId); } async updateUserPermission( @@ -706,7 +697,6 @@ export class ProjectService { projectId, authData.userId!, 'writeUsers', - null, ); if (!canWriteUsers) { @@ -722,7 +712,6 @@ export class ProjectService { Number(projectId), authData.userId!, 'admin', - null, ); if (!isAdmin) { @@ -738,7 +727,6 @@ export class ProjectService { projectId, userId, request.permissions, - null, ); } } diff --git a/src/modules/repositories/projects/project.repository.ts b/src/modules/repositories/projects/project.repository.ts index 3361d34..33a79b8 100644 --- a/src/modules/repositories/projects/project.repository.ts +++ b/src/modules/repositories/projects/project.repository.ts @@ -4,7 +4,11 @@ import { diConstants } from '@bonadocs/di'; import { BonadocsLogger } from '@bonadocs/logger'; import { DbContext, withDbContext } from '../../connection/dbcontext'; -import { SubscriptionResource, SubscriptionStatus } from '../../shared/types/subscriptions'; +import { + SubscriptionPlan, + SubscriptionResource, + SubscriptionStatus, +} from '../../shared/types/subscriptions'; import { queries } from './queries'; import { @@ -28,6 +32,7 @@ import { flagsToPermissions, hasPermission, PermissionName, + ProjectPermissions, } from './util'; @Service() @@ -35,10 +40,7 @@ export class ProjectRepository { constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} @withDbContext - async createProject( - data: CreateProjectDto, - context: DbContext | null, - ): Promise { + async createProject(data: CreateProjectDto, context?: DbContext): Promise { try { context?.beginTransaction(); const createProjectResult = await context?.query({ @@ -50,6 +52,20 @@ export class ProjectRepository { const projectId = createProjectResult?.rows[0].id; + // subscribe to plan + const plan = await this.getPlanById(SubscriptionPlan.free, context!); + await this.subscribeProjectToPlan(plan!, projectId, context!); + + // add admin user + await context!.query({ + text: queries.addUserToProject, + values: [projectId, data.creatorId, ProjectPermissions.admin], + validateResult: (result) => !!result.rowCount, + validationErrorMessage: 'Failed to add user to project', + }); + + await context?.commitTransaction(); + return projectId; } catch (error) { context?.rollbackTransaction(); @@ -65,7 +81,6 @@ export class ProjectRepository { context: DbContext, ): Promise { try { - context.beginTransaction(); const day = 24 * 60 * 60 * 1000; const createProjectSubscriptionResult = await context.query({ text: queries.createProjectSubscription, @@ -79,7 +94,11 @@ export class ProjectRepository { validateResult: (result) => !!result.rowCount, validationErrorMessage: 'Failed to create subscription', }); - return createProjectSubscriptionResult.rows[0].id; + const subscriptionId = createProjectSubscriptionResult.rows[0].id; + + // create subscription resources + await this.addPlanResourceToSubscription(subscriptionId, plan.resources, context); + return subscriptionId; } catch (error) { context.rollbackTransaction(); this.logger.error('Failed to subscribe project to plan', error); @@ -94,7 +113,6 @@ export class ProjectRepository { context: DbContext, ): Promise { try { - context.beginTransaction(); await Promise.all( resources.map(async (resource) => { await context.query({ @@ -122,7 +140,7 @@ export class ProjectRepository { } @withDbContext - async deleteProject(projectId: number, context?: DbContext | null): Promise { + async deleteProject(projectId: number, context?: DbContext): Promise { try { await context!.beginTransaction(); await context!.query({ @@ -140,7 +158,7 @@ export class ProjectRepository { } @withDbContext - async getProjectById(id: number, context?: DbContext | null): Promise { + async getProjectById(id: number, context?: DbContext): Promise { const result = await context!.query({ text: queries.getProjectById, values: [id], @@ -162,7 +180,7 @@ export class ProjectRepository { @withDbContext async getProjectDetails( projectId: number, - context?: DbContext | null, + context?: DbContext, ): Promise { const project = await this.getProjectById(projectId, context!); if (!project) { @@ -203,8 +221,8 @@ export class ProjectRepository { } @withDbContext - async getProjectBySlug(slug: string, context: DbContext): Promise { - const result = await context.query({ + async getProjectBySlug(slug: string, context?: DbContext): Promise { + const result = await context!.query({ text: queries.getProjectBySlug, values: [slug], }); @@ -223,10 +241,7 @@ export class ProjectRepository { } @withDbContext - async getProjectsByUserId( - userId: number, - context?: DbContext | null, - ): Promise { + async getProjectsByUserId(userId: number, context?: DbContext): Promise { const result = await context!.query({ text: queries.getProjectsByUserId, values: [userId], @@ -264,7 +279,7 @@ export class ProjectRepository { } @withDbContext - async revokeAPIKey(projectId: number, id: number, context?: DbContext | null): Promise { + async revokeAPIKey(projectId: number, id: number, context?: DbContext): Promise { await context!.query({ text: queries.revokeApiKey, values: [id, projectId], @@ -277,27 +292,35 @@ export class ProjectRepository { async generateApiKey( data: GenerateApiKeyDto, force: boolean, - context?: DbContext | null, + context?: DbContext, ): Promise { - if (!force) { - await context!.query({ - text: queries.addProjectApiKey, - values: [data.projectId, data.name, data.apiKeyHash, data.expiryDate], - validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to add API key', - }); - } else { - await context!.query({ - text: queries.updateProjectApiKey, - values: [data.projectId, data.name, data.apiKeyHash, data.expiryDate], - validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update API key', - }); + try { + context?.beginTransaction(); + if (!force) { + await context!.query({ + text: queries.addProjectApiKey, + values: [data.projectId, data.name, data.apiKeyHash, data.expiryDate], + validateResult: (result) => !!result.rowCount, + validationErrorMessage: 'Failed to add API key', + }); + } else { + await context!.query({ + text: queries.updateProjectApiKey, + values: [data.projectId, data.name, data.apiKeyHash, data.expiryDate], + validateResult: (result) => !!result.rowCount, + validationErrorMessage: 'Failed to update API key', + }); + } + context?.commitTransaction(); + } catch (error) { + this.logger.error('Transaction failed:', error); + await context?.rollbackTransaction(); + throw error; } } @withDbContext - async getProjectApiKeys(projectId: number, context?: DbContext | null): Promise { + async getProjectApiKeys(projectId: number, context?: DbContext): Promise { const result = await context!.query({ text: queries.getProjectApiKeys, values: [projectId], @@ -402,10 +425,7 @@ export class ProjectRepository { } @withDbContext - async inviteUserToProject( - data: InviteUserToProjectDto, - context?: DbContext | null, - ): Promise { + async inviteUserToProject(data: InviteUserToProjectDto, context?: DbContext): Promise { await context!.query({ text: queries.inviteUserToProject, values: [data.projectId, data.emailAddress, data.permissions], @@ -595,7 +615,7 @@ export class ProjectRepository { @withDbContext async getProjectCollections( projectId: number, - context?: DbContext | null, + context?: DbContext, ): Promise { const result = await context!.query({ text: queries.getProjectCollections, @@ -620,7 +640,7 @@ export class ProjectRepository { async getProjectCollectionById( projectId: number, collectionId: number, - context?: DbContext | null, + context?: DbContext, ): Promise { const result = await context!.query({ text: queries.getProjectCollectionById, @@ -649,16 +669,23 @@ export class ProjectRepository { name: string, uri: string, isPublic: boolean, - context?: DbContext | null, + context?: DbContext, ): Promise { - const queryResult = await context!.query({ - text: queries.updateProjectCollection, - values: [projectId, collectionId, name, uri, isPublic], - validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update project collection', - }); - - return queryResult.rows[0].id; + try { + context?.beginTransaction(); + const queryResult = await context!.query({ + text: queries.updateProjectCollection, + values: [projectId, collectionId, name, uri, isPublic], + validateResult: (result) => !!result.rowCount, + validationErrorMessage: 'Failed to update project collection', + }); + context?.commitTransaction(); + return queryResult.rows[0].id; + } catch (error) { + context?.rollbackTransaction(); + this.logger.error('Failed to update project collection', error); + throw error; + } } @withDbContext @@ -667,12 +694,20 @@ export class ProjectRepository { collectionId: number, context: DbContext, ): Promise { - await context.query({ - text: queries.deleteProjectCollection, - values: [projectId, collectionId], - validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to delete project collection', - }); + try { + context?.beginTransaction(); + await context.query({ + text: queries.deleteProjectCollection, + values: [projectId, collectionId], + validateResult: (result) => !!result.rowCount, + validationErrorMessage: 'Failed to delete project collection', + }); + context?.commitTransaction(); + } catch (error) { + context?.rollbackTransaction(); + this.logger.error('Failed to delete project collection', error); + throw error; + } } @withDbContext @@ -681,15 +716,23 @@ export class ProjectRepository { name: string, uri: string, isPublic: boolean, - context?: DbContext | null, + context?: DbContext, ): Promise { - const queryResult = await context!.query({ - text: queries.addProjectCollection, - values: [projectId, name, uri, isPublic], - validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to add project collection', - }); - return queryResult.rows[0].id; + try { + context?.beginTransaction(); + const queryResult = await context!.query({ + text: queries.addProjectCollection, + values: [projectId, name, uri, isPublic], + validateResult: (result) => !!result.rowCount, + validationErrorMessage: 'Failed to add project collection', + }); + context?.commitTransaction(); + return queryResult.rows[0].id; + } catch (error) { + context?.rollbackTransaction(); + this.logger.error('Failed to create project collection', error); + throw error; + } } @withDbContext @@ -697,7 +740,7 @@ export class ProjectRepository { projectId: number, userId: number, permission: PermissionName, - context?: DbContext | null, + context?: DbContext, ): Promise { const result = await context!.query({ text: queries.getProjectUserPermissions, @@ -718,12 +761,20 @@ export class ProjectRepository { permissionsFlags: number, context?: DbContext | null, ): Promise { - await context!.query({ - text: queries.addUserToProject, - values: [projectId, userId, permissionsFlags], - validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to add user to project', - }); + try { + context?.beginTransaction(); + await context!.query({ + text: queries.addUserToProject, + values: [projectId, userId, permissionsFlags], + validateResult: (result) => !!result.rowCount, + validationErrorMessage: 'Failed to add user to project', + }); + context?.commitTransaction(); + } catch (error) { + context?.rollbackTransaction(); + this.logger.error('Failed to add project', error); + throw error; + } } @withDbContext diff --git a/src/modules/repositories/projects/util.ts b/src/modules/repositories/projects/util.ts index 516086a..aff4749 100644 --- a/src/modules/repositories/projects/util.ts +++ b/src/modules/repositories/projects/util.ts @@ -18,7 +18,7 @@ export const PermissionNames = Object.keys(ProjectPermissions) as PermissionName export function flagsToPermissions(flags: number): PermissionName[] { const permissions: PermissionName[] = []; - const isAdmin = flags === ProjectPermissions.admin; + const isAdmin = Number(flags) === ProjectPermissions.admin; for (const [key, value] of Object.entries(ProjectPermissions)) { if (isAdmin || flags === value) { permissions.push(key as PermissionName); diff --git a/src/modules/repositories/users/user.repository.ts b/src/modules/repositories/users/user.repository.ts index 39e10df..5edb4fc 100644 --- a/src/modules/repositories/users/user.repository.ts +++ b/src/modules/repositories/users/user.repository.ts @@ -14,8 +14,8 @@ export class UserRepository { constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} @withDbContext - async isUserBlacklisted(id: number, context: DbContext): Promise { - const result = await context.query({ + async isUserBlacklisted(id: number, context?: DbContext): Promise { + const result = await context!.query({ text: queries.getUserBlacklistStatus, values: [id], });