test commit

This commit is contained in:
Kirill
2026-05-19 11:25:23 +05:00
parent f8867f6457
commit 5adbe9baa7
81 changed files with 6549 additions and 3108 deletions
+93 -131
View File
@@ -1,177 +1,139 @@
import {
issueEmailCode,
normalizeEmail,
verifyEmailCode,
} from "../lib/auth.js";
import { prisma } from "../lib/prisma.js";
import { NOTIFICATION_EVENTS } from "../../../shared/constants/notification-events.js";
import { NOTIFICATION_EVENTS } from '../../../shared/constants/notification-events.js'
import { issueEmailCode, normalizeEmail, verifyEmailCode } from '../lib/auth.js'
import { prisma } from '../lib/prisma.js'
function mapUserForClient(user) {
const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL);
const userEmail = normalizeEmail(user.email);
const adminEmail = normalizeEmail(process.env.ADMIN_EMAIL)
const userEmail = normalizeEmail(user.email)
return {
id: user.id,
email: user.email,
name: user.name,
phone: user.phone,
isAdmin: Boolean(adminEmail) && userEmail === adminEmail,
};
}
}
export async function registerAuthRoutes(fastify) {
fastify.post("/api/auth/request-code", async (request, reply) => {
const email = normalizeEmail(request.body?.email);
if (!email || !email.includes("@"))
return reply.code(400).send({ error: "Некорректная почта" });
fastify.post('/api/auth/request-code', async (request, reply) => {
const email = normalizeEmail(request.body?.email)
if (!email || !email.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
const code = await issueEmailCode({ email, purpose: "login" });
const code = await issueEmailCode({ email, purpose: 'login' })
const adminEmail = process.env.ADMIN_EMAIL?.trim().toLowerCase();
const isAdmin = email === adminEmail;
const adminEmail = process.env.ADMIN_EMAIL?.trim().toLowerCase()
const isAdmin = email === adminEmail
request.server.eventBus.emit(NOTIFICATION_EVENTS.AUTH_CODE_REQUESTED, {
email,
code,
isAdmin,
});
})
return { ok: true };
});
return { ok: true }
})
fastify.post("/api/auth/verify-code", async (request, reply) => {
const email = normalizeEmail(request.body?.email);
const code = String(request.body?.code || "").trim();
if (!email || !email.includes("@"))
return reply.code(400).send({ error: "Некорректная почта" });
if (!code || code.length !== 6)
return reply.code(400).send({ error: "Код должен быть из 6 цифр" });
fastify.post('/api/auth/verify-code', async (request, reply) => {
const email = normalizeEmail(request.body?.email)
const code = String(request.body?.code || '').trim()
if (!email || !email.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
if (!code || code.length !== 6) return reply.code(400).send({ error: 'Код должен быть из 6 цифр' })
const ok = await verifyEmailCode({ email, purpose: "login", code });
if (!ok)
return reply.code(401).send({ error: "Неверный или истёкший код" });
const ok = await verifyEmailCode({ email, purpose: 'login', code })
if (!ok) return reply.code(401).send({ error: 'Неверный или истёкший код' })
const user = await prisma.user.upsert({
where: { email },
update: {},
create: { email },
});
})
// Ensure notification preference exists
await prisma.notificationPreference.upsert({
where: { userId: user.id },
create: { userId: user.id, globalEnabled: true },
update: {},
});
})
const token = fastify.jwt.sign({ sub: user.id, email: user.email });
return { token, user: mapUserForClient(user) };
});
const token = fastify.jwt.sign({ sub: user.id, email: user.email })
return { token, user: mapUserForClient(user) }
})
fastify.get(
"/api/me",
{ preHandler: [fastify.authenticate] },
async (request) => {
const userId = request.user.sub;
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user) return { user: null };
return { user: mapUserForClient(user) };
},
);
fastify.get('/api/me', { preHandler: [fastify.authenticate] }, async (request) => {
const userId = request.user.sub
const user = await prisma.user.findUnique({ where: { id: userId } })
if (!user) return { user: null }
return { user: mapUserForClient(user) }
})
fastify.post(
"/api/me/change-email/request-code",
{ preHandler: [fastify.authenticate] },
async (request, reply) => {
const userId = request.user.sub;
const newEmail = normalizeEmail(request.body?.newEmail);
if (!newEmail || !newEmail.includes("@"))
return reply.code(400).send({ error: "Некорректная почта" });
fastify.post('/api/me/change-email/request-code', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const userId = request.user.sub
const newEmail = normalizeEmail(request.body?.newEmail)
if (!newEmail || !newEmail.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
const exists = await prisma.user.findUnique({
where: { email: newEmail },
});
if (exists)
return reply.code(409).send({ error: "Эта почта уже занята" });
const exists = await prisma.user.findUnique({
where: { email: newEmail },
})
if (exists) return reply.code(409).send({ error: 'Эта почта уже занята' })
await issueEmailCode({
email: newEmail,
purpose: "change_email",
userId,
});
return { ok: true };
},
);
await issueEmailCode({
email: newEmail,
purpose: 'change_email',
userId,
})
return { ok: true }
})
fastify.post(
"/api/me/change-email/verify",
{ preHandler: [fastify.authenticate] },
async (request, reply) => {
const userId = request.user.sub;
const newEmail = normalizeEmail(request.body?.newEmail);
const code = String(request.body?.code || "").trim();
if (!newEmail || !newEmail.includes("@"))
return reply.code(400).send({ error: "Некорректная почта" });
if (!code || code.length !== 6)
return reply.code(400).send({ error: "Код должен быть из 6 цифр" });
fastify.post('/api/me/change-email/verify', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const userId = request.user.sub
const newEmail = normalizeEmail(request.body?.newEmail)
const code = String(request.body?.code || '').trim()
if (!newEmail || !newEmail.includes('@')) return reply.code(400).send({ error: 'Некорректная почта' })
if (!code || code.length !== 6) return reply.code(400).send({ error: 'Код должен быть из 6 цифр' })
const exists = await prisma.user.findUnique({
where: { email: newEmail },
});
if (exists)
return reply.code(409).send({ error: "Эта почта уже занята" });
const exists = await prisma.user.findUnique({
where: { email: newEmail },
})
if (exists) return reply.code(409).send({ error: 'Эта почта уже занята' })
const ok = await verifyEmailCode({
email: newEmail,
purpose: "change_email",
code,
userId,
});
if (!ok)
return reply.code(401).send({ error: "Неверный или истёкший код" });
const ok = await verifyEmailCode({
email: newEmail,
purpose: 'change_email',
code,
userId,
})
if (!ok) return reply.code(401).send({ error: 'Неверный или истёкший код' })
const user = await prisma.user.update({
where: { id: userId },
data: { email: newEmail },
});
return { user: mapUserForClient(user) };
},
);
const user = await prisma.user.update({
where: { id: userId },
data: { email: newEmail },
})
return { user: mapUserForClient(user) }
})
fastify.patch(
"/api/me/profile",
{ preHandler: [fastify.authenticate] },
async (request, reply) => {
const userId = request.user.sub;
const nameRaw = request.body?.name;
const name =
nameRaw === null || nameRaw === undefined
? null
: String(nameRaw).trim();
const phoneRaw = request.body?.phone;
const phone =
phoneRaw === null || phoneRaw === undefined
? null
: String(phoneRaw).trim();
fastify.patch('/api/me/profile', { preHandler: [fastify.authenticate] }, async (request, reply) => {
const userId = request.user.sub
const nameRaw = request.body?.name
const name = nameRaw === null || nameRaw === undefined ? null : String(nameRaw).trim()
const phoneRaw = request.body?.phone
const phone = phoneRaw === null || phoneRaw === undefined ? null : String(phoneRaw).trim()
if (name !== null && name.length > 40)
return reply.code(400).send({ error: "Имя/ник максимум 40 символов" });
if (phone !== null) {
const compact = phone.replace(/[\s()-]/g, "");
if (compact.length > 20)
return reply.code(400).send({ error: "Телефон слишком длинный" });
if (compact.length && !/^\+?\d{7,20}$/.test(compact)) {
return reply.code(400).send({ error: "Некорректный телефон" });
}
if (name !== null && name.length > 40) return reply.code(400).send({ error: 'Имя/ник максимум 40 символов' })
if (phone !== null) {
const compact = phone.replace(/[\s()-]/g, '')
if (compact.length > 20) return reply.code(400).send({ error: 'Телефон слишком длинный' })
if (compact.length && !/^\+?\d{7,20}$/.test(compact)) {
return reply.code(400).send({ error: 'Некорректный телефон' })
}
}
const updated = await prisma.user.update({
where: { id: userId },
data: {
name: name && name.length ? name : null,
phone: phone && phone.length ? phone : null,
},
});
return { user: mapUserForClient(updated) };
},
);
const updated = await prisma.user.update({
where: { id: userId },
data: {
name: name && name.length ? name : null,
phone: phone && phone.length ? phone : null,
},
})
return { user: mapUserForClient(updated) }
})
}