Compareware_next.js/src/lib/database.ts

374 lines
No EOL
10 KiB
TypeScript

import { prisma, withTransaction } from './prisma';
import { Item} from '../types/database';
import { v4 as uuidv4 } from 'uuid';
import { Prisma } from '../generated/prisma';
// Type for transaction client
type TransactionClient = Prisma.TransactionClient;
// Insert URL
export async function insertUrl(url: string): Promise<number> {
console.log(`[DB] Inserting URL: ${url}`);
const result = await prisma.url.upsert({
where: { url },
update: {},
create: { url },
});
console.log(`[DB] URL inserted/found with ID: ${result.id}`);
return result.id;
}
// Get or create property
async function getOrCreateProperty(tx: TransactionClient, propertyName: string): Promise<number> {
const existing = await tx.property.findUnique({
where: { name: propertyName }
});
if (existing) {
return existing.id;
}
const created = await tx.property.create({
data: { name: propertyName, globalUsageCount: 0 }
});
return created.id;
}
// Get items by URL
export async function getItemsByUrl(url: string): Promise<Item[]> {
console.log(`[DB] Getting items for URL: ${url}`);
return await withTransaction(async (tx) => {
// Get URL record
const urlRecord = await tx.url.findUnique({
where: { url },
include: {
items: {
orderBy: { itemOrder: 'asc' },
include: {
// We don't include itemProperties here since we'll fetch them by globalItemId
}
}
}
});
if (!urlRecord) {
console.log(`[DB] No URL record found for: ${url}`);
return [];
}
console.log(`[DB] Found ${urlRecord.items.length} items for URL: ${url}`);
// For each item, get properties from globalItemId
const items: Item[] = [];
for (const dbItem of urlRecord.items) {
// Get all properties for this globalItemId
const itemProperties = await tx.itemProperty.findMany({
where: { globalItemId: dbItem.globalItemId },
include: { property: true }
});
// Separate core properties from custom properties
const customProperties: Record<string, string> = {};
let name = '';
let description = '';
for (const prop of itemProperties) {
const propName = prop.property.name;
const propValue = prop.value;
if (propName === 'name') {
name = propValue;
} else if (propName === 'description') {
description = propValue;
} else {
customProperties[propName] = propValue;
}
}
const item: Item = {
id: dbItem.id,
name,
description,
wikidataId: dbItem.wikidataId || undefined,
customProperties
};
items.push(item);
}
console.log(`[DB] Returning ${items.length} items with properties`);
return items;
});
}
// Insert item by URL
export async function insertItemByUrl(url: string, item: Item): Promise<void> {
console.log(`[DB] Starting insert for URL: ${url}, Item: ${item.id}, Name: ${item.name}`);
await withTransaction(async (tx) => {
// 1. Get or create URL
const urlRecord = await tx.url.upsert({
where: { url },
update: {},
create: { url },
});
console.log(`[DB] URL ID: ${urlRecord.id}`);
// 2. Check if this specific item already exists for this URL
const existingItem = await tx.item.findUnique({
where: { id: item.id }
});
// 3. Find or create globalItemId based on item name
let globalItemId: string;
if (existingItem) {
// Use existing globalItemId if item already exists
globalItemId = existingItem.globalItemId;
console.log(`[DB] Using existing global item ID: ${globalItemId}`);
} else {
// For new items, find existing globalItemId by name or create new one
const namePropertyId = await getOrCreateProperty(tx, 'name');
// Look for existing global item with the same name
const existingGlobalItem = await tx.itemProperty.findFirst({
where: {
propertyId: namePropertyId,
value: item.name.trim()
},
select: { globalItemId: true }
});
if (existingGlobalItem && item.name.trim()) {
// Use existing globalItemId for items with the same name
globalItemId = existingGlobalItem.globalItemId;
console.log(`[DB] Found existing global item ID for name "${item.name}": ${globalItemId}`);
} else {
// Create new globalItemId for new names or empty names
globalItemId = uuidv4();
console.log(`[DB] Created new global item ID: ${globalItemId}`);
}
}
// 4. Get max order for new items
const maxOrder = await tx.item.findFirst({
where: { urlId: urlRecord.id },
orderBy: { itemOrder: 'desc' },
select: { itemOrder: true }
});
// 5. Upsert the item record (unique by item.id)
await tx.item.upsert({
where: { id: item.id },
update: {
urlId: urlRecord.id,
wikidataId: item.wikidataId,
globalItemId
},
create: {
id: item.id,
urlId: urlRecord.id,
wikidataId: item.wikidataId,
itemOrder: (maxOrder?.itemOrder || 0) + 1,
globalItemId
}
});
// 6. Handle all properties (core + custom)
const allProperties = [
['name', item.name],
['description', item.description],
...Object.entries(item.customProperties)
];
for (const [propName, propValue] of allProperties) {
const propertyId = await getOrCreateProperty(tx, propName);
// Update properties for the globalItemId (this affects all items with same name)
await tx.itemProperty.upsert({
where: {
globalItemId_propertyId: {
globalItemId,
propertyId
}
},
update: { value: propValue },
create: {
globalItemId,
propertyId,
value: propValue
}
});
}
console.log(`[DB] Item ${item.id} with global ID ${globalItemId} inserted/updated successfully`);
});
}
// Delete item by URL
export async function deleteItemByUrl(url: string, itemId: string): Promise<void> {
console.log(`[DB] Deleting item ${itemId} from URL: ${url}`);
await withTransaction(async (tx) => {
// Get URL ID
const urlRecord = await tx.url.findUnique({
where: { url }
});
if (!urlRecord) {
throw new Error(`URL not found: ${url}`);
}
// Get the item to find its global_item_id
const item = await tx.item.findFirst({
where: { id: itemId, urlId: urlRecord.id }
});
if (!item) {
throw new Error(`Item not found: ${itemId}`);
}
// Delete the item (this will cascade to item_properties via foreign key)
await tx.item.delete({
where: { id: itemId }
});
// Clean up item properties for this global item
await tx.itemProperty.deleteMany({
where: { globalItemId: item.globalItemId }
});
console.log(`[DB] Item ${itemId} deleted successfully`);
});
}
// Delete property by URL
export async function deletePropertyByUrl(url: string, propertyName: string): Promise<void> {
console.log(`[DB] Starting soft delete of property ${propertyName} for URL: ${url}`);
await withTransaction(async (tx) => {
// Get URL ID
const urlRecord = await tx.url.findUnique({
where: { url }
});
if (!urlRecord) {
console.error(`[DB] URL not found: ${url}`);
throw new Error(`URL not found: ${url}`);
}
console.log(`[DB] Found URL record with ID: ${urlRecord.id}`);
// Get property ID
const property = await tx.property.findUnique({
where: { name: propertyName }
});
if (!property) {
console.error(`[DB] Property not found: ${propertyName}`);
throw new Error(`Property not found: ${propertyName}`);
}
console.log(`[DB] Found property record with ID: ${property.id}`);
// Remove from selected properties first
const deletedSelectedProperties = await tx.selectedProperty.deleteMany({
where: {
urlId: urlRecord.id,
propertyId: property.id
}
});
console.log(`[DB] Removed ${deletedSelectedProperties.count} selected property records`);
// Get all global item IDs for this URL
const items = await tx.item.findMany({
where: { urlId: urlRecord.id },
select: { globalItemId: true }
});
console.log(`[DB] Found ${items.length} items for URL`);
// Insert into deleted_properties for each global item
let deletedPropertiesCount = 0;
for (const item of items) {
await tx.deletedProperty.upsert({
where: {
urlId_globalItemId_propertyId: {
urlId: urlRecord.id,
globalItemId: item.globalItemId,
propertyId: property.id
}
},
update: {},
create: {
urlId: urlRecord.id,
globalItemId: item.globalItemId,
propertyId: property.id
}
});
deletedPropertiesCount++;
}
console.log(`[DB] Created/updated ${deletedPropertiesCount} deleted property records`);
console.log(`[DB] Successfully soft deleted property ${propertyName} for URL: ${url}`);
});
}
// Add selected property
export async function addSelectedProperty(url: string, propertyName: string): Promise<void> {
console.log(`[DB] Adding selected property ${propertyName} for URL: ${url}`);
await withTransaction(async (tx) => {
// Get or create URL
const urlRecord = await tx.url.upsert({
where: { url },
update: {},
create: { url }
});
// Get or create property
const propertyId = await getOrCreateProperty(tx, propertyName);
// Add to selected properties
await tx.selectedProperty.upsert({
where: {
urlId_propertyId: {
urlId: urlRecord.id,
propertyId
}
},
update: {},
create: {
urlId: urlRecord.id,
propertyId
}
});
console.log(`[DB] Selected property ${propertyName} added for URL: ${url}`);
});
}
// Get selected properties
export async function getSelectedProperties(url: string): Promise<string[]> {
console.log(`[DB] Getting selected properties for URL: ${url}`);
const result = await prisma.selectedProperty.findMany({
where: {
url: { url }
},
include: {
property: true
}
});
const properties = result.map(sp => sp.property.name);
console.log(`[DB] Found ${properties.length} selected properties`);
return properties;
}