Skip to content
This repository was archived by the owner on Dec 12, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
377279c
docs: update spec
Pertempto Nov 19, 2025
143eaa4
feat: update email title
Pertempto Nov 19, 2025
f1dde8a
feat: basic UI
Pertempto Nov 20, 2025
26c1fe7
feat: use kwila cloud email addresses in seed data
Pertempto Nov 20, 2025
0b7e529
fix: credentials
Pertempto Nov 20, 2025
ccda350
chore: Merge branch 'main' into add-invite-codes
Pertempto Nov 20, 2025
c282d7d
Invite history styles now match
opencode-agent[bot] Nov 20, 2025
a792ef2
feat: debug comment author
Pertempto Nov 20, 2025
49ce7dc
fix: syntax
Pertempto Nov 20, 2025
fee916b
fix: author association
Pertempto Nov 20, 2025
c92c58c
fix: create invite
Pertempto Nov 20, 2025
4a233be
New reusable breadcrumb layout
opencode-agent[bot] Nov 20, 2025
cd4ad62
fix: remove unnecessary wrapper
Pertempto Nov 20, 2025
a10b676
Page headers unified
opencode-agent[bot] Nov 20, 2025
1e83488
docs: more tasks
Pertempto Nov 20, 2025
b5608da
feat: improve dashboard styling
Pertempto Nov 20, 2025
c123e41
docs: add more specs
Pertempto Nov 20, 2025
4d07315
docs: add detail
Pertempto Nov 20, 2025
7c93027
docs: another task
Pertempto Nov 20, 2025
b3cfbaa
refactor: use Button component in invite page
Pertempto Nov 20, 2025
afda1ac
fix: loading data
Pertempto Nov 20, 2025
10721f2
feat: more invite code progress WIP
Pertempto Nov 20, 2025
f271a7d
feat: add invite name
Pertempto Nov 21, 2025
5924bbe
fix: name
Pertempto Nov 21, 2025
1b6cb89
chore: revert auth
Pertempto Nov 21, 2025
147504e
fix: JWT authentication for invite API endpoints
Pertempto Nov 21, 2025
c7ec957
refactor: simplify
Pertempto Nov 21, 2025
5124978
fix: type error
Pertempto Nov 21, 2025
cbb6c42
feat: improve share text
Pertempto Nov 21, 2025
e735f31
fix: do not include revoked invites
Pertempto Nov 21, 2025
b23bab3
feat: add SQL function to check creating invites
Pertempto Nov 21, 2025
0bac015
feat: move storage policies to declarative schema
Pertempto Nov 21, 2025
1c7bc90
docs: specs improvements
Pertempto Nov 21, 2025
5e9bedf
docs: update architecture
Pertempto Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/opencode.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: opencode
name: OpenCode Agent

on:
issue_comment:
Expand All @@ -13,7 +13,12 @@ jobs:
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
) && github.event.comment.author_association == 'OWNER'
)
&&
(
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER'
)
runs-on: ubuntu-latest
permissions:
id-token: write
Expand All @@ -25,6 +30,11 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Debug author association
run: |
echo "Comment body: ${{ github.event.comment.body }}"
echo "Author association: ${{ github.event.comment.author_association }}"

- name: Run opencode
uses: sst/opencode/github@latest
env:
Expand Down
2 changes: 1 addition & 1 deletion .opencode/command/do-pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Required behavior (non-interactive flow)
- Run the relevant automated tests immediately after implementing the change. Tests must be run and pass before committing.
- If a change only affects unit tests, run the narrower set of packages to save time.
- If tests fail, refine the code until tests pass. Do not proceed to committing that TODO item until its tests pass.
- Once tests pass, update the spec (check off corresponding item) and commit the change locally using a descriptive conventional commit message (example `feat(7): add backup script`).
- Once tests pass, update the spec (check off corresponding item) and commit the change locally using a descriptive conventional commit message (example `feat: add backup script`).
- Use: `git add -A && git commit -m "<scope>: <short description>"`
3. After all task items for the current section are completed and committed locally:
- Push the branch to the remote:
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ This is a request-driven marketplace that prioritizes relationships over profit.
- **Use TypeScript strict mode**
- **Don't modify git config**
- **Always use pre-existing layouts** from `src/layouts/` for page structure consistency
- **Always use pre-existing component** from `src/components/` for UI consistency and less duplicate code

### Contribution Process

Expand Down
267 changes: 56 additions & 211 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ A trust-based, invite-only marketplace built with Astro, React, and Supabase. Us

All infrastructure defined as code:

- **Supabase**: Database schema via SQL migrations, RLS policies in migrations
- **Supabase**: Database schema via declarative SQL files in `supabase/schemas/`, migrations auto-generated via `supabase db diff`
- **Cloudflare**: Workers config in `wrangler.toml`, environment variables in `.dev.vars`
- **Benefits**: Version controlled, reproducible, reviewable, no manual dashboard configuration
- **Benefits**: Version controlled, reproducible, reviewable, no manual dashboard configuration, single source of truth

