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