import { query, queryOne, execute, transaction } from "../db/connection.ts"; import type { User, Organization, RefreshTokenRecord } from "../types/index.ts"; // ============================================ // USER REPOSITORY // ============================================ /** * Find user by email */ export async function findByEmail(email: string): Promise { return await queryOne( `SELECT * FROM users WHERE email = $1 AND deleted_at IS NULL`, [email.toLowerCase()] ); } /** * Find user by ID */ export async function findById(id: string): Promise { return await queryOne( `SELECT * FROM users WHERE id = $1 AND deleted_at IS NULL`, [id] ); } /** * Find user by verification token */ export async function findByVerificationToken(token: string): Promise { return await queryOne( `SELECT * FROM users WHERE verification_token = $1 AND deleted_at IS NULL`, [token] ); } /** * Find user by password reset token */ export async function findByResetToken(token: string): Promise { return await queryOne( `SELECT * FROM users WHERE reset_token = $1 AND reset_token_expires > NOW() AND deleted_at IS NULL`, [token] ); } /** * Create a new user with organization */ export async function createUserWithOrg(data: { email: string; passwordHash: string; firstName: string; lastName: string; orgName: string; verificationToken: string; }): Promise<{ user: User; organization: Organization }> { return await transaction(async (conn) => { // Generate slug from org name const slug = data.orgName .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, "") .substring(0, 50); // Create organization const orgResult = await conn.queryObject( `INSERT INTO organizations (name, slug, plan, max_users, settings) VALUES ($1, $2, 'free', 3, '{}') RETURNING *`, [data.orgName, slug + "-" + Date.now().toString(36)] ); const organization = orgResult.rows[0]; // Create user as owner const userResult = await conn.queryObject( `INSERT INTO users ( org_id, email, password_hash, first_name, last_name, role, is_verified, verification_token ) VALUES ($1, $2, $3, $4, $5, 'owner', false, $6) RETURNING *`, [ organization.id, data.email.toLowerCase(), data.passwordHash, data.firstName, data.lastName, data.verificationToken, ] ); const user = userResult.rows[0]; return { user, organization }; }); } /** * Create a user in an existing organization (invited user) */ export async function createUser(data: { orgId: string; email: string; passwordHash: string; firstName: string; lastName: string; role: "admin" | "manager" | "user"; isVerified?: boolean; }): Promise { const rows = await query( `INSERT INTO users ( org_id, email, password_hash, first_name, last_name, role, is_verified ) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`, [ data.orgId, data.email.toLowerCase(), data.passwordHash, data.firstName, data.lastName, data.role, data.isVerified ?? false, ] ); return rows[0]; } /** * Update user's email verification status */ export async function verifyEmail(userId: string): Promise { await execute( `UPDATE users SET is_verified = true, verification_token = NULL, updated_at = NOW() WHERE id = $1`, [userId] ); } /** * Set password reset token */ export async function setResetToken( userId: string, token: string, expiresAt: Date ): Promise { await execute( `UPDATE users SET reset_token = $1, reset_token_expires = $2, updated_at = NOW() WHERE id = $3`, [token, expiresAt, userId] ); } /** * Update password and clear reset token */ export async function updatePassword( userId: string, passwordHash: string ): Promise { await execute( `UPDATE users SET password_hash = $1, reset_token = NULL, reset_token_expires = NULL, updated_at = NOW() WHERE id = $2`, [passwordHash, userId] ); } /** * Update last login timestamp */ export async function updateLastLogin(userId: string): Promise { await execute( `UPDATE users SET last_login_at = NOW() WHERE id = $1`, [userId] ); } /** * Store refresh token hash */ export async function storeRefreshToken( userId: string, tokenHash: string, expiresAt: Date ): Promise { await execute( `INSERT INTO refresh_tokens (user_id, token_hash, expires_at) VALUES ($1, $2, $3)`, [userId, tokenHash, expiresAt] ); } /** * Find refresh token by hash */ export async function findRefreshToken(tokenHash: string): Promise { return await queryOne( `SELECT * FROM refresh_tokens WHERE token_hash = $1 AND revoked = false AND expires_at > NOW()`, [tokenHash] ); } /** * Revoke a refresh token */ export async function revokeRefreshToken(tokenHash: string): Promise { await execute( `UPDATE refresh_tokens SET revoked = true, revoked_at = NOW() WHERE token_hash = $1`, [tokenHash] ); } /** * Revoke all refresh tokens for a user (logout everywhere) */ export async function revokeAllUserTokens(userId: string): Promise { await execute( `UPDATE refresh_tokens SET revoked = true, revoked_at = NOW() WHERE user_id = $1 AND revoked = false`, [userId] ); } /** * Clean up expired refresh tokens */ export async function cleanupExpiredTokens(): Promise { return await execute( `DELETE FROM refresh_tokens WHERE expires_at < NOW()` ); }