| 19 | |
| 20 | @Injectable() |
| 21 | export class ApiKeysService { |
| 22 | private readonly logger = new Logger(ApiKeysService.name); |
| 23 | |
| 24 | constructor( |
| 25 | @Inject(DRIZZLE_TOKEN) |
| 26 | private readonly db: NodePgDatabase<typeof schema>, |
| 27 | ) {} |
| 28 | |
| 29 | async create(auth: AuthContext, dto: CreateApiKeyDto) { |
| 30 | if (!auth.organizationId) { |
| 31 | throw new InternalServerErrorException('Organization ID missing in context'); |
| 32 | } |
| 33 | |
| 34 | const { key: plainKey, id: keyId } = this.generateKeyWithId(); |
| 35 | const keyHash = await bcrypt.hash(plainKey, 10); |
| 36 | |
| 37 | const [apiKey] = await this.db |
| 38 | .insert(apiKeys) |
| 39 | .values({ |
| 40 | name: dto.name, |
| 41 | description: dto.description, |
| 42 | keyHash, |
| 43 | keyPrefix: KEY_PREFIX, |
| 44 | keyHint: keyId, |
| 45 | permissions: dto.permissions, |
| 46 | organizationId: dto.organizationId ?? auth.organizationId, |
| 47 | createdBy: auth.userId || 'system', |
| 48 | expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : null, |
| 49 | rateLimit: dto.rateLimit, |
| 50 | isActive: true, |
| 51 | }) |
| 52 | .returning(); |
| 53 | |
| 54 | return { apiKey, plainKey }; |
| 55 | } |
| 56 | |
| 57 | async list(auth: AuthContext, query: ListApiKeysQueryDto) { |
| 58 | if (!auth.organizationId) { |
| 59 | return []; |
| 60 | } |
| 61 | |
| 62 | const conditions = [eq(apiKeys.organizationId, auth.organizationId)]; |
| 63 | |
| 64 | if (query.isActive !== undefined) { |
| 65 | conditions.push(eq(apiKeys.isActive, query.isActive)); |
| 66 | } |
| 67 | |
| 68 | return this.db |
| 69 | .select() |
| 70 | .from(apiKeys) |
| 71 | .where(and(...conditions)) |
| 72 | .orderBy(desc(apiKeys.createdAt)) |
| 73 | .limit(query.limit) |
| 74 | .offset(query.offset); |
| 75 | } |
| 76 | |
| 77 | async get(auth: AuthContext, id: string) { |
| 78 | if (!auth.organizationId) { |
nothing calls this directly
no outgoing calls
no test coverage detected