Files
Sage/.planning/phases/02-household-creation/02-02-PLAN.md
Dani B e20858f608 docs(02): create phase 2 household creation plans
Phase 2: Household Creation & Invites
- 6 plans in 4 waves covering SHARE-01 through SHARE-05
- Data models, repository pattern, and Supabase integration
- Database schema with RLS policies for multi-tenant isolation
- State management with Riverpod and business logic use cases
- Complete UI components for household management
- Navigation integration with authentication flow

Ready for execution: /gsd:execute-phase 2
2026-01-28 15:50:09 -05:00

15 KiB

phase: 02-household-creation plan: 02 type: execute wave: 1 depends_on: [] files_modified: - lib/features/household/data/datasources/household_remote_datasource.dart - lib/features/household/domain/repositories/household_repository.dart - lib/features/household/data/repositories/household_repository_impl.dart - lib/features/household/domain/models/household_models.dart - lib/features/household/domain/entities/household_entity.dart autonomous: true must_haves: truths: - "Supabase database schema supports households, members, and invite codes with proper relationships" - "Row-Level Security policies isolate household data at database layer" - "Database constraints prevent duplicate members and invalid invite codes" - "Realtime subscriptions enable household member sync" artifacts: - path: "supabase/migrations/001_create_household_tables.sql" provides: "Database schema for household functionality" contains: "CREATE TABLE households", "CREATE TABLE household_members", "CREATE TABLE invite_codes" - path: "supabase/migrations/002_household_rls_policies.sql" provides: "Row-Level Security policies for household data isolation" contains: "CREATE POLICY", "ROW LEVEL SECURITY" - path: "supabase/migrations/003_household_indexes.sql" provides: "Performance indexes for household queries" contains: "CREATE INDEX", "household_id", "user_id" key_links: - from: "lib/features/household/data/repositories/household_repository_impl.dart" to: "supabase/migrations/001_create_household_tables.sql" via: "table operations" pattern: "from.*households" - from: "supabase/migrations/002_household_rls_policies.sql" to: "supabase/migrations/001_create_household_tables.sql" via: "security policies" pattern: "POLICY.*FOR.*households" Create Supabase database schema with proper household isolation, member relationships, and invite code management.

Purpose: Establish database foundation that enforces multi-tenant security and supports real-time household synchronization requirements. Output: Complete database schema with tables, RLS policies, and indexes for household operations.

<execution_context> @/.opencode/get-shit-done/workflows/execute-plan.md @/.opencode/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/research/ARCHITECTURE.md

Database Architecture References

@lib/features/authentication/data/repositories/auth_repository_impl.dart

Create Household Tables Migration supabase/migrations/001_create_household_tables.sql Create database schema for household functionality:
1. households table:
```sql
CREATE TABLE households (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  name TEXT NOT NULL CHECK (length(name) >= 1 AND length(name) <= 100),
  created_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
);

CREATE TRIGGER households_updated_at 
  BEFORE UPDATE ON households 
  FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
```

2. household_members table:
```sql
CREATE TABLE household_members (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  household_id UUID NOT NULL REFERENCES households(id) ON DELETE CASCADE,
  user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')),
  joined_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  UNIQUE(household_id, user_id)
);
```

3. invite_codes table:
```sql
CREATE TABLE invite_codes (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  household_id UUID NOT NULL REFERENCES households(id) ON DELETE CASCADE,
  code TEXT NOT NULL UNIQUE CHECK (length(code) = 8),
  created_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
  expires_at TIMESTAMPTZ NOT NULL,
  used_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
  used_at TIMESTAMPTZ,
  CONSTRAINT valid_invite_code CHECK (
    (used_by IS NULL AND used_at IS NULL) OR 
    (used_by IS NOT NULL AND used_at IS NOT NULL)
  )
);
```

Follow Supabase migration patterns and include proper constraints.
supabase db push --dry-run shows valid SQL syntax Household tables created with proper relationships and constraints Create Household RLS Policies supabase/migrations/002_household_rls_policies.sql Create Row-Level Security policies for household data isolation:
1. Enable RLS on all household tables:
```sql
ALTER TABLE households ENABLE ROW LEVEL SECURITY;
ALTER TABLE household_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE invite_codes ENABLE ROW LEVEL SECURITY;
```