### 2. Local Development Parity

Expand Down Expand Up @@ -55,9 +55,9 @@ Every feature should support both power users and those who rank low on the "tec

- **Database**: Supabase (PostgreSQL)
- **Authentication**: Supabase Auth (email/phone OTP via Twilio)
- **Storage**: Supabase Storage (single bucket)
- **Storage**: Supabase Storage (images bucket with structured folders: avatars/, items/, messages/)
- **Real-time**: Supabase Realtime
- **API**: Supabase REST + PostgREST with RLS
- **API**: Supabase REST + PostgREST with RLS, Custom API routes with JWT authentication

### Deployment

Expand All @@ -83,173 +83,7 @@ Every feature should support both power users and those who rank low on the "tec

## Database Schema

### user

- id (uuid, pk)
- display_name (text)
- about (text)
- avatar_url (text)
- vendor_id (text, unique, nullable) -- alphanumeric + underscore/dash
- created_at (timestamp)
- invited_by (uuid, fk -> user.id)

### contact_info

- id (uuid, pk)
- user_id (uuid, fk -> user.id)
- contact_type (enum: email|phone)
- value (text)
- visibility (enum: hidden|connections-only|public)
- created_at (timestamp)

### user_settings

- id (uuid, pk)
- user_id (uuid, fk -> user.id)
- setting_key (text)
- setting_value (jsonb)
- created_at (timestamp)
- updated_at (timestamp)

### category

- id (uuid, pk)
- name (text) -- new, resale, service
- description (text)
- created_at (timestamp)

### item

- id (uuid, pk)
- user_id (uuid, fk -> user.id)
- type (enum: buy|sell)
- category_id (uuid, fk -> category.id)
- title (text)
- description (text)
- price_string (text) -- price or budget
- visibility (enum: hidden|connections-only|public)
- status (enum: active|archived|deleted)
- created_at (timestamp)
- updated_at (timestamp)

### item_image

- id (uuid, pk)
- item_id (uuid, fk -> item.id)
- url (text)
- alt_text (text)
- order_index (integer)
- created_at (timestamp)

### watch

- id (uuid, pk)
- name (text)
- query_params (text)
- notify (uuid, fk -> contact_info.id, nullable)

### connection

- id (uuid, pk)
- user_a (uuid, fk -> user.id) -- requester
- user_b (uuid, fk -> user.id) -- recipient
- status (enum: pending|accepted|declined)
- created_at (timestamp)
- unique(user_a, user_b)

### thread

- id (uuid, pk)
- item_id (uuid, fk -> item.id)
- creator_id (uuid, fk -> user.id) -- thread initiator
- responder_id (uuid, fk -> user.id) -- other participant
- created_at (timestamp)
- unique(item_id, creator_id, responder_id)

### message

- id (uuid, pk)
- thread_id (uuid, fk -> thread.id)
- sender_id (uuid, fk -> user.id)
- content (text)
- read (boolean)
- created_at (timestamp)

### message_image

- id (uuid, pk)
- message_id (uuid, fk -> message.id)
- url (text)
- order_index (integer)
- created_at (timestamp)

### invite

- id (uuid, pk)
- inviter_id (uuid, fk -> user.id)
- invite_code (text, unique) -- 8 alphanumeric characters
- used_by (uuid, fk -> user.id, nullable)
- used_at (timestamp, nullable)
- revoked_at (timestamp, nullable)
- created_at (timestamp)

## Row Level Security (RLS)

### user

- Public profiles: All authenticated users
- Vendor profiles: Accessible via public routes (does not require authentication)

### contact_info

- Hidden: System only
- Connections Only: Direct connections only (status='accepted')
- Public: Anyone can view, even if not authenticated

### user_settings

- Read/Write: Owner only (user_id)

### category

- Public: All authenticated users (read-only)

### item

- Hidden: Creator only
- Connections Only: Creator + direct connections (status='accepted')
- Public: All authenticated users
- Buy items: Creator shown as "Anonymous" to non-connections

### item_image

- Follows parent item visibility rules
- Images inherit visibility from their item

### connection

- Read: Both parties (user_a or user_b)
- Write: user_a creates with status='pending', user_b updates status

### thread

- Read/write: Participants only (creator_id or responder_id)
- Thread creator identity follows item visibility rules

### message

- Read/write: Participants only (sender_id or recipient in thread)
- Message images inherit thread visibility

### message_image

- Follows parent message visibility rules
- Images inherit visibility from their message

### invite

- Read/Write: Inviter only (inviter_id)
- Read: Used by user (used_by) for validation
See [here](supabase/schemas)

## Key Flows

Expand Down Expand Up @@ -296,12 +130,15 @@ Every feature should support both power users and those who rank low on the "tec
### Invite Generation

