create plan for multi household
This commit is contained in:
parent
1281c91c28
commit
fc887bdc65
865
docs/multi-household-architecture-plan.md
Normal file
865
docs/multi-household-architecture-plan.md
Normal file
@ -0,0 +1,865 @@
|
|||||||
|
# Multi-Household & Multi-Store Architecture Plan
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This document outlines the architecture and implementation strategy for extending the application to support:
|
||||||
|
1. **Multiple Households** - Users can belong to multiple households (families, roommates, etc.)
|
||||||
|
2. **Multiple Stores** - Households can manage lists for different store types (Costco, Target, Walmart, etc.)
|
||||||
|
|
||||||
|
## Current Architecture Analysis
|
||||||
|
|
||||||
|
### Existing Schema
|
||||||
|
```sql
|
||||||
|
users (id, username, password, name, role, display_name)
|
||||||
|
grocery_list (id, item_name, quantity, bought, item_image, image_mime_type, added_by, modified_on)
|
||||||
|
grocery_history (id, list_item_id, quantity, added_by, added_on)
|
||||||
|
item_classification (id, item_type, item_group, zone, confidence, source)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Current Limitations
|
||||||
|
- **Single global list** - All users share one grocery list
|
||||||
|
- **No household concept** - Cannot separate different families' items
|
||||||
|
- **Store-specific zones** - Classification system assumes Costco layout
|
||||||
|
- **Single-level roles** - User has same role everywhere (cannot be admin in one household, viewer in another)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design Considerations & Trade-offs
|
||||||
|
|
||||||
|
### Key Questions to Resolve
|
||||||
|
|
||||||
|
#### 1. Item Management Strategy
|
||||||
|
|
||||||
|
**Option A: Shared Item Master (Recommended)**
|
||||||
|
- ✅ **Pro**: Single source of truth for item definitions (name, default image, common classification)
|
||||||
|
- ✅ **Pro**: Consistent item naming across households
|
||||||
|
- ✅ **Pro**: Can build item recommendation system across all households
|
||||||
|
- ✅ **Pro**: Easier to implement smart features (price tracking, common items)
|
||||||
|
- ❌ **Con**: Requires careful privacy controls (who can see which items)
|
||||||
|
- ❌ **Con**: Different households may classify items differently
|
||||||
|
|
||||||
|
**Option B: Per-Household Items**
|
||||||
|
- ✅ **Pro**: Complete household isolation
|
||||||
|
- ✅ **Pro**: Each household fully controls item definitions
|
||||||
|
- ✅ **Pro**: No privacy concerns about item names
|
||||||
|
- ❌ **Con**: Duplicate data across households
|
||||||
|
- ❌ **Con**: Cannot leverage cross-household intelligence
|
||||||
|
- ❌ **Con**: More complex to implement suggestions
|
||||||
|
|
||||||
|
**Option C: Hybrid Approach (RECOMMENDED)**
|
||||||
|
- ✅ **Pro**: Best of both worlds
|
||||||
|
- ✅ **Pro**: Shared item catalog with household-specific classifications
|
||||||
|
- ✅ **Pro**: Privacy-preserving (only households share item usage, not personal data)
|
||||||
|
- **How it works**:
|
||||||
|
- Global `items` table (id, name, default_image, created_at)
|
||||||
|
- Household-specific `household_list` table references item + household
|
||||||
|
- Each household can override classifications per store
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Proposed Schema Design
|
||||||
|
|
||||||
|
### New Tables
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Households (e.g., "Smith Family", "Apartment 5B")
|
||||||
|
CREATE TABLE households (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
created_by INTEGER REFERENCES users(id),
|
||||||
|
invite_code VARCHAR(20) UNIQUE NOT NULL, -- Random code for inviting users
|
||||||
|
code_expires_at TIMESTAMP -- Optional expiration
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Store Types (e.g., "Costco", "Target", "Walmart")
|
||||||
|
CREATE TABLE stores (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
default_zones JSONB, -- Store-specific zone layout
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- User-Household Membership with per-household roles
|
||||||
|
CREATE TABLE 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)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Household-Store Relationship (which stores does this household shop at?)
|
||||||
|
CREATE TABLE 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, -- Default store for this household
|
||||||
|
UNIQUE(household_id, store_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Master Item Catalog (shared across all households)
|
||||||
|
CREATE TABLE 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 -- For popularity tracking
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Household-specific grocery lists (per store)
|
||||||
|
CREATE TABLE 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, -- Household can override item image
|
||||||
|
custom_image_mime_type VARCHAR(50),
|
||||||
|
added_by INTEGER REFERENCES users(id),
|
||||||
|
modified_on TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE(household_id, store_id, item_id) -- One item per household+store combo
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Household-specific item classifications (per store)
|
||||||
|
CREATE TABLE 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,
|
||||||
|
source VARCHAR(20) DEFAULT 'user',
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
UNIQUE(household_id, store_id, item_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- History tracking (who added what, when, to which household+store list)
|
||||||
|
CREATE TABLE 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),
|
||||||
|
added_on TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Indexes for Performance
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Household member lookups
|
||||||
|
CREATE INDEX idx_household_members_user ON household_members(user_id);
|
||||||
|
CREATE INDEX idx_household_members_household ON household_members(household_id);
|
||||||
|
|
||||||
|
-- List queries (most common operations)
|
||||||
|
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);
|
||||||
|
|
||||||
|
-- Item search
|
||||||
|
CREATE INDEX idx_items_name ON items(name);
|
||||||
|
CREATE INDEX idx_items_usage_count ON items(usage_count DESC);
|
||||||
|
|
||||||
|
-- Classification lookups
|
||||||
|
CREATE INDEX idx_household_classifications ON household_item_classifications(household_id, store_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Role System Redesign
|
||||||
|
|
||||||
|
### Dual-Role Hierarchy: System-Wide + Household-Scoped
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// System-wide roles (app administration)
|
||||||
|
users {
|
||||||
|
id, username, password, name, display_name,
|
||||||
|
role: 'system_admin' | 'user' // Kept for app-wide controls
|
||||||
|
}
|
||||||
|
|
||||||
|
// Household-scoped roles (per-household permissions)
|
||||||
|
household_members {
|
||||||
|
household_id, user_id,
|
||||||
|
role: 'admin' | 'user'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### System-Wide Role Definitions
|
||||||
|
|
||||||
|
| Role | Permissions |
|
||||||
|
|------|-------------|
|
||||||
|
| **system_admin** | Create/delete stores globally, view all households (moderation), manage global item catalog, access system metrics, promote users to system_admin |
|
||||||
|
| **user** | Standard user - can create households, join households via invite, manage own profile |
|
||||||
|
|
||||||
|
### Household-Scoped Role Definitions
|
||||||
|
|
||||||
|
| Role | Permissions |
|
||||||
|
|------|-------------|
|
||||||
|
| **admin** | Full household control: delete household, invite/remove members, change member roles, manage stores, add/edit/delete items, mark bought, upload images, update classifications |
|
||||||
|
| **user** | Standard member: add/edit/delete items, mark bought, upload images, update classifications, view all lists |
|
||||||
|
|
||||||
|
### Role Transition Plan
|
||||||
|
|
||||||
|
**Migration Strategy:**
|
||||||
|
1. Create default household "Main Household"
|
||||||
|
2. Migrate all existing users → household_members (old admins become household admins, others become users)
|
||||||
|
3. Keep existing `users.role` column, update values:
|
||||||
|
- `admin` → `system_admin` (app-wide admin)
|
||||||
|
- `editor` → `user` (standard user)
|
||||||
|
- `viewer` → `user` (standard user)
|
||||||
|
4. Migrate grocery_list → household_lists (all to default household + default store)
|
||||||
|
5. Migrate item_classification → household_item_classifications
|
||||||
|
|
||||||
|
---
|
||||||
|
, systemRole } // System-wide role
|
||||||
|
req.household = { id, name, role } // Household-scoped role
|
||||||
|
req.store = { id, name } // Active store context
|
||||||
|
### Authentication Context
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```javascript
|
||||||
|
req.user = { id, username, role }
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```javascript
|
||||||
|
req.user = { id, username }
|
||||||
|
req.household = { id, name, role } // Set by household middleware
|
||||||
|
req.store = { id, name } // Set by store middleware
|
||||||
|
```
|
||||||
|
|
||||||
|
### Middleware Chain with systemRole)
|
||||||
|
router.use(auth);
|
||||||
|
|
||||||
|
// 2. Household middleware (validates household access, sets req.household with householdRole)
|
||||||
|
router.use('/households/:householdId', householdAccess);
|
||||||
|
|
||||||
|
// 3. Household role middleware (checks household-scoped permissions)
|
||||||
|
router.post('/add', requireHouseholdRole(['user', 'admin']), controller.addItem);
|
||||||
|
|
||||||
|
// 4. Admin-only household operations
|
||||||
|
router.delete('/:id', requireHouseholdRole(['admin']), controller.deleteHousehold);
|
||||||
|
|
||||||
|
// 5. System admin middleware (for app-wide operations)
|
||||||
|
router.post('/stores', requireSystemRole('system_admin'), controller.createStore
|
||||||
|
|
||||||
|
// 3. Role middleware (checks household-specific role)
|
||||||
|
rouSystem Administration (system_admin only)
|
||||||
|
GET /api/admin/stores // Manage all stores
|
||||||
|
POST /api/admin/stores // Create new store type
|
||||||
|
PATCH /api/admin/stores/:id // Update store
|
||||||
|
DELETE /api/admin/stores/:id // Delete store (if unused)
|
||||||
|
GET /api/admin/households // View all households (moderation)
|
||||||
|
GET /api/admin/items // Manage global item catalog
|
||||||
|
GET /api/admin/metrics // System-wide analytics
|
||||||
|
|
||||||
|
// Household Management (any user can create)
|
||||||
|
GET /api/households // Get all households user belongs to
|
||||||
|
POST /api/households // Create new household (any user)
|
||||||
|
GET /api/households/:id // Get household details
|
||||||
|
PATCH /api/households/:id // Update household (admin only)
|
||||||
|
DELETE /api/households/:id // Delete household (admin only)
|
||||||
|
|
||||||
|
// Household Members
|
||||||
|
GET /api/households/:id/members // List members (all roles)
|
||||||
|
POST /api/households/:id/invite // Generate/refresh invite code (admin only)
|
||||||
|
POST /api/households/join/:inviteCode // Join household via invite code (joins as 'user')
|
||||||
|
PATCH /api/households/:id/members/:userId // Update member role (admin only)
|
||||||
|
DELETE /api/households/:id/members/:userId // Remove member (admin only, or self)
|
||||||
|
|
||||||
|
// Store Management
|
||||||
|
GET /api/stores // Get all available store types
|
||||||
|
GET /api/households/:id/stores // Get stores for household
|
||||||
|
POST /api/households/:id/stores // Add store to household (admin only)
|
||||||
|
DELETE /api/households/:id/stores/:storeId // Remove store from household (admin only)
|
||||||
|
// Store Management
|
||||||
|
GET /api/stores // Get all available stores
|
||||||
|
POST /api/stores // Create custom store (system admin)
|
||||||
|
GET /api/households/:id/stores // Get stores for household
|
||||||
|
POST /api/households/:id/stores // Add store to household (admin+)
|
||||||
|
DELETE /api/households/:id/stores/:storeId // Remove store (admin+)
|
||||||
|
|
||||||
|
// List Operations (now scoped to household + store)
|
||||||
|
GET /api/households/:hId/stores/:sId/list // Get list
|
||||||
|
POST /api/households/:hId/stores/:sId/list/add // Add item
|
||||||
|
PATCH /api/households/:hId/stores/:sId/list/:itemId // Update item
|
||||||
|
DELETE /api/households/:hId/stores/:sId/list/:itemId // Delete item
|
||||||
|
POST /api/households/:hId/stores/:sId/list/:itemId/buy // Mark bought
|
||||||
|
|
||||||
|
// Item Suggestions (across user's households)
|
||||||
|
GET /api/items/suggestions?q=milk // Search master catalog
|
||||||
|
|
||||||
|
// Classifications (per household + store)
|
||||||
|
GET /api/households/:hId/stores/:sId/classifications/:itemId
|
||||||
|
POST /api/households/:hId/stores/:sId/classifications/:itemId
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## React Context Refactoring Pattern
|
||||||
|
|
||||||
|
### Current Pattern (To Be Replaced)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// Bad: Context is exported, consumers use it directly
|
||||||
|
export const AuthContext = createContext(null);
|
||||||
|
|
||||||
|
export function AuthProvider({ children }) {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ user, setUser }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumer must import context and useContext
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { AuthContext } from '../context/AuthContext';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const { user, setUser } = useContext(AuthContext);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New Pattern (Best Practice)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// Good: Context is internal, custom hook is exported
|
||||||
|
const AuthContext = createContext(null); // Not exported!
|
||||||
|
|
||||||
|
export function AuthProvider({ children }) {
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
const [token, setToken] = useState(null);
|
||||||
|
|
||||||
|
const login = (userData, authToken) => {
|
||||||
|
setUser(userData);
|
||||||
|
setToken(authToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
setUser(null);
|
||||||
|
setToken(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ user, token, login, logout }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export custom hook instead
|
||||||
|
export function useAuth() {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useAuth must be used within AuthProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumer usage - clean and simple
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const { user, login, logout } = useAuth();
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
1. **Encapsulation** - Context implementation is hidden, only the hook is public API
|
||||||
|
2. **Type Safety** - Can add TypeScript types to the hook return value
|
||||||
|
3. **Validation** - Hook can check if used within provider (prevents null errors)
|
||||||
|
4. **Cleaner Imports** - One import instead of two (`useContext` + `Context`)
|
||||||
|
5. **Easier Refactoring** - Can change context internals without affecting consumers
|
||||||
|
6. **Standard Pattern** - Aligns with React best practices and popular libraries
|
||||||
|
|
||||||
|
### Implementation Plan
|
||||||
|
|
||||||
|
**Existing Contexts to Refactor:**
|
||||||
|
- `AuthContext` → `useAuth()`
|
||||||
|
- `SettingsContext` → `useSettings()`
|
||||||
|
- `ConfigContext` → `useConfig()` (if still used)
|
||||||
|
|
||||||
|
**New Contexts to Create:**
|
||||||
|
- `HouseholdContext` → `useHousehold()`
|
||||||
|
- `StoreContext` → `useStore()`
|
||||||
|
|
||||||
|
**Migration Steps:**
|
||||||
|
1. Keep old context export temporarily
|
||||||
|
2. Add custom hook export
|
||||||
|
3. Update all components to use hook
|
||||||
|
4. Remove old context export
|
||||||
|
5. Make context `const` internal to file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Frontend Architecture Changes
|
||||||
|
|
||||||
|
### Context Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// AuthContext - User identity
|
||||||
|
{
|
||||||
|
user: { id, username, display_name, systemRole },
|
||||||
|
token: string,
|
||||||
|
login, logout,
|
||||||
|
isSystemAdmin: boolean // Computed from systemRole
|
||||||
|
}
|
||||||
|
|
||||||
|
// HouseholdContext - Active household + household role
|
||||||
|
{
|
||||||
|
activeHousehold: { id, name, role }, // role is 'admin' or 'user'
|
||||||
|
households: Household[],
|
||||||
|
switchHousehold: (id) => void,
|
||||||
|
createHousehold: (name) => void,
|
||||||
|
joinHousehold: (code) => void,
|
||||||
|
isAdmin: boolean // Computed helper: role === 'admin'
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreContext - Active store
|
||||||
|
{
|
||||||
|
activeStore: { id, name },
|
||||||
|
householdStores: Store[],
|
||||||
|
allStores: Store[], // Available store types (for adding)
|
||||||
|
switchStore: (id) => void,
|
||||||
|
addStore: (storeId) => void // Admin+ onlyme, role },
|
||||||
|
households: Household[],
|
||||||
|
switchHousehold: (id) => void,
|
||||||
|
createHousehold: (name) => void,
|
||||||
|
joinHousehold: (code) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreContext - Active store
|
||||||
|
{
|
||||||
|
/admin → System admin panel (system_admin only)
|
||||||
|
/admin/stores → Manage store types
|
||||||
|
/admin/households → View all households
|
||||||
|
/admin/items → Global item catalog
|
||||||
|
activeStore: { id, name },
|
||||||
|
householdStores: Store[],
|
||||||
|
switchStore: (id) => void
|
||||||
|
} (Owner)</option>
|
||||||
|
<option value={2}>Work Team (Editor)</option>
|
||||||
|
<option value={3}>Apartment 5B (Viewer)</option>
|
||||||
|
<option>+ Create New Household</option>
|
||||||
|
{user.systemRole === 'system_admin' && (
|
||||||
|
<option>⚙️ System Admin</option>
|
||||||
|
)}
|
||||||
|
</HouseholdDropdown>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Store Tabs** (Within Household)
|
||||||
|
```tsx
|
||||||
|
<StoreTabs householdId={activeHousehold.id}>
|
||||||
|
<Tab active>Costco</Tab>
|
||||||
|
<Tab>Target</Tab>
|
||||||
|
<Tab>Walmart</Tab>
|
||||||
|
{(isAdmin || isOwner) && <Tab>+ Add Store</Tab>} → User settings (personal)
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI Components
|
||||||
|
|
||||||
|
**Household Switcher** (Navbar)
|
||||||
|
```tsx
|
||||||
|
<HouseholdDropdown>
|
||||||
|
<option value={1}>Smith Family</option>
|
||||||
|
<option value={2}>Work Team</option>
|
||||||
|
<option value={3}>Apartment 5B</option>
|
||||||
|
<option>+ Create New Household</option>
|
||||||
|
</HouseholdDropdown>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Store Tabs** (Within Household)
|
||||||
|
```tsx
|
||||||
|
<StoreTabs householdId={activeHousehold.id}>
|
||||||
|
<Tab active>Costco</Tab>
|
||||||
|
<Tab>Target</Tab>
|
||||||
|
<Tab>Walmart</Tab>
|
||||||
|
<Tab>+ Add Store</Tab>
|
||||||
|
</StoreTabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
### Phase 1: Database Schema (Breaking Change)
|
||||||
|
|
||||||
|
**Step 1: Backup**
|
||||||
|
```bash
|
||||||
|
pg_dump grocery_list > backup_$(date +%Y%m%d).sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Run Migrations**
|
||||||
|
```sql
|
||||||
|
-- 1. Create new tables
|
||||||
|
CREATE TABLE households (...);
|
||||||
|
CREATE TABLE household_members (...);
|
||||||
|
-- ... (all new tables)
|
||||||
|
|
||||||
|
-- 2. Create default household
|
||||||
|
INSERT INTO households (name, created_by, invite_code)
|
||||||
|
VALUES ('Main Household', 1, 'DEFAULT123');
|
||||||
|
|
||||||
|
-- 3. Migrate users → household_members
|
||||||
|
INSERT INTO household_members (household_id, user_id, role)
|
||||||
|
SELECT 1, id,
|
||||||
|
CASE
|
||||||
|
WHEN role = 'admin' THEN 'admin' -- Old admins become household admins
|
||||||
|
ELSE 'user' -- Everyone else becomes standard user
|
||||||
|
END
|
||||||
|
FROM users;
|
||||||
|
|
||||||
|
-- 4. Create default store
|
||||||
|
INSERT INTO stores (name, default_zones)
|
||||||
|
VALUES ('Costco', '{"zones": [...]}');
|
||||||
|
|
||||||
|
-- 5. Link household to store
|
||||||
|
INSERT INTO household_stores (household_id, store_id, is_default)
|
||||||
|
VALUES (1, 1, TRUE);
|
||||||
|
|
||||||
|
-- 6. Migrate items
|
||||||
|
INSERT INTO items (name, default_image, default_image_mime_type)
|
||||||
|
SELECT DISTINCT item_name, item_image, image_mime_type
|
||||||
|
FROM grocery_list;
|
||||||
|
|
||||||
|
-- 7. Migrate grocery_list → household_lists
|
||||||
|
INSERT INTO household_lists (household_id, store_id, item_id, quantity, bought, added_by, modified_on)
|
||||||
|
SELECT
|
||||||
|
1, -- default household
|
||||||
|
1, -- default store
|
||||||
|
i.id,
|
||||||
|
gl.quantity,
|
||||||
|
gl.bought,
|
||||||
|
gl.added_by,
|
||||||
|
gl.modified_on
|
||||||
|
FROM grocery_list gl
|
||||||
|
JOIN items i ON LOWER(i.name) = LOWER(gl.item_name);
|
||||||
|
|
||||||
|
-- 8. Migrate classifications
|
||||||
|
INSERT INTO household_item_classifications
|
||||||
|
(household_id, store_id, item_id, item_type, item_group, zone, confidence, source)
|
||||||
|
SELECT
|
||||||
|
1, 1, i.id,
|
||||||
|
ic.item_type, ic.item_group, ic.zone, ic.confidence, ic.source
|
||||||
|
FROM item_classification ic
|
||||||
|
JOIN grUpdate system roles (keep role column)
|
||||||
|
UPDATE users SET role = 'system_admin' WHERE role = 'admin';
|
||||||
|
UPDATE users SET role = 'user' WHERE role IN ('editor', 'viewer');
|
||||||
|
|
||||||
|
-- 11. Drop old tables (after verification!)
|
||||||
|
-- DROP TABLE grocery_history;
|
||||||
|
-- DROP TABLE item_classification;
|
||||||
|
-- DROP TABLE grocery_listousehold_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(gl.item_name)
|
||||||
|
JOIN household_lists hl ON hl.item_id = i.id AND hl.household_id = 1 AND hl.store_id = 1;
|
||||||
|
|
||||||
|
-- 10. Drop old tables (after verification!)
|
||||||
|
-- DROP TABLE grocery_history;
|
||||||
|
-- DROP TABLE item_classification;
|
||||||
|
-- DROP TABLE grocery_list;
|
||||||
|
-- ALTER TABLE users DROP COLUMN role;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Backend API (Incremental)
|
||||||
|
|
||||||
|
1. ✅ Create new models (households, stores, household_lists)
|
||||||
|
2. ✅ Create new middleware (householdAccess, storeAccess)
|
||||||
|
3. ✅ Create new controllers (households, stores)
|
||||||
|
4. ✅ Add new routes alongside old ones
|
||||||
|
5. ✅ Update list controllers to be household+store aware
|
||||||
|
6. ✅ Deprecate old routes (return 410 Gone)
|
||||||
|
|
||||||
|
### Phase 3: Frontend UI (Incremental)
|
||||||
|
|
||||||
|
1. ✅ **Refactor Context Pattern** (applies to all contexts)
|
||||||
|
- Move `createContext` inside component files (not exported)
|
||||||
|
- Export custom hooks instead: `useAuth()`, `useHousehold()`, `useStore()`, `useSettings()`
|
||||||
|
- Consumers use hooks directly instead of `useContext(ExportedContext)`
|
||||||
|
2. ✅ Create HouseholdContext with `useHousehold()` hook
|
||||||
|
3. ✅ Create StoreContext with `useStore()` hook
|
||||||
|
4. ✅ Refactor existing AuthContext to use custom `useAuth()` hook
|
||||||
|
5. ✅ Refactor existing SettingsContext to use custom `useSettings()` hook
|
||||||
|
6. ✅ Add household switcher to navbar
|
||||||
|
7. ✅ Create household management pages
|
||||||
|
8. ✅ Add store tabs to list view
|
||||||
|
9. ✅ Update all API calls to use household + store IDs
|
||||||
|
7. ✅ Add invite system UI
|
||||||
|
8. ✅ Update settings page to show household-specific settings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Features (Future)
|
||||||
|
|
||||||
|
### 1. Item Sharing & Privacy
|
||||||
|
|
||||||
|
**Levels:**
|
||||||
|
- **Private**: Only visible to your household
|
||||||
|
- **Public**: Available in global item catalog
|
||||||
|
- **Suggested**: Anonymously contribute to shared catalog
|
||||||
|
|
||||||
|
### 2. Smart Features
|
||||||
|
|
||||||
|
**Cross-Household Intelligence:**
|
||||||
|
- "10,000 households buy milk at Costco" → suggest classification
|
||||||
|
- "Items commonly bought together"
|
||||||
|
- Price tracking across stores
|
||||||
|
- Store-specific suggestions
|
||||||
|
|
||||||
|
**Household Patterns:**
|
||||||
|
- "You usually buy milk every 5 days"
|
||||||
|
- "Bananas are typically added by [User]"
|
||||||
|
- Auto-add recurring items
|
||||||
|
|
||||||
|
### 3. Multi-Store Optimization
|
||||||
|
|
||||||
|
**Store Comparison:**
|
||||||
|
- Track which items each household buys at which store
|
||||||
|
- "This item is 20% cheaper at Target"
|
||||||
|
- Generate shopping lists across stores
|
||||||
|
|
||||||
|
**Route Optimization:**
|
||||||
|
- Sort list by store zone
|
||||||
|
- "You can save 15 minutes by shopping in this order"
|
||||||
|
|
||||||
|
### 4. Enhanced Collaboration
|
||||||
|
|
||||||
|
**Shopping Mode:**
|
||||||
|
- Real-time collaboration (one person shops, another adds from home)
|
||||||
|
- Live updates via WebSockets
|
||||||
|
- "John is currently at Costco (aisle 12)"
|
||||||
|
|
||||||
|
**Shopping Lists:**
|
||||||
|
- Pre-planned lists (weekly meal prep)
|
||||||
|
- Recurring lists (monthly bulk buy)
|
||||||
|
- Shared templates between households
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Timeline
|
||||||
|
|
||||||
|
### Sprint 1: Foundation (2-3 weeks)
|
||||||
|
- [ ] Design finalization & review
|
||||||
|
- [ ] Create migration scripts
|
||||||
|
- [ ] Implement new database tables
|
||||||
|
- [ ] Test migration on staging data
|
||||||
|
- [ ] Create new models (household, store, household_list)
|
||||||
|
|
||||||
|
### Sprint 2: Backend API (2-3 weeks)
|
||||||
|
- [ ] Implement household management endpoints
|
||||||
|
- [ ] Implement store management endpoints
|
||||||
|
- [ ] Update list endpoints for household+store scope
|
||||||
|
- [ ] Create new middleware (householdAccess, storeAccess)
|
||||||
|
- [ ] Update authentication to remove global role
|
||||||
|
|
||||||
|
### Sprint 3: Frontend Core (2-3 weeks)
|
||||||
|
- [ ] **Refactor Context Pattern** (foundational change):
|
||||||
|
- [ ] Refactor AuthContext to internal context + `useAuth()` hook
|
||||||
|
- [ ] Refactor SettingsContext to internal context + `useSettings()` hook
|
||||||
|
- [ ] Update all components using old context pattern
|
||||||
|
- [ ] Create HouseholdContext with `useHousehold()` hook
|
||||||
|
- [ ] Create StoreContext with `useStore()` hook
|
||||||
|
- [ ] Build household switcher UI
|
||||||
|
- [ ] Build store tabs UI
|
||||||
|
- [ ] Update GroceryList page for new API
|
||||||
|
- [ ] Create household management pages
|
||||||
|
|
||||||
|
### Sprint 4: Member Management (1-2 weeks)
|
||||||
|
- [ ] Implement invite code system
|
||||||
|
- [ ] Build member management UI
|
||||||
|
- [ ] Implement role updates
|
||||||
|
- [ ] Add join household flow
|
||||||
|
|
||||||
|
### Sprint 5: Polish & Testing (1-2 weeks)
|
||||||
|
- [ ] End-to-end testing
|
||||||
|
- [ ] Performance optimization
|
||||||
|
- [ ] Mobile responsiveness
|
||||||
|
- [ ] Documentation updates
|
||||||
|
- [ ] Migration dry-run on production backup
|
||||||
|
|
||||||
|
### Sprint 6: Production Migration (1 week)
|
||||||
|
- [ ] Announce maintenance window
|
||||||
|
- [ ] Run migration on production
|
||||||
|
- [ ] Verify data integrity
|
||||||
|
- [ ] Deploy new frontend
|
||||||
|
- [ ] Monitor for issues
|
||||||
|
|
||||||
|
**Total: 9-14 weeks**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risk Assessment & Mitigation
|
||||||
|
|
||||||
|
### High Risk Areas
|
||||||
|
|
||||||
|
1. **Data Loss During Migration**
|
||||||
|
- **Mitigation**: Full backup, dry-run on production copy, rollback plan
|
||||||
|
|
||||||
|
2. **Breaking Existing Users**
|
||||||
|
- **Mitigation**: Default household preserves current behavior, phased rollout
|
||||||
|
|
||||||
|
3. **Performance Degradation**
|
||||||
|
- **Mitigation**: Proper indexing, query optimization, caching strategy
|
||||||
|
|
||||||
|
4. **Complexity Creep**
|
||||||
|
- **Mitigation**: MVP first (basic households), iterate based on feedback
|
||||||
|
|
||||||
|
### Testing Strategy
|
||||||
|
|
||||||
|
1. **Unit Tests**: All new models and controllers
|
||||||
|
2. **Integration Tests**: API endpoint flows
|
||||||
|
3. **Migration Tests**: Verify data integrity post-migration
|
||||||
|
4. **Load Tests**: Multi-household concurrent access
|
||||||
|
5. **User Acceptance**: Beta test with small group before full rollout
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open Questions & Decisions Needed
|
||||||
|
|
||||||
|
### 1. Item Naming Strategy
|
||||||
|
- **Question**: Should "milk" from Household A and "Milk" from Household B be the same item?
|
||||||
|
- **Options**:
|
||||||
|
- Case-insensitive merge (current behavior, recommended)
|
||||||
|
- Exact match only
|
||||||
|
- User prompt for merge confirmation
|
||||||
|
- **Recommendation**: Case-insensitive with optional household override
|
||||||
|
|
||||||
|
### 2. Store Management
|
||||||
|
- **Question**: Should all stores be predefined, or can users create custom stores?
|
||||||
|
- **Options**:
|
||||||
|
- Admin-only store creation (controlled list)
|
||||||
|
- Users can create custom stores (flexible but messy)
|
||||||
|
- Hybrid: predefined + custom
|
||||||
|
- **Recommendation**: Start with predefined stores, add custom later
|
||||||
|
|
||||||
|
### 3. Historical Data
|
||||||
|
- **Question**: When a user leaves a household, what happens to their history?
|
||||||
|
- **Options**:
|
||||||
|
- Keep history, anonymize user
|
||||||
|
- Keep history with user name (allows recovery if re-added)
|
||||||
|
- Delete history
|
||||||
|
- **Recommendation**: Keep history with actual user name preserved
|
||||||
|
- **Rationale**: If user is accidentally removed, their contributions remain attributed correctly when re-added
|
||||||
|
- History queries should JOIN with users table but handle missing users gracefully
|
||||||
|
- Display format: Show user name if still exists, otherwise show "User [id]" or handle as deleted account
|
||||||
|
|
||||||
|
### 4. Invite System
|
||||||
|
- **Question**: Should invite codes expire?
|
||||||
|
- **Options**:
|
||||||
|
- Never expire (simpler)
|
||||||
|
- 7-day expiration (more secure)
|
||||||
|
- Configurable per household
|
||||||
|
- **Recommendation**: Optional expiration, default to never
|
||||||
|
|
||||||
|
### 5. Default Household
|
||||||
|
- **Question**: When user logs in, which household/store do they see?
|
||||||
|
- **Options**:
|
||||||
|
- Last used (remember preference)
|
||||||
|
- Most recently modified list
|
||||||
|
- User-configured default
|
||||||
|
- **Recommendation**: Remember last used in localStorage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary & Next Steps
|
||||||
|
|
||||||
|
### Recommended Approach: **Hybrid Multi-Tenant Architecture**
|
||||||
|
|
||||||
|
**Core Principles:**
|
||||||
|
1. ✅ Shared item catalog with household-specific lists
|
||||||
|
2. ✅ Per-household roles (not global)
|
||||||
|
3. ✅ Store-specific classifications
|
||||||
|
4. ✅ Invite-based household joining
|
||||||
|
5. ✅ Backward-compatible migration
|
||||||
|
|
||||||
|
### Immediate Actions
|
||||||
|
|
||||||
|
1. **Review & Approve**: Get stakeholder buy-in on this architecture
|
||||||
|
2. **Validate Assumptions**: Confirm design decisions (item sharing, store management)
|
||||||
|
3. **Create Detailed Tickets**: Break down sprints into individual tasks
|
||||||
|
4. **Set Up Staging**: Create test environment with production data copy
|
||||||
|
5. **Begin Sprint 1**: Start with database design and migration scripts
|
||||||
|
|
||||||
|
### Success Metrics
|
||||||
|
|
||||||
|
- ✅ Zero data loss during migration
|
||||||
|
- ✅ 100% existing users migrated to default household
|
||||||
|
- ✅ Performance within 20% of current (queries < 200ms)
|
||||||
|
- ✅ Users can create households and invite others
|
||||||
|
- ✅ Lists properly isolated between households
|
||||||
|
- ✅ Mobile UI remains responsive
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix A: Example User Flows
|
||||||
|
|
||||||
|
### Creating a Household
|
||||||
|
1. User clicks "Create Household"
|
||||||
|
2. Enters name "Smith Family"
|
||||||
|
3. System generates invite code "SMITH2026"
|
||||||
|
4. User is set as "admin" role (creator is always admin)
|
||||||
|
5. User can share code with family members
|
||||||
|
|
||||||
|
### Joining a Household
|
||||||
|
1. User receives invite code "SMITH2026"
|
||||||
|
2. Navigates to /join/SMITH2026
|
||||||
|
3. Sees "Join Smith Family?"
|
||||||
|
4. Confirms, added as "user" role by default
|
||||||
|
5. Admin can promote to "admin" role if needed
|
||||||
|
|
||||||
|
### Managing Multiple Households
|
||||||
|
1. User belongs to "Smith Family" and "Work Team"
|
||||||
|
2. Navbar shows dropdown: [Smith Family ▼]
|
||||||
|
3. Clicks dropdown, sees both households
|
||||||
|
4. Switches to "Work Team"
|
||||||
|
5. List updates to show Work Team's items
|
||||||
|
6. Store tabs show Work Team's configured stores
|
||||||
|
|
||||||
|
### Adding Item to Store
|
||||||
|
1. User in "Smith Family" household
|
||||||
|
2. Sees store tabs: [Costco] [Target]
|
||||||
|
3. Clicks "Costco" tab
|
||||||
|
4. Adds "Milk" - goes to Costco list
|
||||||
|
5. Switches to "Target" tab
|
||||||
|
6. Adds "Bread" - goes to Target list
|
||||||
|
7. Milk and Bread are separate list entries (same item, different stores)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix B: Database Size Estimates
|
||||||
|
|
||||||
|
**Current Single List:**
|
||||||
|
- Users: 10
|
||||||
|
- Items: 200
|
||||||
|
- History records: 5,000
|
||||||
|
|
||||||
|
**After Multi-Household (10 households, 5 stores each):**
|
||||||
|
- Users: 10
|
||||||
|
- Households: 10
|
||||||
|
- Household_members: 30 (avg 3 users per household)
|
||||||
|
- Stores: 5
|
||||||
|
- Household_stores: 50
|
||||||
|
- Items: 500 (some shared, some unique)
|
||||||
|
- Household_lists: 2,500 (500 items × 5 stores)
|
||||||
|
- History: 25,000
|
||||||
|
|
||||||
|
**Storage Impact:** ~5x increase in list records, but items are deduplicated.
|
||||||
|
|
||||||
|
**Query Performance:**
|
||||||
|
- Without indexes: O(n) → O(10n) = 10x slower
|
||||||
|
- With indexes: O(log n) → O(log 10n) = minimal impact
|
||||||
|
|
||||||
|
**Conclusion:** With proper indexing, performance should remain acceptable even at 100+ households.
|
||||||
Loading…
Reference in New Issue
Block a user