diff --git a/IMPLEMENTATION_STATUS.md b/IMPLEMENTATION_STATUS.md new file mode 100644 index 0000000..ef9cb78 --- /dev/null +++ b/IMPLEMENTATION_STATUS.md @@ -0,0 +1,203 @@ +# Multi-Household Implementation - Quick Reference + +## Implementation Status + +### ✅ Sprint 1: Database Foundation (COMPLETE) +- [x] Created migration script: `multi_household_architecture.sql` +- [x] Created migration guide: `MIGRATION_GUIDE.md` +- [x] Created migration runner scripts: `run-migration.sh` / `run-migration.bat` +- [x] **Tested migration on 'grocery' database (copy of Costco)** +- [x] Migration successful - all data migrated correctly +- [x] Verification passed - 0 data integrity issues + +**Migration Results:** +- ✅ 1 Household created: "Main Household" (invite code: MAIN755114) +- ✅ 7 Users migrated (2 system_admins, 5 standard users) +- ✅ 122 Items extracted to master catalog +- ✅ 122 Household lists created +- ✅ 27 Classifications migrated +- ✅ 273 History records preserved +- ✅ All users assigned to household (admin/user roles) +- ✅ 0 orphaned records or data loss + +**Database:** `grocery` (using Costco as template for safety) + +### ⏳ Sprint 2: Backend API (NEXT - READY TO START) +- [ ] Create household.model.js +- [ ] Create store.model.js +- [ ] Update list.model.js for household+store scope +- [ ] Create householdAccess middleware +- [ ] Create storeAccess middleware +- [ ] Create households.controller.js +- [ ] Create stores.controller.js +- [ ] Update lists.controller.js +- [ ] Update users.controller.js +- [ ] Create/update routes for new structure + +### ⏳ Sprint 3: Frontend Core (PENDING) +- [ ] Refactor contexts +- [ ] Create household UI +- [ ] Create store UI + +## New Database Schema + +### Core Tables +1. **households** - Household entities with invite codes +2. **stores** - Store types (Costco, Target, etc.) +3. **household_members** - User membership with per-household roles +4. **household_stores** - Which stores each household uses +5. **items** - Master item catalog (shared) +6. **household_lists** - Lists scoped to household + store +7. **household_item_classifications** - Classifications per household + store +8. **household_list_history** - History tracking + +### Key Relationships +- User → household_members → Household (many-to-many) +- Household → household_stores → Store (many-to-many) +- Household + Store → household_lists → Item (unique per combo) +- household_lists → household_list_history (one-to-many) + +## Role System + +### System-Wide (users.role) +- **system_admin**: App infrastructure control +- **user**: Standard user + +### Household-Scoped (household_members.role) +- **admin**: Full household control +- **user**: Standard member + +## Migration Steps + +1. **Backup**: `pg_dump grocery_list > backup.sql` +2. **Run**: `psql -d grocery_list -f backend/migrations/multi_household_architecture.sql` +3. **Verify**: Check counts, run integrity queries +4. **Test**: Ensure app functionality +5. **Cleanup**: Drop old tables after verification + +## API Changes (Planned) + +### Old Format +``` +GET /api/list +POST /api/list/add +``` + +### New Format +``` +GET /api/households/:hId/stores/:sId/list +POST /api/households/:hId/stores/:sId/list/add +``` + +## Frontend Changes (Planned) + +### New Contexts +```jsx +const { user, isSystemAdmin } = useAuth(); +const { activeHousehold, isAdmin } = useHousehold(); +const { activeStore, householdStores } = useStore(); +``` + +### New Routes +``` +/households - List households +/households/:id/stores/:sId - Grocery list +/households/:id/members - Manage members +/join/:inviteCode - Join household +``` + +## Development Workflow + +### Phase 1: Database (Current) +1. Review migration script +2. Test on local dev database +3. Run verification queries +4. Document any issues + +### Phase 2: Backend API (Next) +1. Create household.model.js +2. Create store.model.js +3. Update list.model.js for household scope +4. Create middleware for household access +5. Update routes + +### Phase 3: Frontend +1. Refactor AuthContext → useAuth() +2. Create HouseholdContext → useHousehold() +3. Create StoreContext → useStore() +4. Build household switcher +5. Build store tabs + +## Testing Checklist + +### Database Migration +- [ ] All tables created +- [ ] All indexes created +- [ ] Users migrated to household +- [ ] Items deduplicated correctly +- [ ] Lists migrated with correct references +- [ ] Classifications preserved +- [ ] History preserved +- [ ] No NULL foreign keys + +### Backend API +- [ ] Household CRUD works +- [ ] Member management works +- [ ] Invite codes work +- [ ] Store management works +- [ ] List operations scoped correctly +- [ ] Permissions enforced +- [ ] History tracked correctly + +### Frontend UI +- [ ] Login/logout works +- [ ] Household switcher works +- [ ] Store tabs work +- [ ] Can create household +- [ ] Can join household +- [ ] Can add items +- [ ] Can mark bought +- [ ] Roles respected in UI + +## Rollback Strategy + +If migration fails: +```sql +ROLLBACK; +``` + +If issues found after: +```bash +psql -d grocery_list < backup.sql +``` + +## Support Resources + +- **Migration Script**: `backend/migrations/multi_household_architecture.sql` +- **Guide**: `backend/migrations/MIGRATION_GUIDE.md` +- **Architecture**: `docs/multi-household-architecture-plan.md` +- **Status**: This file + +## Key Decisions + +1. ✅ Keep users.role for system admin +2. ✅ Simplify household roles to admin/user +3. ✅ Preserve user names in history (no anonymization) +4. ✅ Shared item catalog with household-specific lists +5. ✅ Context pattern refactoring (internal context + custom hooks) + +## Timeline + +- **Week 1-2**: Database migration + testing +- **Week 3-4**: Backend API implementation +- **Week 5-6**: Frontend core implementation +- **Week 7**: Member management +- **Week 8-9**: Testing & polish +- **Week 10**: Production migration + +## Contact + +For questions or issues during implementation, refer to: +- Architecture plan for design decisions +- Migration guide for database steps +- This file for quick status updates diff --git a/backend/migrations/MIGRATION_GUIDE.md b/backend/migrations/MIGRATION_GUIDE.md new file mode 100644 index 0000000..65604ca --- /dev/null +++ b/backend/migrations/MIGRATION_GUIDE.md @@ -0,0 +1,243 @@ +# Multi-Household Architecture Migration Guide + +## Pre-Migration Checklist + +- [ ] **Backup Database** + ```bash + pg_dump -U your_user -d grocery_list > backup_$(date +%Y%m%d_%H%M%S).sql + ``` + +- [ ] **Test on Staging First** + - Copy production database to staging environment + - Run migration on staging + - Verify all data migrated correctly + - Test application functionality + +- [ ] **Review Migration Script** + - Read through `multi_household_architecture.sql` + - Understand each step + - Note verification queries + +- [ ] **Announce Maintenance Window** + - Notify users of downtime + - Schedule during low-usage period + - Estimate 15-30 minutes for migration + +## Running the Migration + +### 1. Connect to Database + +```bash +psql -U your_user -d grocery_list +``` + +### 2. Run Migration + +```sql +\i backend/migrations/multi_household_architecture.sql +``` + +The script will: +1. ✅ Create 8 new tables +2. ✅ Create default "Main Household" +3. ✅ Create default "Costco" store +4. ✅ Migrate all users to household members +5. ✅ Extract items to master catalog +6. ✅ Migrate grocery_list → household_lists +7. ✅ Migrate classifications +8. ✅ Migrate history records +9. ✅ Update user system roles + +### 3. Verify Migration + +Run these queries inside psql: + +```sql +-- Check household created +SELECT * FROM households; + +-- Check all users migrated +SELECT u.username, u.role as system_role, hm.role as household_role +FROM users u +JOIN household_members hm ON u.id = hm.user_id +ORDER BY u.id; + +-- Check item counts match +SELECT + (SELECT COUNT(DISTINCT item_name) FROM grocery_list) as old_unique_items, + (SELECT COUNT(*) FROM items) as new_items; + +-- Check list counts +SELECT + (SELECT COUNT(*) FROM grocery_list) as old_lists, + (SELECT COUNT(*) FROM household_lists) as new_lists; + +-- Check classification counts +SELECT + (SELECT COUNT(*) FROM item_classification) as old_classifications, + (SELECT COUNT(*) FROM household_item_classifications) as new_classifications; + +-- Check history counts +SELECT + (SELECT COUNT(*) FROM grocery_history) as old_history, + (SELECT COUNT(*) FROM household_list_history) as new_history; + +-- Verify no data loss - check if all old items have corresponding new records +SELECT gl.item_name +FROM grocery_list gl +LEFT JOIN items i ON LOWER(i.name) = LOWER(TRIM(gl.item_name)) +LEFT JOIN household_lists hl ON hl.item_id = i.id +WHERE hl.id IS NULL; +-- Should return 0 rows + +-- Check invite code +SELECT name, invite_code FROM households; +``` + +### 4. Test Application + +- [ ] Users can log in +- [ ] Can view "Main Household" list +- [ ] Can add items +- [ ] Can mark items as bought +- [ ] History shows correctly +- [ ] Classifications preserved +- [ ] Images display correctly + +## Post-Migration Cleanup + +**Only after verifying everything works correctly:** + +```sql +-- Drop old tables (CAREFUL - THIS IS IRREVERSIBLE) +DROP TABLE IF EXISTS grocery_history CASCADE; +DROP TABLE IF EXISTS item_classification CASCADE; +DROP TABLE IF EXISTS grocery_list CASCADE; +``` + +## Rollback Plan + +### If Migration Fails + +```sql +-- Inside psql during migration +ROLLBACK; + +-- Then restore from backup +\q +psql -U your_user -d grocery_list < backup_YYYYMMDD_HHMMSS.sql +``` + +### If Issues Found After Migration + +```bash +# Drop the database and restore +dropdb grocery_list +createdb grocery_list +psql -U your_user -d grocery_list < backup_YYYYMMDD_HHMMSS.sql +``` + +## Common Issues & Solutions + +### Issue: Duplicate items in items table +**Cause**: Case-insensitive matching not working +**Solution**: Check item names for leading/trailing spaces + +### Issue: Foreign key constraint errors +**Cause**: User or item references not found +**Solution**: Verify all users and items exist before migrating lists + +### Issue: History not showing +**Cause**: household_list_id references incorrect +**Solution**: Check JOIN conditions in history migration + +### Issue: Images not displaying +**Cause**: BYTEA encoding issues +**Solution**: Verify image_mime_type correctly migrated + +## Migration Timeline + +- **T-0**: Begin maintenance window +- **T+2min**: Backup complete +- **T+3min**: Start migration script +- **T+8min**: Migration complete (for ~1000 items) +- **T+10min**: Run verification queries +- **T+15min**: Test application functionality +- **T+20min**: If successful, announce completion +- **T+30min**: End maintenance window + +## Data Integrity Checks + +```sql +-- Ensure all users belong to at least one household +SELECT u.id, u.username +FROM users u +LEFT JOIN household_members hm ON u.id = hm.user_id +WHERE hm.id IS NULL; +-- Should return 0 rows + +-- Ensure all household lists have valid items +SELECT hl.id +FROM household_lists hl +LEFT JOIN items i ON hl.item_id = i.id +WHERE i.id IS NULL; +-- Should return 0 rows + +-- Ensure all history has valid list references +SELECT hlh.id +FROM household_list_history hlh +LEFT JOIN household_lists hl ON hlh.household_list_id = hl.id +WHERE hl.id IS NULL; +-- Should return 0 rows + +-- Check for orphaned classifications +SELECT hic.id +FROM household_item_classifications hic +LEFT JOIN household_lists hl ON hic.item_id = hl.item_id + AND hic.household_id = hl.household_id + AND hic.store_id = hl.store_id +WHERE hl.id IS NULL; +-- Should return 0 rows (or classifications for removed items, which is ok) +``` + +## Success Criteria + +✅ All tables created successfully +✅ All users migrated to "Main Household" +✅ Item count matches (unique items from old → new) +✅ List count matches (all grocery_list items → household_lists) +✅ Classification count matches +✅ History count matches +✅ No NULL foreign keys +✅ Application loads without errors +✅ Users can perform all CRUD operations +✅ Images display correctly +✅ Bought items still marked as bought +✅ Recently bought still shows correctly + +## Next Steps After Migration + +1. ✅ Update backend models (Sprint 2) +2. ✅ Update API routes +3. ✅ Update controllers +4. ✅ Test all endpoints +5. ✅ Update frontend contexts +6. ✅ Update UI components +7. ✅ Enable multi-household features + +## Support & Troubleshooting + +If issues arise: +1. Check PostgreSQL logs: `/var/log/postgresql/` +2. Check application logs +3. Restore from backup if needed +4. Review migration script for errors + +## Monitoring Post-Migration + +For the first 24 hours after migration: +- Monitor error logs +- Watch for performance issues +- Verify user activity normal +- Check for any data inconsistencies +- Be ready to rollback if critical issues found diff --git a/backend/migrations/backups/backup_20260125_000426.sql b/backend/migrations/backups/backup_20260125_000426.sql new file mode 100644 index 0000000..e69de29 diff --git a/backend/migrations/multi_household_architecture.sql b/backend/migrations/multi_household_architecture.sql new file mode 100644 index 0000000..b64a567 --- /dev/null +++ b/backend/migrations/multi_household_architecture.sql @@ -0,0 +1,397 @@ +-- ============================================================================ +-- Multi-Household & Multi-Store Architecture Migration +-- ============================================================================ +-- This migration transforms the single-list app into a multi-tenant system +-- supporting multiple households, each with multiple stores. +-- +-- IMPORTANT: Backup your database before running this migration! +-- pg_dump grocery_list > backup_$(date +%Y%m%d).sql +-- +-- Migration Strategy: +-- 1. Create new tables +-- 2. Create "Main Household" for existing users +-- 3. Migrate existing data to new structure +-- 4. Update roles (keep users.role for system admin) +-- 5. Verify data integrity +-- 6. (Manual step) Drop old tables after verification +-- ============================================================================ + +BEGIN; + +-- ============================================================================ +-- STEP 1: CREATE NEW TABLES +-- ============================================================================ + +-- Households table +CREATE TABLE IF NOT EXISTS households ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + created_by INTEGER REFERENCES users(id) ON DELETE SET NULL, + invite_code VARCHAR(20) UNIQUE NOT NULL, + code_expires_at TIMESTAMP +); + +CREATE INDEX idx_households_invite_code ON households(invite_code); +COMMENT ON TABLE households IS 'Household groups (families, roommates, etc.)'; +COMMENT ON COLUMN households.invite_code IS 'Unique code for inviting users to join household'; + +-- Store types table +CREATE TABLE IF NOT EXISTS stores ( + id SERIAL PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE, + default_zones JSONB, + created_at TIMESTAMP DEFAULT NOW() +); + +COMMENT ON TABLE stores IS 'Store types/chains (Costco, Target, Walmart, etc.)'; +COMMENT ON COLUMN stores.default_zones IS 'JSON array of default zone names for this store type'; + +-- User-Household membership with per-household roles +CREATE TABLE IF NOT EXISTS household_members ( + id SERIAL PRIMARY KEY, + household_id INTEGER REFERENCES households(id) ON DELETE CASCADE, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'user')), + joined_at TIMESTAMP DEFAULT NOW(), + UNIQUE(household_id, user_id) +); + +CREATE INDEX idx_household_members_user ON household_members(user_id); +CREATE INDEX idx_household_members_household ON household_members(household_id); +COMMENT ON TABLE household_members IS 'User membership in households with per-household roles'; +COMMENT ON COLUMN household_members.role IS 'admin: full control, user: standard member'; + +-- Household-Store relationship +CREATE TABLE IF NOT EXISTS household_stores ( + id SERIAL PRIMARY KEY, + household_id INTEGER REFERENCES households(id) ON DELETE CASCADE, + store_id INTEGER REFERENCES stores(id) ON DELETE CASCADE, + is_default BOOLEAN DEFAULT FALSE, + added_at TIMESTAMP DEFAULT NOW(), + UNIQUE(household_id, store_id) +); + +CREATE INDEX idx_household_stores_household ON household_stores(household_id); +COMMENT ON TABLE household_stores IS 'Which stores each household shops at'; + +-- Master item catalog (shared across all households) +CREATE TABLE IF NOT EXISTS items ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE, + default_image BYTEA, + default_image_mime_type VARCHAR(50), + created_at TIMESTAMP DEFAULT NOW(), + usage_count INTEGER DEFAULT 0 +); + +CREATE INDEX idx_items_name ON items(name); +CREATE INDEX idx_items_usage_count ON items(usage_count DESC); +COMMENT ON TABLE items IS 'Master item catalog shared across all households'; +COMMENT ON COLUMN items.usage_count IS 'Popularity metric for suggestions'; + +-- Household-specific grocery lists (per store) +CREATE TABLE IF NOT EXISTS household_lists ( + id SERIAL PRIMARY KEY, + household_id INTEGER REFERENCES households(id) ON DELETE CASCADE, + store_id INTEGER REFERENCES stores(id) ON DELETE CASCADE, + item_id INTEGER REFERENCES items(id) ON DELETE CASCADE, + quantity INTEGER NOT NULL DEFAULT 1, + bought BOOLEAN DEFAULT FALSE, + custom_image BYTEA, + custom_image_mime_type VARCHAR(50), + added_by INTEGER REFERENCES users(id) ON DELETE SET NULL, + modified_on TIMESTAMP DEFAULT NOW(), + UNIQUE(household_id, store_id, item_id) +); + +CREATE INDEX idx_household_lists_household_store ON household_lists(household_id, store_id); +CREATE INDEX idx_household_lists_bought ON household_lists(household_id, store_id, bought); +CREATE INDEX idx_household_lists_modified ON household_lists(modified_on DESC); +COMMENT ON TABLE household_lists IS 'Grocery lists scoped to household + store combination'; + +-- Household-specific item classifications (per store) +CREATE TABLE IF NOT EXISTS household_item_classifications ( + id SERIAL PRIMARY KEY, + household_id INTEGER REFERENCES households(id) ON DELETE CASCADE, + store_id INTEGER REFERENCES stores(id) ON DELETE CASCADE, + item_id INTEGER REFERENCES items(id) ON DELETE CASCADE, + item_type VARCHAR(50), + item_group VARCHAR(100), + zone VARCHAR(100), + confidence DECIMAL(3,2) DEFAULT 1.0 CHECK (confidence >= 0 AND confidence <= 1), + source VARCHAR(20) DEFAULT 'user' CHECK (source IN ('user', 'ml', 'default')), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + UNIQUE(household_id, store_id, item_id) +); + +CREATE INDEX idx_household_classifications ON household_item_classifications(household_id, store_id); +CREATE INDEX idx_household_classifications_type ON household_item_classifications(item_type); +CREATE INDEX idx_household_classifications_zone ON household_item_classifications(zone); +COMMENT ON TABLE household_item_classifications IS 'Item classifications scoped to household + store'; + +-- History tracking +CREATE TABLE IF NOT EXISTS household_list_history ( + id SERIAL PRIMARY KEY, + household_list_id INTEGER REFERENCES household_lists(id) ON DELETE CASCADE, + quantity INTEGER NOT NULL, + added_by INTEGER REFERENCES users(id) ON DELETE SET NULL, + added_on TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_household_history_list ON household_list_history(household_list_id); +CREATE INDEX idx_household_history_user ON household_list_history(added_by); +CREATE INDEX idx_household_history_date ON household_list_history(added_on DESC); +COMMENT ON TABLE household_list_history IS 'Tracks who added items and when'; + +-- ============================================================================ +-- STEP 2: CREATE DEFAULT HOUSEHOLD AND STORE +-- ============================================================================ + +-- Create default household for existing users +INSERT INTO households (name, created_by, invite_code) +SELECT + 'Main Household', + (SELECT id FROM users WHERE role = 'admin' LIMIT 1), -- First admin as creator + 'MAIN' || LPAD(FLOOR(RANDOM() * 1000000)::TEXT, 6, '0') -- Random 6-digit code +WHERE NOT EXISTS (SELECT 1 FROM households WHERE name = 'Main Household'); + +-- Create default Costco store +INSERT INTO stores (name, default_zones) +VALUES ( + 'Costco', + '{ + "zones": [ + "Entrance & Seasonal", + "Fresh Produce", + "Meat & Seafood", + "Dairy & Refrigerated", + "Deli & Prepared Foods", + "Bakery & Bread", + "Frozen Foods", + "Beverages", + "Snacks & Candy", + "Pantry & Dry Goods", + "Health & Beauty", + "Household & Cleaning", + "Other" + ] + }'::jsonb +) +ON CONFLICT (name) DO NOTHING; + +-- Link default household to default store +INSERT INTO household_stores (household_id, store_id, is_default) +SELECT + (SELECT id FROM households WHERE name = 'Main Household'), + (SELECT id FROM stores WHERE name = 'Costco'), + TRUE +WHERE NOT EXISTS ( + SELECT 1 FROM household_stores + WHERE household_id = (SELECT id FROM households WHERE name = 'Main Household') +); + +-- ============================================================================ +-- STEP 3: MIGRATE USERS TO HOUSEHOLD MEMBERS +-- ============================================================================ + +-- Add all existing users to Main Household +-- Old admins become household admins, others become standard users +INSERT INTO household_members (household_id, user_id, role) +SELECT + (SELECT id FROM households WHERE name = 'Main Household'), + id, + CASE + WHEN role = 'admin' THEN 'admin' + ELSE 'user' + END +FROM users +WHERE NOT EXISTS ( + SELECT 1 FROM household_members hm + WHERE hm.user_id = users.id + AND hm.household_id = (SELECT id FROM households WHERE name = 'Main Household') +); + +-- ============================================================================ +-- STEP 4: MIGRATE ITEMS TO MASTER CATALOG +-- ============================================================================ + +-- Extract unique items from grocery_list into master items table +INSERT INTO items (name, default_image, default_image_mime_type, created_at, usage_count) +SELECT + LOWER(TRIM(item_name)) as name, + item_image, + image_mime_type, + MIN(modified_on) as created_at, + COUNT(*) as usage_count +FROM grocery_list +WHERE NOT EXISTS ( + SELECT 1 FROM items WHERE LOWER(items.name) = LOWER(TRIM(grocery_list.item_name)) +) +GROUP BY LOWER(TRIM(item_name)), item_image, image_mime_type +ON CONFLICT (name) DO NOTHING; + +-- ============================================================================ +-- STEP 5: MIGRATE GROCERY_LIST TO HOUSEHOLD_LISTS +-- ============================================================================ + +-- Migrate current list to household_lists +INSERT INTO household_lists ( + household_id, + store_id, + item_id, + quantity, + bought, + custom_image, + custom_image_mime_type, + added_by, + modified_on +) +SELECT + (SELECT id FROM households WHERE name = 'Main Household'), + (SELECT id FROM stores WHERE name = 'Costco'), + i.id, + gl.quantity, + gl.bought, + CASE WHEN gl.item_image != i.default_image THEN gl.item_image ELSE NULL END, -- Only store if different + CASE WHEN gl.item_image != i.default_image THEN gl.image_mime_type ELSE NULL END, + gl.added_by, + gl.modified_on +FROM grocery_list gl +JOIN items i ON LOWER(i.name) = LOWER(TRIM(gl.item_name)) +WHERE NOT EXISTS ( + SELECT 1 FROM household_lists hl + WHERE hl.household_id = (SELECT id FROM households WHERE name = 'Main Household') + AND hl.store_id = (SELECT id FROM stores WHERE name = 'Costco') + AND hl.item_id = i.id +) +ON CONFLICT (household_id, store_id, item_id) DO NOTHING; + +-- ============================================================================ +-- STEP 6: MIGRATE ITEM_CLASSIFICATION TO HOUSEHOLD_ITEM_CLASSIFICATIONS +-- ============================================================================ + +-- Migrate classifications +INSERT INTO household_item_classifications ( + household_id, + store_id, + item_id, + item_type, + item_group, + zone, + confidence, + source, + created_at, + updated_at +) +SELECT + (SELECT id FROM households WHERE name = 'Main Household'), + (SELECT id FROM stores WHERE name = 'Costco'), + i.id, + ic.item_type, + ic.item_group, + ic.zone, + ic.confidence, + ic.source, + ic.created_at, + ic.updated_at +FROM item_classification ic +JOIN grocery_list gl ON ic.id = gl.id +JOIN items i ON LOWER(i.name) = LOWER(TRIM(gl.item_name)) +WHERE NOT EXISTS ( + SELECT 1 FROM household_item_classifications hic + WHERE hic.household_id = (SELECT id FROM households WHERE name = 'Main Household') + AND hic.store_id = (SELECT id FROM stores WHERE name = 'Costco') + AND hic.item_id = i.id +) +ON CONFLICT (household_id, store_id, item_id) DO NOTHING; + +-- ============================================================================ +-- STEP 7: MIGRATE GROCERY_HISTORY TO HOUSEHOLD_LIST_HISTORY +-- ============================================================================ + +-- Migrate history records +INSERT INTO household_list_history (household_list_id, quantity, added_by, added_on) +SELECT + hl.id, + gh.quantity, + gh.added_by, + gh.added_on +FROM grocery_history gh +JOIN grocery_list gl ON gh.list_item_id = gl.id +JOIN items i ON LOWER(i.name) = LOWER(TRIM(gl.item_name)) +JOIN household_lists hl ON hl.item_id = i.id + AND hl.household_id = (SELECT id FROM households WHERE name = 'Main Household') + AND hl.store_id = (SELECT id FROM stores WHERE name = 'Costco') +WHERE NOT EXISTS ( + SELECT 1 FROM household_list_history hlh + WHERE hlh.household_list_id = hl.id + AND hlh.added_by = gh.added_by + AND hlh.added_on = gh.added_on +); + +-- ============================================================================ +-- STEP 8: UPDATE USER ROLES (SYSTEM-WIDE) +-- ============================================================================ + +-- Update system roles: admin → system_admin, others → user +UPDATE users +SET role = 'system_admin' +WHERE role = 'admin'; + +UPDATE users +SET role = 'user' +WHERE role IN ('editor', 'viewer'); + +-- ============================================================================ +-- VERIFICATION QUERIES +-- ============================================================================ + +-- Run these to verify migration success: + +-- Check household created +-- SELECT * FROM households; + +-- Check all users added to household +-- SELECT u.username, u.role as system_role, hm.role as household_role +-- FROM users u +-- JOIN household_members hm ON u.id = hm.user_id +-- ORDER BY u.id; + +-- Check items migrated +-- SELECT COUNT(*) as total_items FROM items; +-- SELECT COUNT(*) as original_items FROM (SELECT DISTINCT item_name FROM grocery_list) sub; + +-- Check lists migrated +-- SELECT COUNT(*) as new_lists FROM household_lists; +-- SELECT COUNT(*) as old_lists FROM grocery_list; + +-- Check classifications migrated +-- SELECT COUNT(*) as new_classifications FROM household_item_classifications; +-- SELECT COUNT(*) as old_classifications FROM item_classification; + +-- Check history migrated +-- SELECT COUNT(*) as new_history FROM household_list_history; +-- SELECT COUNT(*) as old_history FROM grocery_history; + +-- ============================================================================ +-- MANUAL STEPS AFTER VERIFICATION +-- ============================================================================ + +-- After verifying data integrity, uncomment and run these to clean up: + +-- DROP TABLE IF EXISTS grocery_history CASCADE; +-- DROP TABLE IF EXISTS item_classification CASCADE; +-- DROP TABLE IF EXISTS grocery_list CASCADE; + +COMMIT; + +-- ============================================================================ +-- ROLLBACK (if something goes wrong) +-- ============================================================================ + +-- ROLLBACK; + +-- Then restore from backup: +-- psql -U your_user -d grocery_list < backup_YYYYMMDD.sql diff --git a/run-migration.bat b/run-migration.bat new file mode 100644 index 0000000..3b405e3 --- /dev/null +++ b/run-migration.bat @@ -0,0 +1,80 @@ +@echo off +REM Multi-Household Migration Runner (Windows) +REM This script handles the complete migration process with safety checks + +setlocal enabledelayedexpansion + +REM Database configuration +set DB_USER=postgres +set DB_HOST=192.168.7.112 +set DB_NAME=grocery +set PGPASSWORD=Asdwed123A. + +set BACKUP_DIR=backend\migrations\backups +set TIMESTAMP=%date:~-4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2%%time:~6,2% +set TIMESTAMP=%TIMESTAMP: =0% +set BACKUP_FILE=%BACKUP_DIR%\backup_%TIMESTAMP%.sql + +echo ================================================ +echo Multi-Household Architecture Migration +echo ================================================ +echo. + +REM Create backup directory +if not exist "%BACKUP_DIR%" mkdir "%BACKUP_DIR%" + +REM Step 1: Backup (SKIPPED - using database template copy) +echo [1/5] Backup: SKIPPED (using 'grocery' database copy) +echo. + +REM Step 2: Show current stats +echo [2/5] Current database statistics: +psql -h %DB_HOST% -U %DB_USER% -d %DB_NAME% -c "SELECT 'Users' as table_name, COUNT(*) as count FROM users UNION ALL SELECT 'Grocery Items', COUNT(*) FROM grocery_list UNION ALL SELECT 'Classifications', COUNT(*) FROM item_classification UNION ALL SELECT 'History Records', COUNT(*) FROM grocery_history;" +echo. + +REM Step 3: Confirm +echo [3/5] Ready to run migration +echo Database: %DB_NAME% on %DB_HOST% +echo Backup: %BACKUP_FILE% +echo. +set /p CONFIRM="Continue with migration? (yes/no): " +if /i not "%CONFIRM%"=="yes" ( + echo Migration cancelled. + exit /b 0 +) +echo. + +REM Step 4: Run migration +echo [4/5] Running migration script... +psql -h %DB_HOST% -U %DB_USER% -d %DB_NAME% -f backend\migrations\multi_household_architecture.sql +if %errorlevel% neq 0 ( + echo [ERROR] Migration failed! Rolling back... + echo Restoring from backup: %BACKUP_FILE% + psql -h %DB_HOST% -U %DB_USER% -d %DB_NAME% < "%BACKUP_FILE%" + exit /b 1 +) +echo [OK] Migration completed successfully +echo. + +REM Step 5: Verification +echo [5/5] Verifying migration... +psql -h %DB_HOST% -U %DB_USER% -d %DB_NAME% -c "SELECT id, name, invite_code FROM households;" +psql -h %DB_HOST% -U %DB_USER% -d %DB_NAME% -c "SELECT u.id, u.username, u.role as system_role, hm.role as household_role FROM users u LEFT JOIN household_members hm ON u.id = hm.user_id ORDER BY u.id LIMIT 10;" +psql -h %DB_HOST% -U %DB_USER% -d %DB_NAME% -c "SELECT 'Items' as metric, COUNT(*)::text as count FROM items UNION ALL SELECT 'Household Lists', COUNT(*)::text FROM household_lists UNION ALL SELECT 'Classifications', COUNT(*)::text FROM household_item_classifications UNION ALL SELECT 'History Records', COUNT(*)::text FROM household_list_history;" +echo. + +echo ================================================ +echo Migration Complete! +echo ================================================ +echo. +echo Next Steps: +echo 1. Review verification results above +echo 2. Test the application +echo 3. If issues found, rollback with: +echo psql -h %DB_HOST% -U %DB_USER% -d %DB_NAME% ^< %BACKUP_FILE% +echo 4. If successful, proceed to Sprint 2 (Backend API) +echo. +echo Backup location: %BACKUP_FILE% +echo. + +pause diff --git a/run-migration.sh b/run-migration.sh new file mode 100644 index 0000000..8089521 --- /dev/null +++ b/run-migration.sh @@ -0,0 +1,146 @@ +#!/bin/bash + +# Multi-Household Migration Runner +# This script handles the complete migration process with safety checks + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Database configuration (from .env) +DB_USER="postgres" +DB_HOST="192.168.7.112" +DB_NAME="grocery" +export PGPASSWORD="Asdwed123A." + +BACKUP_DIR="./backend/migrations/backups" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +BACKUP_FILE="${BACKUP_DIR}/backup_${TIMESTAMP}.sql" + +echo -e "${BLUE}╔════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ Multi-Household Architecture Migration ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════╝${NC}" +echo "" + +# Create backup directory if it doesn't exist +mkdir -p "$BACKUP_DIR" + +# Step 1: Backup +echo -e "${YELLOW}[1/5] Creating database backup...${NC}" +pg_dump -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" > "$BACKUP_FILE" +if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Backup created: $BACKUP_FILE${NC}" +else + echo -e "${RED}✗ Backup failed!${NC}" + exit 1 +fi +echo "" + +# Step 2: Show current stats +echo -e "${YELLOW}[2/5] Current database statistics:${NC}" +psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c " +SELECT + 'Users' as table_name, COUNT(*) as count FROM users +UNION ALL +SELECT 'Grocery Items', COUNT(*) FROM grocery_list +UNION ALL +SELECT 'Classifications', COUNT(*) FROM item_classification +UNION ALL +SELECT 'History Records', COUNT(*) FROM grocery_history; +" +echo "" + +# Step 3: Confirm +echo -e "${YELLOW}[3/5] Ready to run migration${NC}" +echo -e "Database: ${BLUE}$DB_NAME${NC} on ${BLUE}$DB_HOST${NC}" +echo -e "Backup: ${GREEN}$BACKUP_FILE${NC}" +echo "" +read -p "Continue with migration? (yes/no): " -r +echo "" +if [[ ! $REPLY =~ ^[Yy]es$ ]]; then + echo -e "${RED}Migration cancelled.${NC}" + exit 1 +fi + +# Step 4: Run migration +echo -e "${YELLOW}[4/5] Running migration script...${NC}" +psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -f backend/migrations/multi_household_architecture.sql +if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Migration completed successfully${NC}" +else + echo -e "${RED}✗ Migration failed! Rolling back...${NC}" + echo -e "${YELLOW}Restoring from backup: $BACKUP_FILE${NC}" + psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" < "$BACKUP_FILE" + exit 1 +fi +echo "" + +# Step 5: Verification +echo -e "${YELLOW}[5/5] Verifying migration...${NC}" +psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" << 'EOF' +\echo '' +\echo '=== Household Created ===' +SELECT id, name, invite_code FROM households; + +\echo '' +\echo '=== User Roles ===' +SELECT u.id, u.username, u.role as system_role, hm.role as household_role +FROM users u +LEFT JOIN household_members hm ON u.id = hm.user_id +ORDER BY u.id +LIMIT 10; + +\echo '' +\echo '=== Migration Counts ===' +SELECT + 'Items (Master Catalog)' as metric, COUNT(*)::text as count FROM items +UNION ALL +SELECT 'Household Lists', COUNT(*)::text FROM household_lists +UNION ALL +SELECT 'Classifications', COUNT(*)::text FROM household_item_classifications +UNION ALL +SELECT 'History Records', COUNT(*)::text FROM household_list_history +UNION ALL +SELECT 'Household Members', COUNT(*)::text FROM household_members +UNION ALL +SELECT 'Stores', COUNT(*)::text FROM stores; + +\echo '' +\echo '=== Data Integrity Checks ===' +\echo 'Users without household membership (should be 0):' +SELECT COUNT(*) FROM users u +LEFT JOIN household_members hm ON u.id = hm.user_id +WHERE hm.id IS NULL; + +\echo '' +\echo 'Lists without valid items (should be 0):' +SELECT COUNT(*) FROM household_lists hl +LEFT JOIN items i ON hl.item_id = i.id +WHERE i.id IS NULL; + +\echo '' +\echo 'History without valid lists (should be 0):' +SELECT COUNT(*) FROM household_list_history hlh +LEFT JOIN household_lists hl ON hlh.household_list_id = hl.id +WHERE hl.id IS NULL; +EOF + +echo "" +echo -e "${GREEN}╔════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ Migration Complete! ║${NC}" +echo -e "${GREEN}╚════════════════════════════════════════════════╝${NC}" +echo "" +echo -e "${BLUE}Next Steps:${NC}" +echo -e "1. Review verification results above" +echo -e "2. Test the application" +echo -e "3. If issues found, rollback with:" +echo -e " ${YELLOW}psql -h $DB_HOST -U $DB_USER -d $DB_NAME < $BACKUP_FILE${NC}" +echo -e "4. If successful, proceed to Sprint 2 (Backend API)" +echo "" +echo -e "${YELLOW}Backup location: $BACKUP_FILE${NC}" +echo ""