1. User clicks "Invite someone"
1. System checks last invite timestamp
1. If < 24 hours: shows limit message
1. If eligible: generates 8-character code
1. Creates invite record
1. Returns link: `/signup?code=CODE`
1. User can revoke anytime (sets revoked_at)
1. User enters invitee's full name
1. User confirms two requirements:
- "I have met this person in person multiple times and know them well"
- "I agree to allow this person to have access to my Contacts-Only information"
1. System checks rate limit via `can_create_invite()` database function (uses database time, excludes revoked invites)
1. If non-revoked invite exists within last 24 hours: shows limit message
1. If eligible: generates 8-character code (uppercase alphanumeric, excludes I/L/O/0/1)
1. Creates invite record with invitee name
1. User can copy/share code or revoke anytime (sets revoked_at)

## Site Structure

Expand Down Expand Up @@ -344,54 +181,60 @@ Content:

## Project Structure

(Only important directories and files are shown for brevity)

```
project-root/
├── wrangler.toml # Cloudflare Workers config
├── wrangler.jsonc # Cloudflare Workers config
├── supabase/
│ ├── config.toml # Supabase configuration
│ ├── migrations/
│ │ ├── 001_initial_schema.sql
│ │ ├── 002_rls_policies.sql
│ │ └── 003_indexes.sql
│ └── seed.sql # Test data
│ ├── schemas/ # Declarative database schemas (source of truth)
│ ├── migrations/ # Auto-generated from schemas via `supabase db diff`
│ │ └── *.sql
│ ├── seeds/ # Test data for local development
│ └── storage/ # Seed storage files
│ └── images/
│ ├── avatars/
│ ├── items/
│ └── messages/
├── src/
│ ├── pages/ # Astro routes
│ │ ├── api/ # API endpoints (JWT-authenticated)
│ │ │ └── invites/
│ │ ├── auth/
│ │ ├── index.astro # Landing page
│ │ ├── about.astro # About page
│ │ ├── about.mdx # About page (MDX)
│ │ ├── content-policy.mdx # Content policy (MDX)
│ │ ├── dashboard.astro # User dashboard
│ │ ├── items/
│ │ │ ├── index.astro # Item listings
│ │ │ ├── [id].astro # Item details
│ │ │ └── new.astro # Create item
│ │ ├── vendors.astro # Vendor directory
│ │ ├── profile/[id].astro # User profiles
│ │ ├── messages/
│ │ │ └── index.astro # Message threads
│ │ ├── signup.astro # Invite signup
│ │ ├── [vendor_id].astro # Vendor profile
│ │ └── v/[vendor_id].astro # Alt vendor route
│ │ └── invites.astro # Invite management
│ ├── components/
│ │ ├── react/ # Interactive components
│ │ │ ├── ItemForm.tsx
│ │ │ ├── MessageThread.tsx
│ │ │ ├── ItemFeed.tsx
│ │ │ └── ConnectionsList.tsx
│ │ └── astro/ # Static components
│ │ ├── Header.astro
│ │ └── ItemCard.astro
│ ├── layouts/
│ │ ├── BaseLayout.astro # Common wrapper
│ │ └── AuthLayout.astro # Auth wrapper
│ └── lib/
│ ├── supabase.ts # Database client
│ ├── auth.ts # Auth utilities
│ └── utils/ # Helpers
│ │ ├── Layout.astro # Base layout
│ │ ├── PageLayoutWithBreadcrumbs.astro
│ │ └── ProseLayout.astro # MDX layout
│ ├── lib/
│ │ ├── auth.ts # Auth utilities (createSupabaseWithJWT, etc.)
│ │ ├── database.types.ts # Generated TypeScript types
│ │ ├── globals.ts
│ │ ├── storage.ts
│ │ ├── themeManager.ts
│ │ └── themes.ts
│ ├── styles/
│ │ ├── global.css
│ │ └── themes.css
│ └── middleware.ts # Route protection
├── tests/
│ ├── unit/ # Unit tests
│ └── e2e/ # Integration tests
│ ├── unit/ # Unit tests (Vitest)
│ ├── e2e/ # E2E tests (Playwright)
│ └── setup.ts
└── .github/
└── workflows/
└── ci.yml # CI/CD pipeline
├── ci.yml # CI/CD pipeline
├── code-review.yml
├── opencode.yml
└── preview-deploy.yaml
```

## Security
Expand All @@ -402,6 +245,7 @@ project-root/
- Session management via Supabase Auth
- Protected routes via Astro middleware
- Invite-only prevents open signups
- API routes use JWT bearer tokens with `createSupabaseWithJWT()` for RLS context

### Authorization

Expand Down Expand Up @@ -432,7 +276,8 @@ project-root/

- Auto-compression on upload (balanced quality/size)
- 5 images per item/message
- Single storage bucket (simpler for MVP)
- Single 'images' bucket with structured folders (avatars/, items/, messages/)
- Storage RLS policies enforce visibility rules matching parent entities

## Error Handling

Expand Down
Loading