Files
evo-sync/web/src/routes/profile.ts

117 lines
4.2 KiB
TypeScript
Raw Normal View History

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<AppEnv>();
async function requireUser(c: Parameters<typeof getSessionUserId>[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<string, string> = {};
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<string, string> = {};
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);
});