UNPKG

logicloom-nextjs-starter

Version:

A production-ready Next.js starter template with authentication, i18n, dark mode, and modern patterns

140 lines (118 loc) 4.23 kB
import db from "./db"; import bcrypt from "bcryptjs"; import { randomBytes } from "crypto"; export interface User { id: number; name: string; email: string; password: string; created_at: string; updated_at: string; } export interface Session { id: number; user_id: number; token: string; refresh_token: string | null; expires_at: string; refresh_expires_at: string | null; created_at: string; } // Configuration const BCRYPT_SALT_ROUNDS = Number(process.env.BCRYPT_SALT_ROUNDS) || 10; // User operations export const userHelpers = { create: (name: string, email: string, password: string) => { const hashedPassword = bcrypt.hashSync(password, BCRYPT_SALT_ROUNDS); const stmt = db.prepare( "INSERT INTO users (name, email, password) VALUES (?, ?, ?)" ); const result = stmt.run(name, email, hashedPassword); return result.lastInsertRowid; }, findByEmail: (email: string): User | undefined => { const stmt = db.prepare("SELECT * FROM users WHERE email = ?"); return stmt.get(email) as User | undefined; }, findById: (id: number): User | undefined => { const stmt = db.prepare("SELECT * FROM users WHERE id = ?"); return stmt.get(id) as User | undefined; }, verifyPassword: (password: string, hashedPassword: string): boolean => { return bcrypt.compareSync(password, hashedPassword); }, update: (id: number, data: Partial<Pick<User, "name" | "email">>) => { const fields = []; const values = []; if (data.name) { fields.push("name = ?"); values.push(data.name); } if (data.email) { fields.push("email = ?"); values.push(data.email); } fields.push("updated_at = CURRENT_TIMESTAMP"); values.push(id); const stmt = db.prepare(`UPDATE users SET ${fields.join(", ")} WHERE id = ?`); return stmt.run(...values); }, delete: (id: number) => { const stmt = db.prepare("DELETE FROM users WHERE id = ?"); return stmt.run(id); }, }; // Session operations export const sessionHelpers = { create: (userId: number): { token: string; refreshToken: string } => { const token = randomBytes(32).toString("hex"); const refreshToken = randomBytes(32).toString("hex"); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days const refreshExpiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days const stmt = db.prepare( "INSERT INTO sessions (user_id, token, refresh_token, expires_at, refresh_expires_at) VALUES (?, ?, ?, ?, ?)" ); stmt.run( userId, token, refreshToken, expiresAt.toISOString(), refreshExpiresAt.toISOString() ); return { token, refreshToken }; }, findByToken: (token: string): Session | undefined => { const stmt = db.prepare( "SELECT * FROM sessions WHERE (token = ? OR refresh_token = ?) AND (expires_at > datetime('now') OR refresh_expires_at > datetime('now'))" ); return stmt.get(token, token) as Session | undefined; }, findByAuthToken: (token: string): Session | undefined => { const stmt = db.prepare( "SELECT * FROM sessions WHERE token = ? AND expires_at > datetime('now')" ); return stmt.get(token) as Session | undefined; }, findByRefreshToken: (refreshToken: string): Session | undefined => { const stmt = db.prepare( "SELECT * FROM sessions WHERE refresh_token = ? AND refresh_expires_at > datetime('now')" ); return stmt.get(refreshToken) as Session | undefined; }, delete: (token: string) => { const stmt = db.prepare( "DELETE FROM sessions WHERE token = ? OR refresh_token = ?" ); return stmt.run(token, token); }, deleteByUserId: (userId: number) => { const stmt = db.prepare("DELETE FROM sessions WHERE user_id = ?"); return stmt.run(userId); }, deleteExpired: () => { const stmt = db.prepare( "DELETE FROM sessions WHERE expires_at <= datetime('now') AND (refresh_expires_at IS NULL OR refresh_expires_at <= datetime('now'))" ); return stmt.run(); }, };