mirror of
https://github.com/Tony0410/quietthanks.git
synced 2026-05-24 13:21:38 +08:00
- Multiple entries per day: Home page now starts fresh, Save & New button - Admin user management: Add/delete users, reset passwords, toggle admin - Daily reminders: Browser notifications at configurable time - AI reflections: Generate insights from entries using Claude API - Remove cloud sync placeholder (already have user accounts) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
166 lines
3.8 KiB
TypeScript
166 lines
3.8 KiB
TypeScript
import { cookies } from "next/headers";
|
|
import { db, schema } from "./db";
|
|
import { eq, and, gt } from "drizzle-orm";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import bcrypt from "bcryptjs";
|
|
|
|
const SESSION_COOKIE = "qt_session";
|
|
const SESSION_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
|
|
export interface AuthUser {
|
|
id: string;
|
|
email: string;
|
|
name: string | null;
|
|
isAdmin: boolean;
|
|
}
|
|
|
|
export async function hashPassword(password: string): Promise<string> {
|
|
return bcrypt.hash(password, 10);
|
|
}
|
|
|
|
export async function verifyPassword(
|
|
password: string,
|
|
hash: string
|
|
): Promise<boolean> {
|
|
return bcrypt.compare(password, hash);
|
|
}
|
|
|
|
export async function createSession(userId: string): Promise<string> {
|
|
const sessionId = uuidv4();
|
|
const now = Date.now();
|
|
|
|
await db.insert(schema.sessions).values({
|
|
id: sessionId,
|
|
userId,
|
|
expiresAt: now + SESSION_DURATION_MS,
|
|
createdAt: now,
|
|
});
|
|
|
|
return sessionId;
|
|
}
|
|
|
|
export async function setSessionCookie(sessionId: string) {
|
|
const cookieStore = await cookies();
|
|
cookieStore.set(SESSION_COOKIE, sessionId, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === "production",
|
|
sameSite: "lax",
|
|
maxAge: SESSION_DURATION_MS / 1000,
|
|
path: "/",
|
|
});
|
|
}
|
|
|
|
export async function clearSessionCookie() {
|
|
const cookieStore = await cookies();
|
|
cookieStore.delete(SESSION_COOKIE);
|
|
}
|
|
|
|
export async function getSession(): Promise<AuthUser | null> {
|
|
const cookieStore = await cookies();
|
|
const sessionId = cookieStore.get(SESSION_COOKIE)?.value;
|
|
|
|
if (!sessionId) return null;
|
|
|
|
const now = Date.now();
|
|
|
|
const result = await db
|
|
.select({
|
|
sessionId: schema.sessions.id,
|
|
userId: schema.users.id,
|
|
email: schema.users.email,
|
|
name: schema.users.name,
|
|
isAdmin: schema.users.isAdmin,
|
|
expiresAt: schema.sessions.expiresAt,
|
|
})
|
|
.from(schema.sessions)
|
|
.innerJoin(schema.users, eq(schema.sessions.userId, schema.users.id))
|
|
.where(
|
|
and(eq(schema.sessions.id, sessionId), gt(schema.sessions.expiresAt, now))
|
|
)
|
|
.limit(1);
|
|
|
|
if (result.length === 0) return null;
|
|
|
|
return {
|
|
id: result[0].userId,
|
|
email: result[0].email,
|
|
name: result[0].name,
|
|
isAdmin: result[0].isAdmin === 1,
|
|
};
|
|
}
|
|
|
|
export async function deleteSession(sessionId: string) {
|
|
await db.delete(schema.sessions).where(eq(schema.sessions.id, sessionId));
|
|
}
|
|
|
|
export async function registerUser(
|
|
email: string,
|
|
password: string,
|
|
name?: string
|
|
): Promise<{ user?: AuthUser; error?: string }> {
|
|
const existing = await db
|
|
.select()
|
|
.from(schema.users)
|
|
.where(eq(schema.users.email, email.toLowerCase()))
|
|
.limit(1);
|
|
|
|
if (existing.length > 0) {
|
|
return { error: "Email already registered" };
|
|
}
|
|
|
|
const userId = uuidv4();
|
|
const passwordHash = await hashPassword(password);
|
|
const now = Date.now();
|
|
|
|
await db.insert(schema.users).values({
|
|
id: userId,
|
|
email: email.toLowerCase(),
|
|
passwordHash,
|
|
name: name || null,
|
|
createdAt: now,
|
|
});
|
|
|
|
return {
|
|
user: {
|
|
id: userId,
|
|
email: email.toLowerCase(),
|
|
name: name || null,
|
|
isAdmin: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function loginUser(
|
|
email: string,
|
|
password: string
|
|
): Promise<{ user?: AuthUser; sessionId?: string; error?: string }> {
|
|
const users = await db
|
|
.select()
|
|
.from(schema.users)
|
|
.where(eq(schema.users.email, email.toLowerCase()))
|
|
.limit(1);
|
|
|
|
if (users.length === 0) {
|
|
return { error: "Invalid email or password" };
|
|
}
|
|
|
|
const user = users[0];
|
|
const valid = await verifyPassword(password, user.passwordHash);
|
|
|
|
if (!valid) {
|
|
return { error: "Invalid email or password" };
|
|
}
|
|
|
|
const sessionId = await createSession(user.id);
|
|
|
|
return {
|
|
user: {
|
|
id: user.id,
|
|
email: user.email,
|
|
name: user.name,
|
|
isAdmin: user.isAdmin === 1,
|
|
},
|
|
sessionId,
|
|
};
|
|
}
|