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