forked from gratipay/gratipay.com
-
Notifications
You must be signed in to change notification settings - Fork 0
/
schema.sql
363 lines (315 loc) · 14.9 KB
/
schema.sql
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
-------------------------------------------------------------------------------
-- million trillion trillion
-- | trillion trillion
-- | | trillion
-- | | | billion
-- | | | | million
-- | | | | | thousand
-- | | | | | |
-- numeric(35,2) maxes out at $999,999,999,999,999,999,999,999,999,999,999.00.
-- https://github.com/gratipay/gratipay.com/pull/1274
CREATE TYPE participant_number AS ENUM ('singular', 'plural');
-- https://github.com/gratipay/gratipay.com/pull/2303
CREATE TYPE email_address_with_confirmation AS
(
address text,
confirmed boolean
);
CREATE TABLE participants
( username text PRIMARY KEY
, statement text NOT NULL DEFAULT ''
, last_bill_result text DEFAULT NULL
, session_token text UNIQUE DEFAULT NULL
, session_expires timestamp with time zone DEFAULT (now() + INTERVAL '6 hours')
, ctime timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
, claimed_time timestamp with time zone DEFAULT NULL
, is_admin boolean NOT NULL DEFAULT FALSE
, balance numeric(35,2) NOT NULL DEFAULT 0.0
, anonymous_giving boolean NOT NULL DEFAULT FALSE
, goal numeric(35,2) DEFAULT NULL
, balanced_customer_href text DEFAULT NULL
, last_ach_result text DEFAULT NULL
, is_suspicious boolean DEFAULT NULL
, id bigserial NOT NULL UNIQUE
, username_lower text NOT NULL UNIQUE
, api_key text UNIQUE DEFAULT NULL
, number participant_number NOT NULL DEFAULT 'singular'
, paypal_email text DEFAULT NULL
, anonymous_receiving boolean NOT NULL DEFAULT FALSE
, bitcoin_address text DEFAULT NULL
, avatar_url text
, paypal_fee_cap numeric(35,2)
, email email_address_with_confirmation
, is_closed boolean NOT NULL DEFAULT FALSE
, giving numeric(35,2) NOT NULL DEFAULT 0
, pledging numeric(35,2) NOT NULL DEFAULT 0
, receiving numeric(35,2) NOT NULL DEFAULT 0
, taking numeric(35,2) NOT NULL DEFAULT 0
, npatrons integer NOT NULL DEFAULT 0
, is_free_rider boolean DEFAULT NULL
);
-- https://github.com/gratipay/gratipay.com/pull/1610
CREATE INDEX participants_claimed_time ON participants (claimed_time DESC)
WHERE is_suspicious IS NOT TRUE
AND claimed_time IS NOT null;
CREATE TABLE elsewhere
( id serial PRIMARY KEY
, platform text NOT NULL
, user_id text NOT NULL
, participant text NOT NULL REFERENCES participants ON UPDATE CASCADE ON DELETE RESTRICT
, is_locked boolean NOT NULL DEFAULT FALSE
, access_token text DEFAULT NULL
, refresh_token text DEFAULT NULL
, expires timestamptz DEFAULT NULL
, user_name text
-- Note: using "user_name" instead of "username" avoids having the same
-- column name in the participants and elsewhere tables.
, display_name text
, email text
, avatar_url text
, is_team boolean NOT NULL DEFAULT FALSE
, extra_info json
, token json
, UNIQUE (platform, user_id)
, UNIQUE (platform, participant)
);
-- https://github.com/gratipay/gratipay.com/issues/951
CREATE INDEX elsewhere_participant ON elsewhere(participant);
-- tips -- all times a participant elects to tip another
CREATE TABLE tips
( id serial PRIMARY KEY
, ctime timestamp with time zone NOT NULL
, mtime timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
, tipper text NOT NULL REFERENCES participants ON UPDATE CASCADE ON DELETE RESTRICT
, tippee text NOT NULL REFERENCES participants ON UPDATE CASCADE ON DELETE RESTRICT
, amount numeric(35,2) NOT NULL
, is_funded boolean NOT NULL DEFAULT false
);
CREATE INDEX tips_all ON tips USING btree (tipper, tippee, mtime DESC);
CREATE VIEW current_tips AS
SELECT DISTINCT ON (tipper, tippee) *
FROM tips
ORDER BY tipper, tippee, mtime DESC;
-- Allow updating is_funded via the current_tips view for convenience
CREATE FUNCTION update_tip() RETURNS trigger AS $$
BEGIN
UPDATE tips
SET is_funded = NEW.is_funded
WHERE id = NEW.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER update_current_tip INSTEAD OF UPDATE ON current_tips
FOR EACH ROW EXECUTE PROCEDURE update_tip();
-- https://github.com/gratipay/gratipay.com/pull/2501
CREATE TYPE context_type AS ENUM
('tip', 'take', 'final-gift', 'take-over', 'one-off');
-- transfers -- balance transfers from one user to another
CREATE TABLE transfers
( id serial PRIMARY KEY
, timestamp timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
, tipper text NOT NULL REFERENCES participants ON UPDATE CASCADE ON DELETE RESTRICT
, tippee text NOT NULL REFERENCES participants ON UPDATE CASCADE ON DELETE RESTRICT
, amount numeric(35,2) NOT NULL
, context context_type NOT NULL
);
-- https://github.com/gratipay/gratipay.com/pull/2723
ALTER TABLE transfers ADD CONSTRAINT positive CHECK (amount > 0) NOT VALID;
-- paydays -- payday events, stats about them
CREATE TABLE paydays
( id serial PRIMARY KEY
, ts_start timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
, ts_end timestamp with time zone UNIQUE NOT NULL DEFAULT '1970-01-01T00:00:00+00'::timestamptz
, nparticipants bigint NOT NULL DEFAULT 0
, ntippers bigint NOT NULL DEFAULT 0
, ntips bigint NOT NULL DEFAULT 0
, ntransfers bigint NOT NULL DEFAULT 0
, transfer_volume numeric(35,2) NOT NULL DEFAULT 0.00
, ncc_failing bigint NOT NULL DEFAULT 0
, ncc_missing bigint NOT NULL DEFAULT 0
, ncharges bigint NOT NULL DEFAULT 0
, charge_volume numeric(35,2) NOT NULL DEFAULT 0.00
, charge_fees_volume numeric(35,2) NOT NULL DEFAULT 0.00
, nachs bigint NOT NULL DEFAULT 0
, ach_volume numeric(35,2) NOT NULL DEFAULT 0.00
, ach_fees_volume numeric(35,2) NOT NULL DEFAULT 0.00
, nach_failing bigint NOT NULL DEFAULT 0
, npachinko bigint NOT NULL DEFAULT 0
, pachinko_volume numeric(35,2) NOT NULL DEFAULT 0.00
, nactive bigint NOT NULL DEFAULT 0
, stage integer DEFAULT 0
);
-- https://github.com/gratipay/gratipay.com/pull/2579
CREATE TYPE exchange_status AS ENUM ('pre', 'pending', 'failed', 'succeeded');
-- exchanges -- when a participant moves cash between Gratipay and their bank
CREATE TABLE exchanges
( id serial PRIMARY KEY
, timestamp timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
, amount numeric(35,2) NOT NULL
, fee numeric(35,2) NOT NULL
, participant text NOT NULL REFERENCES participants ON UPDATE CASCADE ON DELETE RESTRICT
, recorder text DEFAULT NULL REFERENCES participants ON UPDATE CASCADE ON DELETE RESTRICT
, note text DEFAULT NULL
, status exchange_status
);
-- https://github.com/gratipay/gratipay.com/issues/406
CREATE TABLE absorptions
( id serial PRIMARY KEY
, timestamp timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
, absorbed_was text NOT NULL -- Not a foreign key!
, absorbed_by text NOT NULL REFERENCES participants ON DELETE RESTRICT ON UPDATE CASCADE
, archived_as text NOT NULL REFERENCES participants ON DELETE RESTRICT ON UPDATE RESTRICT
-- Here we actually want ON UPDATE RESTRICT as a sanity check:
-- noone should be changing usernames of absorbed accounts.
);
-- https://github.com/gratipay/gratipay.com/pull/2701
CREATE TABLE community_members
( slug text NOT NULL
, participant bigint NOT NULL REFERENCES participants(id)
, ctime timestamptz NOT NULL
, mtime timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
, name text NOT NULL
, is_member boolean NOT NULL
);
CREATE INDEX community_members_idx
ON community_members (slug, participant, mtime DESC);
CREATE TABLE communities
( slug text PRIMARY KEY
, name text UNIQUE NOT NULL
, nmembers int NOT NULL
, ctime timestamptz NOT NULL
, CHECK (nmembers >= 0)
);
CREATE FUNCTION upsert_community() RETURNS trigger AS $$
DECLARE
is_member boolean;
BEGIN
IF (SELECT is_suspicious FROM participants WHERE id = NEW.participant) THEN
RETURN NULL;
END IF;
is_member := (
SELECT cur.is_member
FROM community_members cur
WHERE slug = NEW.slug
AND participant = NEW.participant
ORDER BY mtime DESC
LIMIT 1
);
IF (is_member IS NULL AND NEW.is_member IS false OR NEW.is_member = is_member) THEN
RETURN NULL;
END IF;
LOOP
UPDATE communities
SET nmembers = nmembers + (CASE WHEN NEW.is_member THEN 1 ELSE -1 END)
WHERE slug = NEW.slug;
EXIT WHEN FOUND;
BEGIN
INSERT INTO communities
VALUES (NEW.slug, NEW.name, 1, NEW.ctime);
EXCEPTION
WHEN unique_violation THEN
IF (CONSTRAINT_NAME = 'communities_slug_pkey') THEN
CONTINUE; -- Try again
ELSE
RAISE;
END IF;
END;
EXIT;
END LOOP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER upsert_community BEFORE INSERT ON community_members
FOR EACH ROW
EXECUTE PROCEDURE upsert_community();
CREATE VIEW current_community_members AS
SELECT DISTINCT ON (participant, slug) c.*
FROM community_members c
ORDER BY participant, slug, mtime DESC;
-- https://github.com/gratipay/gratipay.com/issues/1100
CREATE TABLE takes
( id serial PRIMARY KEY
, ctime timestamp with time zone NOT NULL
, mtime timestamp with time zone NOT NULL
DEFAULT CURRENT_TIMESTAMP
, member text NOT NULL
REFERENCES participants
ON UPDATE CASCADE
ON DELETE RESTRICT
, team text NOT NULL
REFERENCES participants
ON UPDATE CASCADE
ON DELETE RESTRICT
, amount numeric(35,2) NOT NULL DEFAULT 0.0
, recorder text NOT NULL
REFERENCES participants
ON UPDATE CASCADE
ON DELETE RESTRICT
, CONSTRAINT no_team_recursion CHECK (team != member)
, CONSTRAINT not_negative CHECK ((amount >= (0)::numeric))
);
CREATE VIEW current_takes AS
SELECT * FROM (
SELECT DISTINCT ON (member, team) t.*
FROM takes t
JOIN participants p1 ON p1.username = member
JOIN participants p2 ON p2.username = team
WHERE p1.is_suspicious IS NOT TRUE
AND p2.is_suspicious IS NOT TRUE
ORDER BY member
, team
, mtime DESC
) AS anon WHERE amount > 0;
-- https://github.com/gratipay/gratipay.com/pull/1369
-- The following lets us cast queries to elsewhere_with_participant to get the
-- participant data dereferenced and returned in a composite type along with
-- the elsewhere data.
CREATE TYPE elsewhere_with_participant AS
( id integer
, platform text
, user_id text
, user_name text
, display_name text
, email text
, avatar_url text
, extra_info json
, is_locked boolean
, is_team boolean
, token json
, participant participants
); -- If Postgres had type inheritance this would be even awesomer.
CREATE OR REPLACE FUNCTION load_participant_for_elsewhere (elsewhere)
RETURNS elsewhere_with_participant
AS $$
SELECT $1.id
, $1.platform
, $1.user_id
, $1.user_name
, $1.display_name
, $1.email
, $1.avatar_url
, $1.extra_info
, $1.is_locked
, $1.is_team
, $1.token
, participants.*::participants
FROM participants
WHERE participants.username = $1.participant
;
$$ LANGUAGE SQL;
CREATE CAST (elsewhere AS elsewhere_with_participant)
WITH FUNCTION load_participant_for_elsewhere(elsewhere);
-- https://github.com/gratipay/gratipay.com/pull/2006
CREATE TABLE events
( id serial PRIMARY KEY
, ts timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
, type text NOT NULL
, payload json
);
CREATE INDEX events_ts ON events(ts ASC);
CREATE INDEX events_type ON events(type);
-- https://github.com/gratipay/gratipay.com/issues/1417
CREATE INDEX transfers_tipper_tippee_timestamp_idx
ON transfers
USING btree
(tipper, tippee, timestamp DESC);