// --------------------------------------------------------------------------- // User / Team / Invitation management // --------------------------------------------------------------------------- import { eq, and } from "drizzle-orm"; import { users, teams, teamMembers, invitations } from "./schema"; import type { DrizzleDb } from "@executor/storage-postgres"; export type User = typeof users.$inferSelect; export type Team = typeof teams.$inferSelect; export type TeamMember = typeof teamMembers.$inferSelect; export type Invitation = typeof invitations.$inferSelect; export const makeUserStore = (db: DrizzleDb) => ({ // --- Users --- upsertUser: async (user: { id: string; email: string; name?: string; avatarUrl?: string }) => { const [result] = await db .insert(users) .values(user) .onConflictDoUpdate({ target: users.id, set: { email: user.email, name: user.name, avatarUrl: user.avatarUrl }, }) .returning(); return result!; }, getUser: async (userId: string) => { const rows = await db.select().from(users).where(eq(users.id, userId)); return rows[0] ?? null; }, getUserByEmail: async (email: string) => { const rows = await db.select().from(users).where(eq(users.email, email)); return rows[0] ?? null; }, // --- Teams --- createTeam: async (name: string) => { const [team] = await db.insert(teams).values({ name }).returning(); return team!; }, getTeam: async (teamId: string) => { const rows = await db.select().from(teams).where(eq(teams.id, teamId)); return rows[0] ?? null; }, addMember: async (teamId: string, userId: string, role: string = "member") => { const [member] = await db .insert(teamMembers) .values({ teamId, userId, role }) .onConflictDoNothing() .returning(); return member ?? null; }, removeMember: async (teamId: string, userId: string) => { const result = await db .delete(teamMembers) .where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId))) .returning(); return result.length > 0; }, listMembers: async (teamId: string) => { const rows = await db .select({ userId: teamMembers.userId, role: teamMembers.role, createdAt: teamMembers.createdAt, email: users.email, name: users.name, avatarUrl: users.avatarUrl, }) .from(teamMembers) .innerJoin(users, eq(teamMembers.userId, users.id)) .where(eq(teamMembers.teamId, teamId)); return rows; }, getTeamsForUser: async (userId: string) => { const rows = await db .select({ teamId: teamMembers.teamId, role: teamMembers.role, teamName: teams.name, teamCreatedAt: teams.createdAt, }) .from(teamMembers) .innerJoin(teams, eq(teamMembers.teamId, teams.id)) .where(eq(teamMembers.userId, userId)); return rows; }, // --- Invitations --- createInvitation: async (teamId: string, email: string, invitedBy: string) => { const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days const [invitation] = await db .insert(invitations) .values({ teamId, email, invitedBy, status: "pending", expiresAt }) .returning(); return invitation!; }, getPendingInvitations: async (email: string) => { const rows = await db .select() .from(invitations) .where(and(eq(invitations.email, email), eq(invitations.status, "pending"))); return rows.filter((r) => r.expiresAt > new Date()); }, getTeamInvitations: async (teamId: string) => { const rows = await db .select() .from(invitations) .where(and(eq(invitations.teamId, teamId), eq(invitations.status, "pending"))); return rows.filter((r) => r.expiresAt > new Date()); }, acceptInvitation: async (invitationId: string) => { const [updated] = await db .update(invitations) .set({ status: "accepted" }) .where(eq(invitations.id, invitationId)) .returning(); return updated ?? null; }, });