-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpgpm-achievements--0.15.3.sql
More file actions
280 lines (241 loc) · 9.81 KB
/
pgpm-achievements--0.15.3.sql
File metadata and controls
280 lines (241 loc) · 9.81 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
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
\echo Use "CREATE EXTENSION pgpm-achievements" to load this file. \quit
CREATE SCHEMA IF NOT EXISTS status_private;
GRANT USAGE ON SCHEMA status_private TO authenticated, anonymous;
ALTER DEFAULT PRIVILEGES IN SCHEMA status_private
GRANT EXECUTE ON FUNCTIONS TO authenticated;
CREATE SCHEMA IF NOT EXISTS status_public;
GRANT USAGE ON SCHEMA status_public TO authenticated, anonymous;
ALTER DEFAULT PRIVILEGES IN SCHEMA status_public
GRANT EXECUTE ON FUNCTIONS TO authenticated;
CREATE TABLE status_public.user_steps (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id uuid NOT NULL,
name text NOT NULL,
count int NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE status_public.user_steps IS 'The user achieving a requirement for a level. Log table that has every single step ever taken.';
COMMENT ON COLUMN status_public.user_steps.id IS 'Unique identifier for this step record';
COMMENT ON COLUMN status_public.user_steps.user_id IS 'User who performed this step';
COMMENT ON COLUMN status_public.user_steps.name IS 'Name of the level requirement this step counts toward';
COMMENT ON COLUMN status_public.user_steps.count IS 'Number of units this step contributes (default 1)';
COMMENT ON COLUMN status_public.user_steps.created_at IS 'Timestamp when this step was recorded';
CREATE INDEX ON status_public.user_steps (user_id, name);
CREATE FUNCTION status_private.user_completed_step(step text, user_id uuid DEFAULT jwt_public.current_user_id()) RETURNS void AS $EOFCODE$
INSERT INTO status_public.user_steps ( name, user_id, count )
VALUES ( step, user_id, 1 );
$EOFCODE$ LANGUAGE sql VOLATILE SECURITY DEFINER;
CREATE FUNCTION status_private.user_incompleted_step(step text, user_id uuid DEFAULT jwt_public.current_user_id()) RETURNS void AS $EOFCODE$
BEGIN
DELETE FROM status_public.user_steps s
WHERE s.user_id = user_incompleted_step.user_id
AND s.name = step;
DELETE FROM status_public.user_achievements a
WHERE a.user_id = user_incompleted_step.user_id
AND a.name = step;
END;
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
CREATE FUNCTION status_private.tg_achievement() RETURNS trigger AS $EOFCODE$
DECLARE
is_null boolean;
task_name text;
BEGIN
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
task_name = TG_ARGV[1]::text;
EXECUTE format('SELECT ($1).%s IS NULL', TG_ARGV[0])
USING NEW INTO is_null;
IF (is_null IS FALSE) THEN
PERFORM status_private.user_completed_step(task_name);
END IF;
RETURN NEW;
END IF;
END;
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
CREATE FUNCTION status_private.tg_achievement_toggle() RETURNS trigger AS $EOFCODE$
DECLARE
is_null boolean;
task_name text;
BEGIN
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
task_name = TG_ARGV[1]::text;
EXECUTE format('SELECT ($1).%s IS NULL', TG_ARGV[0])
USING NEW INTO is_null;
IF (is_null IS TRUE) THEN
PERFORM status_private.user_incompleted_step(task_name);
ELSE
PERFORM status_private.user_completed_step(task_name);
END IF;
RETURN NEW;
END IF;
END;
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
CREATE FUNCTION status_private.tg_achievement_boolean() RETURNS trigger AS $EOFCODE$
DECLARE
is_true boolean;
task_name text;
BEGIN
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
task_name = TG_ARGV[1]::text;
EXECUTE format('SELECT ($1).%s IS TRUE', TG_ARGV[0])
USING NEW INTO is_true;
IF (is_true IS TRUE) THEN
PERFORM status_private.user_completed_step(task_name);
END IF;
RETURN NEW;
END IF;
END;
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
CREATE FUNCTION status_private.tg_achievement_toggle_boolean() RETURNS trigger AS $EOFCODE$
DECLARE
is_true boolean;
task_name text;
BEGIN
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
task_name = TG_ARGV[1]::text;
EXECUTE format('SELECT ($1).%s IS TRUE', TG_ARGV[0])
USING NEW INTO is_true;
IF (is_true IS TRUE) THEN
PERFORM status_private.user_completed_step(task_name);
ELSE
PERFORM status_private.user_incompleted_step(task_name);
END IF;
RETURN NEW;
END IF;
END;
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
CREATE TABLE status_public.user_achievements (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id uuid NOT NULL,
name text NOT NULL,
count int NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT user_achievements_unique_key
UNIQUE (user_id, name)
);
COMMENT ON TABLE status_public.user_achievements IS 'This table represents the users progress for particular level requirements, tallying the total count. This table is updated via triggers and should not be updated maually.';
COMMENT ON COLUMN status_public.user_achievements.id IS 'Unique identifier for this achievement progress record';
COMMENT ON COLUMN status_public.user_achievements.user_id IS 'User whose progress is being tracked';
COMMENT ON COLUMN status_public.user_achievements.name IS 'Name of the level requirement this progress relates to';
COMMENT ON COLUMN status_public.user_achievements.count IS 'Accumulated count toward the requirement (updated by triggers)';
COMMENT ON COLUMN status_public.user_achievements.created_at IS 'Timestamp when this progress record was first created';
CREATE INDEX ON status_public.user_achievements (user_id, name);
CREATE FUNCTION status_private.upsert_achievement(vuser_id uuid, vname text, vcount int) RETURNS void AS $EOFCODE$
BEGIN
INSERT INTO status_public.user_achievements (user_id, name, count)
VALUES
(vuser_id, vname, GREATEST(vcount, 0))
ON CONFLICT ON CONSTRAINT user_achievements_unique_key
DO UPDATE SET
-- look ma! you can actually do aliases inside on conflict
count = user_achievements.count + EXCLUDED.count
;
END;
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
CREATE TABLE status_public.levels (
name text NOT NULL PRIMARY KEY
);
COMMENT ON TABLE status_public.levels IS 'Levels for achievement';
COMMENT ON COLUMN status_public.levels.name IS 'Unique level name used as the primary key (e.g. bronze, silver, gold)';
GRANT SELECT ON status_public.levels TO PUBLIC;
CREATE TABLE status_public.level_requirements (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
name text NOT NULL,
level text NOT NULL,
required_count int DEFAULT 1,
priority int DEFAULT 100,
UNIQUE (name, level)
);
COMMENT ON TABLE status_public.level_requirements IS 'Requirements to achieve a level';
COMMENT ON COLUMN status_public.level_requirements.id IS 'Unique identifier for this requirement';
COMMENT ON COLUMN status_public.level_requirements.name IS 'Requirement name (e.g. posts_created, logins); matches user_steps.name';
COMMENT ON COLUMN status_public.level_requirements.level IS 'Level this requirement belongs to (references levels.name)';
COMMENT ON COLUMN status_public.level_requirements.required_count IS 'Number of steps needed to satisfy this requirement (default 1)';
COMMENT ON COLUMN status_public.level_requirements.priority IS 'Display/evaluation order; lower numbers are checked first (default 100)';
CREATE INDEX ON status_public.level_requirements (name, level, priority);
GRANT SELECT ON status_public.levels TO authenticated;
CREATE FUNCTION status_public.steps_required(vlevel text, vrole_id uuid DEFAULT jwt_public.current_user_id()) RETURNS SETOF status_public.level_requirements AS $EOFCODE$
BEGIN
RETURN QUERY
SELECT
level_requirements.id,
level_requirements.name,
level_requirements.level,
-1*(coalesce(user_achievements.count,0)-level_requirements.required_count) as required_count,
level_requirements.priority
FROM
status_public.level_requirements
FULL OUTER JOIN status_public.user_achievements ON (
user_achievements.name = level_requirements.name
AND user_achievements.user_id =vrole_id
)
JOIN status_public.levels ON (level_requirements.level = levels.name)
WHERE
level_requirements.level = vlevel
AND -1*(coalesce(user_achievements.count,0)-level_requirements.required_count) > 0
ORDER BY priority ASC
;
END;
$EOFCODE$ LANGUAGE plpgsql STABLE;
CREATE FUNCTION status_public.user_achieved(vlevel text, vrole_id uuid DEFAULT jwt_public.current_user_id()) RETURNS boolean AS $EOFCODE$
DECLARE
c int;
BEGIN
SELECT COUNT(*) FROM
status_public.steps_required(
vlevel,
vrole_id
)
INTO c;
RETURN c <= 0;
END;
$EOFCODE$ LANGUAGE plpgsql STABLE;
ALTER TABLE status_public.user_achievements
ENABLE ROW LEVEL SECURITY;
CREATE POLICY can_select_user_achievements
ON status_public.user_achievements
AS PERMISSIVE
FOR SELECT
TO PUBLIC
USING (
jwt_public.current_user_id() = user_id
);
CREATE POLICY can_insert_user_achievements
ON status_public.user_achievements
AS PERMISSIVE
FOR INSERT
TO PUBLIC
WITH CHECK (
false
);
CREATE POLICY can_update_user_achievements
ON status_public.user_achievements
AS PERMISSIVE
FOR UPDATE
TO PUBLIC
USING (
false
);
CREATE POLICY can_delete_user_achievements
ON status_public.user_achievements
AS PERMISSIVE
FOR DELETE
TO PUBLIC
USING (
false
);
GRANT INSERT ON status_public.user_achievements TO authenticated;
GRANT SELECT ON status_public.user_achievements TO authenticated;
GRANT UPDATE ON status_public.user_achievements TO authenticated;
GRANT DELETE ON status_public.user_achievements TO authenticated;
CREATE FUNCTION status_private.tg_update_achievements_tg() RETURNS trigger AS $EOFCODE$
DECLARE
BEGIN
PERFORM status_private.upsert_achievement(NEW.user_id, NEW.name, NEW.count);
RETURN NEW;
END;
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
CREATE TRIGGER update_achievements_tg
AFTER INSERT
ON status_public.user_steps
FOR EACH ROW
EXECUTE PROCEDURE status_private.tg_update_achievements_tg();