feat: latin-only slugs, server-side avatar generation, remove unused User fields
This commit is contained in:
@@ -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;
|
||||
@@ -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?
|
||||
|
||||
Vendored
+3
-1
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 } })
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user