-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsupabase_migration.sql
More file actions
218 lines (184 loc) · 10.1 KB
/
Copy pathsupabase_migration.sql
File metadata and controls
218 lines (184 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
-- ============================================================
-- ExpoPay — Complete Supabase Schema Migration
-- Run this in: Supabase Dashboard → SQL Editor → New Query
-- ============================================================
-- ────────────────────────────────────────────────────────────
-- 1. PROFILES
-- ────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT,
universal_id TEXT UNIQUE,
display_name TEXT,
full_name TEXT,
phone_number TEXT,
stellar_address TEXT,
stellar_secret TEXT,
app_pin TEXT,
preferred_currency TEXT DEFAULT 'XLM',
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Safely add columns just in case the table already existed before this script
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS phone_number TEXT;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS stellar_address TEXT;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS stellar_secret TEXT;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS app_pin TEXT;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS preferred_currency TEXT DEFAULT 'XLM';
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS avatar_url TEXT;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();
-- Auto-update updated_at
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS profiles_updated_at ON profiles;
CREATE TRIGGER profiles_updated_at
BEFORE UPDATE ON profiles
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- Indexes
CREATE INDEX IF NOT EXISTS profiles_universal_id_idx ON profiles(universal_id);
CREATE INDEX IF NOT EXISTS profiles_stellar_address_idx ON profiles(stellar_address);
-- RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own profile" ON profiles;
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
DROP POLICY IF EXISTS "Users can update own profile" ON profiles;
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id);
DROP POLICY IF EXISTS "Users can insert own profile" ON profiles;
CREATE POLICY "Users can insert own profile"
ON profiles FOR INSERT
WITH CHECK (auth.uid() = id);
-- ────────────────────────────────────────────────────────────
-- 2. TRANSACTIONS
-- ────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sender_id UUID REFERENCES profiles(id) ON DELETE SET NULL,
recipient_id UUID REFERENCES profiles(id) ON DELETE SET NULL,
sender_universal_id TEXT,
recipient_universal_id TEXT,
amount NUMERIC(20, 7) NOT NULL,
currency TEXT DEFAULT 'XLM',
tx_hash TEXT UNIQUE,
status TEXT DEFAULT 'pending'
CHECK (status IN ('pending','completed','failed')),
note TEXT,
purpose TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS transactions_sender_idx ON transactions(sender_id);
CREATE INDEX IF NOT EXISTS transactions_recipient_idx ON transactions(recipient_id);
CREATE INDEX IF NOT EXISTS transactions_created_idx ON transactions(created_at DESC);
-- RLS
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own transactions" ON transactions;
CREATE POLICY "Users can view own transactions"
ON transactions FOR SELECT
USING (auth.uid() = sender_id OR auth.uid() = recipient_id);
-- ────────────────────────────────────────────────────────────
-- 3. CONTRACTS
-- ────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS contracts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Parties
payer_id UUID REFERENCES profiles(id) ON DELETE SET NULL,
freelancer_id UUID REFERENCES profiles(id) ON DELETE SET NULL,
payer_universal_id TEXT NOT NULL,
freelancer_universal_id TEXT NOT NULL,
payer_stellar_address TEXT,
freelancer_stellar_address TEXT,
-- On-chain
escrow_id BIGINT,
tx_hash_create TEXT,
tx_hash_deliver TEXT,
tx_hash_release TEXT,
tx_hash_refund TEXT,
tx_hash_dispute TEXT,
-- Contract details
title TEXT NOT NULL,
description TEXT,
amount NUMERIC(20, 7) NOT NULL,
currency TEXT DEFAULT 'XLM',
expiry_timestamp BIGINT, -- Unix seconds
-- State machine
status TEXT DEFAULT 'funded'
CHECK (status IN (
'created','funded','delivered',
'released','disputed','refunded','cancelled'
)),
-- Dispute tracking
dispute_reason TEXT,
disputed_by TEXT CHECK (disputed_by IN ('payer','freelancer')),
-- TRUE when payer disputes a contract that was already in 'delivered' state.
-- Blocks payer self-refund to prevent "get work for free" attack.
dispute_after_delivery BOOLEAN DEFAULT FALSE,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
funded_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
released_at TIMESTAMPTZ,
disputed_at TIMESTAMPTZ,
refunded_at TIMESTAMPTZ
);
-- Indexes
CREATE INDEX IF NOT EXISTS contracts_payer_idx ON contracts(payer_id);
CREATE INDEX IF NOT EXISTS contracts_freelancer_idx ON contracts(freelancer_id);
CREATE INDEX IF NOT EXISTS contracts_status_idx ON contracts(status);
CREATE INDEX IF NOT EXISTS contracts_created_idx ON contracts(created_at DESC);
-- RLS
ALTER TABLE contracts ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Contract parties can view" ON contracts;
CREATE POLICY "Contract parties can view"
ON contracts FOR SELECT
USING (auth.uid() = payer_id OR auth.uid() = freelancer_id);
DROP POLICY IF EXISTS "Contract parties can update" ON contracts;
CREATE POLICY "Contract parties can update"
ON contracts FOR UPDATE
USING (auth.uid() = payer_id OR auth.uid() = freelancer_id);
DROP POLICY IF EXISTS "Authenticated users can create contracts" ON contracts;
CREATE POLICY "Authenticated users can create contracts"
ON contracts FOR INSERT
WITH CHECK (auth.uid() = payer_id);
-- ────────────────────────────────────────────────────────────
-- 4. SERVICE ROLE BYPASS (for server-side API using supabaseAdmin)
-- The supabaseAdmin client uses the service_role key which
-- bypasses RLS automatically — no additional policy needed.
-- ────────────────────────────────────────────────────────────
-- ────────────────────────────────────────────────────────────
-- 5. FX QUOTES
-- ────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS fx_quotes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES profiles(id) ON DELETE CASCADE,
from_currency TEXT NOT NULL,
to_currency TEXT NOT NULL,
rate NUMERIC(20, 8) NOT NULL,
source_amount NUMERIC(20, 7) NOT NULL,
target_amount NUMERIC(20, 7) NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
used BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS fx_quotes_user_idx ON fx_quotes(user_id);
CREATE INDEX IF NOT EXISTS fx_quotes_expires_idx ON fx_quotes(expires_at DESC);
ALTER TABLE fx_quotes ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own quotes" ON fx_quotes;
CREATE POLICY "Users can view own quotes"
ON fx_quotes FOR SELECT
USING (auth.uid() = user_id);
-- ────────────────────────────────────────────────────────────
-- 6. GRANT USAGE (standard Supabase setup)
-- ────────────────────────────────────────────────────────────
GRANT USAGE ON SCHEMA public TO anon, authenticated;
GRANT ALL ON ALL TABLES IN SCHEMA public TO authenticated;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO authenticated;