import { Hono } from "hono"; import { db } from "../db/client.js"; import { users } from "../db/schema.js"; import { eq, and, ne } from "drizzle-orm"; import { hashPassword, verifyPassword, getSessionUserId, type AppEnv } from "../lib/auth.js"; import { validateProfile, validateResetPassword } from "../lib/validate.js"; import { render } from "../lib/render.js"; export const profileRouter = new Hono(); async function requireUser(c: Parameters[0]) { const id = getSessionUserId(c); if (!id) return null; const [user] = await db.select().from(users).where(eq(users.id, id)).limit(1); return user ?? null; } profileRouter.get("/profile", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); return c.html(render("profile_view.njk", { user })); }); profileRouter.get("/profile/edit", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); return c.html(render("profile_edit.njk", { user })); }); profileRouter.post("/profile/edit", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); const form = await c.req.formData(); const data: Record = {}; form.forEach((v, k) => { data[k] = String(v); }); const errors = validateProfile(data); if (!errors.length) { const existing = await db.select().from(users).where( and(eq(users.phone, data.phone.trim()), ne(users.id, user.id)) ).limit(1); if (existing.length) errors.push("Пользователь с таким телефоном уже существует"); } if (errors.length) { return c.html(render("profile_edit.njk", { user, errors, form: data })); } await db.update(users) .set({ first_name: data.first_name.trim(), last_name: data.last_name.trim(), phone: data.phone.trim() }) .where(eq(users.id, user.id)); const [updated] = await db.select().from(users).where(eq(users.id, user.id)).limit(1); return c.html(render("profile_edit.njk", { user: updated, success: "Профиль обновлен" })); }); profileRouter.get("/profile/change-password", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); return c.html(render("profile_change_password.njk", { user })); }); profileRouter.post("/profile/change-password", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); const form = await c.req.formData(); const data: Record = {}; form.forEach((v, k) => { data[k] = String(v); }); const errors: string[] = []; const currentPassword = data.current_password ?? ""; if (!currentPassword) { errors.push("Введите текущий пароль"); } else if (!(await verifyPassword(currentPassword, user.password_hash))) { errors.push("Неверный текущий пароль"); } errors.push(...validateResetPassword(data)); if (errors.length) { return c.html(render("profile_change_password.njk", { user, errors })); } await db.update(users) .set({ password_hash: await hashPassword(data.password) }) .where(eq(users.id, user.id)); return c.html(render("profile_change_password.njk", { user, success: "Пароль изменен" })); }); profileRouter.get("/profile/delete", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); return c.html(render("profile_delete.njk", { user })); }); profileRouter.post("/profile/delete", async (c) => { const user = await requireUser(c); if (!user) return c.redirect("/login", 303); const form = await c.req.formData(); const password = String(form.get("password") ?? ""); if (!password) { return c.html(render("profile_delete.njk", { user, errors: ["Введите пароль для подтверждения"] })); } if (!(await verifyPassword(password, user.password_hash))) { return c.html(render("profile_delete.njk", { user, errors: ["Неверный пароль"] })); } await db.delete(users).where(eq(users.id, user.id)); const session = c.get("session") as { deleteSession: () => void }; session.deleteSession(); return c.redirect("/", 303); });