Files
Termix/src/backend/database/repositories/termix-identity-repository.ts
T

238 lines
6.0 KiB
TypeScript
Raw Normal View History

+8
2026-07-19 12:29:52 -05:00
import { and, asc, eq } from "drizzle-orm";
import { termixIdentities, termixIdentityKeys } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
+3
2026-08-06 14:41:39 -05:00
import { rowsAffected } from "./mutation-result.js";
import { insertReturning, updateReturning } from "./returning.js";
+8
2026-07-19 12:29:52 -05:00
export type TermixIdentityRecord = typeof termixIdentities.$inferSelect;
export type NewTermixIdentityRecord = typeof termixIdentities.$inferInsert;
export type TermixIdentityUpdate = Partial<
Pick<NewTermixIdentityRecord, "handle" | "description" | "updatedAt">
>;
export type TermixIdentityKeyRecord = typeof termixIdentityKeys.$inferSelect;
export type NewTermixIdentityKeyRecord = typeof termixIdentityKeys.$inferInsert;
export type TermixIdentityKeyUpdate = Partial<
Pick<NewTermixIdentityKeyRecord, "enabled" | "label">
>;
export class TermixIdentityRepository {
constructor(
private readonly context: DatabaseContext,
private readonly onWrite?: () => void | Promise<void>,
) {}
async findIdentityForUser(
userId: string,
): Promise<TermixIdentityRecord | null> {
const rows = await this.context.drizzle
.select()
.from(termixIdentities)
.where(eq(termixIdentities.userId, userId))
.limit(1);
return rows[0] ?? null;
}
async findIdentityByHandle(
handle: string,
): Promise<TermixIdentityRecord | null> {
const rows = await this.context.drizzle
.select()
.from(termixIdentities)
.where(eq(termixIdentities.handle, handle))
.limit(1);
return rows[0] ?? null;
}
async isHandleTaken(handle: string): Promise<boolean> {
const rows = await this.context.drizzle
.select({ id: termixIdentities.id })
.from(termixIdentities)
.where(eq(termixIdentities.handle, handle))
.limit(1);
return rows.length > 0;
}
async createIdentity(
identity: NewTermixIdentityRecord,
): Promise<TermixIdentityRecord> {
+3
2026-08-06 14:41:39 -05:00
const rows = await insertReturning(
this.context,
termixIdentities,
identity,
);
+8
2026-07-19 12:29:52 -05:00
await this.afterWrite();
return rows[0];
}
async updateIdentityForUser(
userId: string,
update: TermixIdentityUpdate,
): Promise<TermixIdentityRecord | null> {
+3
2026-08-06 14:41:39 -05:00
const rows = await updateReturning(
this.context,
termixIdentities,
update,
eq(termixIdentities.userId, userId),
);
+8
2026-07-19 12:29:52 -05:00
if (rows.length > 0) {
await this.afterWrite();
}
return rows[0] ?? null;
}
async deleteIdentityForUser(userId: string): Promise<boolean> {
+3
2026-08-06 14:41:39 -05:00
const result = await this.context.drizzle
+8
2026-07-19 12:29:52 -05:00
.delete(termixIdentities)
+3
2026-08-06 14:41:39 -05:00
.where(eq(termixIdentities.userId, userId));
+8
2026-07-19 12:29:52 -05:00
+3
2026-08-06 14:41:39 -05:00
if (rowsAffected(result) > 0) {
+8
2026-07-19 12:29:52 -05:00
await this.afterWrite();
}
+3
2026-08-06 14:41:39 -05:00
return rowsAffected(result) > 0;
+8
2026-07-19 12:29:52 -05:00
}
async deleteByUserId(userId: string): Promise<{
identitiesDeleted: number;
keysDeleted: number;
}> {
+3
2026-08-06 14:41:39 -05:00
const keyResult = await this.context.drizzle
+8
2026-07-19 12:29:52 -05:00
.delete(termixIdentityKeys)
+3
2026-08-06 14:41:39 -05:00
.where(eq(termixIdentityKeys.userId, userId));
+8
2026-07-19 12:29:52 -05:00
+3
2026-08-06 14:41:39 -05:00
const result = await this.context.drizzle
+8
2026-07-19 12:29:52 -05:00
.delete(termixIdentities)
+3
2026-08-06 14:41:39 -05:00
.where(eq(termixIdentities.userId, userId));
+8
2026-07-19 12:29:52 -05:00
+3
2026-08-06 14:41:39 -05:00
if (rowsAffected(keyResult) > 0 || rowsAffected(result) > 0) {
+8
2026-07-19 12:29:52 -05:00
await this.afterWrite();
}
return {
+3
2026-08-06 14:41:39 -05:00
identitiesDeleted: rowsAffected(result),
keysDeleted: rowsAffected(keyResult),
+8
2026-07-19 12:29:52 -05:00
};
}
async listKeysByIdentityId(
identityId: number,
): Promise<TermixIdentityKeyRecord[]> {
return this.context.drizzle
.select()
.from(termixIdentityKeys)
.where(eq(termixIdentityKeys.identityId, identityId))
.orderBy(asc(termixIdentityKeys.id));
}
async listEnabledKeysByIdentityId(
identityId: number,
): Promise<TermixIdentityKeyRecord[]> {
return this.context.drizzle
.select()
.from(termixIdentityKeys)
.where(
and(
eq(termixIdentityKeys.identityId, identityId),
eq(termixIdentityKeys.enabled, true),
),
)
.orderBy(asc(termixIdentityKeys.id));
}
async listLinkedCredentialIds(identityId: number): Promise<number[]> {
const rows = await this.context.drizzle
.select({ credentialId: termixIdentityKeys.credentialId })
.from(termixIdentityKeys)
.where(
and(
eq(termixIdentityKeys.identityId, identityId),
eq(termixIdentityKeys.enabled, true),
),
);
return Array.from(
new Set(
rows
.map((row) => row.credentialId)
.filter(
(credentialId): credentialId is number => credentialId !== null,
),
),
);
}
async createKey(
key: NewTermixIdentityKeyRecord,
): Promise<TermixIdentityKeyRecord> {
+3
2026-08-06 14:41:39 -05:00
const rows = await insertReturning(this.context, termixIdentityKeys, key);
+8
2026-07-19 12:29:52 -05:00
await this.afterWrite();
return rows[0];
}
async updateKeyForUser(
userId: string,
id: number,
update: TermixIdentityKeyUpdate,
): Promise<TermixIdentityKeyRecord | null> {
+3
2026-08-06 14:41:39 -05:00
const rows = await updateReturning(
this.context,
termixIdentityKeys,
update,
and(eq(termixIdentityKeys.id, id), eq(termixIdentityKeys.userId, userId)),
);
+8
2026-07-19 12:29:52 -05:00
if (rows.length > 0) {
await this.afterWrite();
}
return rows[0] ?? null;
}
async deleteKeyForUser(userId: string, id: number): Promise<boolean> {
+3
2026-08-06 14:41:39 -05:00
const result = await this.context.drizzle
+8
2026-07-19 12:29:52 -05:00
.delete(termixIdentityKeys)
.where(
and(
eq(termixIdentityKeys.id, id),
eq(termixIdentityKeys.userId, userId),
),
+3
2026-08-06 14:41:39 -05:00
);
+8
2026-07-19 12:29:52 -05:00
+3
2026-08-06 14:41:39 -05:00
if (rowsAffected(result) > 0) {
+8
2026-07-19 12:29:52 -05:00
await this.afterWrite();
}
+3
2026-08-06 14:41:39 -05:00
return rowsAffected(result) > 0;
+8
2026-07-19 12:29:52 -05:00
}
async findKeyForUser(
userId: string,
id: number,
): Promise<TermixIdentityKeyRecord | null> {
const rows = await this.context.drizzle
.select()
.from(termixIdentityKeys)
.where(
and(
eq(termixIdentityKeys.id, id),
eq(termixIdentityKeys.userId, userId),
),
)
.limit(1);
return rows[0] ?? null;
}
private async afterWrite(): Promise<void> {
await this.onWrite?.();
}
}