diff --git a/apps/api/src/api-key/service/api-key.service.ts b/apps/api/src/api-key/service/api-key.service.ts index 99ed9371..6a21d322 100644 --- a/apps/api/src/api-key/service/api-key.service.ts +++ b/apps/api/src/api-key/service/api-key.service.ts @@ -5,8 +5,7 @@ import { addHoursToDate } from '../../common/add-hours-to-date' import { generateApiKey } from '../../common/api-key-generator' import { toSHA256 } from '../../common/to-sha256' import { UpdateApiKey } from '../dto/update.api-key/update.api-key' -import { ApiKey, EventSource, EventType, User } from '@prisma/client' -import createEvent from '../../common/create-event' +import { User } from '@prisma/client' @Injectable() export class ApiKeyService { @@ -43,21 +42,6 @@ export class ApiKeyService { } }) - createEvent( - { - triggeredBy: user, - entity: apiKey as ApiKey, - type: EventType.API_KEY_ADDED, - source: EventSource.API_KEY, - title: `API key created`, - metadata: { - apiKeyId: apiKey.id, - name: apiKey.name - } - }, - this.prisma - ) - this.logger.log(`User ${user.id} created API key ${apiKey.id}`) return { @@ -93,46 +77,20 @@ export class ApiKeyService { } }) - createEvent( - { - triggeredBy: user, - entity: updatedApiKey as ApiKey, - type: EventType.API_KEY_UPDATED, - source: EventSource.API_KEY, - title: `API key updated`, - metadata: { - apiKeyId: updatedApiKey.id, - name: updatedApiKey.name - } - }, - this.prisma - ) - this.logger.log(`User ${user.id} updated API key ${apiKeyId}`) return updatedApiKey } async deleteApiKey(user: User, apiKeyId: string) { - const apiKey = await this.prisma.apiKey.delete({ + await this.prisma.apiKey.delete({ where: { id: apiKeyId, userId: user.id } }) - createEvent( - { - triggeredBy: user, - type: EventType.API_KEY_DELETED, - source: EventSource.API_KEY, - title: `API key deleted`, - metadata: { - name: apiKey.name - } - }, - this.prisma - ) + this.logger.log(`User ${user.id} deleted API key ${apiKeyId}`) } async getApiKeyById(user: User, apiKeyId: string) { diff --git a/apps/api/src/approval/approval.e2e.spec.ts b/apps/api/src/approval/approval.e2e.spec.ts index ee89352d..ae89d4af 100644 --- a/apps/api/src/approval/approval.e2e.spec.ts +++ b/apps/api/src/approval/approval.e2e.spec.ts @@ -25,6 +25,10 @@ import { ApprovalStatus, Authority, Environment, + EventSeverity, + EventSource, + EventTriggerer, + EventType, Project, Secret, User, @@ -36,6 +40,9 @@ import { VariableModule } from '../variable/variable.module' import { UserModule } from '../user/user.module' import { WorkspaceRoleService } from '../workspace-role/service/workspace-role.service' import { WorkspaceRoleModule } from '../workspace-role/workspace-role.module' +import { EventService } from '../event/service/event.service' +import { EventModule } from '../event/event.module' +import fetchEvents from '../common/fetch-events' describe('Approval Controller Tests', () => { let app: NestFastifyApplication @@ -47,6 +54,7 @@ describe('Approval Controller Tests', () => { let secretService: SecretService let variableService: VariableService let workspaceRoleService: WorkspaceRoleService + let eventService: EventService let workspace1: Workspace let project1: Project @@ -67,7 +75,8 @@ describe('Approval Controller Tests', () => { EnvironmentModule, SecretModule, VariableModule, - WorkspaceRoleModule + WorkspaceRoleModule, + EventModule ] }) .overrideProvider(MAIL_SERVICE) @@ -84,6 +93,7 @@ describe('Approval Controller Tests', () => { secretService = moduleRef.get(SecretService) variableService = moduleRef.get(VariableService) workspaceRoleService = moduleRef.get(WorkspaceRoleService) + eventService = moduleRef.get(EventService) await app.init() await app.getHttpAdapter().getInstance().ready() @@ -157,6 +167,24 @@ describe('Approval Controller Tests', () => { }) }) + it('should have created a APPROVAL_CREATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.APPROVAL + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.APPROVAL) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.APPROVAL_CREATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) + it('should allow user with WORKSPACE_ADMIN to view the approval', async () => { const approval = await prisma.approval.findFirst({ where: { @@ -295,6 +323,24 @@ describe('Approval Controller Tests', () => { expect(response.json().reason).toBe('updated') }) + it('should have created a APPROVAL_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.APPROVAL + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.APPROVAL) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.APPROVAL_UPDATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) + it('should update the workspace if the approval is approved', async () => { let approval = await prisma.approval.findFirst({ where: { @@ -334,6 +380,24 @@ describe('Approval Controller Tests', () => { workspace1 = updatedWorkspace }) + it('should have created a APPROVAL_APPROVED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.APPROVAL + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.APPROVAL) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.APPROVAL_APPROVED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) + it('should not be able to approve an already approved approval', async () => { const approval = await prisma.approval.findFirst({ where: { @@ -442,6 +506,24 @@ describe('Approval Controller Tests', () => { expect(approval).toBeNull() }) + it('should have created a APPROVAL_DELETED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.APPROVAL + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.APPROVAL) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.APPROVAL_DELETED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) + it('should allow creating project with the same name till it is not approved', async () => { const result1 = (await projectService.createProject( user1, @@ -1530,6 +1612,24 @@ describe('Approval Controller Tests', () => { expect(response.statusCode).toBe(200) }) + it('should have created a APPROVAL_REJECTED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.APPROVAL + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.APPROVAL) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.APPROVAL_REJECTED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) + it('should delete the item if the approval is rejected', async () => { // Create a new project const result = (await projectService.createProject( diff --git a/apps/api/src/approval/service/approval.service.ts b/apps/api/src/approval/service/approval.service.ts index 4b24a5b6..dfd29d83 100644 --- a/apps/api/src/approval/service/approval.service.ts +++ b/apps/api/src/approval/service/approval.service.ts @@ -64,7 +64,7 @@ export class ApprovalService { this.logger.log(`Approval with id ${approvalId} updated by ${user.id}`) - createEvent( + await createEvent( { triggeredBy: user, entity: approval, @@ -73,7 +73,8 @@ export class ApprovalService { title: `Approval with id ${approvalId} updated`, metadata: { approvalId - } + }, + workspaceId: approval.workspaceId }, this.prisma ) @@ -102,15 +103,17 @@ export class ApprovalService { this.logger.log(`Approval with id ${approvalId} deleted by ${user.id}`) - createEvent( + await createEvent( { triggeredBy: user, + entity: approval, type: EventType.APPROVAL_DELETED, source: EventSource.APPROVAL, title: `Approval with id ${approvalId} deleted`, metadata: { approvalId - } + }, + workspaceId: approval.workspaceId }, this.prisma ) @@ -145,7 +148,7 @@ export class ApprovalService { this.logger.log(`Approval with id ${approvalId} rejected by ${user.id}`) - createEvent( + await createEvent( { triggeredBy: user, entity: approval, @@ -154,7 +157,8 @@ export class ApprovalService { title: `Approval with id ${approvalId} rejected`, metadata: { approvalId - } + }, + workspaceId: approval.workspaceId }, this.prisma ) @@ -360,7 +364,7 @@ export class ApprovalService { this.logger.log(`Approval with id ${approvalId} approved by ${user.id}`) - createEvent( + await createEvent( { triggeredBy: user, entity: approval, @@ -369,7 +373,8 @@ export class ApprovalService { title: `Approval with id ${approvalId} approved`, metadata: { approvalId - } + }, + workspaceId: approval.workspaceId }, this.prisma ) diff --git a/apps/api/src/auth/auth.e2e.spec.ts b/apps/api/src/auth/auth.e2e.spec.ts index 473289fb..26211dad 100644 --- a/apps/api/src/auth/auth.e2e.spec.ts +++ b/apps/api/src/auth/auth.e2e.spec.ts @@ -9,6 +9,7 @@ import { MAIL_SERVICE } from '../mail/services/interface.service' import { MockMailService } from '../mail/services/mock.service' import { AppModule } from '../app/app.module' import { Otp } from '@prisma/client' +import cleanUp from '../common/cleanup' describe('Auth Controller Tests', () => { let app: NestFastifyApplication @@ -31,6 +32,8 @@ describe('Auth Controller Tests', () => { await app.init() await app.getHttpAdapter().getInstance().ready() + + await cleanUp(prisma) }) it('should be defined', async () => { @@ -122,4 +125,8 @@ describe('Auth Controller Tests', () => { expect(response.statusCode).toBe(401) }) + + afterAll(async () => { + await cleanUp(prisma) + }) }) diff --git a/apps/api/src/common/create-approval.ts b/apps/api/src/common/create-approval.ts index ffb4064e..c97744c0 100644 --- a/apps/api/src/common/create-approval.ts +++ b/apps/api/src/common/create-approval.ts @@ -62,7 +62,7 @@ export default async function createApproval( `Approval for ${data.itemType} with id ${data.itemId} created by ${data.user.id}` ) - createEvent( + await createEvent( { triggeredBy: data.user, entity: approval, @@ -74,7 +74,8 @@ export default async function createApproval( itemId: data.itemId, action: data.action, reason: data.reason - } + }, + workspaceId: data.workspaceId }, prisma ) diff --git a/apps/api/src/common/create-event.ts b/apps/api/src/common/create-event.ts index 7d927e14..228ed547 100644 --- a/apps/api/src/common/create-event.ts +++ b/apps/api/src/common/create-event.ts @@ -1,6 +1,5 @@ import { Logger } from '@nestjs/common' import { - ApiKey, Environment, EventSeverity, EventTriggerer, @@ -29,13 +28,13 @@ export default async function createEvent( | Project | Environment | WorkspaceRole - | ApiKey | Secret | Variable | Approval type: EventType source: EventSource title: string + workspaceId: string description?: string metadata: JsonObject }, @@ -45,84 +44,19 @@ export default async function createEvent( throw new Error('User must be provided for non-system events') } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const baseData: any = { - triggerer: data.triggerer ?? EventTriggerer.USER, - severity: data.severity ?? EventSeverity.INFO, - type: data.type, - source: data.source, - title: data.title, - description: data.description ?? '', - metadata: data.metadata - } - - if (data.triggeredBy) { - baseData.sourceUserId = data.triggeredBy.id - } - - try { - switch (data.source) { - case EventSource.WORKSPACE: { - if (data.entity) { - baseData.sourceWorkspaceId = data.entity.id - } - break - } - case EventSource.PROJECT: { - if (data.entity) { - baseData.sourceProjectId = data.entity.id - } - break - } - case EventSource.ENVIRONMENT: { - if (data.entity) { - baseData.sourceEnvironmentId = data.entity.id - } - break - } - case EventSource.WORKSPACE_ROLE: { - if (data.entity) { - baseData.sourceWorkspaceRoleId = data.entity.id - } - break - } - case EventSource.API_KEY: { - if (data.entity) { - baseData.sourceApiKeyId = data.entity.id - } - break - } - case EventSource.SECRET: { - if (data.entity) { - baseData.sourceSecretId = data.entity.id - } - break - } - case EventSource.VARIABLE: { - if (data.entity) { - baseData.sourceVariableId = data.entity.id - } - break - } - case EventSource.APPROVAL: { - if (data.entity) { - baseData.sourceApprovalId = data.entity.id - } - break - } - case EventSource.USER: { - break - } - default: { - throw new Error('Invalid event source') - } - } - } catch (error) { - console.error('Error creating event', data, error) - } - const event = await prisma.event.create({ - data: baseData + data: { + triggerer: data.triggerer ?? EventTriggerer.USER, + severity: data.severity ?? EventSeverity.INFO, + type: data.type, + source: data.source, + title: data.title, + description: data.description ?? '', + metadata: data.metadata, + userId: data.triggeredBy?.id, + itemId: data.entity?.id, + workspaceId: data.workspaceId + } }) logger.log(`Event with id ${event.id} created`) diff --git a/apps/api/src/common/fetch-events.ts b/apps/api/src/common/fetch-events.ts index 18f8e178..edbbdcb2 100644 --- a/apps/api/src/common/fetch-events.ts +++ b/apps/api/src/common/fetch-events.ts @@ -1,16 +1,20 @@ -import { User } from '@prisma/client' -import { NestFastifyApplication } from '@nestjs/platform-fastify' +import { EventSeverity, EventSource, User } from '@prisma/client' +import { EventService } from 'src/event/service/event.service' export default async function fetchEvents( - app: NestFastifyApplication, + eventService: EventService, user: User, - query?: string + workspaceId: string, + source?: EventSource, + severity?: EventSeverity ): Promise { - return app.inject({ - method: 'GET', - headers: { - 'x-e2e-user-email': user.email - }, - url: `/event${query ? '?' + query : ''}` - }) + return await eventService.getEvents( + user, + workspaceId, + 0, + 10, + '', + severity, + source + ) } diff --git a/apps/api/src/environment/environment.e2e.spec.ts b/apps/api/src/environment/environment.e2e.spec.ts index 0bc3a155..fe0d18de 100644 --- a/apps/api/src/environment/environment.e2e.spec.ts +++ b/apps/api/src/environment/environment.e2e.spec.ts @@ -26,12 +26,14 @@ import { WorkspaceService } from '../workspace/service/workspace.service' import { ProjectService } from '../project/service/project.service' import { EventModule } from '../event/event.module' import { WorkspaceModule } from '../workspace/workspace.module' +import { EventService } from '../event/service/event.service' describe('Environment Controller Tests', () => { let app: NestFastifyApplication let prisma: PrismaService let projectService: ProjectService let workspaceService: WorkspaceService + let eventService: EventService let user1: User, user2: User let workspace1: Workspace @@ -58,6 +60,7 @@ describe('Environment Controller Tests', () => { prisma = moduleRef.get(PrismaService) projectService = moduleRef.get(ProjectService) workspaceService = moduleRef.get(WorkspaceService) + eventService = moduleRef.get(EventService) await app.init() await app.getHttpAdapter().getInstance().ready() @@ -241,28 +244,23 @@ describe('Environment Controller Tests', () => { expect(environments.filter((e) => e.isDefault).length).toBe(1) }) - // it('should have created a ENVIRONMENT_ADDED event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'environmentId=' + environment1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.ENVIRONMENT, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.ENVIRONMENT_ADDED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a ENVIRONMENT_ADDED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.ENVIRONMENT + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.ENVIRONMENT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.ENVIRONMENT_ADDED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to update an environment', async () => { const response = await app.inject({ @@ -289,7 +287,8 @@ describe('Environment Controller Tests', () => { secrets: [], createdAt: expect.any(String), updatedAt: expect.any(String), - pendingCreation: false + pendingCreation: false, + project: expect.any(Object) }) environment1 = response.json() @@ -350,28 +349,23 @@ describe('Environment Controller Tests', () => { ) }) - // it('should create a ENVIRONMENT_UPDATED event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'environmentId=' + environment1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.ENVIRONMENT, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.ENVIRONMENT_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should create a ENVIRONMENT_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.ENVIRONMENT + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.ENVIRONMENT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.ENVIRONMENT_UPDATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should make other environments non-default if the current environment is the default one', async () => { const response = await app.inject({ @@ -504,6 +498,24 @@ describe('Environment Controller Tests', () => { expect(response.statusCode).toBe(200) }) + it('should have created a ENVIRONMENT_DELETED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.ENVIRONMENT + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.ENVIRONMENT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.ENVIRONMENT_DELETED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) + it('should not be able to delete an environment that does not exist', async () => { const response = await app.inject({ method: 'DELETE', diff --git a/apps/api/src/environment/service/environment.service.ts b/apps/api/src/environment/service/environment.service.ts index 25af45b2..dcb92c08 100644 --- a/apps/api/src/environment/service/environment.service.ts +++ b/apps/api/src/environment/service/environment.service.ts @@ -91,7 +91,7 @@ export class EnvironmentService { const result = await this.prisma.$transaction(ops) const environment: EnvironmentWithProject = result[result.length - 1] - createEvent( + await createEvent( { triggeredBy: user, entity: environment, @@ -103,7 +103,8 @@ export class EnvironmentService { name: environment.name, projectId, projectName: project.name - } + }, + workspaceId: project.workspaceId }, this.prisma ) @@ -382,6 +383,7 @@ export class EnvironmentService { lastUpdatedById: user.id }, include: { + project: true, secrets: true, lastUpdatedBy: true } @@ -391,7 +393,7 @@ export class EnvironmentService { const result = await this.prisma.$transaction(ops) const updatedEnvironment = result[result.length - 1] - createEvent( + await createEvent( { triggeredBy: user, entity: updatedEnvironment, @@ -402,7 +404,8 @@ export class EnvironmentService { environmentId: updatedEnvironment.id, name: updatedEnvironment.name, projectId: updatedEnvironment.projectId - } + }, + workspaceId: updatedEnvironment.project.workspaceId }, this.prisma ) @@ -445,17 +448,19 @@ export class EnvironmentService { await this.prisma.$transaction(op) - createEvent( + await createEvent( { triggeredBy: user, type: EventType.ENVIRONMENT_DELETED, source: EventSource.ENVIRONMENT, + entity: environment, title: `Environment deleted`, metadata: { environmentId: environment.id, name: environment.name, projectId: environment.projectId - } + }, + workspaceId: environment.project.workspaceId }, this.prisma ) diff --git a/apps/api/src/event/controller/event.controller.ts b/apps/api/src/event/controller/event.controller.ts index 4587c792..ac05401b 100644 --- a/apps/api/src/event/controller/event.controller.ts +++ b/apps/api/src/event/controller/event.controller.ts @@ -1,6 +1,6 @@ -import { Controller, Get, Query } from '@nestjs/common' +import { Controller, Get, Param, Query } from '@nestjs/common' import { EventService } from '../service/event.service' -import { Authority, EventSeverity, User } from '@prisma/client' +import { Authority, EventSeverity, EventSource, User } from '@prisma/client' import { CurrentUser } from '../../decorators/user.decorator' import { RequiredApiKeyAuthorities } from '../../decorators/required-api-key-authorities.decorator' import { ApiTags } from '@nestjs/swagger' @@ -10,37 +10,25 @@ import { ApiTags } from '@nestjs/swagger' export class EventController { constructor(private readonly eventService: EventService) {} - @Get() + @Get(':workspaceId') @RequiredApiKeyAuthorities(Authority.READ_EVENT) async getEvents( @CurrentUser() user: User, - @Query('page') page: number = 1, + @Param('workspaceId') workspaceId: string, + @Query('page') page: number = 0, @Query('limit') limit: number = 10, @Query('search') search: string = '', @Query('severity') severity: EventSeverity, - @Query('workspaceId') workspaceId: string, - @Query('projectId') projectId: string, - @Query('environmentId') environmentId: string, - @Query('secretId') secretId: string, - @Query('variableId') variableId: string, - @Query('apiKeyId') apiKeyId: string, - @Query('workspaceRoleId') workspaceRoleId: string + @Query('source') source: EventSource ) { - return this.eventService.getEvents( + return await this.eventService.getEvents( user, - { - workspaceId, - projectId, - environmentId, - secretId, - variableId, - apiKeyId, - workspaceRoleId - }, + workspaceId, page, limit, search, - severity + severity, + source ) } } diff --git a/apps/api/src/event/event.e2e.spec.ts b/apps/api/src/event/event.e2e.spec.ts index 318f28b4..e41c7e1f 100644 --- a/apps/api/src/event/event.e2e.spec.ts +++ b/apps/api/src/event/event.e2e.spec.ts @@ -3,7 +3,7 @@ import { NestFastifyApplication } from '@nestjs/platform-fastify' import { - Authority, + Approval, Environment, EventSeverity, EventSource, @@ -22,12 +22,9 @@ import { AppModule } from '../app/app.module' import { MAIL_SERVICE } from '../mail/services/interface.service' import { MockMailService } from '../mail/services/mock.service' import { EventModule } from './event.module' -import { UserModule } from '../user/user.module' -import { UserService } from '../user/service/user.service' import cleanUp from '../common/cleanup' import { WorkspaceService } from '../workspace/service/workspace.service' import { WorkspaceModule } from '../workspace/workspace.module' -import { ApiKeyService } from '../api-key/service/api-key.service' import { EnvironmentService } from '../environment/service/environment.service' import { WorkspaceRoleService } from '../workspace-role/service/workspace-role.service' import { ProjectService } from '../project/service/project.service' @@ -35,9 +32,7 @@ import { SecretService } from '../secret/service/secret.service' import { SecretModule } from '../secret/secret.module' import { ProjectModule } from '../project/project.module' import { EnvironmentModule } from '../environment/environment.module' -import { ApiKeyModule } from '../api-key/api-key.module' import createEvent from '../common/create-event' -import fetchEvents from '../common/fetch-events' import { VariableService } from '../variable/service/variable.service' import { VariableModule } from '../variable/variable.module' @@ -45,9 +40,7 @@ describe('Event Controller Tests', () => { let app: NestFastifyApplication let prisma: PrismaService - let userService: UserService let workspaceService: WorkspaceService - let apiKeyService: ApiKeyService let environmentService: EnvironmentService let workspaceRoleService: WorkspaceRoleService let projectService: ProjectService @@ -59,20 +52,16 @@ describe('Event Controller Tests', () => { let project: Project let environment: Environment - const totalEvents = [] - beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [ AppModule, EventModule, - UserModule, WorkspaceModule, WorkspaceRoleModule, SecretModule, ProjectModule, EnvironmentModule, - ApiKeyModule, VariableModule ] }) @@ -84,9 +73,7 @@ describe('Event Controller Tests', () => { new FastifyAdapter() ) prisma = moduleRef.get(PrismaService) - userService = moduleRef.get(UserService) workspaceService = moduleRef.get(WorkspaceService) - apiKeyService = moduleRef.get(ApiKeyService) environmentService = moduleRef.get(EnvironmentService) workspaceRoleService = moduleRef.get(WorkspaceRoleService) projectService = moduleRef.get(ProjectService) @@ -101,7 +88,8 @@ describe('Event Controller Tests', () => { user = await prisma.user.create({ data: { email: 'johndoe@keyshade.xyz', - name: 'John Doe' + name: 'John Doe', + isOnboardingFinished: true } }) }) @@ -111,348 +99,363 @@ describe('Event Controller Tests', () => { expect(prisma).toBeDefined() }) - // it('should be able to fetch a user event', async () => { - // const updatedUser = await userService.updateSelf(user, { - // isOnboardingFinished: true - // }) - // user = updatedUser - - // expect(updatedUser).toBeDefined() - - // const response = await fetchEvents(app, user) - - // totalEvents.push({ - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.USER, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.USER_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // }) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(totalEvents) - // }) - - // it('should be able to fetch API key event', async () => { - // const newApiKey = await apiKeyService.createApiKey(user, { - // name: 'My API key', - // authorities: [Authority.READ_API_KEY] - // }) - - // expect(newApiKey).toBeDefined() - - // const response = await fetchEvents(app, user, `apiKeyId=${newApiKey.id}`) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.API_KEY, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.API_KEY_ADDED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // totalEvents.push(event) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual([event]) - // }) - - // it('should be able to fetch a workspace event', async () => { - // const newWorkspace = await workspaceService.createWorkspace(user, { - // name: 'My workspace', - // description: 'Some description', - // approvalEnabled: false - // }) - // workspace = newWorkspace - - // expect(newWorkspace).toBeDefined() - - // const response = await fetchEvents( - // app, - // user, - // `workspaceId=${newWorkspace.id}` - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.WORKSPACE_CREATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // totalEvents.push(event) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual([event]) - // }) - - // it('should be able to fetch a project event', async () => { - // const newProject = (await projectService.createProject(user, workspace.id, { - // name: 'My project', - // description: 'Some description', - // environments: [], - // storePrivateKey: false, - // isPublic: false - // })) as Project - // project = newProject - - // expect(newProject).toBeDefined() - - // const response = await fetchEvents(app, user, `projectId=${newProject.id}`) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.PROJECT, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.PROJECT_CREATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // totalEvents.push(event) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual([event]) - // }) - - // it('should be able to fetch an environment event', async () => { - // const newEnvironment = (await environmentService.createEnvironment( - // user, - // { - // name: 'My environment', - // description: 'Some description', - // isDefault: false - // }, - // project.id - // )) as Environment - // environment = newEnvironment - - // expect(newEnvironment).toBeDefined() - - // const response = await fetchEvents( - // app, - // user, - // `environmentId=${newEnvironment.id}` - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.ENVIRONMENT, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.ENVIRONMENT_ADDED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // totalEvents.push(event) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual([event]) - // }) - - // it('should be able to fetch a secret event', async () => { - // const newSecret = (await secretService.createSecret( - // user, - // { - // name: 'My secret', - // value: 'My value', - // note: 'Some note', - // environmentId: environment.id, - // rotateAfter: '720' - // }, - // project.id - // )) as Secret - - // expect(newSecret).toBeDefined() - - // const response = await fetchEvents(app, user, `secretId=${newSecret.id}`) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.SECRET, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.SECRET_ADDED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // totalEvents.push(event) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual([event]) - // }) - - // it('should be able to fetch a variable event', async () => { - // const newVariable = (await variableService.createVariable( - // user, - // { - // name: 'My variable', - // value: 'My value', - // note: 'Some note', - // environmentId: environment.id - // }, - // project.id - // )) as Variable - - // expect(newVariable).toBeDefined() - - // const response = await fetchEvents( - // app, - // user, - // `variableId=${newVariable.id}` - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.VARIABLE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.VARIABLE_ADDED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // totalEvents.push(event) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual([event]) - // }) - - // it('should be able to fetch a workspace role event', async () => { - // const newWorkspaceRole = await workspaceRoleService.createWorkspaceRole( - // user, - // workspace.id, - // { - // name: 'My role', - // description: 'Some description', - // colorCode: '#000000', - // authorities: [], - // projectIds: [project.id] - // } - // ) - - // expect(newWorkspaceRole).toBeDefined() - - // const response = await fetchEvents( - // app, - // user, - // `workspaceRoleId=${newWorkspaceRole.id}` - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE_ROLE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.WORKSPACE_ROLE_CREATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // totalEvents.push(event) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual([event]) - // }) - - // it('should be able to fetch all events', async () => { - // const response = await fetchEvents(app, user) - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(totalEvents) - // }) - - // it('should throw an error with wrong severity value', async () => { - // const response = await fetchEvents(app, user, 'severity=WRONG') - - // expect(response.statusCode).toBe(400) - // }) - - // it('should throw an error if user is not provided in event creation for user-triggered event', async () => { - // try { - // createEvent( - // { - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.USER_UPDATED, - // source: EventSource.USER, - // title: 'User updated', - // description: 'User updated', - // metadata: {} - // }, - // prisma - // ) - // } catch (error) { - // expect(error).toBeDefined() - // } - // }) - - // it('should throw an exception for invalid event source', async () => { - // try { - // createEvent( - // { - // triggerer: EventTriggerer.SYSTEM, - // severity: EventSeverity.INFO, - // type: EventType.USER_UPDATED, - // source: 'INVALID' as EventSource, - // title: 'User updated', - // description: 'User updated', - // metadata: {} - // }, - // prisma - // ) - // } catch (error) { - // expect(error).toBeDefined() - // } - // }) - - // it('should throw an exception for invalid event type', async () => { - // try { - // createEvent( - // { - // triggerer: EventTriggerer.SYSTEM, - // severity: EventSeverity.INFO, - // type: EventType.WORKSPACE_CREATED, - // source: EventSource.WORKSPACE, - // title: 'User updated', - // description: 'User updated', - // entity: { - // id: '1' - // } as Workspace, - // metadata: {} - // }, - // prisma - // ) - // } catch (error) { - // expect(error).toBeDefined() - // } - // }) + it('should be able to fetch a workspace event', async () => { + const newWorkspace = await workspaceService.createWorkspace(user, { + name: 'My workspace', + description: 'Some description', + approvalEnabled: false + }) + workspace = newWorkspace + + expect(newWorkspace).toBeDefined() + + const response = await app.inject({ + method: 'GET', + url: `/event/${newWorkspace.id}?source=WORKSPACE`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + const event = response.json()[0] + + expect(event.id).toBeDefined() + expect(event.title).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_CREATED) + expect(event.timestamp).toBeDefined() + expect(event.itemId).toBe(newWorkspace.id) + expect(event.userId).toBe(user.id) + expect(event.workspaceId).toBe(newWorkspace.id) + }) + + it('should be able to fetch a project event', async () => { + const newProject = (await projectService.createProject(user, workspace.id, { + name: 'My project', + description: 'Some description', + environments: [], + storePrivateKey: false, + isPublic: false + })) as Project + project = newProject + + expect(newProject).toBeDefined() + + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?source=PROJECT`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + const event = response.json()[0] + + expect(event.id).toBeDefined() + expect(event.title).toBeDefined() + expect(event.source).toBe(EventSource.PROJECT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.PROJECT_CREATED) + expect(event.timestamp).toBeDefined() + expect(event.itemId).toBe(newProject.id) + expect(event.userId).toBe(user.id) + expect(event.workspaceId).toBe(workspace.id) + }) + + it('should be able to fetch an environment event', async () => { + const newEnvironment = (await environmentService.createEnvironment( + user, + { + name: 'My environment', + description: 'Some description', + isDefault: false + }, + project.id + )) as Environment + environment = newEnvironment + + expect(newEnvironment).toBeDefined() + + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?source=ENVIRONMENT`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + const event = response.json()[0] + + expect(event.id).toBeDefined() + expect(event.title).toBeDefined() + expect(event.source).toBe(EventSource.ENVIRONMENT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.ENVIRONMENT_ADDED) + expect(event.timestamp).toBeDefined() + expect(event.itemId).toBe(newEnvironment.id) + expect(event.userId).toBe(user.id) + expect(event.workspaceId).toBe(workspace.id) + }) + + it('should be able to fetch a secret event', async () => { + const newSecret = (await secretService.createSecret( + user, + { + name: 'My secret', + value: 'My value', + note: 'Some note', + environmentId: environment.id, + rotateAfter: '720' + }, + project.id + )) as Secret + + expect(newSecret).toBeDefined() + + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?source=SECRET`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + const event = response.json()[0] + + expect(event.id).toBeDefined() + expect(event.title).toBeDefined() + expect(event.source).toBe(EventSource.SECRET) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.SECRET_ADDED) + expect(event.timestamp).toBeDefined() + expect(event.itemId).toBe(newSecret.id) + expect(event.userId).toBe(user.id) + expect(event.workspaceId).toBe(workspace.id) + }) + + it('should be able to fetch a variable event', async () => { + const newVariable = (await variableService.createVariable( + user, + { + name: 'My variable', + value: 'My value', + note: 'Some note', + environmentId: environment.id + }, + project.id + )) as Variable + + expect(newVariable).toBeDefined() + + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?source=VARIABLE`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + // expect(response.json()).toBe({}) + const event = response.json()[0] + + expect(event.id).toBeDefined() + expect(event.title).toBeDefined() + expect(event.source).toBe(EventSource.VARIABLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.VARIABLE_ADDED) + expect(event.timestamp).toBeDefined() + expect(event.itemId).toBe(newVariable.id) + expect(event.userId).toBe(user.id) + expect(event.workspaceId).toBe(workspace.id) + }) + + it('should be able to fetch a workspace role event', async () => { + const newWorkspaceRole = await workspaceRoleService.createWorkspaceRole( + user, + workspace.id, + { + name: 'My role', + description: 'Some description', + colorCode: '#000000', + authorities: [], + projectIds: [project.id] + } + ) + + expect(newWorkspaceRole).toBeDefined() + + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?source=WORKSPACE_ROLE`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + const event = response.json()[0] + + expect(event.id).toBeDefined() + expect(event.title).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE_ROLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_ROLE_CREATED) + expect(event.timestamp).toBeDefined() + expect(event.itemId).toBe(newWorkspaceRole.id) + expect(event.userId).toBe(user.id) + expect(event.workspaceId).toBe(workspace.id) + }) + + it('should be able to fetch a approval event', async () => { + const workspace = await workspaceService.createWorkspace(user, { + name: 'My workspace 100', + description: 'Some description', + approvalEnabled: true + }) + + const updateWorkspaceResponse = (await workspaceService.updateWorkspace( + user, + workspace.id, + { + name: 'My workspace 10' + } + )) as Approval + + expect(updateWorkspaceResponse).toBeDefined() + + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?source=APPROVAL`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + const event = response.json()[0] + + expect(event.id).toBeDefined() + expect(event.title).toBeDefined() + expect(event.source).toBe(EventSource.APPROVAL) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.APPROVAL_CREATED) + expect(event.timestamp).toBeDefined() + expect(event.itemId).toBe(updateWorkspaceResponse.id) + expect(event.userId).toBe(user.id) + }) + + it('should be able to fetch all events', async () => { + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(200) + expect(response.json()).toHaveLength(6) + }) + + it('should throw an error with wrong severity value', async () => { + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?severity=INVALID`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(400) + }) + + it('should throw an error with wrong source value', async () => { + const response = await app.inject({ + method: 'GET', + url: `/event/${workspace.id}?source=INVALID`, + headers: { + 'x-e2e-user-email': user.email + } + }) + + expect(response.statusCode).toBe(400) + }) + + it('should throw an error if user is not provided in event creation for user-triggered event', async () => { + try { + await createEvent( + { + triggerer: EventTriggerer.USER, + severity: EventSeverity.INFO, + type: EventType.ACCEPTED_INVITATION, + source: EventSource.WORKSPACE, + title: 'User updated', + description: 'User updated', + metadata: {}, + workspaceId: workspace.id + }, + prisma + ) + } catch (error) { + expect(error).toBeDefined() + } + }) + + it('should throw an exception for invalid event source', async () => { + try { + await createEvent( + { + triggerer: EventTriggerer.SYSTEM, + severity: EventSeverity.INFO, + type: EventType.INVITED_TO_WORKSPACE, + source: 'INVALID' as EventSource, + title: 'User updated', + description: 'User updated', + metadata: {}, + workspaceId: workspace.id + }, + prisma + ) + } catch (error) { + expect(error).toBeDefined() + } + }) + + it('should throw an exception for invalid event type', async () => { + try { + await createEvent( + { + triggerer: EventTriggerer.SYSTEM, + severity: EventSeverity.INFO, + type: EventType.WORKSPACE_CREATED, + source: EventSource.WORKSPACE, + title: 'User updated', + description: 'User updated', + entity: { + id: '1' + } as Workspace, + metadata: {}, + workspaceId: workspace.id + }, + prisma + ) + } catch (error) { + expect(error).toBeDefined() + } + }) afterAll(async () => { await cleanUp(prisma) diff --git a/apps/api/src/event/service/event.service.ts b/apps/api/src/event/service/event.service.ts index 65ebb645..0336cceb 100644 --- a/apps/api/src/event/service/event.service.ts +++ b/apps/api/src/event/service/event.service.ts @@ -1,11 +1,7 @@ import { BadRequestException, Injectable } from '@nestjs/common' -import { Authority, EventSeverity, User } from '@prisma/client' +import { Authority, EventSeverity, EventSource, User } from '@prisma/client' import getWorkspaceWithAuthority from '../../common/get-workspace-with-authority' -import getProjectWithAuthority from '../../common/get-project-with-authority' -import getEnvironmentWithAuthority from '../../common/get-environment-with-authority' -import getSecretWithAuthority from '../../common/get-secret-with-authority' import { PrismaService } from '../../prisma/prisma.service' -import getVariableWithAuthority from '../../common/get-variable-with-authority' @Injectable() export class EventService { @@ -13,110 +9,48 @@ export class EventService { async getEvents( user: User, - context: { - workspaceId?: string - projectId?: string - environmentId?: string - secretId?: string - variableId?: string - apiKeyId?: string - workspaceRoleId?: string - }, + workspaceId: string, page: number, limit: number, search: string, - severity?: EventSeverity + severity?: EventSeverity, + source?: EventSource ) { if (severity && !Object.values(EventSeverity).includes(severity)) { throw new BadRequestException('Invalid "severity" value') } - const whereCondition = { - severity: severity, - title: { - contains: search - } + if (source && !Object.values(EventSource).includes(source)) { + throw new BadRequestException('Invalid "source" value') } - // Set context-specific condition - if (context.workspaceId) { - await getWorkspaceWithAuthority( - user.id, - context.workspaceId, - Authority.READ_WORKSPACE, - this.prisma - ) - whereCondition['sourceWorkspaceId'] = context.workspaceId - } else if (context.projectId) { - await getProjectWithAuthority( - user.id, - context.projectId, - Authority.READ_PROJECT, - this.prisma - ) - whereCondition['sourceProjectId'] = context.projectId - } else if (context.environmentId) { - await getEnvironmentWithAuthority( - user.id, - context.environmentId, - Authority.READ_ENVIRONMENT, - this.prisma - ) - whereCondition['sourceEnvironmentId'] = context.environmentId - } else if (context.secretId) { - await getSecretWithAuthority( - user.id, - context.secretId, - Authority.READ_SECRET, - this.prisma - ) - whereCondition['sourceSecretId'] = context.secretId - } else if (context.variableId) { - await getVariableWithAuthority( - user.id, - context.variableId, - Authority.READ_VARIABLE, - this.prisma - ) - whereCondition['sourceVariableId'] = context.variableId - } else if (context.apiKeyId) { - whereCondition['sourceApiKeyId'] = context.apiKeyId - } else if (context.workspaceRoleId) { - const workspaceRole = await this.prisma.workspaceRole.findUnique({ - where: { - id: context.workspaceRoleId - }, - include: { - workspace: true - } - }) - await getWorkspaceWithAuthority( - user.id, - workspaceRole.workspace.id, - Authority.READ_WORKSPACE_ROLE, - this.prisma - ) - whereCondition['sourceWorkspaceRoleId'] = context.workspaceRoleId - } else { - whereCondition['sourceUserId'] = user.id - } + // Check for workspace authority + await getWorkspaceWithAuthority( + user.id, + workspaceId, + Authority.READ_EVENT, + this.prisma + ) - // Get the events - return await this.prisma.event.findMany({ - where: whereCondition, + const query = { + where: { + workspaceId, + title: { + contains: search + } + }, skip: page * limit, take: limit, - select: { - id: true, - title: true, - description: true, - severity: true, - timestamp: true, - source: true, - triggerer: true, - type: true, - metadata: true + orderBy: { + timestamp: 'desc' } - }) + } + + if (source) { + query.where['source'] = source + } + + // @ts-expect-error - Prisma does not have a type for severity + return await this.prisma.event.findMany(query) } } diff --git a/apps/api/src/prisma/migrations/20240309065342_refactor_event/migration.sql b/apps/api/src/prisma/migrations/20240309065342_refactor_event/migration.sql new file mode 100644 index 00000000..55a66bbd --- /dev/null +++ b/apps/api/src/prisma/migrations/20240309065342_refactor_event/migration.sql @@ -0,0 +1,85 @@ +/* + Warnings: + + - The values [API_KEY,USER] on the enum `EventSource` will be removed. If these variants are still used in the database, this will fail. + - The values [WORKSPACE_DELETED,API_KEY_UPDATED,API_KEY_DELETED,API_KEY_ADDED,USER_UPDATED] on the enum `EventType` will be removed. If these variants are still used in the database, this will fail. + - You are about to drop the column `sourceApiKeyId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceApprovalId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceEnvironmentId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceProjectId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceSecretId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceUserId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceVariableId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceWorkspaceId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceWorkspaceMembershipId` on the `Event` table. All the data in the column will be lost. + - You are about to drop the column `sourceWorkspaceRoleId` on the `Event` table. All the data in the column will be lost. + +*/ +-- AlterEnum +BEGIN; +CREATE TYPE "EventSource_new" AS ENUM ('SECRET', 'VARIABLE', 'ENVIRONMENT', 'PROJECT', 'WORKSPACE', 'WORKSPACE_ROLE', 'APPROVAL'); +ALTER TABLE "Event" ALTER COLUMN "source" TYPE "EventSource_new" USING ("source"::text::"EventSource_new"); +ALTER TYPE "EventSource" RENAME TO "EventSource_old"; +ALTER TYPE "EventSource_new" RENAME TO "EventSource"; +DROP TYPE "EventSource_old"; +COMMIT; + +-- AlterEnum +BEGIN; +CREATE TYPE "EventType_new" AS ENUM ('INVITED_TO_WORKSPACE', 'REMOVED_FROM_WORKSPACE', 'ACCEPTED_INVITATION', 'DECLINED_INVITATION', 'CANCELLED_INVITATION', 'LEFT_WORKSPACE', 'WORKSPACE_MEMBERSHIP_UPDATED', 'WORKSPACE_UPDATED', 'WORKSPACE_CREATED', 'WORKSPACE_ROLE_CREATED', 'WORKSPACE_ROLE_UPDATED', 'WORKSPACE_ROLE_DELETED', 'PROJECT_CREATED', 'PROJECT_UPDATED', 'PROJECT_DELETED', 'SECRET_UPDATED', 'SECRET_DELETED', 'SECRET_ADDED', 'VARIABLE_UPDATED', 'VARIABLE_DELETED', 'VARIABLE_ADDED', 'ENVIRONMENT_UPDATED', 'ENVIRONMENT_DELETED', 'ENVIRONMENT_ADDED', 'APPROVAL_CREATED', 'APPROVAL_UPDATED', 'APPROVAL_DELETED', 'APPROVAL_APPROVED', 'APPROVAL_REJECTED'); +ALTER TABLE "Event" ALTER COLUMN "type" TYPE "EventType_new" USING ("type"::text::"EventType_new"); +ALTER TYPE "EventType" RENAME TO "EventType_old"; +ALTER TYPE "EventType_new" RENAME TO "EventType"; +DROP TYPE "EventType_old"; +COMMIT; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceApiKeyId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceApprovalId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceEnvironmentId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceProjectId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceSecretId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceUserId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceVariableId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceWorkspaceId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceWorkspaceMembershipId_fkey"; + +-- DropForeignKey +ALTER TABLE "Event" DROP CONSTRAINT "Event_sourceWorkspaceRoleId_fkey"; + +-- AlterTable +ALTER TABLE "Event" DROP COLUMN "sourceApiKeyId", +DROP COLUMN "sourceApprovalId", +DROP COLUMN "sourceEnvironmentId", +DROP COLUMN "sourceProjectId", +DROP COLUMN "sourceSecretId", +DROP COLUMN "sourceUserId", +DROP COLUMN "sourceVariableId", +DROP COLUMN "sourceWorkspaceId", +DROP COLUMN "sourceWorkspaceMembershipId", +DROP COLUMN "sourceWorkspaceRoleId", +ADD COLUMN "itemId" TEXT, +ADD COLUMN "userId" TEXT, +ADD COLUMN "workspaceId" TEXT; + +-- AddForeignKey +ALTER TABLE "Event" ADD CONSTRAINT "Event_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Event" ADD CONSTRAINT "Event_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/api/src/prisma/schema.prisma b/apps/api/src/prisma/schema.prisma index fc0cc396..9e1ccab7 100644 --- a/apps/api/src/prisma/schema.prisma +++ b/apps/api/src/prisma/schema.prisma @@ -30,12 +30,10 @@ enum ApprovalAction { enum EventSource { SECRET VARIABLE - API_KEY ENVIRONMENT PROJECT WORKSPACE WORKSPACE_ROLE - USER APPROVAL } @@ -59,7 +57,6 @@ enum EventType { LEFT_WORKSPACE WORKSPACE_MEMBERSHIP_UPDATED WORKSPACE_UPDATED - WORKSPACE_DELETED WORKSPACE_CREATED WORKSPACE_ROLE_CREATED WORKSPACE_ROLE_UPDATED @@ -73,13 +70,9 @@ enum EventType { VARIABLE_UPDATED VARIABLE_DELETED VARIABLE_ADDED - API_KEY_UPDATED - API_KEY_DELETED - API_KEY_ADDED ENVIRONMENT_UPDATED ENVIRONMENT_DELETED ENVIRONMENT_ADDED - USER_UPDATED APPROVAL_CREATED APPROVAL_UPDATED APPROVAL_DELETED @@ -167,27 +160,12 @@ model Event { metadata Json title String description String? + itemId String? - sourceUser User? @relation(fields: [sourceUserId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceUserId String? - sourceWorkspace Workspace? @relation(fields: [sourceWorkspaceId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceWorkspaceId String? - sourceWorkspaceRole WorkspaceRole? @relation(fields: [sourceWorkspaceRoleId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceWorkspaceRoleId String? - sourceProject Project? @relation(fields: [sourceProjectId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceProjectId String? - sourceEnvironment Environment? @relation(fields: [sourceEnvironmentId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceEnvironmentId String? - sourceSecret Secret? @relation(fields: [sourceSecretId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceSecretId String? - sourceVariable Variable? @relation(fields: [sourceVariableId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceVariableId String? - sourceApiKey ApiKey? @relation(fields: [sourceApiKeyId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceApiKeyId String? - sourceWorkspaceMembership WorkspaceMember? @relation(fields: [sourceWorkspaceMembershipId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceWorkspaceMembershipId String? - sourceApproval Approval? @relation(fields: [sourceApprovalId], references: [id], onDelete: SetNull, onUpdate: Cascade) - sourceApprovalId String? + user User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade) + userId String? + workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: SetNull, onUpdate: Cascade) + workspaceId String? } model Notification { @@ -256,8 +234,6 @@ model Environment { project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade) projectId String - - events Event[] } model Project { @@ -276,10 +252,8 @@ model Project { lastUpdatedBy User? @relation(fields: [lastUpdatedById], references: [id], onUpdate: Cascade, onDelete: SetNull) lastUpdatedById String? - workspaceId String - workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - - events Event[] + workspaceId String + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade, onUpdate: Cascade) secrets Secret[] variables Variable[] environments Environment[] @@ -309,9 +283,7 @@ model WorkspaceRole { authorities Authority[] workspaceMembers WorkspaceMemberRoleAssociation[] - - events Event[] - projects ProjectWorkspaceRoleAssociation[] + projects ProjectWorkspaceRoleAssociation[] workspaceId String workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade, onUpdate: Cascade) @@ -340,7 +312,6 @@ model WorkspaceMember { workspaceId String invitationAccepted Boolean @default(false) roles WorkspaceMemberRoleAssociation[] - events Event[] @@unique([workspaceId, userId]) } @@ -378,8 +349,6 @@ model Secret { environmentId String environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - - events Event[] } model VariableVersion { @@ -414,8 +383,6 @@ model Variable { environmentId String environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade) - - events Event[] } model ApiKey { @@ -426,7 +393,6 @@ model ApiKey { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt authorities Authority[] - events Event[] user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) userId String @@ -487,7 +453,5 @@ model Approval { workspaceId String workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade, onUpdate: Cascade) - events Event[] - @@index([itemType, itemId]) } diff --git a/apps/api/src/project/project.e2e.spec.ts b/apps/api/src/project/project.e2e.spec.ts index ba94303e..12dda08c 100644 --- a/apps/api/src/project/project.e2e.spec.ts +++ b/apps/api/src/project/project.e2e.spec.ts @@ -17,24 +17,25 @@ import { EventType, Project, User, - Workspace, - WorkspaceRole + Workspace } from '@prisma/client' import { v4 } from 'uuid' import fetchEvents from '../common/fetch-events' +import { EventService } from '../event/service/event.service' +import { EventModule } from '../event/event.module' describe('Project Controller Tests', () => { let app: NestFastifyApplication let prisma: PrismaService + let eventService: EventService let user1: User, user2: User let workspace1: Workspace let project1: Project, project2: Project, otherProject: Project - let adminRole1: WorkspaceRole beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [AppModule, ProjectModule] + imports: [AppModule, ProjectModule, EventModule] }) .overrideProvider(MAIL_SERVICE) .useClass(MockMailService) @@ -44,6 +45,7 @@ describe('Project Controller Tests', () => { new FastifyAdapter() ) prisma = moduleRef.get(PrismaService) + eventService = moduleRef.get(EventService) await app.init() await app.getHttpAdapter().getInstance().ready() @@ -144,8 +146,6 @@ describe('Project Controller Tests', () => { workspace1 = result[2] otherProject = result[6] - - adminRole1 = result[3] }) it('should be defined', async () => { @@ -219,24 +219,23 @@ describe('Project Controller Tests', () => { }) }) - // it('should have created a PROJECT_CREATED event', async () => { - // const response = await fetchEvents(app, user1, 'projectId=' + project1.id) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.PROJECT, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.PROJECT_CREATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a PROJECT_CREATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.PROJECT + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.PROJECT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.PROJECT_CREATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should have added the project to the admin role of the workspace', async () => { const adminRole = await prisma.workspaceRole.findUnique({ @@ -399,24 +398,23 @@ describe('Project Controller Tests', () => { }) }) - // it('should have created a PROJECT_UPDATED event', async () => { - // const response = await fetchEvents(app, user1, 'projectId=' + project1.id) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.PROJECT, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.PROJECT_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a PROJECT_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.PROJECT + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.PROJECT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.PROJECT_UPDATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBe(project1.id) + }) it('should be able to fetch a project by its id', async () => { const response = await app.inject({ @@ -649,6 +647,24 @@ describe('Project Controller Tests', () => { expect(response.statusCode).toBe(200) }) + it('should have created a PROJECT_DELETED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.PROJECT + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.PROJECT) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.PROJECT_DELETED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBe(project1.id) + }) + it('should have removed all environments of the project', async () => { const environments = await prisma.environment.findMany({ where: { @@ -710,29 +726,6 @@ describe('Project Controller Tests', () => { }) }) - // it('should have created a PROJECT_DELETED event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.PROJECT_DELETED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) - afterAll(async () => { await cleanUp(prisma) }) diff --git a/apps/api/src/project/service/project.service.ts b/apps/api/src/project/service/project.service.ts index 4f965bf9..337381b4 100644 --- a/apps/api/src/project/service/project.service.ts +++ b/apps/api/src/project/service/project.service.ts @@ -168,7 +168,7 @@ export class ProjectService { ...createEnvironmentOps ]) - createEvent( + await createEvent( { triggeredBy: user, entity: newProject, @@ -180,7 +180,8 @@ export class ProjectService { name: newProject.name, workspaceId, workspaceName: workspace.name - } + }, + workspaceId }, this.prisma ) @@ -505,7 +506,7 @@ export class ProjectService { ...versionUpdateOps ]) - createEvent( + await createEvent( { triggeredBy: user, entity: updatedProject, @@ -515,7 +516,8 @@ export class ProjectService { metadata: { projectId: updatedProject.id, name: updatedProject.name - } + }, + workspaceId: updatedProject.workspaceId }, this.prisma ) @@ -559,23 +561,18 @@ export class ProjectService { await this.prisma.$transaction(op) - const workspace = await this.prisma.workspace.findUnique({ - where: { - id: project.workspaceId - } - }) - - createEvent( + await createEvent( { triggeredBy: user, type: EventType.PROJECT_DELETED, - source: EventSource.WORKSPACE, - entity: workspace, + source: EventSource.PROJECT, + entity: project, title: `Project deleted`, metadata: { projectId: project.id, name: project.name - } + }, + workspaceId: project.workspaceId }, this.prisma ) diff --git a/apps/api/src/secret/secret.e2e.spec.ts b/apps/api/src/secret/secret.e2e.spec.ts index 4c730ce3..a6cff478 100644 --- a/apps/api/src/secret/secret.e2e.spec.ts +++ b/apps/api/src/secret/secret.e2e.spec.ts @@ -31,6 +31,7 @@ import { EnvironmentService } from '../environment/service/environment.service' import { v4 } from 'uuid' import fetchEvents from '../common/fetch-events' import { SecretService } from './service/secret.service' +import { EventService } from '../event/service/event.service' describe('Secret Controller Tests', () => { let app: NestFastifyApplication @@ -39,6 +40,7 @@ describe('Secret Controller Tests', () => { let workspaceService: WorkspaceService let environmentService: EnvironmentService let secretService: SecretService + let eventService: EventService let user1: User, user2: User let workspace1: Workspace, workspace2: Workspace @@ -71,6 +73,7 @@ describe('Secret Controller Tests', () => { workspaceService = moduleRef.get(WorkspaceService) environmentService = moduleRef.get(EnvironmentService) secretService = moduleRef.get(SecretService) + eventService = moduleRef.get(EventService) await app.init() await app.getHttpAdapter().getInstance().ready() @@ -355,24 +358,23 @@ describe('Secret Controller Tests', () => { ) }) - // it('should have created a SECRET_ADDED event', async () => { - // const response = await fetchEvents(app, user1, 'secretId=' + secret1.id) + it('should have created a SECRET_ADDED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.SECRET + ) - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.SECRET, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.SECRET_ADDED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } + const event = response[0] - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + expect(event.source).toBe(EventSource.SECRET) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.SECRET_ADDED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should not be able to update a non-existing secret', async () => { const response = await app.inject({ @@ -465,24 +467,23 @@ describe('Secret Controller Tests', () => { expect(secretVersion.length).toBe(2) }) - // it('should have created a SECRET_UPDATED event', async () => { - // const response = await fetchEvents(app, user1, 'secretId=' + secret1.id) + it('should have created a SECRET_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.SECRET + ) - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.SECRET, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.SECRET_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } + const event = response[0] - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + expect(event.source).toBe(EventSource.SECRET) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.SECRET_UPDATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBe(secret1.id) + }) it('should be able to update the environment of a secret', async () => { const response = await app.inject({ @@ -561,25 +562,6 @@ describe('Secret Controller Tests', () => { ) }) - // it('should have created a SECRET_UPDATED event', async () => { - // const response = await fetchEvents(app, user1, 'secretId=' + secret1.id) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.SECRET, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.SECRET_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) - it('should not be able to move a secret of the same name to an environment', async () => { const newSecret = await prisma.secret.create({ data: { @@ -966,6 +948,24 @@ describe('Secret Controller Tests', () => { expect(response.statusCode).toBe(200) }) + it('should have created a SECRET_DELETED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.SECRET + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.SECRET) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.SECRET_DELETED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBe(secret1.id) + }) + afterAll(async () => { await app.close() }) diff --git a/apps/api/src/secret/service/secret.service.ts b/apps/api/src/secret/service/secret.service.ts index 4b2cc6ba..2eb5267e 100644 --- a/apps/api/src/secret/service/secret.service.ts +++ b/apps/api/src/secret/service/secret.service.ts @@ -127,7 +127,7 @@ export class SecretService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: secret, @@ -141,7 +141,8 @@ export class SecretService { projectName: project.name, environmentId: environment.id, environmentName: environment.name - } + }, + workspaceId: project.workspaceId }, this.prisma ) @@ -596,7 +597,7 @@ export class SecretService { }) } - createEvent( + await createEvent( { triggeredBy: user, entity: secret, @@ -608,7 +609,8 @@ export class SecretService { name: secret.name, projectId: secret.projectId, projectName: secret.project.name - } + }, + workspaceId: secret.project.workspaceId }, this.prisma ) @@ -633,7 +635,7 @@ export class SecretService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: secret, @@ -647,7 +649,8 @@ export class SecretService { projectName: secret.project.name, environmentId: environment.id, environmentName: environment.name - } + }, + workspaceId: secret.project.workspaceId }, this.prisma ) @@ -672,7 +675,7 @@ export class SecretService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: secret, @@ -684,7 +687,8 @@ export class SecretService { name: secret.name, projectId: secret.projectId, projectName: secret.project.name - } + }, + workspaceId: secret.project.workspaceId }, this.prisma ) @@ -726,15 +730,17 @@ export class SecretService { await this.prisma.$transaction(op) - createEvent( + await createEvent( { triggeredBy: user, type: EventType.SECRET_DELETED, source: EventSource.SECRET, + entity: secret, title: `Secret deleted`, metadata: { secretId: secret.id - } + }, + workspaceId: secret.project.workspaceId }, this.prisma ) diff --git a/apps/api/src/user/service/user.service.ts b/apps/api/src/user/service/user.service.ts index 49dce185..1961dd3e 100644 --- a/apps/api/src/user/service/user.service.ts +++ b/apps/api/src/user/service/user.service.ts @@ -1,6 +1,6 @@ import { ConflictException, Inject, Injectable, Logger } from '@nestjs/common' import { UpdateUserDto } from '../dto/update.user/update.user' -import { EventSource, EventType, User } from '@prisma/client' +import { User } from '@prisma/client' import { PrismaService } from '../../prisma/prisma.service' import { CreateUserDto } from '../dto/create.user/create.user' import { @@ -8,7 +8,6 @@ import { MAIL_SERVICE } from '../../mail/services/interface.service' import createUser from '../../common/create-user' -import createEvent from '../../common/create-event' @Injectable() export class UserService { @@ -41,19 +40,6 @@ export class UserService { data }) - createEvent( - { - title: 'User updated', - type: EventType.USER_UPDATED, - triggeredBy: user, - source: EventSource.USER, - metadata: { - userId: user.id - } - }, - this.prisma - ) - return updatedUser } diff --git a/apps/api/src/variable/service/variable.service.ts b/apps/api/src/variable/service/variable.service.ts index 2879766a..a91f5503 100644 --- a/apps/api/src/variable/service/variable.service.ts +++ b/apps/api/src/variable/service/variable.service.ts @@ -130,7 +130,7 @@ export class VariableService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: variable, @@ -144,7 +144,8 @@ export class VariableService { projectName: project.name, environmentId: environment.id, environmentName: environment.name - } + }, + workspaceId: project.workspaceId }, this.prisma ) @@ -537,7 +538,7 @@ export class VariableService { }) } - createEvent( + await createEvent( { triggeredBy: user, entity: variable, @@ -549,7 +550,8 @@ export class VariableService { name: variable.name, projectId: variable.projectId, projectName: variable.project.name - } + }, + workspaceId: variable.project.workspaceId }, this.prisma ) @@ -578,7 +580,7 @@ export class VariableService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: variable, @@ -592,7 +594,8 @@ export class VariableService { projectName: variable.project.name, environmentId: environment.id, environmentName: environment.name - } + }, + workspaceId: variable.project.workspaceId }, this.prisma ) @@ -617,7 +620,7 @@ export class VariableService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: variable, @@ -630,7 +633,8 @@ export class VariableService { projectId: variable.projectId, projectName: variable.project.name, rollbackVersion - } + }, + workspaceId: variable.project.workspaceId }, this.prisma ) @@ -674,18 +678,20 @@ export class VariableService { await this.prisma.$transaction(op) - createEvent( + await createEvent( { triggeredBy: user, type: EventType.VARIABLE_DELETED, source: EventSource.VARIABLE, title: `Variable deleted`, + entity: variable, metadata: { variableId: variable.id, name: variable.name, projectId: variable.projectId, projectName: variable.project.name - } + }, + workspaceId: variable.project.workspaceId }, this.prisma ) diff --git a/apps/api/src/variable/variable.e2e.spec.ts b/apps/api/src/variable/variable.e2e.spec.ts index 3b9e3a7d..c8999b86 100644 --- a/apps/api/src/variable/variable.e2e.spec.ts +++ b/apps/api/src/variable/variable.e2e.spec.ts @@ -31,6 +31,7 @@ import { EnvironmentService } from '../environment/service/environment.service' import { v4 } from 'uuid' import fetchEvents from '../common/fetch-events' import { VariableService } from './service/variable.service' +import { EventService } from '../event/service/event.service' describe('Variable Controller Tests', () => { let app: NestFastifyApplication @@ -39,6 +40,7 @@ describe('Variable Controller Tests', () => { let workspaceService: WorkspaceService let environmentService: EnvironmentService let variableService: VariableService + let eventService: EventService let user1: User, user2: User let workspace1: Workspace, workspace2: Workspace @@ -71,6 +73,7 @@ describe('Variable Controller Tests', () => { workspaceService = moduleRef.get(WorkspaceService) environmentService = moduleRef.get(EnvironmentService) variableService = moduleRef.get(VariableService) + eventService = moduleRef.get(EventService) await app.init() await app.getHttpAdapter().getInstance().ready() @@ -357,24 +360,23 @@ describe('Variable Controller Tests', () => { ) }) - // it('should have created a VARIABLE_ADDED event', async () => { - // const response = await fetchEvents(app, user1, 'variableId=' + variable1.id) + it('should have created a VARIABLE_ADDED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.VARIABLE + ) - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.VARIABLE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.VARIABLE_ADDED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } + const event = response[0] - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + expect(event.source).toBe(EventSource.VARIABLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.VARIABLE_ADDED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should not be able to update a non-existing variable', async () => { const response = await app.inject({ @@ -467,24 +469,23 @@ describe('Variable Controller Tests', () => { expect(variableVersion.length).toBe(2) }) - // it('should have created a VARIABLE_UPDATED event', async () => { - // const response = await fetchEvents(app, user1, 'variableId=' + variable1.id) + it('should have created a VARIABLE_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.VARIABLE + ) - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.VARIABLE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.VARIABLE_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } + const event = response[0] - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + expect(event.source).toBe(EventSource.VARIABLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.VARIABLE_UPDATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to update the environment of a variable', async () => { const response = await app.inject({ @@ -563,25 +564,6 @@ describe('Variable Controller Tests', () => { ) }) - // it('should have created a VARIABLE_UPDATED event', async () => { - // const response = await fetchEvents(app, user1, 'variableId=' + variable1.id) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.VARIABLE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.VARIABLE_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) - it('should not be able to move a variable of the same name to an environment', async () => { const newVariable = await prisma.variable.create({ data: { @@ -841,6 +823,24 @@ describe('Variable Controller Tests', () => { expect(response.statusCode).toBe(200) }) + it('should have created a VARIABLE_DELETED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.VARIABLE + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.VARIABLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.VARIABLE_DELETED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) + afterAll(async () => { await app.close() }) diff --git a/apps/api/src/workspace-role/service/workspace-role.service.ts b/apps/api/src/workspace-role/service/workspace-role.service.ts index 08616ee1..2ee4de70 100644 --- a/apps/api/src/workspace-role/service/workspace-role.service.ts +++ b/apps/api/src/workspace-role/service/workspace-role.service.ts @@ -21,6 +21,7 @@ import { UpdateWorkspaceRole } from '../dto/update-workspace-role/update-workspa import { PrismaService } from '../../prisma/prisma.service' import createEvent from '../../common/create-event' import { WorkspaceRoleWithProjects } from '../workspace-role.types' +import { v4 } from 'uuid' @Injectable() export class WorkspaceRoleService { @@ -55,44 +56,64 @@ export class WorkspaceRoleService { ) } - const workspaceRole = await this.prisma.workspaceRole.create({ - data: { - name: dto.name, - description: dto.description, - colorCode: dto.colorCode, - authorities: dto.authorities ?? [], - hasAdminAuthority: false, - projects: { - connect: dto.projectIds?.map((id) => ({ id })) - }, - workspace: { - connect: { - id: workspaceId + const workspaceRoleId = v4() + + const op = [] + + // Create the workspace role + op.push( + this.prisma.workspaceRole.create({ + data: { + id: workspaceRoleId, + name: dto.name, + description: dto.description, + colorCode: dto.colorCode, + authorities: dto.authorities ?? [], + hasAdminAuthority: false, + workspace: { + connect: { + id: workspaceId + } } - } - }, - include: { - projects: { - select: { - projectId: true + }, + include: { + projects: { + select: { + projectId: true + } } } - } - }) + }) + ) + + // Create the project associations + if (dto.projectIds && dto.projectIds.length > 0) { + op.push( + this.prisma.projectWorkspaceRoleAssociation.createMany({ + data: dto.projectIds.map((projectId) => ({ + roleId: workspaceRoleId, + projectId + })) + }) + ) + } + + const workspaceRole = (await this.prisma.$transaction(op))[0] - createEvent( + await createEvent( { triggeredBy: user, entity: workspaceRole, type: EventType.WORKSPACE_ROLE_CREATED, source: EventSource.WORKSPACE_ROLE, - title: `Workspace deleted`, + title: `Workspace role created`, metadata: { workspaceRoleId: workspaceRole.id, name: workspaceRole.name, workspaceId, workspaceName: workspace.name - } + }, + workspaceId }, this.prisma ) @@ -172,7 +193,7 @@ export class WorkspaceRoleService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: workspaceRole, @@ -183,7 +204,8 @@ export class WorkspaceRoleService { workspaceRoleId: workspaceRole.id, name: workspaceRole.name, workspaceId: workspaceRole.workspaceId - } + }, + workspaceId: workspaceRole.workspaceId }, this.prisma ) @@ -214,17 +236,19 @@ export class WorkspaceRoleService { } }) - createEvent( + await createEvent( { triggeredBy: user, type: EventType.WORKSPACE_ROLE_DELETED, source: EventSource.WORKSPACE_ROLE, title: `Workspace role deleted`, + entity: workspaceRole, metadata: { workspaceRoleId: workspaceRole.id, name: workspaceRole.name, workspaceId: workspaceRole.workspaceId - } + }, + workspaceId: workspaceRole.workspaceId }, this.prisma ) diff --git a/apps/api/src/workspace-role/workspace-role.e2e.spec.ts b/apps/api/src/workspace-role/workspace-role.e2e.spec.ts index f13a80da..8932ac77 100644 --- a/apps/api/src/workspace-role/workspace-role.e2e.spec.ts +++ b/apps/api/src/workspace-role/workspace-role.e2e.spec.ts @@ -5,6 +5,10 @@ import { import { PrismaService } from '../prisma/prisma.service' import { Authority, + EventSeverity, + EventSource, + EventTriggerer, + EventType, Project, User, Workspace, @@ -17,10 +21,14 @@ import { MockMailService } from '../mail/services/mock.service' import { Test } from '@nestjs/testing' import { v4 } from 'uuid' import cleanUp from '../common/cleanup' +import fetchEvents from '../common/fetch-events' +import { EventService } from '../event/service/event.service' +import { EventModule } from '../event/event.module' describe('Workspace Role Controller Tests', () => { let app: NestFastifyApplication let prisma: PrismaService + let eventService: EventService let alice: User let bob: User @@ -33,7 +41,7 @@ describe('Workspace Role Controller Tests', () => { beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [AppModule, WorkspaceRoleModule] + imports: [AppModule, WorkspaceRoleModule, EventModule] }) .overrideProvider(MAIL_SERVICE) .useClass(MockMailService) @@ -42,6 +50,7 @@ describe('Workspace Role Controller Tests', () => { new FastifyAdapter() ) prisma = moduleRef.get(PrismaService) + eventService = moduleRef.get(EventService) await app.init() await app.getHttpAdapter().getInstance().ready() @@ -337,6 +346,24 @@ describe('Workspace Role Controller Tests', () => { ) }) + it('should have created a WORKSPACE_ROLE_CREATED event', async () => { + const response = await fetchEvents( + eventService, + alice, + workspaceAlice.id, + EventSource.WORKSPACE_ROLE + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.WORKSPACE_ROLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_ROLE_CREATED) + expect(event.workspaceId).toBe(workspaceAlice.id) + expect(event.itemId).toBeDefined() + }) + it('should not be able to create a workspace role for other workspace', async () => { const response = await app.inject({ method: 'POST', @@ -458,6 +485,24 @@ describe('Workspace Role Controller Tests', () => { adminRole1 = response.json() }) + it('should have created a WORKSPACE_ROLE_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + alice, + workspaceAlice.id, + EventSource.WORKSPACE_ROLE + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.WORKSPACE_ROLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_ROLE_UPDATED) + expect(event.workspaceId).toBe(workspaceAlice.id) + expect(event.itemId).toBeDefined() + }) + it('should not be able to add WORKSPACE_ADMIN authority to the role', async () => { const response = await app.inject({ method: 'PUT', @@ -650,6 +695,24 @@ describe('Workspace Role Controller Tests', () => { expect(response.statusCode).toBe(200) }) + it('should have created a WORKSPACE_ROLE_DELETED event', async () => { + const response = await fetchEvents( + eventService, + alice, + workspaceAlice.id, + EventSource.WORKSPACE_ROLE + ) + + const event = response[0] + + expect(event.source).toBe(EventSource.WORKSPACE_ROLE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_ROLE_DELETED) + expect(event.workspaceId).toBe(workspaceAlice.id) + expect(event.itemId).toBeDefined() + }) + it('should not be able to delete the auto generated admin role', async () => { const response = await app.inject({ method: 'DELETE', diff --git a/apps/api/src/workspace/service/workspace.service.ts b/apps/api/src/workspace/service/workspace.service.ts index af11bc6e..3b177b03 100644 --- a/apps/api/src/workspace/service/workspace.service.ts +++ b/apps/api/src/workspace/service/workspace.service.ts @@ -111,7 +111,7 @@ export class WorkspaceService { ]) const workspace = result[0] - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -121,7 +121,8 @@ export class WorkspaceService { metadata: { workspaceId: workspace.id, name: workspace.name - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -264,7 +265,7 @@ export class WorkspaceService { throw new InternalServerErrorException('Error in transaction') } - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -275,7 +276,8 @@ export class WorkspaceService { workspaceId: workspace.id, name: workspace.name, newOwnerId: userId - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -303,20 +305,6 @@ export class WorkspaceService { } }) - createEvent( - { - triggeredBy: user, - type: EventType.WORKSPACE_DELETED, - source: EventSource.WORKSPACE, - title: `Workspace deleted`, - metadata: { - workspaceId: workspace.id, - name: workspace.name - } - }, - this.prisma - ) - this.log.debug(`Deleted workspace ${workspace.name} (${workspace.id})`) } @@ -336,7 +324,7 @@ export class WorkspaceService { if (members && members.length > 0) { await this.addMembersToWorkspace(workspace, user, members) - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -347,7 +335,8 @@ export class WorkspaceService { workspaceId: workspace.id, name: workspace.name, members: members.map((m) => m.email) - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -395,7 +384,7 @@ export class WorkspaceService { }) } - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -406,7 +395,8 @@ export class WorkspaceService { workspaceId: workspace.id, name: workspace.name, members: userIds - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -472,7 +462,7 @@ export class WorkspaceService { createNewAssociations ]) - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -484,7 +474,8 @@ export class WorkspaceService { name: workspace.name, userId, roleIds - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -587,7 +578,7 @@ export class WorkspaceService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -596,7 +587,8 @@ export class WorkspaceService { title: `${user.name} accepted invitation to workspace ${workspace.name}`, metadata: { workspaceId: workspaceId - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -627,7 +619,7 @@ export class WorkspaceService { // Delete the membership await this.deleteMembership(workspaceId, inviteeId) - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -637,7 +629,8 @@ export class WorkspaceService { metadata: { workspaceId: workspaceId, inviteeId - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -663,7 +656,7 @@ export class WorkspaceService { } }) - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -672,7 +665,8 @@ export class WorkspaceService { title: `${user.name} declined invitation to workspace ${workspace.name}`, metadata: { workspaceId: workspaceId - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -713,7 +707,7 @@ export class WorkspaceService { // Delete the membership await this.deleteMembership(workspaceId, user.id) - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -722,7 +716,8 @@ export class WorkspaceService { title: `User left workspace`, metadata: { workspaceId: workspaceId - } + }, + workspaceId: workspace.id }, this.prisma ) @@ -1076,7 +1071,7 @@ export class WorkspaceService { }) this.log.debug(`Updated workspace ${workspace.name} (${workspace.id})`) - createEvent( + await createEvent( { triggeredBy: user, entity: workspace, @@ -1086,7 +1081,8 @@ export class WorkspaceService { metadata: { workspaceId: workspace.id, name: workspace.name - } + }, + workspaceId: workspace.id }, this.prisma ) diff --git a/apps/api/src/workspace/workspace.e2e.spec.ts b/apps/api/src/workspace/workspace.e2e.spec.ts index ffd65952..9d7dfe69 100644 --- a/apps/api/src/workspace/workspace.e2e.spec.ts +++ b/apps/api/src/workspace/workspace.e2e.spec.ts @@ -20,6 +20,8 @@ import { } from '@prisma/client' import cleanUp from '../common/cleanup' import fetchEvents from '../common/fetch-events' +import { EventService } from '../event/service/event.service' +import { EventModule } from '../event/event.module' const createMembership = async ( adminRoleId: string, @@ -47,6 +49,7 @@ const createMembership = async ( describe('Workspace Controller Tests', () => { let app: NestFastifyApplication let prisma: PrismaService + let eventService: EventService let user1: User, user2: User, user3: User let workspace1: Workspace, workspace2: Workspace @@ -54,7 +57,7 @@ describe('Workspace Controller Tests', () => { beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [AppModule, WorkspaceModule] + imports: [AppModule, WorkspaceModule, EventModule] }) .overrideProvider(MAIL_SERVICE) .useClass(MockMailService) @@ -64,6 +67,7 @@ describe('Workspace Controller Tests', () => { new FastifyAdapter() ) prisma = moduleRef.get(PrismaService) + eventService = moduleRef.get(EventService) await app.init() await app.getHttpAdapter().getInstance().ready() @@ -189,28 +193,24 @@ describe('Workspace Controller Tests', () => { workspace2 = response.json() }) - // it('should have created a WORKSPACE_CREATED event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.WORKSPACE_CREATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a WORKSPACE_CREATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_CREATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should have created a new role with name Admin', async () => { adminRole = await prisma.workspaceRole.findUnique({ @@ -326,28 +326,24 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a WORKSPACE_UPDATED event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.WORKSPACE_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a WORKSPACE_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_UPDATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should do nothing if null or empty array is sent for invitation of user', async () => { const response = await app.inject({ @@ -428,28 +424,24 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a INVITED_TO_WORKSPACE event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.INVITED_TO_WORKSPACE, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a INVITED_TO_WORKSPACE event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.INVITED_TO_WORKSPACE) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to cancel the invitation', async () => { const response = await app.inject({ @@ -491,28 +483,24 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a CANCELLED_INVITATION event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.CANCELLED_INVITATION, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a CANCELLED_INVITATION event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.CANCELLED_INVITATION) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to decline invitation to the workspace', async () => { await createMembership(adminRole.id, user2.id, workspace1.id, prisma) @@ -556,28 +544,24 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a DECLINED_INVITATION event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.DECLINED_INVITATION, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a DECLINED_INVITATION event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.DECLINED_INVITATION) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to accept the invitation to the workspace', async () => { await createMembership(adminRole.id, user2.id, workspace1.id, prisma) @@ -627,28 +611,24 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a ACCEPT_INVITATION event', async () => { - // const response = await fetchEvents( - // app, - // user2, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.ACCEPTED_INVITATION, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a ACCEPT_INVITATION event', async () => { + const response = await fetchEvents( + eventService, + user2, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.ACCEPTED_INVITATION) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to leave the workspace', async () => { const response = await app.inject({ @@ -707,28 +687,24 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a LEFT_WORKSPACE event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.LEFT_WORKSPACE, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a LEFT_WORKSPACE event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.LEFT_WORKSPACE) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to update the role of a member', async () => { await createMembership(adminRole.id, user2.id, workspace1.id, prisma) @@ -767,28 +743,24 @@ describe('Workspace Controller Tests', () => { ]) }) - // it('should have created a WORKSPACE_MEMBERSHIP_UPDATED event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.WORKSPACE_MEMBERSHIP_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a WORKSPACE_MEMBERSHIP_UPDATED event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.WORKSPACE_MEMBERSHIP_UPDATED) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should be able to remove users from workspace', async () => { const response = await app.inject({ @@ -832,28 +804,24 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a REMOVED_FROM_WORKSPACE event', async () => { - // const response = await fetchEvents( - // app, - // user1, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.REMOVED_FROM_WORKSPACE, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) + it('should have created a REMOVED_FROM_WORKSPACE event', async () => { + const response = await fetchEvents( + eventService, + user1, + workspace1.id, + EventSource.WORKSPACE + ) + + const event = response[0] + + expect(event).toBeDefined() + expect(event.source).toBe(EventSource.WORKSPACE) + expect(event.triggerer).toBe(EventTriggerer.USER) + expect(event.severity).toBe(EventSeverity.INFO) + expect(event.type).toBe(EventType.REMOVED_FROM_WORKSPACE) + expect(event.workspaceId).toBe(workspace1.id) + expect(event.itemId).toBeDefined() + }) it('should not be able to update the role of a non existing member', async () => { const response = await app.inject({ @@ -1113,29 +1081,6 @@ describe('Workspace Controller Tests', () => { }) }) - // it('should have created a WORKSPACE_UPDATED event', async () => { - // const response = await fetchEvents( - // app, - // user2, - // 'workspaceId=' + workspace1.id - // ) - - // const event = { - // id: expect.any(String), - // title: expect.any(String), - // description: expect.any(String), - // source: EventSource.WORKSPACE, - // triggerer: EventTriggerer.USER, - // severity: EventSeverity.INFO, - // type: EventType.WORKSPACE_UPDATED, - // timestamp: expect.any(String), - // metadata: expect.any(Object) - // } - - // expect(response.statusCode).toBe(200) - // expect(response.json()).toEqual(expect.arrayContaining([event])) - // }) - it('should not be able to export data of a non-existing workspace', async () => { const response = await app.inject({ method: 'GET',