diff --git a/client/src/shared/model/auth.ts b/client/src/shared/model/auth.ts index 542b83e..bb3d6c4 100644 --- a/client/src/shared/model/auth.ts +++ b/client/src/shared/model/auth.ts @@ -7,9 +7,6 @@ export type AuthUser = { id: string email: string displayName?: string | null - firstName?: string | null - lastName?: string | null - gender?: string | null avatar?: string | null avatarStyle?: string | null isAdmin?: boolean diff --git a/server/prisma/migrations/20260522143134_remove_unused_user_fields/migration.sql b/server/prisma/migrations/20260522143134_remove_unused_user_fields/migration.sql new file mode 100644 index 0000000..4b0a7e4 --- /dev/null +++ b/server/prisma/migrations/20260522143134_remove_unused_user_fields/migration.sql @@ -0,0 +1,27 @@ +/* + Warnings: + + - You are about to drop the column `firstName` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `gender` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `lastName` on the `User` table. All the data in the column will be lost. + +*/ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_User" ( + "id" TEXT NOT NULL PRIMARY KEY, + "email" TEXT NOT NULL, + "displayName" TEXT, + "avatar" TEXT, + "avatarStyle" TEXT, + "passwordHash" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_User" ("avatar", "avatarStyle", "createdAt", "displayName", "email", "id", "passwordHash", "updatedAt") SELECT "avatar", "avatarStyle", "createdAt", "displayName", "email", "id", "passwordHash", "updatedAt" FROM "User"; +DROP TABLE "User"; +ALTER TABLE "new_User" RENAME TO "User"; +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index be0aa90..1a21149 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -78,9 +78,6 @@ model User { id String @id @default(cuid()) email String @unique displayName String? - firstName String? - lastName String? - gender String? avatar String? avatarStyle String? passwordHash String? diff --git a/server/src/lib/bootstrap-admin.js b/server/src/lib/bootstrap-admin.js index e79df0b..a4462f9 100644 --- a/server/src/lib/bootstrap-admin.js +++ b/server/src/lib/bootstrap-admin.js @@ -1,4 +1,5 @@ import { normalizeEmail } from './auth.js' +import { generateAvatar } from './generate-avatar.js' import { prisma } from './prisma.js' export async function ensureAdminUser() { @@ -8,10 +9,11 @@ export async function ensureAdminUser() { throw new Error('ADMIN_EMAIL должен быть валидным email') } + const avatarUri = await generateAvatar(adminEmail) await prisma.user.upsert({ where: { email: adminEmail }, update: {}, - create: { email: adminEmail }, + create: { email: adminEmail, avatar: avatarUri, avatarStyle: 'avataaars' }, }) // Ensure admin notification settings exist diff --git a/server/src/lib/generate-avatar.js b/server/src/lib/generate-avatar.js new file mode 100644 index 0000000..e95d905 --- /dev/null +++ b/server/src/lib/generate-avatar.js @@ -0,0 +1,9 @@ +import { createAvatar } from '@dicebear/core' +import { avataaars } from '@dicebear/collection' + +const DEFAULT_STYLE = avataaars + +export async function generateAvatar(seed) { + const avatar = createAvatar(DEFAULT_STYLE, { seed: String(seed) }) + return avatar.toDataUri() +} diff --git a/server/src/routes/__tests__/oauth-social.test.js b/server/src/routes/__tests__/oauth-social.test.js index 9e60924..e4dc118 100644 --- a/server/src/routes/__tests__/oauth-social.test.js +++ b/server/src/routes/__tests__/oauth-social.test.js @@ -2,22 +2,16 @@ import { describe, it, expect } from 'vitest' import { prisma } from '../../lib/prisma.js' describe('OAuth — User model fields', () => { - it('stores displayName, firstName, lastName, gender, avatar fields on User model', async () => { + it('stores displayName and avatar fields on User model', async () => { const user = await prisma.user.create({ data: { email: 'test-oauth@example.com', displayName: 'Test User', - firstName: 'Test', - lastName: 'User', - gender: 'male', avatar: 'https://example.com/avatar.jpg', }, }) expect(user.displayName).toBe('Test User') - expect(user.firstName).toBe('Test') - expect(user.lastName).toBe('User') - expect(user.gender).toBe('male') expect(user.avatar).toBe('https://example.com/avatar.jpg') await prisma.user.delete({ where: { id: user.id } }) @@ -31,9 +25,6 @@ describe('OAuth — User model fields', () => { }) expect(user.displayName).toBeNull() - expect(user.firstName).toBeNull() - expect(user.lastName).toBeNull() - expect(user.gender).toBeNull() expect(user.avatar).toBeNull() await prisma.user.delete({ where: { id: user.id } }) diff --git a/server/src/routes/api/_product-helpers.js b/server/src/routes/api/_product-helpers.js index 3d9d5e6..082dd7b 100644 --- a/server/src/routes/api/_product-helpers.js +++ b/server/src/routes/api/_product-helpers.js @@ -5,7 +5,7 @@ export function slugify(input) { .toLowerCase() .trim() .replace(/\s+/g, '-') - .replace(/[^a-z0-9-а-яё]/gi, '') + .replace(/[^a-z0-9-]/gi, '') } export function safeExtFromFilename(filename) { diff --git a/server/src/routes/auth.js b/server/src/routes/auth.js index 0fe9e20..719e90e 100644 --- a/server/src/routes/auth.js +++ b/server/src/routes/auth.js @@ -8,6 +8,7 @@ import { validatePassword, verifyEmailCode, } from '../lib/auth.js' +import { generateAvatar } from '../lib/generate-avatar.js' import { prisma } from '../lib/prisma.js' import { checkLoginRateLimit } from '../lib/rate-limit.js' @@ -18,9 +19,6 @@ export function mapUserForClient(user) { id: user.id, email: user.email, displayName: user.displayName, - firstName: user.firstName, - lastName: user.lastName, - gender: user.gender, avatar: user.avatar, avatarStyle: user.avatarStyle, isAdmin: Boolean(adminEmail) && userEmail === adminEmail, @@ -55,10 +53,11 @@ export async function registerAuthRoutes(fastify) { const ok = await verifyEmailCode({ email, purpose: 'login', code }) if (!ok) return reply.code(401).send({ error: 'Неверный или истёкший код' }) + const avatarUri = await generateAvatar(email) const user = await prisma.user.upsert({ where: { email }, update: {}, - create: { email }, + create: { email, avatar: avatarUri, avatarStyle: 'avataaars' }, }) // Ensure notification preference exists @@ -88,12 +87,13 @@ export async function registerAuthRoutes(fastify) { if (exists) return reply.code(409).send({ error: 'Эта почта уже зарегистрирована' }) const passwordHash = await hashPassword(password) + const avatarUri = await generateAvatar(email) const user = await prisma.user.create({ data: { email, passwordHash, displayName: displayName || null, - avatar: null, + avatar: avatarUri, avatarStyle: 'avataaars', }, }) diff --git a/server/src/routes/oauth-social.js b/server/src/routes/oauth-social.js index cfb3c9e..44718c9 100644 --- a/server/src/routes/oauth-social.js +++ b/server/src/routes/oauth-social.js @@ -1,4 +1,5 @@ import { normalizeEmail } from '../lib/auth.js' +import { generateAvatar } from '../lib/generate-avatar.js' import { prisma } from '../lib/prisma.js' function clientRedirect(fastify, reply, token) { @@ -57,7 +58,7 @@ async function findOrCreateUserFromOAuth({ provider, providerUserId, accessToken data: { email: norm, displayName: norm.split('@')[0], - avatar: null, + avatar: await generateAvatar(norm), avatarStyle: 'avataaars', }, })