Skip to content

Commit fe052dc

Browse files
committed
feat(friendship)!: redesign table friendship
In the table `friendship` the columns `a_account_id` and `b_account_id`were renamed to `account_id`and `friend_account_id, a new column `is_close_friend` was added, the policies were updated. Several friendship related functions were added. Test scripts were updated accordingly.
1 parent 662f4db commit fe052dc

File tree

11 files changed

+622
-194
lines changed

11 files changed

+622
-194
lines changed

src/deploy/function_friendship.sql

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
BEGIN;
2+
3+
-- accept friendship request
4+
5+
CREATE OR REPLACE FUNCTION vibetype.friendship_accept(
6+
requestor_account_id UUID
7+
) RETURNS VOID AS $$
8+
DECLARE
9+
_friend_account_id UUID;
10+
_count INTEGER;
11+
BEGIN
12+
13+
_friend_account_id := vibetype.invoker_account_id();
14+
15+
UPDATE vibetype.friendship SET
16+
status = 'accepted'::vibetype.friendship_status
17+
-- updated_by filled by trigger
18+
WHERE account_id = requestor_account_id AND friend_account_id = _friend_account_id
19+
AND status = 'requested'::vibetype.friendship_status;
20+
21+
GET DIAGNOSTICS _count = ROW_COUNT;
22+
IF _count = 0 THEN
23+
RAISE EXCEPTION 'Friendship request does not exist' USING ERRCODE = 'VTFAC';
24+
END IF;
25+
26+
INSERT INTO vibetype.friendship(account_id, friend_account_id, status, created_by)
27+
VALUES (_friend_account_id, requestor_account_id, 'accepted'::vibetype.friendship_status, _friend_account_id);
28+
29+
END; $$ LANGUAGE plpgsql SECURITY INVOKER;
30+
31+
COMMENT ON FUNCTION vibetype.friendship_accept(UUID) IS 'Accepts a friendship request.';
32+
33+
GRANT EXECUTE ON FUNCTION vibetype.friendship_accept(UUID) TO vibetype_account;
34+
35+
-- reject or cancel friendship
36+
37+
CREATE OR REPLACE FUNCTION vibetype.friendship_cancel(
38+
friend_account_id UUID
39+
) RETURNS VOID AS $$
40+
DECLARE
41+
_account_id UUID;
42+
BEGIN
43+
44+
_account_id := vibetype.invoker_account_id();
45+
46+
DELETE FROM vibetype.friendship f
47+
WHERE (account_id = _account_id AND f.friend_account_id = friendship_cancel.friend_account_id)
48+
OR (account_id = friendship_cancel.friend_account_id AND f.friend_account_id = _account_id);
49+
50+
END; $$ LANGUAGE plpgsql SECURITY INVOKER;
51+
52+
COMMENT ON FUNCTION vibetype.friendship_cancel(UUID) IS 'Rejects or cancels a friendship (in both directions).';
53+
54+
GRANT EXECUTE ON FUNCTION vibetype.friendship_cancel(UUID) TO vibetype_account;
55+
56+
-- create notification for a request
57+
58+
CREATE OR REPLACE FUNCTION vibetype.friendship_notify_request(
59+
friend_account_id UUID,
60+
language TEXT
61+
) RETURNS VOID AS $$
62+
BEGIN
63+
64+
INSERT INTO vibetype_private.notification (channel, payload)
65+
VALUES (
66+
'friendship_request',
67+
jsonb_pretty(jsonb_build_object(
68+
'data', jsonb_build_object(
69+
'requestor_account_id', vibetype.invoker_account_id(),
70+
'requestee_account_id', friendship_notify_request.friend_account_id
71+
),
72+
'template', jsonb_build_object('language', friendship_notify_request.language)
73+
))
74+
);
75+
76+
END; $$ LANGUAGE plpgsql SECURITY DEFINER;
77+
78+
COMMENT ON FUNCTION vibetype.friendship_notify_request(UUID, TEXT) IS 'Creates a notification for a friendship_request';
79+
80+
GRANT EXECUTE ON FUNCTION vibetype.friendship_notify_request(UUID, TEXT) TO vibetype_account;
81+
82+
-- request friendship
83+
84+
CREATE OR REPLACE FUNCTION vibetype.friendship_request(
85+
friend_account_id UUID,
86+
language TEXT
87+
) RETURNS VOID AS $$
88+
DECLARE
89+
_account_id UUID;
90+
BEGIN
91+
92+
_account_id := vibetype.invoker_account_id();
93+
94+
IF EXISTS(
95+
SELECT 1
96+
FROM vibetype.friendship f
97+
WHERE (f.account_id = _account_id AND f.friend_account_id = friendship_request.friend_account_id)
98+
OR (f.account_id = friendship_request.friend_account_id AND f.friend_account_id = _account_id)
99+
)
100+
THEN
101+
RAISE EXCEPTION 'Friendship already exists or has already been requested.' USING ERRCODE = 'VTREQ';
102+
END IF;
103+
104+
INSERT INTO vibetype.friendship(account_id, friend_account_id, status, created_by)
105+
VALUES (_account_id, friendship_request.friend_account_id, 'requested'::vibetype.friendship_status, _account_id);
106+
107+
PERFORM vibetype.friendship_notify_request(friendship_request.friend_account_id, friendship_request.language);
108+
109+
END; $$ LANGUAGE plpgsql SECURITY INVOKER;
110+
111+
COMMENT ON FUNCTION vibetype.friendship_request(UUID, TEXT) IS 'Starts a new friendship request.';
112+
113+
GRANT EXECUTE ON FUNCTION vibetype.friendship_request(UUID, TEXT) TO vibetype_account;
114+
115+
116+
-- toggle closeness of friendship
117+
118+
CREATE OR REPLACE FUNCTION vibetype.friendship_toggle_closeness(
119+
friend_account_id UUID
120+
) RETURNS BOOLEAN AS $$
121+
DECLARE
122+
_account_id UUID;
123+
_is_close_friend BOOLEAN;
124+
current_status vibetype.friendship_status;
125+
BEGIN
126+
127+
_account_id := vibetype.invoker_account_id();
128+
129+
SELECT status INTO current_status
130+
FROM vibetype.friendship f
131+
WHERE f.account_id = _account_id AND f.friend_account_id = friendship_toggle_closeness.friend_account_id;
132+
133+
IF current_status IS NULL OR current_status != 'accepted'::vibetype.friendship_status THEN
134+
RAISE EXCEPTION 'Friendship request does not exist' USING ERRCODE = 'VTFTC';
135+
END IF;
136+
137+
UPDATE vibetype.friendship f
138+
SET is_close_friend = NOT is_close_friend
139+
WHERE account_id = vibetype.invoker_account_id()
140+
AND f.friend_account_id = friendship_toggle_closeness.friend_account_id
141+
RETURNING is_close_friend INTO _is_close_friend;
142+
143+
RETURN _is_close_friend;
144+
145+
END; $$ LANGUAGE plpgsql SECURITY INVOKER;
146+
147+
COMMENT ON FUNCTION vibetype.friendship_toggle_closeness(UUID) IS 'Toggles a frien1dship relation between ''not a close friend'' and ''close friend''.';
148+
149+
GRANT EXECUTE ON FUNCTION vibetype.friendship_toggle_closeness(UUID) TO vibetype_account;
150+
151+
COMMIT;

src/deploy/table_friendship.sql

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
1-
BEGIN;
2-
31
CREATE TABLE vibetype.friendship (
42
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
53

6-
a_account_id UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE,
7-
b_account_id UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE,
4+
account_id UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE,
5+
friend_account_id UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE,
6+
7+
is_close_friend BOOLEAN NOT NULL DEFAULT false,
88
status vibetype.friendship_status NOT NULL DEFAULT 'requested'::vibetype.friendship_status,
99

1010
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
1111
created_by UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE,
1212
updated_at TIMESTAMP WITH TIME ZONE,
1313
updated_by UUID REFERENCES vibetype.account(id) ON DELETE SET NULL,
1414

15-
UNIQUE (a_account_id, b_account_id),
16-
CONSTRAINT friendship_creator_participant CHECK (created_by = a_account_id or created_by = b_account_id),
17-
CONSTRAINT friendship_creator_updater_difference CHECK (created_by <> updated_by),
18-
CONSTRAINT friendship_ordering CHECK (a_account_id < b_account_id),
19-
CONSTRAINT friendship_updater_participant CHECK (updated_by IS NULL or updated_by = a_account_id or updated_by = b_account_id)
15+
UNIQUE (account_id, friend_account_id),
16+
CONSTRAINT friendship_creator_friend CHECK (account_id <> friend_account_id),
17+
CONSTRAINT friendship_creator_participant CHECK (created_by = account_id)
2018
);
2119

2220
CREATE INDEX idx_friendship_created_by ON vibetype.friendship USING btree (created_by);
2321
CREATE INDEX idx_friendship_updated_by ON vibetype.friendship USING btree (updated_by);
2422

2523
COMMENT ON TABLE vibetype.friendship IS 'A friend relation together with its status.';
2624
COMMENT ON COLUMN vibetype.friendship.id IS E'@omit create,update\nThe friend relation''s internal id.';
27-
COMMENT ON COLUMN vibetype.friendship.a_account_id IS E'@omit update\nThe ''left'' side of the friend relation. It must be lexically less than the ''right'' side.';
28-
COMMENT ON COLUMN vibetype.friendship.b_account_id IS E'@omit update\nThe ''right'' side of the friend relation. It must be lexically greater than the ''left'' side.';
25+
COMMENT ON COLUMN vibetype.friendship.account_id IS E'@omit update\nThe one side of the friend relation. If the status is ''requested'' then it is the requestor account.';
26+
COMMENT ON COLUMN vibetype.friendship.friend_account_id IS E'@omit update\nThe other side of the friend relation. If the status is ''requested'' then it is the requestee account.';
27+
COMMENT ON COLUMN vibetype.friendship.is_close_friend IS E'@omit create\nThe flag indicating whether account_id considers friend_account_id as a close friend or not.';
2928
COMMENT ON COLUMN vibetype.friendship.status IS E'@omit create\nThe status of the friend relation.';
3029
COMMENT ON COLUMN vibetype.friendship.created_at IS E'@omit create,update\nThe timestamp when the friend relation was created.';
3130
COMMENT ON COLUMN vibetype.friendship.created_by IS E'@omit update\nThe account that created the friend relation was created.';
@@ -45,35 +44,51 @@ GRANT INSERT, SELECT, UPDATE, DELETE ON TABLE vibetype.friendship TO vibetype_ac
4544

4645
ALTER TABLE vibetype.friendship ENABLE ROW LEVEL SECURITY;
4746

47+
CREATE POLICY friendship_not_blocked ON vibetype.friendship FOR ALL
48+
USING (
49+
account_id NOT IN (SELECT id FROM vibetype_private.account_block_ids())
50+
AND friend_account_id NOT IN (SELECT id FROM vibetype_private.account_block_ids())
51+
);
52+
4853
-- Only allow interactions with friendships in which the current user is involved.
49-
CREATE POLICY friendship_existing ON vibetype.friendship FOR ALL
54+
CREATE POLICY friendship_select ON vibetype.friendship FOR SELECT
5055
USING (
51-
(
52-
vibetype.invoker_account_id() = a_account_id
53-
AND b_account_id NOT IN (SELECT id FROM vibetype_private.account_block_ids())
54-
)
56+
account_id = vibetype.invoker_account_id()
5557
OR
56-
(
57-
vibetype.invoker_account_id() = b_account_id
58-
AND a_account_id NOT IN (SELECT id FROM vibetype_private.account_block_ids())
59-
)
60-
)
61-
WITH CHECK (FALSE);
58+
friend_account_id = vibetype.invoker_account_id()
59+
);
6260

6361
-- Only allow creation by the current user.
6462
CREATE POLICY friendship_insert ON vibetype.friendship FOR INSERT
6563
WITH CHECK (
6664
created_by = vibetype.invoker_account_id()
6765
);
6866

69-
-- Only allow update by the current user and only the state transition requested -> accepted.
70-
CREATE POLICY friendship_update ON vibetype.friendship FOR UPDATE
67+
-- Only allow update by the current user if it is about accepting a friendship request.
68+
CREATE POLICY friendship_update_accept ON vibetype.friendship FOR UPDATE
7169
USING (
70+
friend_account_id = vibetype.invoker_account_id()
71+
AND
7272
status = 'requested'::vibetype.friendship_status
7373
) WITH CHECK (
7474
status = 'accepted'::vibetype.friendship_status
7575
AND
7676
updated_by = vibetype.invoker_account_id()
7777
);
7878

79-
COMMIT;
79+
-- Only allow update by the current user if it is already an accepted relation.
80+
CREATE POLICY friendship_update_toggle_closeness ON vibetype.friendship FOR UPDATE
81+
USING (
82+
status = 'accepted'::vibetype.friendship_status
83+
AND
84+
account_id = vibetype.invoker_account_id()
85+
) WITH CHECK (
86+
status = 'accepted'::vibetype.friendship_status
87+
AND
88+
updated_by = vibetype.invoker_account_id()
89+
);
90+
91+
CREATE POLICY friendship_delete ON vibetype.friendship FOR DELETE
92+
USING (
93+
TRUE
94+
);

src/revert/function_friendship.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
BEGIN;
2+
3+
DROP FUNCTION vibetype.friendship_accept(UUID);
4+
DROP FUNCTION vibetype.friendship_cancel(UUID);
5+
DROP FUNCTION vibetype.friendship_notify_request(UUID, TEXT);
6+
DROP FUNCTION vibetype.friendship_request(UUID, TEXT);
7+
DROP FUNCTION vibetype.friendship_toggle_closeness(UUID);
8+
9+
COMMIT;

src/revert/table_friendship.sql

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
BEGIN;
22

3-
DROP POLICY friendship_update ON vibetype.friendship;
3+
DROP POLICY friendship_not_blocked ON vibetype.friendship;
4+
DROP POLICY friendship_select ON vibetype.friendship;
45
DROP POLICY friendship_insert ON vibetype.friendship;
5-
DROP POLICY friendship_existing ON vibetype.friendship;
6+
DROP POLICY friendship_update_accept ON vibetype.friendship;
7+
DROP POLICY friendship_update_toggle_closeness ON vibetype.friendship;
8+
DROP POLICY friendship_delete ON vibetype.friendship;
69

710
DROP TRIGGER vibetype_trigger_friendship_update ON vibetype.friendship;
811

src/sqitch.plan

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,4 @@ table_preference_event_location [schema_public table_account_public role_account
9595
role_zammad 1970-01-01T00:00:00Z Sven Thelemann <sven.thelemann@t-online.de> # Add role zammad.
9696
database_zammad [role_zammad] 1970-01-01T00:00:00Z Sven Thelemann <sven.thelemann@t-online.de> # Add the database for zammad.
9797
function_account_search [schema_public table_account_public role_account] 1970-01-01T00:00:00Z Svens Thelemann <sven.thelemann@t-online.de> # Add a function for searching accounts based on a substring query.
98+
function_friendship [schema_public table_friendship role_account] 1970-01-01T00:00:00Z Svens Thelemann <sven.thelemann@t-online.de> # Add functions for handling friendships.

src/verify/function_friendship.sql

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
BEGIN;
2+
3+
DO $$
4+
BEGIN
5+
6+
IF NOT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype.friendship_accept(UUID)', 'EXECUTE')) THEN
7+
RAISE EXCEPTION 'Test failed: vibetype_account does not have EXECUTE privilege for vibetype.friendship_accept(UUID).';
8+
END IF;
9+
10+
IF NOT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype.friendship_cancel(UUID)', 'EXECUTE')) THEN
11+
RAISE EXCEPTION 'Test failed: vibetype_account does not have EXECUTE privilege for vibetype.friendship_cancel(UUID).';
12+
END IF;
13+
14+
IF NOT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype.friendship_notify_request(UUID, TEXT)', 'EXECUTE')) THEN
15+
RAISE EXCEPTION 'Test failed: vibetype_account does not have EXECUTE privilege for vibetype.friendship_notify_request(UUID, TEXT).';
16+
END IF;
17+
18+
IF NOT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype.friendship_request(UUID, TEXT)', 'EXECUTE')) THEN
19+
RAISE EXCEPTION 'Test failed: vibetype_account does not have EXECUTE privilege for vibetype.friendship_request(UUID, TEXT).';
20+
END IF;
21+
22+
IF NOT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype.friendship_toggle_closeness(UUID)', 'EXECUTE')) THEN
23+
RAISE EXCEPTION 'Test failed: vibetype_account does not have EXECUTE privilege for vibetype.friendship_toggle_closeness(UUID).';
24+
END IF;
25+
26+
END $$;
27+
28+
ROLLBACK;

src/verify/table_friendship.sql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ BEGIN;
22

33
SELECT
44
id,
5-
a_account_id,
6-
b_account_id,
5+
account_id,
6+
friend_account_id,
7+
is_close_friend,
78
status,
89
created_at,
910
created_by,

0 commit comments

Comments
 (0)