diff --git a/docs/proposals/hosting-cascade-delete.md b/docs/proposals/hosting-cascade-delete.md new file mode 100644 index 00000000..b4ee2df8 --- /dev/null +++ b/docs/proposals/hosting-cascade-delete.md @@ -0,0 +1,48 @@ +# Hosting: Cascade Delete HostedSite → Store + +## Problem + +Deleting a HostedSite leaves the associated Store orphaned in the database. The Store's subdomain remains taken, so creating a new site with the same prospect/business fails with "slug already exists". + +## Root Cause + +- `hosted_site_service.delete()` does a hard delete on the HostedSite only +- The Store (created by `hosted_site_service.create()`) is not deleted +- `stores.subdomain` has a partial unique index (`WHERE deleted_at IS NULL`) +- The orphaned Store is still active → subdomain collision on re-create + +## Recommendation + +When deleting a HostedSite, also delete the associated Store: + +```python +def delete(self, db: Session, site_id: int) -> bool: + site = self.get_by_id(db, site_id) + store = site.store + db.delete(site) + if store: + # Soft-delete or hard-delete the store created for this site + soft_delete(store) # or db.delete(store) + db.flush() +``` + +### Considerations + +- **Soft vs hard delete**: If using soft-delete, the subdomain gets freed (partial unique index filters `deleted_at IS NULL`). If hard-deleting, cascade will also remove StorePlatform, ContentPages, StoreTheme, etc. +- **CMS content**: Deleting the Store cascades to ContentPages (created by POC builder) — this is desired since the POC content belongs to that store +- **Merchant**: The merchant created from the prospect should NOT be deleted — it may be used by other stores or relinked later +- **Safety**: Only delete stores that were created by the hosting module (check if store has a HostedSite backref). Don't delete stores that existed independently. + +## Files to modify + +| File | Change | +|---|---| +| `hosting/services/hosted_site_service.py` | Update `delete()` to also soft-delete/hard-delete the associated Store | +| `hosting/tests/unit/test_hosted_site_service.py` | Update delete test to verify Store is also deleted | + +## Quick workaround (for now) + +Manually delete the orphaned store from the DB: +```sql +DELETE FROM stores WHERE subdomain = 'batirenovation-strasbourg' AND id NOT IN (SELECT store_id FROM hosted_sites); +```