2. households table policies:
```sql
-- Users can view households they belong to
CREATE POLICY "Users can view their households" ON households
  FOR SELECT USING (
    id IN (
      SELECT household_id FROM household_members 
      WHERE user_id = auth.uid()
    )
  );

-- Users can insert households (will be owner via member insertion)
CREATE POLICY "Users can create households" ON households
  FOR INSERT WITH CHECK (created_by = auth.uid());

-- Only household owners can update households
CREATE POLICY "Owners can update households" ON households
  FOR UPDATE USING (
    id IN (
      SELECT household_id FROM household_members 
      WHERE user_id = auth.uid() AND role = 'owner'
    )
  );
```

3. household_members table policies:
```sql
-- Users can view members of their households
CREATE POLICY "Users can view household members" ON household_members
  FOR SELECT USING (
    household_id IN (
      SELECT household_id FROM household_members 
      WHERE user_id = auth.uid()
    )
  );

-- Users can insert themselves as members (via join flow)
CREATE POLICY "Users can join households" ON household_members
  FOR INSERT WITH CHECK (user_id = auth.uid());

-- Only owners can manage member roles and remove members
CREATE POLICY "Owners can manage members" ON household_members
  FOR UPDATE USING (
    household_id IN (
      SELECT household_id FROM household_members 
      WHERE user_id = auth.uid() AND role = 'owner'
    )
  );

-- Only owners can remove members (except themselves if last owner handled in app logic)
CREATE POLICY "Owners can remove members" ON household_members
  FOR DELETE USING (
    household_id IN (
      SELECT household_id FROM household_members 
      WHERE user_id = auth.uid() AND role = 'owner'
    )
  );
```

4. invite_codes table policies:
```sql
-- Users can view invite codes for their households
CREATE POLICY "Users can view household invite codes" ON invite_codes
  FOR SELECT USING (
    household_id IN (
      SELECT household_id FROM household_members 
      WHERE user_id = auth.uid()
    )
  );

-- Only owners can create invite codes
CREATE POLICY "Owners can create invite codes" ON invite_codes
  FOR INSERT WITH CHECK (
    created_by = auth.uid() AND
    household_id IN (
      SELECT household_id FROM household_members 
      WHERE user_id = auth.uid() AND role = 'owner'
    )
  );

-- Only owners can revoke invite codes
CREATE POLICY "Owners can revoke invite codes" ON invite_codes
  FOR DELETE USING (
    created_by = auth.uid()
  );
```

Ensure policies enforce multi-tenant isolation at database layer.
supabase db push --dry-run shows valid RLS policies Row-Level Security policies enforce household data isolation with proper role-based access Create Household Performance Indexes supabase/migrations/003_household_indexes.sql Create performance indexes for household operations:
1. households table indexes:
```sql
-- For finding households by creator
CREATE INDEX idx_households_created_by ON households(created_by);

-- For household sorting by creation date
CREATE INDEX idx_households_created_at ON households(created_at);
```

2. household_members table indexes:
```sql
-- For finding user's households (primary query)
CREATE INDEX idx_household_members_user_id ON household_members(user_id);

-- For finding household members (primary query)
CREATE INDEX idx_household_members_household_id ON household_members(household_id);

-- Composite index for member role queries
CREATE INDEX idx_household_members_household_role ON household_members(household_id, role);

-- For checking if user is member of household
CREATE UNIQUE INDEX idx_household_members_unique ON household_members(household_id, user_id);
```

3. invite_codes table indexes:
```sql
-- For invite code lookup (primary query)
CREATE UNIQUE INDEX idx_invite_codes_code ON invite_codes(code);

-- For finding household's active invites
CREATE INDEX idx_invite_codes_household_id ON invite_codes(household_id);

-- For cleaning expired invites
CREATE INDEX idx_invite_codes_expires_at ON invite_codes(expires_at);

-- For finding unused invites
CREATE INDEX idx_invite_codes_unused ON invite_codes(used_by) WHERE used_by IS NULL;
```

4. Realtime function for household changes:
```sql
-- Function to broadcast household member changes
CREATE OR REPLACE FUNCTION broadcast_household_change()
RETURNS TRIGGER AS $$
BEGIN
  -- This will be used by Supabase Realtime
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger for household member changes
CREATE TRIGGER household_members_broadcast
  AFTER INSERT OR UPDATE OR DELETE ON household_members
  FOR EACH ROW EXECUTE FUNCTION broadcast_household_change();
```

