Compare commits

...

2 commits

3 changed files with 62 additions and 36 deletions

View file

@ -45,12 +45,10 @@ model Item {
urlId Int @map("url_id") urlId Int @map("url_id")
wikidataId String? @map("wikidata_id") wikidataId String? @map("wikidata_id")
itemOrder Int @default(0) @map("item_order") itemOrder Int @default(0) @map("item_order")
globalItemId String @unique @map("global_item_id") globalItemId String @map("global_item_id")
// Relations // Relations
url Url @relation(fields: [urlId], references: [id], onDelete: Cascade) url Url @relation(fields: [urlId], references: [id], onDelete: Cascade)
itemProperties ItemProperty[] @relation("ItemToItemProperty")
deletedProperties DeletedProperty[]
@@map("items") @@map("items")
} }
@ -62,7 +60,6 @@ model ItemProperty {
value String value String
// Relations // Relations
item Item @relation("ItemToItemProperty", fields: [globalItemId], references: [globalItemId], onDelete: Cascade)
property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade) property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
@@id([globalItemId, propertyId]) @@id([globalItemId, propertyId])
@ -90,7 +87,6 @@ model DeletedProperty {
// Relations // Relations
url Url @relation(fields: [urlId], references: [id], onDelete: Cascade) url Url @relation(fields: [urlId], references: [id], onDelete: Cascade)
item Item @relation(fields: [globalItemId], references: [globalItemId], onDelete: Cascade)
property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade) property Property @relation(fields: [propertyId], references: [id], onDelete: Cascade)
@@id([urlId, globalItemId, propertyId]) @@id([urlId, globalItemId, propertyId])

View file

