From c0ded946d8a029876ecb803d75dc029b01ac7de5 Mon Sep 17 00:00:00 2001 From: gabriel20xx Date: Wed, 12 Nov 2025 00:29:32 +0100 Subject: [PATCH] feat: enhance soft delete functionality for fields to cascade delete items and their values if last field is removed --- CollabTableServer/src/routes/fieldRoutes.ts | 34 +++++++++++++++++---- CollabTableServer/src/routes/syncRoutes.ts | 15 ++++++++- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/CollabTableServer/src/routes/fieldRoutes.ts b/CollabTableServer/src/routes/fieldRoutes.ts index 6fd7641..8bec5c7 100644 --- a/CollabTableServer/src/routes/fieldRoutes.ts +++ b/CollabTableServer/src/routes/fieldRoutes.ts @@ -54,16 +54,38 @@ router.put('/:id', async (req: Request, res: Response) => { router.delete('/:id', async (req: Request, res: Response) => { try { const updatedAt = Date.now(); - const result = await dbAdapter.execute( + // Soft delete the field first + const field = await dbAdapter.queryOne('SELECT * FROM fields WHERE id = ?', [req.params.id]); + if (!field) { + return res.status(404).json({ error: 'Field not found' }); + } + await dbAdapter.execute( 'UPDATE fields SET isDeleted = 1, updatedAt = ? WHERE id = ?', [updatedAt, req.params.id] ); - - if (result.changes === 0) { - return res.status(404).json({ error: 'Field not found' }); + + // Remove any item_values referencing this field (already handled in sync route, but ensure consistency for direct REST usage) + await dbAdapter.execute('DELETE FROM item_values WHERE fieldId = ?', [req.params.id]); + + // Determine if this was the last remaining (non-deleted) field in its list. + const listId = field.listId; + const remaining = await dbAdapter.queryOne( + 'SELECT COUNT(*) as cnt FROM fields WHERE listId = ? AND isDeleted = 0', + [listId] + ); + const remainingCount = remaining ? (remaining.cnt ?? remaining.CNT ?? remaining.count ?? 0) : 0; + + if (remainingCount === 0) { + // Last field deleted -> soft delete all items and purge their values for this list + try { + await dbAdapter.execute('UPDATE items SET isDeleted = 1, updatedAt = ? WHERE listId = ?', [updatedAt, listId]); + await dbAdapter.execute('DELETE FROM item_values WHERE itemId IN (SELECT id FROM items WHERE listId = ?)', [listId]); + } catch (cascadeErr) { + console.warn('[FIELD DELETE] Cascade item cleanup failed:', cascadeErr); + } } - - res.json({ message: 'Field deleted successfully' }); + + res.json({ message: 'Field deleted successfully', cascadeItemsDeleted: remainingCount === 0 }); } catch (error) { res.status(500).json({ error: 'Failed to delete field' }); } diff --git a/CollabTableServer/src/routes/syncRoutes.ts b/CollabTableServer/src/routes/syncRoutes.ts index 74b6390..b91df38 100644 --- a/CollabTableServer/src/routes/syncRoutes.ts +++ b/CollabTableServer/src/routes/syncRoutes.ts @@ -151,13 +151,26 @@ router.post('/sync', async (req: Request, res: Response) => { field.updatedAt, field.isDeleted ? 1 : 0 ]); - // If a field was deleted, remove any item_values referencing it so clients converge if (field.isDeleted) { + // Remove item_values for this field try { await tx.execute('DELETE FROM item_values WHERE fieldId = ?', [field.id]); } catch (err) { console.warn('[SYNC] Cascade delete for field failed:', String(err)); } + // Check if this was the last remaining non-deleted field for the list + try { + const remaining = await tx.queryOne('SELECT COUNT(*) as cnt FROM fields WHERE listId = ? AND isDeleted = 0', [field.listId]); + const remainingCount = remaining ? (remaining.cnt ?? remaining.CNT ?? remaining.count ?? 0) : 0; + if (remainingCount === 0) { + // Soft delete items in this list and purge their values + await tx.execute('UPDATE items SET isDeleted = 1, updatedAt = ? WHERE listId = ?', [field.updatedAt, field.listId]); + await tx.execute('DELETE FROM item_values WHERE itemId IN (SELECT id FROM items WHERE listId = ?)', [field.listId]); + console.log('[SYNC] Last field deleted; cascaded item+value cleanup for list', field.listId); + } + } catch (cascadeErr) { + console.warn('[SYNC] Cascade last-field cleanup failed:', cascadeErr); + } } } }