feat(CC-057): add ConsentsController (grant/revoke/list consent records per purpose)
This commit is contained in:
parent
c8a517cd2d
commit
497aaa919b
1 changed files with 81 additions and 0 deletions
81
src/consents/consents.controller.ts
Normal file
81
src/consents/consents.controller.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { Body, Controller, Get, IsString, Post } from '@nestjs/common';
|
||||
import { and, desc, eq, isNull } from 'drizzle-orm';
|
||||
import { CurrentSession } from '../auth/session.decorator';
|
||||
import type { AuthenticatedSession } from '../auth/tenant.guard';
|
||||
import { db } from '../db/client';
|
||||
import { consentRecords } from '../db/schema';
|
||||
|
||||
class GrantConsentDto {
|
||||
@IsString() purpose!: string;
|
||||
}
|
||||
|
||||
class RevokeConsentDto {
|
||||
@IsString() purpose!: string;
|
||||
}
|
||||
|
||||
@Controller('consents')
|
||||
export class ConsentsController {
|
||||
@Get()
|
||||
async list(@CurrentSession() session: AuthenticatedSession) {
|
||||
return db.query.consentRecords.findMany({
|
||||
where: and(
|
||||
eq(consentRecords.tenantId, session.tenantId),
|
||||
eq(consentRecords.userId, session.userId),
|
||||
),
|
||||
orderBy: [desc(consentRecords.grantedAt)],
|
||||
});
|
||||
}
|
||||
|
||||
@Post('grant')
|
||||
async grant(
|
||||
@CurrentSession() session: AuthenticatedSession,
|
||||
@Body() dto: GrantConsentDto,
|
||||
) {
|
||||
// Revoke any previous record for this purpose first
|
||||
const existing = await db.query.consentRecords.findFirst({
|
||||
where: and(
|
||||
eq(consentRecords.tenantId, session.tenantId),
|
||||
eq(consentRecords.userId, session.userId),
|
||||
eq(consentRecords.purpose, dto.purpose),
|
||||
isNull(consentRecords.revokedAt),
|
||||
),
|
||||
});
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const [created] = await db
|
||||
.insert(consentRecords)
|
||||
.values({
|
||||
tenantId: session.tenantId,
|
||||
userId: session.userId,
|
||||
purpose: dto.purpose,
|
||||
grantedAt: new Date(),
|
||||
})
|
||||
.returning();
|
||||
return created;
|
||||
}
|
||||
|
||||
@Post('revoke')
|
||||
async revoke(
|
||||
@CurrentSession() session: AuthenticatedSession,
|
||||
@Body() dto: RevokeConsentDto,
|
||||
) {
|
||||
const active = await db.query.consentRecords.findFirst({
|
||||
where: and(
|
||||
eq(consentRecords.tenantId, session.tenantId),
|
||||
eq(consentRecords.userId, session.userId),
|
||||
eq(consentRecords.purpose, dto.purpose),
|
||||
isNull(consentRecords.revokedAt),
|
||||
),
|
||||
});
|
||||
if (!active) {
|
||||
return { revoked: false, reason: 'no active consent for this purpose' };
|
||||
}
|
||||
const [updated] = await db
|
||||
.update(consentRecords)
|
||||
.set({ revokedAt: new Date() })
|
||||
.where(eq(consentRecords.id, active.id))
|
||||
.returning();
|
||||
return { revoked: true, record: updated };
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue