Skip to content

Commit

Permalink
feat(api): add event (#115)
Browse files Browse the repository at this point in the history
Co-authored-by: agentR47@gmail.com <raj@raj-beast>
  • Loading branch information
rajdip-b and agentR47@gmail.com authored Feb 9, 2024
1 parent 69a832e commit 19e6603
Show file tree
Hide file tree
Showing 20 changed files with 1,127 additions and 106 deletions.
11 changes: 1 addition & 10 deletions .github/workflows/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
tags:
- '*'
pull_request:
types: [opened]
paths: ['apps/api/**', '.github/workflows/api.yaml']

jobs:
validate:
Expand Down Expand Up @@ -61,15 +61,6 @@ jobs:
docker compose up -d
pnpm run e2e:api
docker compose down
ls -l
- name: Upload unit test coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: api-unit-tests
files: /coverage/api/coverage-final.json
- name: Upload e2e test coverage reports to Codecov
uses: codecov/codecov-action@v3
Expand Down
48 changes: 46 additions & 2 deletions apps/api/src/api-key/service/api-key.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ 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 { User } from '@prisma/client'
import { ApiKey, EventSource, EventType, User } from '@prisma/client'
import createEvent from '../../common/create-event'

@Injectable()
export class ApiKeyService {
Expand Down Expand Up @@ -42,6 +43,21 @@ 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 {
Expand Down Expand Up @@ -77,18 +93,46 @@ 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) {
return this.prisma.apiKey.delete({
const apiKey = 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
)
}

async getApiKeyById(user: User, apiKeyId: string) {
Expand Down
4 changes: 3 additions & 1 deletion apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ApiKeyModule } from '../api-key/api-key.module'
import { WorkspaceModule } from '../workspace/workspace.module'
import { WorkspaceRoleModule } from '../workspace-role/workspace-role.module'
import { ApiKeyGuard } from '../auth/guard/api-key/api-key.guard'
import { EventModule } from '../event/event.module'

@Module({
controllers: [AppController],
Expand All @@ -34,7 +35,8 @@ import { ApiKeyGuard } from '../auth/guard/api-key/api-key.guard'
ProjectModule,
EnvironmentModule,
WorkspaceModule,
WorkspaceRoleModule
WorkspaceRoleModule,
EventModule
],
providers: [
{
Expand Down
165 changes: 165 additions & 0 deletions apps/api/src/common/create-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {
ApiKey,
Environment,
EventSeverity,
EventTriggerer,
EventType,
EventSource,
PrismaClient,
Project,
Secret,
User,
Workspace,
WorkspaceMember,
WorkspaceRole
} from '@prisma/client'
import { JsonObject } from '@prisma/client/runtime/library'

export default async function createEvent(
data: {
triggerer?: EventTriggerer
severity?: EventSeverity
triggeredBy?: User
entity?:
| Workspace
| Project
| Environment
| WorkspaceRole
| WorkspaceMember
| ApiKey
| Secret
type: EventType
source: EventSource
title: string
description?: string
metadata: JsonObject
},
prisma: PrismaClient
) {
if (data.triggerer !== EventTriggerer.SYSTEM && !data.triggeredBy) {
throw new Error('User must be provided for non-system events')
}

const baseData = {
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,
sourceUser: data.triggeredBy.id
? {
connect: {
id: data.triggeredBy.id
}
}
: undefined
}

try {
switch (data.source) {
case EventSource.WORKSPACE: {
const entity = data.entity as Workspace
await prisma.event.create({
data: {
...baseData,
sourceWorkspace: data.entity
? {
connect: {
id: entity.id
}
}
: undefined
}
})
break
}
case EventSource.PROJECT: {
const entity = data.entity as Project
await prisma.event.create({
data: {
...baseData,
sourceProject: data.entity
? { connect: { id: entity.id } }
: undefined
}
})
break
}
case EventSource.ENVIRONMENT: {
const entity = data.entity as Environment
await prisma.event.create({
data: {
...baseData,
sourceEnvironment: data.entity
? { connect: { id: entity.id } }
: undefined
}
})
break
}
case EventSource.WORKSPACE_ROLE: {
const entity = data.entity as WorkspaceRole
await prisma.event.create({
data: {
...baseData,
sourceWorkspaceRole: data.entity
? { connect: { id: entity.id } }
: undefined
}
})
break
}
case EventSource.WORKSPACE_MEMBER: {
const entity = data.entity as WorkspaceMember
await prisma.event.create({
data: {
...baseData,
sourceWorkspaceMembership: data.entity
? { connect: { id: entity.id } }
: undefined
}
})
break
}
case EventSource.API_KEY: {
const entity = data.entity as ApiKey
await prisma.event.create({
data: {
...baseData,
sourceApiKey: data.entity
? { connect: { id: entity.id } }
: undefined
}
})
break
}
case EventSource.SECRET: {
const entity = data.entity as Secret
await prisma.event.create({
data: {
...baseData,
sourceSecret: data.entity
? { connect: { id: entity.id } }
: undefined
}
})
break
}
case EventSource.USER: {
await prisma.event.create({
data: {
...baseData
}
})
break
}
default: {
throw new Error('Invalid event source')
}
}
} catch (error) {
console.error('Error creating event', error)
}
}
56 changes: 56 additions & 0 deletions apps/api/src/common/get-secret-with-authority.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Authority, PrismaClient, Secret, User } from '@prisma/client'
import { SecretWithProjectAndVersion } from '../secret/secret.types'
import getCollectiveProjectAuthorities from './get-collective-project-authorities'
import { ConflictException, NotFoundException } from '@nestjs/common'

export default async function getSecretWithAuthority(
userId: User['id'],
secretId: Secret['id'],
authority: Authority,
prisma: PrismaClient
): Promise<SecretWithProjectAndVersion> {
// Fetch the secret
const secret = await prisma.secret.findUnique({
where: {
id: secretId
},
include: {
versions: true,
project: {
include: {
workspace: {
include: {
members: true
}
}
}
}
}
})

if (!secret) {
throw new NotFoundException(`Secret with id ${secretId} not found`)
}

// Check if the user has the project in their workspace role list
const permittedAuthorities = await getCollectiveProjectAuthorities(
userId,
secret.project,
prisma
)

// Check if the user has the required authorities
if (
!permittedAuthorities.has(authority) &&
!permittedAuthorities.has(Authority.WORKSPACE_ADMIN)
) {
throw new ConflictException(
`User ${userId} does not have the required authorities`
)
}

// Remove the workspace from the secret
secret.project.workspace = undefined

return secret
}
Loading

0 comments on commit 19e6603

Please sign in to comment.