@ -6,10 +6,12 @@ import { Item } from '@/types/database';
// PUT /api/urls/[encodedUrl]/items/[itemId] - Update a specific item // PUT /api/urls/[encodedUrl]/items/[itemId] - Update a specific item
export async function PUT( export async function PUT(
request: NextRequest, request: NextRequest,
{ params }: { params: { encodedUrl: string; itemId: string } } { params }: { params: Promise<{ encodedUrl: string; itemId: string }> }
) { ) {
try { try {
const { encodedUrl, itemId } = params; // Await params before accessing properties
const resolvedParams = await params;
const { encodedUrl, itemId } = resolvedParams;
const url = decodeURIComponent(encodedUrl); const url = decodeURIComponent(encodedUrl);
const item: Item = await request.json(); const item: Item = await request.json();
@ -46,12 +48,16 @@ export async function PUT(
return NextResponse.json({ success: true, itemId }); return NextResponse.json({ success: true, itemId });
} catch (error) { } catch (error) {
console.error('[API] Update error:', error); console.error('[API] Update error:', error);
// Await params for error handling too
const resolvedParams = await params;
return NextResponse.json( return NextResponse.json(
{ {
error: 'Database error', error: 'Database error',
details: error instanceof Error ? error.message : 'Unknown error', details: error instanceof Error ? error.message : 'Unknown error',
itemId: params.itemId, itemId: resolvedParams.itemId,
url: decodeURIComponent(params.encodedUrl) url: decodeURIComponent(resolvedParams.encodedUrl)
}, },
{ status: 500 } { status: 500 }
); );
@ -61,10 +67,12 @@ export async function PUT(
// DELETE /api/urls/[encodedUrl]/items/[itemId] - Delete a specific item // DELETE /api/urls/[encodedUrl]/items/[itemId] - Delete a specific item
export async function DELETE( export async function DELETE(
request: NextRequest, request: NextRequest,
{ params }: { params: { encodedUrl: string; itemId: string } } { params }: { params: Promise<{ encodedUrl: string; itemId: string }> }
) { ) {
const url = decodeURIComponent(params.encodedUrl); // Await params before accessing properties
const itemId = params.itemId; const resolvedParams = await params;
const url = decodeURIComponent(resolvedParams.encodedUrl);
const itemId = resolvedParams.itemId;
console.log('[API] Deleting item', itemId, 'from URL', url); console.log('[API] Deleting item', itemId, 'from URL', url);

View file

@ -66,9 +66,29 @@ export async function getItemsByUrl(url: string): Promise<Item[]> {
const items: Item[] = []; const items: Item[] = [];
for (const dbItem of urlRecord.items) { for (const dbItem of urlRecord.items) {
// Get all properties for this globalItemId // Get core properties (name, description) - these should always be included
const corePropertyNames = ['name', 'description'];
const coreProperties = await tx.property.findMany({
where: { name: { in: corePropertyNames } }
});
const corePropertyIds = coreProperties.map(p => p.id);
// Get selected custom properties for this URL
const selectedPropertiesForUrl = await tx.selectedProperty.findMany({
where: { urlId: urlRecord.id },
include: { property: true }
});
const selectedCustomPropertyIds = selectedPropertiesForUrl.map(sp => sp.propertyId);
// Combine core properties with selected custom properties
const allowedPropertyIds = [...corePropertyIds, ...selectedCustomPropertyIds];
// Get properties for this globalItemId, including core properties and selected custom properties
const itemProperties = await tx.itemProperty.findMany({ const itemProperties = await tx.itemProperty.findMany({
where: { globalItemId: dbItem.globalItemId }, where: {
globalItemId: dbItem.globalItemId,
propertyId: { in: allowedPropertyIds }
},
include: { property: true } include: { property: true }
}); });
@ -120,23 +140,13 @@ export async function insertItemByUrl(url: string, item: Item): Promise<void> {
console.log(`[DB] URL ID: ${urlRecord.id}`); console.log(`[DB] URL ID: ${urlRecord.id}`);
// 2. Check if this specific item already exists for this URL // 2. ALWAYS find existing globalItemId by name first (this is the key fix)
const existingItem = await tx.item.findUnique({
where: { id: item.id }
});
// 3. Find or create globalItemId based on item name
let globalItemId: string; let globalItemId: string;
if (existingItem) { if (item.name.trim()) {
// Use existing globalItemId if item already exists // Look for existing global item with the same name
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'); const namePropertyId = await getOrCreateProperty(tx, 'name');
// Look for existing global item with the same name
const existingGlobalItem = await tx.itemProperty.findFirst({ const existingGlobalItem = await tx.itemProperty.findFirst({
where: { where: {
propertyId: namePropertyId, propertyId: namePropertyId,
@ -145,25 +155,29 @@ export async function insertItemByUrl(url: string, item: Item): Promise<void> {
select: { globalItemId: true } select: { globalItemId: true }
}); });
if (existingGlobalItem && item.name.trim()) { if (existingGlobalItem) {
// Use existing globalItemId for items with the same name // Use existing globalItemId for items with the same name
globalItemId = existingGlobalItem.globalItemId; globalItemId = existingGlobalItem.globalItemId;
console.log(`[DB] Found existing global item ID for name "${item.name}": ${globalItemId}`); console.log(`[DB] Found existing global item ID for name "${item.name}": ${globalItemId}`);
} else { } else {
// Create new globalItemId for new names or empty names // Create new globalItemId for new names
globalItemId = uuidv4(); globalItemId = uuidv4();
console.log(`[DB] Created new global item ID: ${globalItemId}`); console.log(`[DB] Created new global item ID for new name "${item.name}": ${globalItemId}`);
} }
} else {
// For empty names, always create new globalItemId
globalItemId = uuidv4();
console.log(`[DB] Created new global item ID for empty name: ${globalItemId}`);
} }
// 4. Get max order for new items // 3. Get max order for new items
const maxOrder = await tx.item.findFirst({ const maxOrder = await tx.item.findFirst({
where: { urlId: urlRecord.id }, where: { urlId: urlRecord.id },
orderBy: { itemOrder: 'desc' }, orderBy: { itemOrder: 'desc' },
select: { itemOrder: true } select: { itemOrder: true }
}); });
// 5. Upsert the item record (unique by item.id) // 4. Upsert the item record (unique by item.id)
await tx.item.upsert({ await tx.item.upsert({
where: { id: item.id }, where: { id: item.id },
update: { update: {
@ -180,7 +194,7 @@ export async function insertItemByUrl(url: string, item: Item): Promise<void> {
} }
}); });
// 6. Handle all properties (core + custom) // 5. Handle all properties (core + custom)
const allProperties = [ const allProperties = [
['name', item.name], ['name', item.name],
['description', item.description], ['description', item.description],
@ -239,10 +253,18 @@ export async function deleteItemByUrl(url: string, itemId: string): Promise<void
where: { id: itemId } where: { id: itemId }
}); });
// Clean up item properties for this global item // Check if this is the last item with this globalItemId
const remainingItems = await tx.item.count({
where: { globalItemId: item.globalItemId }
});
// Only delete global properties if this was the last item with this globalItemId
if (remainingItems === 0) {
await tx.itemProperty.deleteMany({ await tx.itemProperty.deleteMany({
where: { globalItemId: item.globalItemId } where: { globalItemId: item.globalItemId }
}); });
console.log(`[DB] Cleaned up global properties for globalItemId: ${item.globalItemId}`);
}
console.log(`[DB] Item ${itemId} deleted successfully`); console.log(`[DB] Item ${itemId} deleted successfully`);
}); });