Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 253x 165x 165x 165x 32x 32x 11x 11x 10x 10x 8x 8x 14x 12x 12x 211x | import { eq, and } from "drizzle-orm";
import type { Database } from "@/lib/db";
import { invitationsTable } from "@/lib/db/schema";
import type { Invitation, InvitationStatus } from "@/types";
import { generateId } from "@/lib/id";
export class InvitationRepository {
constructor(private db: Database) {}
async create(data: {
id?: string;
collectionId: string;
email: string;
token: string;
}): Promise<Invitation> {
const id = data.id || generateId("inv");
const [inv] = await this.db
.insert(invitationsTable)
.values({
id,
collectionId: data.collectionId,
email: data.email,
token: data.token,
status: "pending",
createdAt: new Date(),
updatedAt: new Date(),
acceptedAt: null,
})
.returning();
return this.mapToInvitation(inv);
}
async findById(id: string): Promise<Invitation | null> {
const [inv] = await this.db
.select()
.from(invitationsTable)
.where(eq(invitationsTable.id, id));
return inv ? this.mapToInvitation(inv) : null;
}
async findByToken(token: string): Promise<Invitation | null> {
const [inv] = await this.db
.select()
.from(invitationsTable)
.where(eq(invitationsTable.token, token));
return inv ? this.mapToInvitation(inv) : null;
}
async findByCollection(collectionId: string): Promise<Invitation[]> {
const invs = await this.db
.select()
.from(invitationsTable)
.where(eq(invitationsTable.collectionId, collectionId));
return invs.map((i) => this.mapToInvitation(i));
}
async findByEmail(email: string, collectionId: string): Promise<Invitation | null> {
const [inv] = await this.db
.select()
.from(invitationsTable)
.where(
and(eq(invitationsTable.email, email), eq(invitationsTable.collectionId, collectionId)),
);
return inv ? this.mapToInvitation(inv) : null;
}
async update(id: string, data: Partial<Invitation>): Promise<Invitation> {
// Allow-list: only status and acceptedAt may be updated by callers.
// Flags, email, collectionId, and token are immutable after creation.
const [inv] = await this.db
.update(invitationsTable)
.set({
...(data.status !== undefined && { status: data.status }),
...(data.acceptedAt !== undefined && { acceptedAt: data.acceptedAt }),
updatedAt: new Date(),
})
.where(eq(invitationsTable.id, id))
.returning();
return this.mapToInvitation(inv);
}
async delete(id: string): Promise<void> {
await this.db.delete(invitationsTable).where(eq(invitationsTable.id, id));
}
async deleteByCollection(collectionId: string): Promise<void> {
await this.db.delete(invitationsTable).where(eq(invitationsTable.collectionId, collectionId));
}
// Scrub the email (NOT delete the row). Used during account deletion for an
// invitation the user accepted in ANOTHER owner's collection: the invitation
// belongs to that owner's collection (survives), but its email must no longer
// identify the deleted user. Matches by the user's OLD email. Returns the
// affected-row count.
async scrubEmail(oldEmail: string, tombstone: string): Promise<number> {
const rows = await this.db
.update(invitationsTable)
.set({ email: tombstone, updatedAt: new Date() })
.where(eq(invitationsTable.email, oldEmail))
.returning({ id: invitationsTable.id });
return rows.length;
}
private mapToInvitation(row: typeof invitationsTable.$inferSelect): Invitation {
return {
id: row.id,
collectionId: row.collectionId,
email: row.email,
token: row.token,
status: row.status as InvitationStatus,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
acceptedAt: row.acceptedAt,
};
}
}
|