@pgpm/achievements 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/Makefile +6 -0
- package/README.md +5 -0
- package/__tests__/__snapshots__/achievements.test.ts.snap +180 -0
- package/__tests__/__snapshots__/triggers.test.ts.snap +112 -0
- package/__tests__/achievements.test.ts +250 -0
- package/__tests__/triggers.test.ts +167 -0
- package/deploy/schemas/status_private/procedures/status_triggers.sql +148 -0
- package/deploy/schemas/status_private/procedures/upsert_achievement.sql +24 -0
- package/deploy/schemas/status_private/procedures/user_completed_step.sql +16 -0
- package/deploy/schemas/status_private/procedures/user_incompleted_step.sql +22 -0
- package/deploy/schemas/status_private/schema.sql +15 -0
- package/deploy/schemas/status_public/procedures/steps_required.sql +59 -0
- package/deploy/schemas/status_public/procedures/user_achieved.sql +29 -0
- package/deploy/schemas/status_public/schema.sql +15 -0
- package/deploy/schemas/status_public/tables/level_requirements/table.sql +21 -0
- package/deploy/schemas/status_public/tables/levels/table.sql +15 -0
- package/deploy/schemas/status_public/tables/user_achievements/policies/enable_row_level_security.sql +11 -0
- package/deploy/schemas/status_public/tables/user_achievements/policies/user_achievements_policy.sql +38 -0
- package/deploy/schemas/status_public/tables/user_achievements/table.sql +20 -0
- package/deploy/schemas/status_public/tables/user_levels/table.sql +20 -0
- package/deploy/schemas/status_public/tables/user_steps/table.sql +18 -0
- package/deploy/schemas/status_public/tables/user_steps/triggers/update_achievements_tg.sql +25 -0
- package/jest.config.js +15 -0
- package/launchql-achievements.control +8 -0
- package/launchql.plan +20 -0
- package/package.json +29 -0
- package/revert/schemas/status_private/procedures/status_triggers.sql +10 -0
- package/revert/schemas/status_private/procedures/upsert_achievement.sql +7 -0
- package/revert/schemas/status_private/procedures/user_completed_step.sql +7 -0
- package/revert/schemas/status_private/procedures/user_incompleted_step.sql +7 -0
- package/revert/schemas/status_private/schema.sql +7 -0
- package/revert/schemas/status_public/procedures/steps_required.sql +7 -0
- package/revert/schemas/status_public/procedures/user_achieved.sql +7 -0
- package/revert/schemas/status_public/schema.sql +7 -0
- package/revert/schemas/status_public/tables/level_requirements/table.sql +7 -0
- package/revert/schemas/status_public/tables/levels/table.sql +7 -0
- package/revert/schemas/status_public/tables/user_achievements/policies/enable_row_level_security.sql +8 -0
- package/revert/schemas/status_public/tables/user_achievements/policies/user_achievements_policy.sql +18 -0
- package/revert/schemas/status_public/tables/user_achievements/table.sql +7 -0
- package/revert/schemas/status_public/tables/user_levels/table.sql +7 -0
- package/revert/schemas/status_public/tables/user_steps/table.sql +7 -0
- package/revert/schemas/status_public/tables/user_steps/triggers/update_achievements_tg.sql +8 -0
- package/sqitch.plan +20 -0
- package/sql/launchql-achievements--0.4.6.sql +264 -0
- package/verify/schemas/status_private/procedures/status_triggers.sql +10 -0
- package/verify/schemas/status_private/procedures/upsert_achievement.sql +7 -0
- package/verify/schemas/status_private/procedures/user_completed_step.sql +7 -0
- package/verify/schemas/status_private/procedures/user_incompleted_step.sql +7 -0
- package/verify/schemas/status_private/schema.sql +7 -0
- package/verify/schemas/status_public/procedures/steps_required.sql +7 -0
- package/verify/schemas/status_public/procedures/user_achieved.sql +7 -0
- package/verify/schemas/status_public/schema.sql +7 -0
- package/verify/schemas/status_public/tables/level_requirements/table.sql +7 -0
- package/verify/schemas/status_public/tables/levels/table.sql +7 -0
- package/verify/schemas/status_public/tables/user_achievements/policies/enable_row_level_security.sql +7 -0
- package/verify/schemas/status_public/tables/user_achievements/policies/user_achievements_policy.sql +15 -0
- package/verify/schemas/status_public/tables/user_achievements/table.sql +7 -0
- package/verify/schemas/status_public/tables/user_levels/table.sql +7 -0
- package/verify/schemas/status_public/tables/user_steps/table.sql +7 -0
- package/verify/schemas/status_public/tables/user_steps/triggers/update_achievements_tg.sql +8 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { getConnections, PgTestClient } from 'pgsql-test';
|
|
2
|
+
import { snapshot } from 'graphile-test';
|
|
3
|
+
|
|
4
|
+
let pg: PgTestClient;
|
|
5
|
+
let teardown: () => Promise<void>;
|
|
6
|
+
|
|
7
|
+
const user_id = 'b9d22af1-62c7-43a5-b8c4-50630bbd4962';
|
|
8
|
+
|
|
9
|
+
const levels = ['newbie', 'advanced'];
|
|
10
|
+
|
|
11
|
+
const newbie = [
|
|
12
|
+
['upload_profile_image'],
|
|
13
|
+
['complete_action', 5],
|
|
14
|
+
['accept_cookies'],
|
|
15
|
+
['accept_privacy'],
|
|
16
|
+
['agree_to_terms']
|
|
17
|
+
];
|
|
18
|
+
const advanced = [
|
|
19
|
+
['invite_users', 15],
|
|
20
|
+
['complete_action', 15]
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
({ pg, teardown } = await getConnections());
|
|
25
|
+
|
|
26
|
+
await pg.any(`CREATE TABLE status_public.mytable (
|
|
27
|
+
id serial,
|
|
28
|
+
name text,
|
|
29
|
+
toggle text,
|
|
30
|
+
is_approved boolean,
|
|
31
|
+
is_verified boolean default false
|
|
32
|
+
);`);
|
|
33
|
+
|
|
34
|
+
await pg.any(`CREATE TRIGGER mytable_tg1
|
|
35
|
+
BEFORE INSERT ON status_public.mytable
|
|
36
|
+
FOR EACH ROW
|
|
37
|
+
EXECUTE FUNCTION status_private.tg_achievement('name', 'tg_achievement');`);
|
|
38
|
+
|
|
39
|
+
await pg.any(`CREATE TRIGGER mytable_tg2
|
|
40
|
+
BEFORE UPDATE ON status_public.mytable
|
|
41
|
+
FOR EACH ROW
|
|
42
|
+
WHEN (NEW.name IS DISTINCT FROM OLD.name)
|
|
43
|
+
EXECUTE FUNCTION status_private.tg_achievement('name', 'tg_achievement');`);
|
|
44
|
+
|
|
45
|
+
await pg.any(`CREATE TRIGGER mytable_tg3
|
|
46
|
+
BEFORE INSERT ON status_public.mytable
|
|
47
|
+
FOR EACH ROW
|
|
48
|
+
EXECUTE FUNCTION status_private.tg_achievement_toggle('toggle', 'tg_achievement_toggle');`);
|
|
49
|
+
|
|
50
|
+
await pg.any(`CREATE TRIGGER mytable_tg4
|
|
51
|
+
BEFORE UPDATE ON status_public.mytable
|
|
52
|
+
FOR EACH ROW
|
|
53
|
+
WHEN (NEW.toggle IS DISTINCT FROM OLD.toggle)
|
|
54
|
+
EXECUTE FUNCTION status_private.tg_achievement_toggle('toggle', 'tg_achievement_toggle');`);
|
|
55
|
+
|
|
56
|
+
await pg.any(`CREATE TRIGGER mytable_tg5
|
|
57
|
+
BEFORE INSERT ON status_public.mytable
|
|
58
|
+
FOR EACH ROW
|
|
59
|
+
EXECUTE FUNCTION status_private.tg_achievement_boolean('is_approved', 'tg_achievement_boolean');`);
|
|
60
|
+
|
|
61
|
+
await pg.any(`CREATE TRIGGER mytable_tg6
|
|
62
|
+
BEFORE UPDATE ON status_public.mytable
|
|
63
|
+
FOR EACH ROW
|
|
64
|
+
WHEN (NEW.is_approved IS DISTINCT FROM OLD.is_approved)
|
|
65
|
+
EXECUTE FUNCTION status_private.tg_achievement_boolean('is_approved', 'tg_achievement_boolean');`);
|
|
66
|
+
|
|
67
|
+
await pg.any(`CREATE TRIGGER mytable_tg7
|
|
68
|
+
BEFORE INSERT ON status_public.mytable
|
|
69
|
+
FOR EACH ROW
|
|
70
|
+
EXECUTE FUNCTION status_private.tg_achievement_toggle_boolean('is_verified', 'tg_achievement_toggle_boolean');`);
|
|
71
|
+
|
|
72
|
+
await pg.any(`CREATE TRIGGER mytable_tg8
|
|
73
|
+
BEFORE UPDATE ON status_public.mytable
|
|
74
|
+
FOR EACH ROW
|
|
75
|
+
WHEN (NEW.is_verified IS DISTINCT FROM OLD.is_verified)
|
|
76
|
+
EXECUTE FUNCTION status_private.tg_achievement_toggle_boolean('is_verified', 'tg_achievement_toggle_boolean');`);
|
|
77
|
+
|
|
78
|
+
await pg.setContext({
|
|
79
|
+
'jwt.claims.user_id': user_id
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
afterAll(async () => {
|
|
84
|
+
await teardown();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
beforeEach(async () => {
|
|
88
|
+
await pg.beforeEach();
|
|
89
|
+
|
|
90
|
+
for (const name of levels) {
|
|
91
|
+
await pg.any(
|
|
92
|
+
`INSERT INTO status_public.levels (name) VALUES ($1) ON CONFLICT DO NOTHING`,
|
|
93
|
+
[name]
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const [name, required_count = 1] of newbie) {
|
|
98
|
+
await pg.any(
|
|
99
|
+
`INSERT INTO status_public.level_requirements (name, level, required_count)
|
|
100
|
+
VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`,
|
|
101
|
+
[name, 'newbie', required_count]
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
for (const [name, required_count = 1] of advanced) {
|
|
105
|
+
await pg.any(
|
|
106
|
+
`INSERT INTO status_public.level_requirements (name, level, required_count)
|
|
107
|
+
VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`,
|
|
108
|
+
[name, 'advanced', required_count]
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterEach(async () => {
|
|
114
|
+
await pg.afterEach();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('newbie', async () => {
|
|
118
|
+
const beforeInsert = await pg.any(
|
|
119
|
+
`SELECT * FROM status_public.user_achievements ORDER BY name`
|
|
120
|
+
);
|
|
121
|
+
expect(snapshot({ beforeInsert })).toMatchSnapshot();
|
|
122
|
+
|
|
123
|
+
await pg.any(
|
|
124
|
+
`INSERT INTO status_public.mytable (name) VALUES ($1)`,
|
|
125
|
+
['upload_profile_image']
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const afterFirstInsert = await pg.any(
|
|
129
|
+
`SELECT * FROM status_public.user_achievements ORDER BY name`
|
|
130
|
+
);
|
|
131
|
+
expect(snapshot({ afterFirstInsert })).toMatchSnapshot();
|
|
132
|
+
|
|
133
|
+
await pg.any(`UPDATE status_public.mytable SET toggle = 'yo'`);
|
|
134
|
+
|
|
135
|
+
const afterUpdateToggleToValue = await pg.any(
|
|
136
|
+
`SELECT * FROM status_public.user_achievements ORDER BY name`
|
|
137
|
+
);
|
|
138
|
+
expect(snapshot({ afterUpdateToggleToValue })).toMatchSnapshot();
|
|
139
|
+
|
|
140
|
+
await pg.any(`UPDATE status_public.mytable SET toggle = NULL`);
|
|
141
|
+
|
|
142
|
+
const afterUpdateToggleToNull = await pg.any(
|
|
143
|
+
`SELECT * FROM status_public.user_achievements ORDER BY name`
|
|
144
|
+
);
|
|
145
|
+
expect(snapshot({ afterUpdateToggleToNull })).toMatchSnapshot();
|
|
146
|
+
|
|
147
|
+
await pg.any(`UPDATE status_public.mytable SET is_verified = TRUE`);
|
|
148
|
+
|
|
149
|
+
const afterIsVerifiedIsTrue = await pg.any(
|
|
150
|
+
`SELECT * FROM status_public.user_achievements ORDER BY name`
|
|
151
|
+
);
|
|
152
|
+
expect(snapshot({ afterIsVerifiedIsTrue })).toMatchSnapshot();
|
|
153
|
+
|
|
154
|
+
await pg.any(`UPDATE status_public.mytable SET is_verified = FALSE`);
|
|
155
|
+
|
|
156
|
+
const afterIsVerifiedIsFalse = await pg.any(
|
|
157
|
+
`SELECT * FROM status_public.user_achievements ORDER BY name`
|
|
158
|
+
);
|
|
159
|
+
expect(snapshot({ afterIsVerifiedIsFalse })).toMatchSnapshot();
|
|
160
|
+
|
|
161
|
+
await pg.any(`UPDATE status_public.mytable SET is_approved = TRUE`);
|
|
162
|
+
|
|
163
|
+
const afterIsApprovedTrue = await pg.any(
|
|
164
|
+
`SELECT * FROM status_public.user_achievements ORDER BY name`
|
|
165
|
+
);
|
|
166
|
+
expect(snapshot({ afterIsApprovedTrue })).toMatchSnapshot();
|
|
167
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
-- Deploy schemas/status_private/procedures/status_triggers to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_private/schema
|
|
4
|
+
-- requires: schemas/status_private/procedures/user_completed_step
|
|
5
|
+
-- requires: schemas/status_private/procedures/user_incompleted_step
|
|
6
|
+
|
|
7
|
+
BEGIN;
|
|
8
|
+
|
|
9
|
+
CREATE FUNCTION status_private.tg_achievement ()
|
|
10
|
+
RETURNS TRIGGER
|
|
11
|
+
AS $$
|
|
12
|
+
DECLARE
|
|
13
|
+
is_null boolean;
|
|
14
|
+
task_name text;
|
|
15
|
+
BEGIN
|
|
16
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
17
|
+
task_name = TG_ARGV[1]::text;
|
|
18
|
+
EXECUTE format('SELECT ($1).%s IS NULL', TG_ARGV[0])
|
|
19
|
+
USING NEW INTO is_null;
|
|
20
|
+
IF (is_null IS FALSE) THEN
|
|
21
|
+
PERFORM status_private.user_completed_step(task_name);
|
|
22
|
+
END IF;
|
|
23
|
+
RETURN NEW;
|
|
24
|
+
END IF;
|
|
25
|
+
END;
|
|
26
|
+
$$
|
|
27
|
+
LANGUAGE 'plpgsql'
|
|
28
|
+
VOLATILE;
|
|
29
|
+
|
|
30
|
+
CREATE FUNCTION status_private.tg_achievement_toggle ()
|
|
31
|
+
RETURNS TRIGGER
|
|
32
|
+
AS $$
|
|
33
|
+
DECLARE
|
|
34
|
+
is_null boolean;
|
|
35
|
+
task_name text;
|
|
36
|
+
BEGIN
|
|
37
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
38
|
+
task_name = TG_ARGV[1]::text;
|
|
39
|
+
EXECUTE format('SELECT ($1).%s IS NULL', TG_ARGV[0])
|
|
40
|
+
USING NEW INTO is_null;
|
|
41
|
+
IF (is_null IS TRUE) THEN
|
|
42
|
+
PERFORM status_private.user_incompleted_step(task_name);
|
|
43
|
+
ELSE
|
|
44
|
+
PERFORM status_private.user_completed_step(task_name);
|
|
45
|
+
END IF;
|
|
46
|
+
RETURN NEW;
|
|
47
|
+
END IF;
|
|
48
|
+
END;
|
|
49
|
+
$$
|
|
50
|
+
LANGUAGE 'plpgsql'
|
|
51
|
+
VOLATILE;
|
|
52
|
+
|
|
53
|
+
CREATE FUNCTION status_private.tg_achievement_boolean ()
|
|
54
|
+
RETURNS TRIGGER
|
|
55
|
+
AS $$
|
|
56
|
+
DECLARE
|
|
57
|
+
is_true boolean;
|
|
58
|
+
task_name text;
|
|
59
|
+
BEGIN
|
|
60
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
61
|
+
task_name = TG_ARGV[1]::text;
|
|
62
|
+
EXECUTE format('SELECT ($1).%s IS TRUE', TG_ARGV[0])
|
|
63
|
+
USING NEW INTO is_true;
|
|
64
|
+
IF (is_true IS TRUE) THEN
|
|
65
|
+
PERFORM status_private.user_completed_step(task_name);
|
|
66
|
+
END IF;
|
|
67
|
+
RETURN NEW;
|
|
68
|
+
END IF;
|
|
69
|
+
END;
|
|
70
|
+
$$
|
|
71
|
+
LANGUAGE 'plpgsql'
|
|
72
|
+
VOLATILE;
|
|
73
|
+
|
|
74
|
+
CREATE FUNCTION status_private.tg_achievement_toggle_boolean ()
|
|
75
|
+
RETURNS TRIGGER
|
|
76
|
+
AS $$
|
|
77
|
+
DECLARE
|
|
78
|
+
is_true boolean;
|
|
79
|
+
task_name text;
|
|
80
|
+
BEGIN
|
|
81
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
82
|
+
task_name = TG_ARGV[1]::text;
|
|
83
|
+
EXECUTE format('SELECT ($1).%s IS TRUE', TG_ARGV[0])
|
|
84
|
+
USING NEW INTO is_true;
|
|
85
|
+
IF (is_true IS TRUE) THEN
|
|
86
|
+
PERFORM status_private.user_completed_step(task_name);
|
|
87
|
+
ELSE
|
|
88
|
+
PERFORM status_private.user_incompleted_step(task_name);
|
|
89
|
+
END IF;
|
|
90
|
+
RETURN NEW;
|
|
91
|
+
END IF;
|
|
92
|
+
END;
|
|
93
|
+
$$
|
|
94
|
+
LANGUAGE 'plpgsql'
|
|
95
|
+
VOLATILE;
|
|
96
|
+
|
|
97
|
+
-- CREATE FUNCTION app_private.tg_achievement_using_field ()
|
|
98
|
+
-- RETURNS TRIGGER
|
|
99
|
+
-- AS $$
|
|
100
|
+
-- DECLARE
|
|
101
|
+
-- is_null boolean;
|
|
102
|
+
-- task_name citext;
|
|
103
|
+
-- user_id uuid;
|
|
104
|
+
-- BEGIN
|
|
105
|
+
-- IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
106
|
+
-- task_name = TG_ARGV[1]::citext;
|
|
107
|
+
-- EXECUTE format('SELECT ($1).%s IS NULL', TG_ARGV[0])
|
|
108
|
+
-- USING NEW INTO is_null;
|
|
109
|
+
-- EXECUTE format('SELECT ($1).%s::uuid', TG_ARGV[2])
|
|
110
|
+
-- USING NEW INTO user_id;
|
|
111
|
+
-- IF (is_null IS FALSE) THEN
|
|
112
|
+
-- PERFORM app_private.user_completed_task(task_name, user_id);
|
|
113
|
+
-- END IF;
|
|
114
|
+
-- RETURN NEW;
|
|
115
|
+
-- END IF;
|
|
116
|
+
-- END;
|
|
117
|
+
-- $$
|
|
118
|
+
-- LANGUAGE 'plpgsql'
|
|
119
|
+
-- VOLATILE;
|
|
120
|
+
|
|
121
|
+
-- CREATE FUNCTION app_private.tg_achievement_toggle_using_field ()
|
|
122
|
+
-- RETURNS TRIGGER
|
|
123
|
+
-- AS $$
|
|
124
|
+
-- DECLARE
|
|
125
|
+
-- is_null boolean;
|
|
126
|
+
-- task_name citext;
|
|
127
|
+
-- user_id uuid;
|
|
128
|
+
-- BEGIN
|
|
129
|
+
-- IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
130
|
+
-- task_name = TG_ARGV[1]::citext;
|
|
131
|
+
-- EXECUTE format('SELECT ($1).%s IS NULL', TG_ARGV[0])
|
|
132
|
+
-- USING NEW INTO is_null;
|
|
133
|
+
-- EXECUTE format('SELECT ($1).%s::uuid', TG_ARGV[2])
|
|
134
|
+
-- USING NEW INTO user_id;
|
|
135
|
+
-- IF (is_null IS TRUE) THEN
|
|
136
|
+
-- PERFORM app_private.user_incompleted_task(task_name, user_id);
|
|
137
|
+
-- ELSE
|
|
138
|
+
-- PERFORM app_private.user_completed_task(task_name, user_id);
|
|
139
|
+
-- END IF;
|
|
140
|
+
-- RETURN NEW;
|
|
141
|
+
-- END IF;
|
|
142
|
+
-- END;
|
|
143
|
+
-- $$
|
|
144
|
+
-- LANGUAGE 'plpgsql'
|
|
145
|
+
-- VOLATILE;
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
COMMIT;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- Deploy schemas/status_private/procedures/upsert_achievement to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_private/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/user_achievements/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
|
|
8
|
+
CREATE FUNCTION status_private.upsert_achievement(
|
|
9
|
+
vuser_id uuid, vname text, vcount int
|
|
10
|
+
) returns void as $$
|
|
11
|
+
BEGIN
|
|
12
|
+
INSERT INTO status_public.user_achievements (user_id, name, count)
|
|
13
|
+
VALUES
|
|
14
|
+
(vuser_id, vname, GREATEST(vcount, 0))
|
|
15
|
+
ON CONFLICT ON CONSTRAINT user_achievements_unique_key
|
|
16
|
+
DO UPDATE SET
|
|
17
|
+
-- look ma! you can actually do aliases inside on conflict
|
|
18
|
+
count = user_achievements.count + EXCLUDED.count
|
|
19
|
+
;
|
|
20
|
+
END;
|
|
21
|
+
$$
|
|
22
|
+
LANGUAGE 'plpgsql' VOLATILE;
|
|
23
|
+
|
|
24
|
+
COMMIT;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Deploy schemas/status_private/procedures/user_completed_step to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_private/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/user_steps/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
|
|
8
|
+
CREATE FUNCTION status_private.user_completed_step (
|
|
9
|
+
step text,
|
|
10
|
+
user_id uuid DEFAULT jwt_public.current_user_id()
|
|
11
|
+
) RETURNS void AS $EOFCODE$
|
|
12
|
+
INSERT INTO status_public.user_steps ( name, user_id, count )
|
|
13
|
+
VALUES ( step, user_id, 1 );
|
|
14
|
+
$EOFCODE$ LANGUAGE sql VOLATILE SECURITY DEFINER;
|
|
15
|
+
|
|
16
|
+
COMMIT;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- Deploy schemas/status_private/procedures/user_incompleted_step to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_private/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/user_steps/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
|
|
8
|
+
CREATE FUNCTION status_private.user_incompleted_step (
|
|
9
|
+
step text,
|
|
10
|
+
user_id uuid DEFAULT jwt_public.current_user_id()
|
|
11
|
+
) RETURNS void AS $EOFCODE$
|
|
12
|
+
BEGIN
|
|
13
|
+
DELETE FROM status_public.user_steps s
|
|
14
|
+
WHERE s.user_id = user_incompleted_step.user_id
|
|
15
|
+
AND s.name = step;
|
|
16
|
+
DELETE FROM status_public.user_achievements a
|
|
17
|
+
WHERE a.user_id = user_incompleted_step.user_id
|
|
18
|
+
AND a.name = step;
|
|
19
|
+
END;
|
|
20
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
21
|
+
|
|
22
|
+
COMMIT;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- Deploy schemas/status_private/schema to pg
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
BEGIN;
|
|
5
|
+
|
|
6
|
+
CREATE SCHEMA IF NOT EXISTS status_private;
|
|
7
|
+
|
|
8
|
+
GRANT USAGE ON SCHEMA status_private
|
|
9
|
+
TO authenticated, anonymous;
|
|
10
|
+
|
|
11
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA status_private
|
|
12
|
+
GRANT EXECUTE ON FUNCTIONS
|
|
13
|
+
TO authenticated;
|
|
14
|
+
|
|
15
|
+
COMMIT;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/procedures/steps_required to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/level_requirements/table
|
|
5
|
+
-- requires: schemas/status_public/tables/user_achievements/table
|
|
6
|
+
|
|
7
|
+
BEGIN;
|
|
8
|
+
|
|
9
|
+
-- good for debugging...
|
|
10
|
+
|
|
11
|
+
-- SELECT
|
|
12
|
+
-- level_requirements.name,
|
|
13
|
+
-- level_requirements.level,
|
|
14
|
+
|
|
15
|
+
-- coalesce(user_achievements.count,0) as completed,
|
|
16
|
+
-- level_requirements.required_count as required,
|
|
17
|
+
-- -1*(coalesce(user_achievements.count,0)-level_requirements.required_count) as count
|
|
18
|
+
|
|
19
|
+
-- FROM
|
|
20
|
+
-- status_public.level_requirements
|
|
21
|
+
-- FULL OUTER JOIN status_public.user_achievements ON (
|
|
22
|
+
-- user_achievements.name = level_requirements.name
|
|
23
|
+
-- AND user_achievements.user_id ='b9d22af1-62c7-43a5-b8c4-50630bbd4962'
|
|
24
|
+
-- )
|
|
25
|
+
-- JOIN status_public.levels ON (level_requirements.level = levels.name)
|
|
26
|
+
-- ;
|
|
27
|
+
|
|
28
|
+
CREATE FUNCTION status_public.steps_required(
|
|
29
|
+
vlevel text,
|
|
30
|
+
vrole_id uuid DEFAULT jwt_public.current_user_id()
|
|
31
|
+
)
|
|
32
|
+
RETURNS SETOF status_public.level_requirements
|
|
33
|
+
AS $$
|
|
34
|
+
BEGIN
|
|
35
|
+
RETURN QUERY
|
|
36
|
+
SELECT
|
|
37
|
+
level_requirements.id,
|
|
38
|
+
level_requirements.name,
|
|
39
|
+
level_requirements.level,
|
|
40
|
+
-1*(coalesce(user_achievements.count,0)-level_requirements.required_count) as required_count,
|
|
41
|
+
level_requirements.priority
|
|
42
|
+
FROM
|
|
43
|
+
status_public.level_requirements
|
|
44
|
+
FULL OUTER JOIN status_public.user_achievements ON (
|
|
45
|
+
user_achievements.name = level_requirements.name
|
|
46
|
+
AND user_achievements.user_id =vrole_id
|
|
47
|
+
)
|
|
48
|
+
JOIN status_public.levels ON (level_requirements.level = levels.name)
|
|
49
|
+
WHERE
|
|
50
|
+
level_requirements.level = vlevel
|
|
51
|
+
AND -1*(coalesce(user_achievements.count,0)-level_requirements.required_count) > 0
|
|
52
|
+
ORDER BY priority ASC
|
|
53
|
+
;
|
|
54
|
+
END;
|
|
55
|
+
$$
|
|
56
|
+
LANGUAGE 'plpgsql'
|
|
57
|
+
STABLE;
|
|
58
|
+
COMMIT;
|
|
59
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/procedures/user_achieved to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
-- requires: schemas/status_public/procedures/steps_required
|
|
5
|
+
-- requires: schemas/status_public/tables/level_requirements/table
|
|
6
|
+
-- requires: schemas/status_public/tables/user_achievements/table
|
|
7
|
+
|
|
8
|
+
BEGIN;
|
|
9
|
+
|
|
10
|
+
CREATE FUNCTION status_public.user_achieved(
|
|
11
|
+
vlevel text,
|
|
12
|
+
vrole_id uuid DEFAULT jwt_public.current_user_id()
|
|
13
|
+
) returns boolean as $$
|
|
14
|
+
DECLARE
|
|
15
|
+
c int;
|
|
16
|
+
BEGIN
|
|
17
|
+
SELECT COUNT(*) FROM
|
|
18
|
+
status_public.steps_required(
|
|
19
|
+
vlevel,
|
|
20
|
+
vrole_id
|
|
21
|
+
)
|
|
22
|
+
INTO c;
|
|
23
|
+
|
|
24
|
+
RETURN c <= 0;
|
|
25
|
+
END;
|
|
26
|
+
$$
|
|
27
|
+
LANGUAGE 'plpgsql' STABLE;
|
|
28
|
+
|
|
29
|
+
COMMIT;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/schema to pg
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
BEGIN;
|
|
5
|
+
|
|
6
|
+
CREATE SCHEMA IF NOT EXISTS status_public;
|
|
7
|
+
|
|
8
|
+
GRANT USAGE ON SCHEMA status_public
|
|
9
|
+
TO authenticated, anonymous;
|
|
10
|
+
|
|
11
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA status_public
|
|
12
|
+
GRANT EXECUTE ON FUNCTIONS
|
|
13
|
+
TO authenticated;
|
|
14
|
+
|
|
15
|
+
COMMIT;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/level_requirements/table to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/levels/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
|
|
8
|
+
CREATE TABLE status_public.level_requirements (
|
|
9
|
+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
|
10
|
+
name text NOT NULL,
|
|
11
|
+
level text NOT NULL,
|
|
12
|
+
required_count int DEFAULT 1,
|
|
13
|
+
priority int DEFAULT 100,
|
|
14
|
+
unique(name, level)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
COMMENT ON TABLE status_public.level_requirements IS 'Requirements to achieve a level';
|
|
18
|
+
CREATE INDEX ON status_public.level_requirements (name, level, priority);
|
|
19
|
+
GRANT SELECT ON TABLE status_public.levels TO authenticated;
|
|
20
|
+
|
|
21
|
+
COMMIT;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/levels/table to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE status_public.levels (
|
|
8
|
+
name text NOT NULL PRIMARY KEY
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
COMMENT ON TABLE status_public.levels IS 'Levels for achievement';
|
|
12
|
+
|
|
13
|
+
GRANT SELECT ON TABLE status_public.levels TO public;
|
|
14
|
+
|
|
15
|
+
COMMIT;
|
package/deploy/schemas/status_public/tables/user_achievements/policies/enable_row_level_security.sql
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/user_achievements/policies/enable_row_level_security to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/user_achievements/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
|
|
8
|
+
ALTER TABLE status_public.user_achievements
|
|
9
|
+
ENABLE ROW LEVEL SECURITY;
|
|
10
|
+
|
|
11
|
+
COMMIT;
|
package/deploy/schemas/status_public/tables/user_achievements/policies/user_achievements_policy.sql
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/user_achievements/policies/user_achievements_policy to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/user_achievements/table
|
|
5
|
+
-- requires: schemas/status_public/tables/user_achievements/policies/enable_row_level_security
|
|
6
|
+
|
|
7
|
+
BEGIN;
|
|
8
|
+
|
|
9
|
+
CREATE POLICY can_select_user_achievements ON status_public.user_achievements
|
|
10
|
+
FOR SELECT
|
|
11
|
+
USING (
|
|
12
|
+
jwt_public.current_user_id() = user_id
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE POLICY can_insert_user_achievements ON status_public.user_achievements
|
|
16
|
+
FOR INSERT
|
|
17
|
+
WITH CHECK (
|
|
18
|
+
FALSE
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE POLICY can_update_user_achievements ON status_public.user_achievements
|
|
22
|
+
FOR UPDATE
|
|
23
|
+
USING (
|
|
24
|
+
FALSE
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE POLICY can_delete_user_achievements ON status_public.user_achievements
|
|
28
|
+
FOR DELETE
|
|
29
|
+
USING (
|
|
30
|
+
FALSE
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
GRANT INSERT ON TABLE status_public.user_achievements TO authenticated;
|
|
34
|
+
GRANT SELECT ON TABLE status_public.user_achievements TO authenticated;
|
|
35
|
+
GRANT UPDATE ON TABLE status_public.user_achievements TO authenticated;
|
|
36
|
+
GRANT DELETE ON TABLE status_public.user_achievements TO authenticated;
|
|
37
|
+
|
|
38
|
+
COMMIT;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/user_achievements/table to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE status_public.user_achievements (
|
|
8
|
+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
|
9
|
+
user_id uuid NOT NULL,
|
|
10
|
+
name text NOT NULL, -- relates to level_requirements.name
|
|
11
|
+
count int NOT NULL DEFAULT 0,
|
|
12
|
+
created_at timestamptz NOT NULL DEFAULT current_timestamp,
|
|
13
|
+
constraint user_achievements_unique_key unique (user_id, name)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
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.';
|
|
17
|
+
|
|
18
|
+
CREATE INDEX ON status_public.user_achievements (user_id, name);
|
|
19
|
+
|
|
20
|
+
COMMIT;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/user_levels/table to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
-- NOT using yet, so commented it out for simplicity
|
|
8
|
+
|
|
9
|
+
-- CREATE TABLE status_public.user_levels (
|
|
10
|
+
-- id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
|
11
|
+
-- user_id uuid NOT NULL,
|
|
12
|
+
-- name text NOT NULL, -- references levels
|
|
13
|
+
-- created_at timestamptz NOT NULL DEFAULT current_timestamp
|
|
14
|
+
-- );
|
|
15
|
+
|
|
16
|
+
-- COMMENT ON TABLE status_public.user_levels IS 'Cache table of the achieved levels';
|
|
17
|
+
|
|
18
|
+
-- CREATE INDEX ON status_public.user_levels (user_id, name);
|
|
19
|
+
|
|
20
|
+
COMMIT;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/user_steps/table to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE status_public.user_steps (
|
|
8
|
+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (),
|
|
9
|
+
user_id uuid NOT NULL,
|
|
10
|
+
name text NOT NULL, -- references level_requirement
|
|
11
|
+
count int NOT NULL DEFAULT 1,
|
|
12
|
+
created_at timestamptz NOT NULL DEFAULT current_timestamp
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
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.';
|
|
16
|
+
CREATE INDEX ON status_public.user_steps (user_id, name);
|
|
17
|
+
|
|
18
|
+
COMMIT;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- Deploy schemas/status_public/tables/user_steps/triggers/update_achievements_tg to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/status_public/schema
|
|
4
|
+
-- requires: schemas/status_public/tables/user_steps/table
|
|
5
|
+
-- requires: schemas/status_public/tables/user_achievements/table
|
|
6
|
+
-- requires: schemas/status_private/procedures/upsert_achievement
|
|
7
|
+
|
|
8
|
+
BEGIN;
|
|
9
|
+
|
|
10
|
+
CREATE FUNCTION status_private.tg_update_achievements_tg()
|
|
11
|
+
RETURNS TRIGGER AS $$
|
|
12
|
+
DECLARE
|
|
13
|
+
BEGIN
|
|
14
|
+
PERFORM status_private.upsert_achievement(NEW.user_id, NEW.name, NEW.count);
|
|
15
|
+
RETURN NEW;
|
|
16
|
+
END;
|
|
17
|
+
$$
|
|
18
|
+
LANGUAGE 'plpgsql' VOLATILE SECURITY DEFINER;
|
|
19
|
+
|
|
20
|
+
CREATE TRIGGER update_achievements_tg
|
|
21
|
+
AFTER INSERT ON status_public.user_steps
|
|
22
|
+
FOR EACH ROW
|
|
23
|
+
EXECUTE PROCEDURE status_private.tg_update_achievements_tg ();
|
|
24
|
+
|
|
25
|
+
COMMIT;
|