Include indexes that support the most common household queries and real-time sync.
supabase db push --dry-run shows valid index creation Performance indexes optimize household queries and support real-time synchronization Create Household Database Functions supabase/migrations/004_household_functions.sql Create database functions for household operations:
1. Generate unique invite code function:
```sql
CREATE OR REPLACE FUNCTION generate_invite_code()
RETURNS TEXT AS $$
DECLARE
  new_code TEXT;
  code_exists BOOLEAN;
BEGIN
  LOOP
    -- Generate 8-character alphanumeric code
    new_code := upper(substring(encode(gen_random_bytes(4), 'hex'), 1, 8));
    
    -- Check for collision
    SELECT EXISTS(SELECT 1 FROM invite_codes WHERE code = new_code AND used_by IS NULL) INTO code_exists;
    
    EXIT WHEN NOT code_exists;
  END LOOP;
  
  RETURN new_code;
END;
$$ LANGUAGE plpgsql;
```

2. Get user's households with member info:
```sql
CREATE OR REPLACE FUNCTION get_user_households(user_uuid UUID)
RETURNS TABLE (
  household_id UUID,
  household_name TEXT,
  user_role TEXT,
  member_count BIGINT,
  created_at TIMESTAMPTZ
) AS $$
BEGIN
  RETURN QUERY
  SELECT 
    h.id,
    h.name,
    hm.role,
    (SELECT COUNT(*) FROM household_members WHERE household_id = h.id),
    h.created_at
  FROM households h
  JOIN household_members hm ON h.id = hm.household_id
  WHERE hm.user_id = user_uuid
  ORDER BY h.created_at DESC;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
```

3. Validate and consume invite code:
```sql
CREATE OR REPLACE FUNCTION join_household_by_code(
  invite_code TEXT,
  user_uuid UUID
)
RETURNS TABLE (
  success BOOLEAN,
  household_id UUID,
  household_name TEXT,
  error_message TEXT
) AS $$
DECLARE
  invite_record RECORD;
  household_record RECORD;
  is_already_member BOOLEAN;
BEGIN
  -- Find valid invite code
  SELECT ic.*, h.name as household_name INTO invite_record
  FROM invite_codes ic
  JOIN households h ON ic.household_id = h.id
  WHERE ic.code = upper(invite_code)
    AND ic.used_by IS NULL
    AND ic.expires_at > now();
  
  IF NOT FOUND THEN
    RETURN QUERY SELECT false, NULL::UUID, NULL::TEXT, 'Invalid or expired invite code'::TEXT;
    RETURN;
  END IF;
  
  -- Check if already member
  SELECT EXISTS(
    SELECT 1 FROM household_members 
    WHERE household_id = invite_record.household_id AND user_id = user_uuid
  ) INTO is_already_member;
  
  IF is_already_member THEN
    RETURN QUERY SELECT false, NULL::UUID, NULL::TEXT, 'Already a member of this household'::TEXT;
    RETURN;
  END IF;
  
  -- Add as member with editor role
  INSERT INTO household_members (household_id, user_id, role)
  VALUES (invite_record.household_id, user_uuid, 'editor');
  
  -- Mark invite as used
  UPDATE invite_codes 
  SET used_by = user_uuid, used_at = now()
  WHERE id = invite_record.id;
  
  -- Get household info
  SELECT id, name INTO household_record
  FROM households WHERE id = invite_record.household_id;
  
  RETURN QUERY SELECT true, household_record.id, household_record.name, NULL::TEXT;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
```

Include proper SECURITY DEFINER settings and error handling.
supabase db push --dry-run shows valid function creation Database functions provide efficient household operations with proper security 1. Database schema supports all household operations from SHARE-01 through SHARE-05 2. RLS policies enforce multi-tenant isolation at database layer 3. Performance indexes optimize common household queries 4. Database functions provide efficient invite code generation and validation 5. Realtime triggers support household member synchronization

<success_criteria> Supabase database schema is complete with household tables, security policies, indexes, and functions ready for application integration. </success_criteria>

After completion, create `.planning/phases/02-household-creation/02-02-SUMMARY.md` with database schema implementation summary