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