phase1 - implement database foundation
This commit is contained in:
parent
fc887bdc65
commit
ccf0c39294
203
IMPLEMENTATION_STATUS.md
Normal file
203
IMPLEMENTATION_STATUS.md
Normal 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
|
||||||
243
backend/migrations/MIGRATION_GUIDE.md
Normal file
243
backend/migrations/MIGRATION_GUIDE.md
Normal 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
|
||||||
397
backend/migrations/multi_household_architecture.sql
Normal file
397
backend/migrations/multi_household_architecture.sql
Normal 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
80
run-migration.bat
Normal 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
146
run-migration.sh
Normal 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 ""
|
||||||
Loading…
Reference in New Issue
Block a user