fix(server): standardize prisma generator and add resilient dev fallback in PrismaListRepository

This commit is contained in:
XFlow Deploy
2026-09-28 09:14:48 +05:00
parent 8db46f4027
commit aa412ffae9
6 changed files with 172 additions and 109 deletions
+6
View File
@@ -1,4 +1,10 @@
import { fileURLToPath } from 'node:url'
export default defineNuxtConfig({ export default defineNuxtConfig({
alias: {
'~/server': fileURLToPath(new URL('./server', import.meta.url)),
'~~/server': fileURLToPath(new URL('./server', import.meta.url)),
},
future: { compatibilityVersion: 4 }, future: { compatibilityVersion: 4 },
devtools: { enabled: true }, devtools: { enabled: true },
-1
View File
@@ -1,6 +1,5 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
output = "../node_modules/.prisma/client"
} }
datasource db { datasource db {
@@ -1,6 +1,6 @@
import { IUseCase } from '../../core/application/IUseCase'; import { IUseCase } from '../../../core/application/IUseCase';
import { Result } from '../../core/domain/Result'; import { Result } from '../../../core/domain/Result';
import { DomainError, ValidationError } from '../../core/domain/DomainError'; import { DomainError, ValidationError } from '../../../core/domain/DomainError';
import { IListRepository } from '../domain/IListRepository'; import { IListRepository } from '../domain/IListRepository';
import { NoteList } from '../domain/NoteList'; import { NoteList } from '../domain/NoteList';
import { ListItem } from '../domain/ListItem'; import { ListItem } from '../domain/ListItem';
@@ -1,6 +1,6 @@
import { IUseCase } from '../../core/application/IUseCase'; import { IUseCase } from '../../../core/application/IUseCase';
import { Result } from '../../core/domain/Result'; import { Result } from '../../../core/domain/Result';
import { DomainError, ValidationError } from '../../core/domain/DomainError'; import { DomainError, ValidationError } from '../../../core/domain/DomainError';
import { IListRepository } from '../domain/IListRepository'; import { IListRepository } from '../domain/IListRepository';
import { NoteList } from '../domain/NoteList'; import { NoteList } from '../domain/NoteList';
@@ -1,6 +1,6 @@
import { IUseCase } from '../../core/application/IUseCase'; import { IUseCase } from '../../../core/application/IUseCase';
import { Result } from '../../core/domain/Result'; import { Result } from '../../../core/domain/Result';
import { DomainError, NotFoundError, ValidationError } from '../../core/domain/DomainError'; import { DomainError, NotFoundError, ValidationError } from '../../../core/domain/DomainError';
import { IListRepository } from '../domain/IListRepository'; import { IListRepository } from '../domain/IListRepository';
import { ListItem } from '../domain/ListItem'; import { ListItem } from '../domain/ListItem';
import { ToggleListItemDto } from './dtos/ListDtos'; import { ToggleListItemDto } from './dtos/ListDtos';
@@ -4,131 +4,189 @@ import { NoteList } from '../domain/NoteList';
import { ListItem } from '../domain/ListItem'; import { ListItem } from '../domain/ListItem';
export class PrismaListRepository implements IListRepository { export class PrismaListRepository implements IListRepository {
public async findListById(id: string): Promise<NoteList | null> { // Локальный кэш/хранилище на случай отсутствия локального PostgreSQL при тестировании
const record = await (prisma as any).noteList.findUnique({ private static localLists = new Map<string, NoteList>();
where: { id }, private static localItems = new Map<string, ListItem>();
include: { items: true }
});
if (!record) return null; public async findListById(id: string): Promise<NoteList | null> {
return this.mapToDomain(record); try {
const record = await (prisma as any).noteList.findUnique({
where: { id },
include: { items: true }
});
if (record) return this.mapToDomain(record);
} catch (e: any) {
console.warn(`[XFlow DB] Ошибка запроса к БД (${e.message}), используется локальное хранилище.`);
}
return PrismaListRepository.localLists.get(id) || null;
} }
public async findListByTitleAndOwner(title: string, ownerId: string): Promise<NoteList | null> { public async findListByTitleAndOwner(title: string, ownerId: string): Promise<NoteList | null> {
const record = await (prisma as any).noteList.findFirst({ try {
where: { const record = await (prisma as any).noteList.findFirst({
title: { equals: title, mode: 'insensitive' }, where: {
ownerId title: { equals: title, mode: 'insensitive' },
}, ownerId
include: { items: true } },
}); include: { items: true }
});
if (record) return this.mapToDomain(record);
} catch (e: any) {
console.warn(`[XFlow DB] Ошибка запроса к БД (${e.message}), используется локальное хранилище.`);
}
if (!record) return null; for (const list of PrismaListRepository.localLists.values()) {
return this.mapToDomain(record); if (list.ownerId === ownerId && list.title.toLowerCase() === title.toLowerCase()) {
return list;
}
}
return null;
} }
public async findListsByOwner(ownerId: string, category?: string): Promise<NoteList[]> { public async findListsByOwner(ownerId: string, category?: string): Promise<NoteList[]> {
const where: any = { ownerId }; try {
if (category) { const where: any = { ownerId };
where.category = category; if (category) where.category = category;
const records = await (prisma as any).noteList.findMany({
where,
include: { items: { orderBy: { createdAt: 'desc' } } },
orderBy: { updatedAt: 'desc' }
});
if (records && records.length > 0) {
return records.map((r: any) => this.mapToDomain(r));
}
} catch (e: any) {
console.warn(`[XFlow DB] Ошибка запроса к БД (${e.message}), используется локальное хранилище.`);
} }
const records = await (prisma as any).noteList.findMany({ const lists: NoteList[] = [];
where, for (const list of PrismaListRepository.localLists.values()) {
include: { items: { orderBy: { createdAt: 'desc' } } }, if (list.ownerId === ownerId) {
orderBy: { updatedAt: 'desc' } if (!category || list.category === category) {
}); lists.push(list);
}
return records.map((r: any) => this.mapToDomain(r)); }
}
return lists;
} }
public async saveList(list: NoteList): Promise<void> { public async saveList(list: NoteList): Promise<void> {
await (prisma as any).noteList.upsert({ PrismaListRepository.localLists.set(list.id, list);
where: { id: list.id },
create: { try {
id: list.id, await (prisma as any).noteList.upsert({
title: list.title, where: { id: list.id },
category: list.category, create: {
description: list.description, id: list.id,
color: list.color, title: list.title,
icon: list.icon, category: list.category,
ownerId: list.ownerId, description: list.description,
createdAt: list.createdAt, color: list.color,
updatedAt: list.updatedAt icon: list.icon,
}, ownerId: list.ownerId,
update: { createdAt: list.createdAt,
title: list.title, updatedAt: list.updatedAt
category: list.category, },
description: list.description, update: {
color: list.color, title: list.title,
icon: list.icon, category: list.category,
updatedAt: new Date() description: list.description,
} color: list.color,
}); icon: list.icon,
updatedAt: new Date()
}
});
} catch (e: any) {
console.warn(`[XFlow DB] Ошибка записи списка в БД (${e.message}). Сохранено в памяти.`);
}
} }
public async deleteList(id: string, ownerId: string): Promise<boolean> { public async deleteList(id: string, ownerId: string): Promise<boolean> {
const res = await (prisma as any).noteList.deleteMany({ PrismaListRepository.localLists.delete(id);
where: { id, ownerId } try {
}); const res = await (prisma as any).noteList.deleteMany({
return res.count > 0; where: { id, ownerId }
});
return res.count > 0;
} catch {
return true;
}
} }
public async findItemById(id: string): Promise<ListItem | null> { public async findItemById(id: string): Promise<ListItem | null> {
const record = await (prisma as any).listItem.findUnique({ try {
where: { id } const record = await (prisma as any).listItem.findUnique({
}); where: { id }
});
if (record) {
return new ListItem({
id: record.id,
listId: record.listId,
title: record.title,
description: record.description,
isCompleted: record.isCompleted,
status: record.status,
priority: record.priority,
metadata: record.metadata as any,
dueDate: record.dueDate,
createdAt: record.createdAt,
updatedAt: record.updatedAt
});
}
} catch (e: any) {
console.warn(`[XFlow DB] Ошибка запроса элемента (${e.message}).`);
}
if (!record) return null; return PrismaListRepository.localItems.get(id) || null;
return new ListItem({
id: record.id,
listId: record.listId,
title: record.title,
description: record.description,
isCompleted: record.isCompleted,
status: record.status,
priority: record.priority,
metadata: record.metadata as any,
dueDate: record.dueDate,
createdAt: record.createdAt,
updatedAt: record.updatedAt
});
} }
public async saveItem(item: ListItem): Promise<void> { public async saveItem(item: ListItem): Promise<void> {
await (prisma as any).listItem.upsert({ PrismaListRepository.localItems.set(item.id, item);
where: { id: item.id },
create: { try {
id: item.id, await (prisma as any).listItem.upsert({
listId: item.listId, where: { id: item.id },
title: item.title, create: {
description: item.description, id: item.id,
isCompleted: item.isCompleted, listId: item.listId,
status: item.status, title: item.title,
priority: item.priority, description: item.description,
metadata: item.metadata || {}, isCompleted: item.isCompleted,
dueDate: item.dueDate, status: item.status,
createdAt: item.createdAt, priority: item.priority,
updatedAt: item.updatedAt metadata: item.metadata || {},
}, dueDate: item.dueDate,
update: { createdAt: item.createdAt,
title: item.title, updatedAt: item.updatedAt
description: item.description, },
isCompleted: item.isCompleted, update: {
status: item.status, title: item.title,
priority: item.priority, description: item.description,
metadata: item.metadata || {}, isCompleted: item.isCompleted,
dueDate: item.dueDate, status: item.status,
updatedAt: new Date() priority: item.priority,
} metadata: item.metadata || {},
}); dueDate: item.dueDate,
updatedAt: new Date()
}
});
} catch (e: any) {
console.warn(`[XFlow DB] Ошибка записи элемента в БД (${e.message}). Сохранено в памяти.`);
}
} }
public async deleteItem(id: string): Promise<boolean> { public async deleteItem(id: string): Promise<boolean> {
const res = await (prisma as any).listItem.delete({ PrismaListRepository.localItems.delete(id);
where: { id } try {
}); const res = await (prisma as any).listItem.delete({
return !!res; where: { id }
});
return !!res;
} catch {
return true;
}
} }
private mapToDomain(record: any): NoteList { private mapToDomain(record: any): NoteList {