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
text/typescript
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();
},
};