phase1 - implement database foundation

This commit is contained in:
Nico 2026-01-25 00:18:04 -08:00
parent fc887bdc65
commit ccf0c39294
6 changed files with 1069 additions and 0 deletions

203
IMPLEMENTATION_STATUS.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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

80
run-migration.bat Normal file
View File

@ -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

146
run-migration.sh Normal file
View File

@ -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 ""