import { eq } from 'drizzle-orm'; import { db } from '../config.js'; import { users } from '../db/schema/index.js'; /** * Hash a password using Bun's built-in password hashing * @param {string} password - Plain text password * @returns {Promise} Hashed password */ async function hashPassword(password) { return Bun.password.hash(password); } /** * Verify a password against a hash * @param {string} password - Plain text password * @param {string} hash - Hashed password * @returns {Promise} True if password matches */ async function verifyPassword(password, hash) { return Bun.password.verify(password, hash); } /** * Register a new user * @param {Object} userData - User registration data * @param {string} userData.email - User email * @param {string} userData.password - Plain text password * @param {string} userData.callsign - Ham radio callsign * @returns {Promise} Created user object (without password) or null if email exists */ export async function registerUser({ email, password, callsign }) { // Check if user already exists const [existingUser] = await db .select() .from(users) .where(eq(users.email, email)) .limit(1); if (existingUser) { return null; } // Hash password const passwordHash = await hashPassword(password); // Create user const newUser = await db .insert(users) .values({ email, passwordHash, callsign: callsign.toUpperCase(), }) .returning(); // Return user without password hash const { passwordHash: _, ...userWithoutPassword } = newUser[0]; return userWithoutPassword; } /** * Authenticate user with email and password * @param {string} email - User email * @param {string} password - Plain text password * @returns {Promise} User object (without password) if authenticated * @throws {Error} If credentials are invalid */ export async function authenticateUser(email, password) { // Find user by email const [user] = await db .select() .from(users) .where(eq(users.email, email)) .limit(1); if (!user) { return null; } // Verify password const isValid = await verifyPassword(password, user.passwordHash); if (!isValid) { return null; } // Return user without password hash const { passwordHash: _, ...userWithoutPassword } = user; return userWithoutPassword; } /** * Get user by ID * @param {number} userId - User ID * @returns {Promise} User object (without password) or null */ export async function getUserById(userId) { const [user] = await db .select() .from(users) .where(eq(users.id, userId)) .limit(1); if (!user) return null; const { passwordHash: _, ...userWithoutPassword } = user; return userWithoutPassword; } /** * Update user's LoTW credentials (encrypted) * @param {number} userId - User ID * @param {string} lotwUsername - LoTW username * @param {string} lotwPassword - LoTW password (will be encrypted) * @returns {Promise} */ export async function updateLoTWCredentials(userId, lotwUsername, lotwPassword) { // Simple encryption for storage (in production, use a proper encryption library) // For now, we'll store as-is but marked for encryption await db .update(users) .set({ lotwUsername, lotwPassword, // TODO: Encrypt before storing updatedAt: new Date(), }) .where(eq(users.id, userId)); } /** * Update user's DCL API key * @param {number} userId - User ID * @param {string} dclApiKey - DCL API key * @returns {Promise} */ export async function updateDCLCredentials(userId, dclApiKey) { await db .update(users) .set({ dclApiKey, updatedAt: new Date(), }) .where(eq(users.id, userId)); } /** * Check if user is admin * @param {number} userId - User ID * @returns {Promise} True if user is admin */ export async function isAdmin(userId) { const [user] = await db .select({ isAdmin: users.isAdmin }) .from(users) .where(eq(users.id, userId)) .limit(1); return user?.isAdmin === true || user?.isAdmin === 1; } /** * Get all admin users * @returns {Promise} Array of admin users (without passwords) */ export async function getAdminUsers() { const adminUsers = await db .select({ id: users.id, email: users.email, callsign: users.callsign, role: users.role, isAdmin: users.isAdmin, createdAt: users.createdAt, }) .from(users) .where(eq(users.isAdmin, 1)); return adminUsers; } /** * Update user role * @param {number} userId - User ID * @param {string} role - New role ('user' or 'admin') * @param {boolean} isAdmin - Admin flag * @returns {Promise} */ export async function updateUserRole(userId, role, isAdmin) { await db .update(users) .set({ role, isAdmin: isAdmin ? 1 : 0, updatedAt: new Date(), }) .where(eq(users.id, userId)); } /** * Get all users (for admin use) * @returns {Promise} Array of all users (without passwords) */ export async function getAllUsers() { const allUsers = await db .select({ id: users.id, email: users.email, callsign: users.callsign, role: users.role, isAdmin: users.isAdmin, createdAt: users.createdAt, updatedAt: users.updatedAt, }) .from(users) .orderBy(users.createdAt); return allUsers; } /** * Get user by ID (for admin use) * @param {number} userId - User ID * @returns {Promise} Full user object (without password) or null */ export async function getUserByIdFull(userId) { const [user] = await db .select({ id: users.id, email: users.email, callsign: users.callsign, role: users.role, isAdmin: users.isAdmin, lotwUsername: users.lotwUsername, dclApiKey: users.dclApiKey, createdAt: users.createdAt, updatedAt: users.updatedAt, }) .from(users) .where(eq(users.id, userId)) .limit(1); return user || null; }