docs: add TPB app checklist and stop tracking migration sql
This commit is contained in:
parent
d90df35d57
commit
9daacaf0d8
3 changed files with 60 additions and 237 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -44,5 +44,5 @@ app.*.map.json
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
TPB.md
|
/TPB.md
|
||||||
supabase_migration.sql
|
/supabase_migration.sql
|
||||||
|
|
|
||||||
58
TPB_APP_CHECKLIST.md
Normal file
58
TPB_APP_CHECKLIST.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# TPB App Checklist (Flutter Scope)
|
||||||
|
|
||||||
|
This checklist tracks only the Flutter app implementation against `TPB.md`.
|
||||||
|
Backend infrastructure (RLS, triggers, storage bucket settings, SQL policies) is intentionally out of scope here.
|
||||||
|
|
||||||
|
## 1) Authentication & Onboarding
|
||||||
|
|
||||||
|
- [x] Forgot-password entry from login
|
||||||
|
- [x] Password-recovery dialog on auth recovery event
|
||||||
|
- [x] Ensure default collection on first login
|
||||||
|
- [x] Add user as owner/member when creating collection
|
||||||
|
|
||||||
|
## 2) Lightning Add (Scanner & Active Collection)
|
||||||
|
|
||||||
|
- [x] Persist and restore active collection
|
||||||
|
- [x] Scanner supports active collection selection
|
||||||
|
- [x] Lookup scanned `hw_id` in `global_cars`
|
||||||
|
- [x] Found branch uses bottom sheet + add action
|
||||||
|
- [x] New discovery branch captures Name/Series/Year in bottom sheet
|
||||||
|
- [x] Insert into `hotwheels` with active collection
|
||||||
|
- [x] Insert validation vote flow in app (`car_votes`)
|
||||||
|
- [x] Continuous scanner loop / fast next-scan UX
|
||||||
|
|
||||||
|
## 3) Private Photo Upload & Processing
|
||||||
|
|
||||||
|
- [x] Car detail supports camera capture / update photo
|
||||||
|
- [x] Upload path format in app: `uid/entryid.jpg`
|
||||||
|
- [x] In-app compression/resizing target (1080px, <=500KB)
|
||||||
|
- [x] Save path into `hotwheels.user_image_url`
|
||||||
|
|
||||||
|
## 4) Garage Display
|
||||||
|
|
||||||
|
- [x] Query cars by active collection
|
||||||
|
- [x] Generate signed URLs for private images
|
||||||
|
- [x] Fallback to `assets/img/icon_bg_removed.png` when no image
|
||||||
|
- [x] Pagination / lazy loading
|
||||||
|
- [x] Multi-select move between collections
|
||||||
|
- [x] Single-car move action
|
||||||
|
|
||||||
|
## 5) Sharing & Collaboration UX
|
||||||
|
|
||||||
|
- [x] Invite member by email
|
||||||
|
- [x] Remove member flow
|
||||||
|
- [x] Leave collection flow (non-owner)
|
||||||
|
- [x] Owner guardrails in manage collection flow
|
||||||
|
|
||||||
|
## 6) Community Validation & Reporting (App UX)
|
||||||
|
|
||||||
|
- [x] Show verification status and confirmation count
|
||||||
|
- [x] Confirm catalog entry action from car detail
|
||||||
|
- [x] Report catalog issue action with reason + optional note
|
||||||
|
- [x] Prevent duplicate open report submission per user/car in app flow
|
||||||
|
- [x] My Reports screen with status display (open/reviewed/resolved/dismissed)
|
||||||
|
|
||||||
|
## Current App-Only Verdict
|
||||||
|
|
||||||
|
- App-side TPB implementation is complete for the defined Flutter workflows.
|
||||||
|
- Any further remaining TPB work should now be treated as either backend operations or new product scope.
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
-- HW Collector Hub – Multi-Collection Migration
|
|
||||||
-- Run this in the Supabase SQL Editor (one-time).
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
-- 1) Collections table
|
|
||||||
CREATE TABLE IF NOT EXISTS collections (
|
|
||||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 2) Collection members (join table)
|
|
||||||
CREATE TABLE IF NOT EXISTS collection_members (
|
|
||||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
||||||
collection_id UUID NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
|
|
||||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
||||||
role TEXT NOT NULL DEFAULT 'member'
|
|
||||||
CHECK (role IN ('owner', 'member')),
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now(),
|
|
||||||
UNIQUE(collection_id, user_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 3) Add collection_id column to hotwheels
|
|
||||||
ALTER TABLE hotwheels
|
|
||||||
ADD COLUMN IF NOT EXISTS collection_id UUID REFERENCES collections(id) ON DELETE CASCADE;
|
|
||||||
|
|
||||||
-- ── RLS: collections ──────────────────────────────────────────────────
|
|
||||||
ALTER TABLE collections ENABLE ROW LEVEL SECURITY;
|
|
||||||
|
|
||||||
-- Users can see collections they are a member of.
|
|
||||||
CREATE POLICY "Members can view collections"
|
|
||||||
ON collections FOR SELECT
|
|
||||||
USING (
|
|
||||||
owner_id = auth.uid()
|
|
||||||
OR id IN (
|
|
||||||
SELECT collection_id FROM collection_members
|
|
||||||
WHERE user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Any authenticated user can create a collection.
|
|
||||||
CREATE POLICY "Authenticated users can create collections"
|
|
||||||
ON collections FOR INSERT
|
|
||||||
WITH CHECK (auth.uid() = owner_id);
|
|
||||||
|
|
||||||
-- Only the owner can update.
|
|
||||||
CREATE POLICY "Owner can update collection"
|
|
||||||
ON collections FOR UPDATE
|
|
||||||
USING (owner_id = auth.uid());
|
|
||||||
|
|
||||||
-- Only the owner can delete.
|
|
||||||
CREATE POLICY "Owner can delete collection"
|
|
||||||
ON collections FOR DELETE
|
|
||||||
USING (owner_id = auth.uid());
|
|
||||||
|
|
||||||
-- ── RLS: collection_members ──────────────────────────────────────────
|
|
||||||
ALTER TABLE collection_members ENABLE ROW LEVEL SECURITY;
|
|
||||||
|
|
||||||
-- Users can see their own memberships (avoids infinite recursion).
|
|
||||||
CREATE POLICY "Users can view own memberships"
|
|
||||||
ON collection_members FOR SELECT
|
|
||||||
USING (user_id = auth.uid());
|
|
||||||
|
|
||||||
-- Owner can add members.
|
|
||||||
CREATE POLICY "Owner can add members"
|
|
||||||
ON collection_members FOR INSERT
|
|
||||||
WITH CHECK (
|
|
||||||
collection_id IN (
|
|
||||||
SELECT id FROM collections WHERE owner_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Owner can remove members.
|
|
||||||
CREATE POLICY "Owner can remove members"
|
|
||||||
ON collection_members FOR DELETE
|
|
||||||
USING (
|
|
||||||
collection_id IN (
|
|
||||||
SELECT id FROM collections WHERE owner_id = auth.uid()
|
|
||||||
)
|
|
||||||
OR user_id = auth.uid() -- members can remove themselves
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ── RLS: hotwheels (update existing) ────────────────────────────────
|
|
||||||
-- Drop old policies first (they were user_id based).
|
|
||||||
DROP POLICY IF EXISTS "Enable read access for all users" ON hotwheels;
|
|
||||||
DROP POLICY IF EXISTS "Enable insert for authenticated users only" ON hotwheels;
|
|
||||||
DROP POLICY IF EXISTS "Enable update for users based on user_id" ON hotwheels;
|
|
||||||
DROP POLICY IF EXISTS "Enable delete for users based on user_id" ON hotwheels;
|
|
||||||
|
|
||||||
-- Members of a collection can see its cars.
|
|
||||||
CREATE POLICY "Collection members can view cars"
|
|
||||||
ON hotwheels FOR SELECT
|
|
||||||
USING (
|
|
||||||
collection_id IN (
|
|
||||||
SELECT collection_id FROM collection_members
|
|
||||||
WHERE user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Members can add cars.
|
|
||||||
CREATE POLICY "Collection members can insert cars"
|
|
||||||
ON hotwheels FOR INSERT
|
|
||||||
WITH CHECK (
|
|
||||||
collection_id IN (
|
|
||||||
SELECT collection_id FROM collection_members
|
|
||||||
WHERE user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Members can update cars in their collections.
|
|
||||||
CREATE POLICY "Collection members can update cars"
|
|
||||||
ON hotwheels FOR UPDATE
|
|
||||||
USING (
|
|
||||||
collection_id IN (
|
|
||||||
SELECT collection_id FROM collection_members
|
|
||||||
WHERE user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Members can delete cars from their collections.
|
|
||||||
CREATE POLICY "Collection members can delete cars"
|
|
||||||
ON hotwheels FOR DELETE
|
|
||||||
USING (
|
|
||||||
collection_id IN (
|
|
||||||
SELECT collection_id FROM collection_members
|
|
||||||
WHERE user_id = auth.uid()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
-- MIGRATION HELPER: Move existing cars into a default collection
|
|
||||||
-- for each user that already has cars.
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
DO $$
|
|
||||||
DECLARE
|
|
||||||
_user RECORD;
|
|
||||||
_coll UUID;
|
|
||||||
BEGIN
|
|
||||||
FOR _user IN
|
|
||||||
SELECT DISTINCT user_id FROM hotwheels WHERE collection_id IS NULL
|
|
||||||
LOOP
|
|
||||||
-- Create a default "My Collection" for this user.
|
|
||||||
INSERT INTO collections (name, owner_id)
|
|
||||||
VALUES ('My Collection', _user.user_id)
|
|
||||||
RETURNING id INTO _coll;
|
|
||||||
|
|
||||||
-- Owner is also a member.
|
|
||||||
INSERT INTO collection_members (collection_id, user_id, role)
|
|
||||||
VALUES (_coll, _user.user_id, 'owner');
|
|
||||||
|
|
||||||
-- Assign all existing cars to this collection.
|
|
||||||
UPDATE hotwheels
|
|
||||||
SET collection_id = _coll
|
|
||||||
WHERE user_id = _user.user_id AND collection_id IS NULL;
|
|
||||||
END LOOP;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
-- After migration, make collection_id NOT NULL.
|
|
||||||
ALTER TABLE hotwheels ALTER COLUMN collection_id SET NOT NULL;
|
|
||||||
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
-- RPC: Look up a user's ID by email (for inviting)
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
CREATE OR REPLACE FUNCTION get_user_id_by_email(lookup_email TEXT)
|
|
||||||
RETURNS UUID
|
|
||||||
LANGUAGE sql
|
|
||||||
SECURITY DEFINER -- runs as postgres, can read auth.users
|
|
||||||
SET search_path = ''
|
|
||||||
AS $$
|
|
||||||
SELECT id FROM auth.users
|
|
||||||
WHERE email = lower(lookup_email)
|
|
||||||
LIMIT 1;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
-- RPC: Get collection members with their emails
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
CREATE OR REPLACE FUNCTION get_collection_members(p_collection_id UUID)
|
|
||||||
RETURNS TABLE(
|
|
||||||
id UUID,
|
|
||||||
user_id UUID,
|
|
||||||
email TEXT,
|
|
||||||
role TEXT,
|
|
||||||
created_at TIMESTAMPTZ
|
|
||||||
)
|
|
||||||
LANGUAGE sql
|
|
||||||
SECURITY DEFINER
|
|
||||||
SET search_path = ''
|
|
||||||
AS $$
|
|
||||||
SELECT
|
|
||||||
cm.id,
|
|
||||||
cm.user_id,
|
|
||||||
u.email,
|
|
||||||
cm.role,
|
|
||||||
cm.created_at
|
|
||||||
FROM public.collection_members cm
|
|
||||||
JOIN auth.users u ON u.id = cm.user_id
|
|
||||||
WHERE cm.collection_id = p_collection_id
|
|
||||||
AND cm.collection_id IN (
|
|
||||||
SELECT cm2.collection_id FROM public.collection_members cm2
|
|
||||||
WHERE cm2.user_id = auth.uid()
|
|
||||||
)
|
|
||||||
ORDER BY
|
|
||||||
CASE cm.role WHEN 'owner' THEN 0 ELSE 1 END,
|
|
||||||
cm.created_at;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
-- Community reporting: car_reports
|
|
||||||
-- ══════════════════════════════════════════════════════════════════════
|
|
||||||
CREATE TABLE IF NOT EXISTS car_reports (
|
|
||||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
||||||
hw_id VARCHAR NOT NULL REFERENCES global_cars(hw_id) ON DELETE CASCADE,
|
|
||||||
hotwheels_id BIGINT REFERENCES hotwheels(id) ON DELETE SET NULL,
|
|
||||||
reporter_user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
||||||
reason TEXT NOT NULL,
|
|
||||||
note TEXT,
|
|
||||||
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'reviewed', 'resolved', 'dismissed')),
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE car_reports ENABLE ROW LEVEL SECURITY;
|
|
||||||
|
|
||||||
-- Reporters can create reports only for themselves.
|
|
||||||
CREATE POLICY "Users can create own car reports"
|
|
||||||
ON car_reports FOR INSERT
|
|
||||||
WITH CHECK (reporter_user_id = auth.uid());
|
|
||||||
|
|
||||||
-- Reporters can see their own reports.
|
|
||||||
CREATE POLICY "Users can view own car reports"
|
|
||||||
ON car_reports FOR SELECT
|
|
||||||
USING (reporter_user_id = auth.uid());
|
|
||||||
Loading…
Reference in a new issue