mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Update
This commit is contained in:
1 parent
71667d6104
commit
f46f0d59df
24 files changed
+2647
-516
No files matched your search
@@ -0,0 +1,136 @@
|
||||
# Supabase - Agent Guidelines
|
||||
|
||||
**STRICT RULES - DO NOT VIOLATE**
|
||||
|
||||
## Architecture
|
||||
|
||||
This contains Supabase configuration:
|
||||
- **Migrations**: SQL migrations in `migrations/`
|
||||
- **Edge Functions**: Deno functions in `functions/`
|
||||
- **Email Templates**: Auth email templates in `email-templates/`
|
||||
- **Config**: Local development config in `config.toml`
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
functions/
|
||||
delete-relationship/ # Delete couple relationship
|
||||
manage-couple/ # Join/leave couple
|
||||
revenuecat-webhook/ # Subscription webhook handler
|
||||
send-message-notification/ # Push notification for messages
|
||||
send-notification/ # Push notification for matches
|
||||
submit-response/ # Save response, check matches
|
||||
sync-subscription/ # Sync RevenueCat status
|
||||
migrations/ # SQL migrations
|
||||
email-templates/ # Auth email templates
|
||||
config.toml # Local dev configuration
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### Edge Function Deployment
|
||||
|
||||
**ALWAYS use `verify_jwt: false`** when deploying via MCP tools.
|
||||
|
||||
Supabase's built-in JWT verification causes 401 errors. Handle auth manually:
|
||||
|
||||
```typescript
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(
|
||||
authHeader.replace("Bearer ", "")
|
||||
);
|
||||
```
|
||||
|
||||
### Edge Function Pattern
|
||||
|
||||
All edge functions MUST follow this structure:
|
||||
|
||||
```typescript
|
||||
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||
};
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
// Handle CORS preflight
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL")!,
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
|
||||
);
|
||||
|
||||
// Manual auth verification
|
||||
const authHeader = req.headers.get("Authorization");
|
||||
if (!authHeader) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Missing authorization header" }),
|
||||
{ status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(
|
||||
authHeader.replace("Bearer ", "")
|
||||
);
|
||||
|
||||
// ... function logic
|
||||
} catch (error) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Internal server error" }),
|
||||
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Function Auth Methods
|
||||
|
||||
| Function | Auth Method |
|
||||
|----------|-------------|
|
||||
| `manage-couple` | Manual via getUser() |
|
||||
| `submit-response` | Manual via getUser() |
|
||||
| `sync-subscription` | Manual via getUser() |
|
||||
| `delete-relationship` | Manual via getUser() |
|
||||
| `revenuecat-webhook` | Webhook signature validation |
|
||||
| `send-notification` | Internal trigger (no auth) |
|
||||
| `send-message-notification` | Internal trigger (no auth) |
|
||||
|
||||
### Migration Rules
|
||||
|
||||
- Use `apply_migration` MCP tool for DDL operations
|
||||
- Never hardcode generated IDs in data migrations
|
||||
- Always verify target environment (prod vs non-prod) before running
|
||||
|
||||
### Database Patterns
|
||||
|
||||
- Use UPSERT with `onConflict` for responses: `onConflict: "user_id,question_id"`
|
||||
- Use UPSERT for matches: `onConflict: "couple_id,question_id"`
|
||||
- RLS policies enforce couple-level data isolation
|
||||
|
||||
### Profile Creation
|
||||
|
||||
The `on_auth_user_created` trigger automatically creates a profile. Never assume a profile exists without checking - check for null before operations.
|
||||
|
||||
## MCP Environments
|
||||
|
||||
| Environment | MCP Server |
|
||||
|-------------|------------|
|
||||
| Production | `mcp__sauci-prod__*` |
|
||||
| Non-Production | `mcp__sauci-non-prod__*` |
|
||||
|
||||
**ALWAYS verify environment before destructive operations.**
|
||||
|
||||
## Forbidden Actions
|
||||
|
||||
1. **DO NOT** use `verify_jwt: true` when deploying functions via MCP
|
||||
2. **DO NOT** skip CORS headers in responses
|
||||
3. **DO NOT** use service role key on client-side
|
||||
4. **DO NOT** hardcode IDs in migrations
|
||||
5. **DO NOT** assume profiles exist - always check for null
|
||||
6. **DO NOT** run migrations on production without explicit confirmation
|
||||
7. **DO NOT** expose internal error details in responses
|
||||
@@ -1028,6 +1028,7 @@ CREATE POLICY "Users can view own feedback screenshots"
|
||||
);
|
||||
|
||||
-- Chat media policies
|
||||
-- IMPORTANT: Use storage.objects.name (not just "name") to avoid ambiguity with profiles.name
|
||||
CREATE POLICY "Users can view chat media in their matches"
|
||||
ON storage.objects FOR SELECT TO authenticated
|
||||
USING (
|
||||
@@ -1035,7 +1036,7 @@ CREATE POLICY "Users can view chat media in their matches"
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM matches m
|
||||
JOIN profiles p ON p.couple_id = m.couple_id
|
||||
WHERE (storage.foldername(name))[1] = m.id::text
|
||||
WHERE (storage.foldername(storage.objects.name))[1] = m.id::text
|
||||
AND p.id = auth.uid()
|
||||
)
|
||||
);
|
||||
@@ -1047,7 +1048,7 @@ CREATE POLICY "Users can upload chat media to their matches"
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM matches m
|
||||
JOIN profiles p ON p.couple_id = m.couple_id
|
||||
WHERE (storage.foldername(name))[1] = m.id::text
|
||||
WHERE (storage.foldername(storage.objects.name))[1] = m.id::text
|
||||
AND p.id = auth.uid()
|
||||
)
|
||||
);
|
||||
@@ -1059,7 +1060,7 @@ CREATE POLICY "Users can delete their own chat media"
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM matches m
|
||||
JOIN profiles p ON p.couple_id = m.couple_id
|
||||
WHERE (storage.foldername(name))[1] = m.id::text
|
||||
WHERE (storage.foldername(storage.objects.name))[1] = m.id::text
|
||||
AND p.id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
-- Fix storage policies that incorrectly reference p.name instead of storage.objects.name
|
||||
-- Bug: Using unqualified "name" in a subquery with profiles table causes Postgres to resolve
|
||||
-- it to profiles.name instead of storage.objects.name
|
||||
-- Fix: Explicitly qualify as storage.objects.name to reference the storage object's file path
|
||||
|
||||
-- Drop existing broken policies
|
||||
DROP POLICY IF EXISTS "Users can upload chat media to their matches" ON storage.objects;
|
||||
DROP POLICY IF EXISTS "Users can view chat media in their matches" ON storage.objects;
|
||||
DROP POLICY IF EXISTS "Users can delete their own chat media" ON storage.objects;
|
||||
|
||||
-- Recreate with explicit table qualification to avoid column name ambiguity
|
||||
CREATE POLICY "Users can upload chat media to their matches"
|
||||
ON storage.objects FOR INSERT TO authenticated
|
||||
WITH CHECK (
|
||||
bucket_id = 'chat-media'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM matches m
|
||||
JOIN profiles p ON p.couple_id = m.couple_id
|
||||
WHERE (storage.foldername(storage.objects.name))[1] = m.id::text
|
||||
AND p.id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can view chat media in their matches"
|
||||
ON storage.objects FOR SELECT TO authenticated
|
||||
USING (
|
||||
bucket_id = 'chat-media'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM matches m
|
||||
JOIN profiles p ON p.couple_id = m.couple_id
|
||||
WHERE (storage.foldername(storage.objects.name))[1] = m.id::text
|
||||
AND p.id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can delete their own chat media"
|
||||
ON storage.objects FOR DELETE TO authenticated
|
||||
USING (
|
||||
bucket_id = 'chat-media'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM matches m
|
||||
JOIN profiles p ON p.couple_id = m.couple_id
|
||||
WHERE (storage.foldername(storage.objects.name))[1] = m.id::text
|
||||
AND p.id = auth.uid()
|
||||
)
|
||||
);
|
||||
Reference in new issue
Block a user