From 9e5af43b29775c31756191d02ba7a728606bd1e2 Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Wed, 19 Mar 2025 23:30:58 +0100 Subject: [PATCH 01/12] feat(invitation): add invitations A table `vibetype.invitation` was added that inherits from table `notification`which was moved from schema `maevsi_private` to schema `maevsi`. In orrder to make functions that were originally created in `test_account_blocking.sql` generally available for all tests (including tests for invitations), these functions were moved to `function_test_utilities.sql`. --- ...unction_account_password_reset_request.sql | 2 +- src/deploy/function_account_registration.sql | 2 +- .../function_account_registration_refresh.sql | 5 +- src/deploy/function_invite.sql | 59 ++- .../function_notification_acknowledge.sql | 11 +- src/deploy/function_test_utilities.sql | 422 ++++++++++++++++++ src/deploy/table_invitation.sql | 16 + src/deploy/table_invitation_policy.sql | 22 + src/deploy/table_notification.sql | 14 +- src/deploy/test_account_block.sql | 384 +--------------- src/deploy/test_invitation.sql | 5 + src/revert/function_test_utilities.sql | 23 + src/revert/table_invitation.sql | 5 + src/revert/table_invitation_policy.sql | 6 + src/revert/table_notification.sql | 2 +- src/revert/test_account_block.sql | 20 +- src/revert/test_invitation.sql | 3 + src/sqitch.plan | 6 +- src/verify/function_account_registration.sql | 2 +- src/verify/function_test_utilities.sql | 28 ++ src/verify/table_invitation.sql | 16 + src/verify/table_invitation_policy.sql | 15 + src/verify/table_notification.sql | 2 +- src/verify/test_invitation.sql | 38 ++ test/schema/schema.definition.sql | 313 ++++++++----- 25 files changed, 866 insertions(+), 555 deletions(-) create mode 100644 src/deploy/function_test_utilities.sql create mode 100644 src/deploy/table_invitation.sql create mode 100644 src/deploy/table_invitation_policy.sql create mode 100644 src/deploy/test_invitation.sql create mode 100644 src/revert/function_test_utilities.sql create mode 100644 src/revert/table_invitation.sql create mode 100644 src/revert/table_invitation_policy.sql create mode 100644 src/revert/test_invitation.sql create mode 100644 src/verify/function_test_utilities.sql create mode 100644 src/verify/table_invitation.sql create mode 100644 src/verify/table_invitation_policy.sql create mode 100644 src/verify/test_invitation.sql diff --git a/src/deploy/function_account_password_reset_request.sql b/src/deploy/function_account_password_reset_request.sql index fe3c26dc..0ae861a2 100644 --- a/src/deploy/function_account_password_reset_request.sql +++ b/src/deploy/function_account_password_reset_request.sql @@ -24,7 +24,7 @@ BEGIN IF (_notify_data IS NULL) THEN -- noop ELSE - INSERT INTO vibetype_private.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload) VALUES ( 'account_password_reset_request', jsonb_pretty(jsonb_build_object( 'account', _notify_data, diff --git a/src/deploy/function_account_registration.sql b/src/deploy/function_account_registration.sql index 66f770cc..c469ce75 100644 --- a/src/deploy/function_account_registration.sql +++ b/src/deploy/function_account_registration.sql @@ -40,7 +40,7 @@ BEGIN INSERT INTO vibetype.contact(account_id, created_by) VALUES (_new_account_private.id, _new_account_private.id); - INSERT INTO vibetype_private.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), diff --git a/src/deploy/function_account_registration_refresh.sql b/src/deploy/function_account_registration_refresh.sql index 995fcceb..ca822d51 100644 --- a/src/deploy/function_account_registration_refresh.sql +++ b/src/deploy/function_account_registration_refresh.sql @@ -23,11 +23,10 @@ BEGIN updated.email_address, updated.email_address_verification, updated.email_address_verification_valid_until - FROM updated, vibetype.account - WHERE updated.id = account.id + FROM updated JOIN vibetype.account ON updated.id = account.id INTO _new_account_notify; - INSERT INTO vibetype_private.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), diff --git a/src/deploy/function_invite.sql b/src/deploy/function_invite.sql index 5d33109b..b8e99261 100644 --- a/src/deploy/function_invite.sql +++ b/src/deploy/function_invite.sql @@ -2,19 +2,21 @@ BEGIN; CREATE FUNCTION vibetype.invite( guest_id UUID, - language TEXT -) RETURNS VOID AS $$ + "language" TEXT +) RETURNS UUID AS $$ DECLARE _contact RECORD; _email_address TEXT; _event RECORD; - _event_creator_profile_picture_upload_id UUID; _event_creator_profile_picture_upload_storage_key TEXT; _event_creator_username TEXT; _guest RECORD; + _id UUID; BEGIN -- Guest UUID - SELECT * FROM vibetype.guest INTO _guest WHERE guest.id = invite.guest_id; + SELECT * INTO _guest + FROM vibetype.guest + WHERE guest.id = invite.guest_id; IF ( _guest IS NULL @@ -25,30 +27,16 @@ BEGIN END IF; -- Event - SELECT - id, - address_id, - description, - "end", - guest_count_maximum, - is_archived, - is_in_person, - is_remote, - name, - slug, - start, - url, - visibility, - created_at, - created_by - FROM vibetype.event INTO _event WHERE "event".id = _guest.event_id; + SELECT * INTO _event FROM vibetype.event WHERE id = _guest.event_id; IF (_event IS NULL) THEN RAISE 'Event not accessible!' USING ERRCODE = 'no_data_found'; END IF; -- Contact - SELECT account_id, email_address FROM vibetype.contact INTO _contact WHERE contact.id = _guest.contact_id; + SELECT account_id, email_address INTO _contact + FROM vibetype.contact + WHERE id = _guest.contact_id; IF (_contact IS NULL) THEN RAISE 'Contact not accessible!' USING ERRCODE = 'no_data_found'; @@ -62,7 +50,9 @@ BEGIN END IF; ELSE -- Account - SELECT email_address FROM vibetype_private.account INTO _email_address WHERE account.id = _contact.account_id; + SELECT email_address INTO _email_address + FROM vibetype_private.account + WHERE id = _contact.account_id; IF (_email_address IS NULL) THEN RAISE 'Account email address not accessible!' USING ERRCODE = 'no_data_found'; @@ -70,14 +60,19 @@ BEGIN END IF; -- Event creator username - SELECT username FROM vibetype.account INTO _event_creator_username WHERE account.id = _event.created_by; + SELECT username INTO _event_creator_username + FROM vibetype.account + WHERE id = _event.created_by; -- Event creator profile picture storage key - SELECT upload_id FROM vibetype.profile_picture INTO _event_creator_profile_picture_upload_id WHERE profile_picture.account_id = _event.created_by; - SELECT storage_key FROM vibetype.upload INTO _event_creator_profile_picture_upload_storage_key WHERE upload.id = _event_creator_profile_picture_upload_id; + SELECT u.storage_key INTO _event_creator_profile_picture_upload_storage_key + FROM vibetype.profile_picture p + JOIN vibetype.upload u ON p.upload_id = u.id + WHERE p.account_id = _event.created_by; - INSERT INTO vibetype_private.notification (channel, payload) + INSERT INTO vibetype.invitation (guest_id, channel, payload, created_by) VALUES ( + invite.guest_id, 'event_invitation', jsonb_pretty(jsonb_build_object( 'data', jsonb_build_object( @@ -88,12 +83,16 @@ BEGIN 'guestId', _guest.id ), 'template', jsonb_build_object('language', invite.language) - )) - ); + )), + vibetype.invoker_account_id() + ) + RETURNING id INTO _id; + + RETURN _id; END; $$ LANGUAGE PLPGSQL STRICT SECURITY DEFINER; -COMMENT ON FUNCTION vibetype.invite(UUID, TEXT) IS 'Adds a notification for the invitation channel.'; +COMMENT ON FUNCTION vibetype.invite(UUID, TEXT) IS 'Adds an invitation and a notification.'; GRANT EXECUTE ON FUNCTION vibetype.invite(UUID, TEXT) TO vibetype_account; diff --git a/src/deploy/function_notification_acknowledge.sql b/src/deploy/function_notification_acknowledge.sql index be59321c..8eec35a5 100644 --- a/src/deploy/function_notification_acknowledge.sql +++ b/src/deploy/function_notification_acknowledge.sql @@ -4,10 +4,15 @@ CREATE FUNCTION vibetype.notification_acknowledge( id UUID, is_acknowledged BOOLEAN ) RETURNS VOID AS $$ +DECLARE + update_count INTEGER; BEGIN - IF (EXISTS (SELECT 1 FROM vibetype_private.notification WHERE "notification".id = notification_acknowledge.id)) THEN - UPDATE vibetype_private.notification SET is_acknowledged = notification_acknowledge.is_acknowledged WHERE "notification".id = notification_acknowledge.id; - ELSE + UPDATE vibetype.notification SET + is_acknowledged = notification_acknowledge.is_acknowledged + WHERE id = notification_acknowledge.id; + + GET DIAGNOSTICS update_count = ROW_COUNT; + IF update_count = 0 THEN RAISE 'Notification with given id not found!' USING ERRCODE = 'no_data_found'; END IF; END; diff --git a/src/deploy/function_test_utilities.sql b/src/deploy/function_test_utilities.sql new file mode 100644 index 00000000..4170c9d5 --- /dev/null +++ b/src/deploy/function_test_utilities.sql @@ -0,0 +1,422 @@ +BEGIN; + +CREATE PROCEDURE vibetype_test.set_local_superuser() +AS $$ +DECLARE + _superuser_name TEXT; +BEGIN + SELECT usename INTO _superuser_name + FROM pg_user + WHERE usesuper = true + ORDER BY usename + LIMIT 1; + + IF _superuser_name IS NOT NULL THEN + EXECUTE format('SET LOCAL role = %I', _superuser_name); + ELSE + RAISE NOTICE 'No superuser found!'; + END IF; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON PROCEDURE vibetype_test.set_local_superuser() TO vibetype_anonymous, vibetype_account; + + +CREATE FUNCTION vibetype_test.account_create ( + _username TEXT, + _email TEXT +) RETURNS UUID AS $$ +DECLARE + _id UUID; + _verification UUID; +BEGIN + _id := vibetype.account_registration(_username, _email, 'password', 'en'); + + SELECT email_address_verification INTO _verification + FROM vibetype_private.account + WHERE id = _id; + + PERFORM vibetype.account_email_address_verification(_verification); + + RETURN _id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.account_create(TEXT, TEXT) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.account_remove ( + _username TEXT +) RETURNS VOID AS $$ +DECLARE + _id UUID; +BEGIN + SELECT id INTO _id FROM vibetype.account WHERE username = _username; + + IF _id IS NOT NULL THEN + + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _id || ''''; + + DELETE FROM vibetype.event WHERE created_by = _id; + + PERFORM vibetype.account_delete('password'); + + SET LOCAL role = 'ci'; + END IF; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.account_remove(TEXT) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.contact_select_by_account_id ( + _account_id UUID +) RETURNS UUID AS $$ +DECLARE + _id UUID; +BEGIN + SELECT id INTO _id + FROM vibetype.contact + WHERE created_by = _account_id AND account_id = _account_id; + + RETURN _id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.contact_select_by_account_id(UUID) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.contact_create ( + _created_by UUID, + _email_address TEXT +) RETURNS UUID AS $$ +DECLARE + _id UUID; + _account_id UUID; +BEGIN + SELECT id FROM vibetype_private.account WHERE email_address = _email_address INTO _account_id; + + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + + INSERT INTO vibetype.contact(created_by, email_address) + VALUES (_created_by, _email_address) + RETURNING id INTO _id; + + IF (_account_id IS NOT NULL) THEN + UPDATE vibetype.contact SET account_id = _account_id WHERE id = _id; + END IF; + + SET LOCAL role = 'ci'; + + RETURN _id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.contact_create(UUID, TEXT) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.event_create ( + _created_by UUID, + _name TEXT, + _slug TEXT, + _start TEXT, + _visibility TEXT +) RETURNS UUID AS $$ +DECLARE + _id UUID; +BEGIN + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + + INSERT INTO vibetype.event(created_by, name, slug, start, visibility) + VALUES (_created_by, _name, _slug, _start::TIMESTAMP WITH TIME ZONE, _visibility::vibetype.event_visibility) + RETURNING id INTO _id; + + SET LOCAL role = 'ci'; + + RETURN _id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.event_create(UUID, TEXT, TEXT, TEXT, TEXT) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.guest_create ( + _created_by UUID, + _event_id UUID, + _contact_id UUID +) RETURNS UUID AS $$ +DECLARE + _id UUID; +BEGIN + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + + INSERT INTO vibetype.guest(contact_id, event_id) + VALUES (_contact_id, _event_id) + RETURNING id INTO _id; + + SET LOCAL role = 'ci'; + + RETURN _id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.guest_create(UUID, UUID, UUID) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.event_category_create ( + _category TEXT +) RETURNS VOID AS $$ +BEGIN + INSERT INTO vibetype.event_category(category) VALUES (_category); +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.event_category_create(TEXT) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.event_category_mapping_create ( + _created_by UUID, + _event_id UUID, + _category TEXT +) RETURNS VOID AS $$ +BEGIN + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + + INSERT INTO vibetype.event_category_mapping(event_id, category) + VALUES (_event_id, _category); + + SET LOCAL role = 'ci'; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.event_category_mapping_create(UUID, UUID, TEXT) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.account_block_create ( + _created_by UUID, + _blocked_account_id UUID +) RETURNS UUID AS $$ +DECLARE + _id UUID; +BEGIN + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + + INSERT INTO vibetype.account_block(created_by, blocked_account_id) + VALUES (_created_by, _blocked_Account_id) + RETURNING id INTO _id; + + SET LOCAL role = 'ci'; + + RETURN _id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.account_block_create(UUID, UUID) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.account_block_remove ( + _created_by UUID, + _blocked_account_id UUID +) RETURNS VOID AS $$ +DECLARE + _id UUID; +BEGIN + DELETE FROM vibetype.account_block + WHERE created_by = _created_by and blocked_account_id = _blocked_account_id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.account_block_remove(UUID, UUID) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.event_test ( + _test_case TEXT, + _account_id UUID, + _expected_result UUID[] +) RETURNS VOID AS $$ +BEGIN + IF _account_id IS NULL THEN + SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL jwt.claims.account_id = ''; + ELSE + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; + END IF; + + IF EXISTS (SELECT id FROM vibetype.event EXCEPT SELECT * FROM unnest(_expected_result)) THEN + RAISE EXCEPTION 'some event should not appear in the query result'; + END IF; + + IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT id FROM vibetype.event) THEN + RAISE EXCEPTION 'some event is missing in the query result'; + END IF; + + SET LOCAL role = 'ci'; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.event_test(TEXT, UUID, UUID[]) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.event_category_mapping_test ( + _test_case TEXT, + _account_id UUID, + _expected_result UUID[] +) RETURNS VOID AS $$ +BEGIN + IF _account_id IS NULL THEN + SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL jwt.claims.account_id = ''; + ELSE + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; + END IF; + + IF EXISTS (SELECT event_id FROM vibetype.event_category_mapping EXCEPT SELECT * FROM unnest(_expected_result)) THEN + RAISE EXCEPTION 'some event_category_mappings should not appear in the query result'; + END IF; + + IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT event_id FROM vibetype.event_category_mapping) THEN + RAISE EXCEPTION 'some event_category_mappings is missing in the query result'; + END IF; + + SET LOCAL role = 'ci'; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.event_category_mapping_test(TEXT, UUID, UUID[]) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.contact_test ( + _test_case TEXT, + _account_id UUID, + _expected_result UUID[] +) RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + IF _account_id IS NULL THEN + SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL jwt.claims.account_id = ''; + ELSE + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; + END IF; + + IF EXISTS (SELECT id FROM vibetype.contact EXCEPT SELECT * FROM unnest(_expected_result)) THEN + RAISE EXCEPTION 'some contact should not appear in the query result'; + END IF; + + IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT id FROM vibetype.contact) THEN + RAISE EXCEPTION 'some contact is missing in the query result'; + END IF; + + SET LOCAL role = 'ci'; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.contact_test(TEXT, UUID, UUID[]) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.guest_test ( + _test_case TEXT, + _account_id UUID, + _expected_result UUID[] +) RETURNS VOID AS $$ +BEGIN + IF _account_id IS NULL THEN + SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL jwt.claims.account_id = ''; + ELSE + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; + END IF; + + IF EXISTS (SELECT id FROM vibetype.guest EXCEPT SELECT * FROM unnest(_expected_result)) THEN + RAISE EXCEPTION 'some guest should not appear in the query result'; + END IF; + + IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT id FROM vibetype.guest) THEN + RAISE EXCEPTION 'some guest is missing in the query result'; + END IF; + + SET LOCAL role = 'ci'; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.guest_test(TEXT, UUID, UUID[]) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.guest_claim_from_account_guest ( + _account_id UUID +) +RETURNS UUID[] AS $$ +DECLARE + _guest vibetype.guest; + _result UUID[] := ARRAY[]::UUID[]; + _text TEXT := ''; +BEGIN + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; + + -- reads all guests where _account_id is invited, + -- sets jwt.claims.guests to a string representation of these guests + -- and returns an array of these guests. + + FOR _guest IN + SELECT g.id + FROM vibetype.guest g JOIN vibetype.contact c + ON g.contact_id = c.id + WHERE c.account_id = _account_id + LOOP + _text := _text || ',"' || _guest.id || '"'; + _result := array_append(_result, _guest.id); + END LOOP; + + IF LENGTH(_text) > 0 THEN + _text := SUBSTR(_text, 2); + END IF; + + EXECUTE 'SET LOCAL jwt.claims.guests = ''[' || _text || ']'''; + + SET LOCAL role = 'ci'; + + RETURN _result; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.guest_claim_from_account_guest(UUID) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.invoker_set ( + _invoker_id UUID +) +RETURNS VOID AS $$ +BEGIN + SET LOCAL role = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _invoker_id || ''''; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.invoker_set(UUID) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.invoker_unset () +RETURNS VOID AS $$ +BEGIN + CALL vibetype_test.set_local_superuser(); + EXECUTE 'SET LOCAL jwt.claims.account_id = '''''; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.invoker_unset() TO vibetype_account; + + +CREATE FUNCTION vibetype_test.uuid_array_test ( + _test_case TEXT, + _array UUID[], + _expected_array UUID[] +) +RETURNS VOID AS $$ +BEGIN + IF EXISTS (SELECT * FROM unnest(_array) EXCEPT SELECT * FROM unnest(_expected_array)) THEN + RAISE EXCEPTION 'some uuid should not appear in the array'; + END IF; + + IF EXISTS (SELECT * FROM unnest(_expected_array) EXCEPT SELECT * FROM unnest(_array)) THEN + RAISE EXCEPTION 'some expected uuid is missing in the array'; + END IF; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.uuid_array_test(TEXT, UUID[], UUID[]) TO vibetype_account; + + +COMMIT; diff --git a/src/deploy/table_invitation.sql b/src/deploy/table_invitation.sql new file mode 100644 index 00000000..8a21cc1d --- /dev/null +++ b/src/deploy/table_invitation.sql @@ -0,0 +1,16 @@ +BEGIN; + +CREATE TABLE vibetype.invitation ( + guest_id UUID NOT NULL REFERENCES vibetype.guest(id), + -- created_at is already column of table notification + created_by UUID NOT NULL REFERENCES vibetype.account(id) +) +INHERITS (vibetype.notification); + +COMMENT ON TABLE vibetype.invitation IS '@omit update,delete\nStores invitations and their statuses.'; +COMMENT ON COLUMN vibetype.invitation.guest_id IS 'The ID of the guest associated with this invitation.'; +COMMENT ON COLUMN vibetype.invitation.created_by IS E'@omit create\nReference to the account that created the invitation.'; + +CREATE INDEX idx_invitation_guest_id ON vibetype.invitation USING btree (guest_id); + +COMMIT; diff --git a/src/deploy/table_invitation_policy.sql b/src/deploy/table_invitation_policy.sql new file mode 100644 index 00000000..ece1b928 --- /dev/null +++ b/src/deploy/table_invitation_policy.sql @@ -0,0 +1,22 @@ +BEGIN; + +GRANT SELECT, INSERT ON vibetype.invitation TO vibetype_account; + +ALTER TABLE vibetype.invitation ENABLE ROW LEVEL SECURITY; + +CREATE POLICY invitation_select ON vibetype.invitation FOR SELECT USING ( + created_by = vibetype.invoker_account_id() +); + +CREATE POLICY invitation_insert ON vibetype.invitation FOR INSERT WITH CHECK ( + created_by = vibetype.invoker_account_id() + AND + vibetype.invoker_account_id() = ( + SELECT e.created_by + FROM vibetype.guest g + JOIN vibetype.event e ON g.event_id = e.id + WHERE g.id = guest_id + ) +); + +COMMIT; diff --git a/src/deploy/table_notification.sql b/src/deploy/table_notification.sql index c426227d..31a646b0 100644 --- a/src/deploy/table_notification.sql +++ b/src/deploy/table_notification.sql @@ -1,6 +1,6 @@ BEGIN; -CREATE TABLE vibetype_private.notification ( +CREATE TABLE vibetype.notification ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel TEXT NOT NULL, @@ -10,11 +10,11 @@ CREATE TABLE vibetype_private.notification ( created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP ); -COMMENT ON TABLE vibetype_private.notification IS 'A notification.'; -COMMENT ON COLUMN vibetype_private.notification.id IS 'The notification''s internal id.'; -COMMENT ON COLUMN vibetype_private.notification.channel IS 'The notification''s channel.'; -COMMENT ON COLUMN vibetype_private.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; -COMMENT ON COLUMN vibetype_private.notification.payload IS 'The notification''s payload.'; -COMMENT ON COLUMN vibetype_private.notification.created_at IS 'The timestamp of the notification''s creation.'; +COMMENT ON TABLE vibetype.notification IS 'A notification.'; +COMMENT ON COLUMN vibetype.notification.id IS 'The notification''s internal id.'; +COMMENT ON COLUMN vibetype.notification.channel IS 'The notification''s channel.'; +COMMENT ON COLUMN vibetype.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; +COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload.'; +COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; COMMIT; diff --git a/src/deploy/test_account_block.sql b/src/deploy/test_account_block.sql index 1ba77ad9..4ac153dd 100644 --- a/src/deploy/test_account_block.sql +++ b/src/deploy/test_account_block.sql @@ -1,387 +1,5 @@ BEGIN; -CREATE PROCEDURE vibetype_test.set_local_superuser() -AS $$ -DECLARE - _superuser_name TEXT; -BEGIN - SELECT usename INTO _superuser_name - FROM pg_user - WHERE usesuper = true - ORDER BY usename - LIMIT 1; - - IF _superuser_name IS NOT NULL THEN - EXECUTE format('SET LOCAL role = %I', _superuser_name); - ELSE - RAISE NOTICE 'No superuser found!'; - END IF; -END $$ LANGUAGE plpgsql; - -GRANT EXECUTE ON PROCEDURE vibetype_test.set_local_superuser() TO vibetype_anonymous, vibetype_account; - - -CREATE FUNCTION vibetype_test.account_create ( - _username TEXT, - _email TEXT -) RETURNS UUID AS $$ -DECLARE - _id UUID; - _verification UUID; -BEGIN - _id := vibetype.account_registration(_username, _email, 'password', 'en'); - - SELECT email_address_verification INTO _verification - FROM vibetype_private.account - WHERE id = _id; - - PERFORM vibetype.account_email_address_verification(_verification); - - RETURN _id; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.account_remove ( - _username TEXT -) RETURNS VOID AS $$ -DECLARE - _id UUID; -BEGIN - SELECT id INTO _id FROM vibetype.account WHERE username = _username; - - IF _id IS NOT NULL THEN - - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _id || ''''; - - DELETE FROM vibetype.event WHERE created_by = _id; - - PERFORM vibetype.account_delete('password'); - - CALL vibetype_test.set_local_superuser(); - END IF; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.contact_select_by_account_id ( - _account_id UUID -) RETURNS UUID AS $$ -DECLARE - _id UUID; -BEGIN - SELECT id INTO _id - FROM vibetype.contact - WHERE created_by = _account_id AND account_id = _account_id; - - RETURN _id; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.contact_create ( - _created_by UUID, - _email_address TEXT -) RETURNS UUID AS $$ -DECLARE - _id UUID; - _account_id UUID; -BEGIN - SELECT id FROM vibetype_private.account WHERE email_address = _email_address INTO _account_id; - - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; - - INSERT INTO vibetype.contact(created_by, email_address) - VALUES (_created_by, _email_address) - RETURNING id INTO _id; - - IF (_account_id IS NOT NULL) THEN - UPDATE vibetype.contact SET account_id = _account_id WHERE id = _id; - END IF; - - CALL vibetype_test.set_local_superuser(); - - RETURN _id; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.event_create ( - _created_by UUID, - _name TEXT, - _slug TEXT, - _start TEXT, - _visibility TEXT -) RETURNS UUID AS $$ -DECLARE - _id UUID; -BEGIN - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; - - INSERT INTO vibetype.event(created_by, name, slug, start, visibility) - VALUES (_created_by, _name, _slug, _start::TIMESTAMP WITH TIME ZONE, _visibility::vibetype.event_visibility) - RETURNING id INTO _id; - - CALL vibetype_test.set_local_superuser(); - - RETURN _id; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.guest_create ( - _created_by UUID, - _event_id UUID, - _contact_id UUID -) RETURNS UUID AS $$ -DECLARE - _id UUID; -BEGIN - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; - - INSERT INTO vibetype.guest(contact_id, event_id) - VALUES (_contact_id, _event_id) - RETURNING id INTO _id; - - CALL vibetype_test.set_local_superuser(); - - RETURN _id; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.event_category_create ( - _category TEXT -) RETURNS VOID AS $$ -BEGIN - INSERT INTO vibetype.event_category(category) VALUES (_category); -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.event_category_mapping_create ( - _created_by UUID, - _event_id UUID, - _category TEXT -) RETURNS VOID AS $$ -BEGIN - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; - - INSERT INTO vibetype.event_category_mapping(event_id, category) - VALUES (_event_id, _category); - - CALL vibetype_test.set_local_superuser(); -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.account_block_create ( - _created_by UUID, - _blocked_account_id UUID -) RETURNS UUID AS $$ -DECLARE - _id UUID; -BEGIN - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; - - INSERT INTO vibetype.account_block(created_by, blocked_account_id) - VALUES (_created_by, _blocked_Account_id) - RETURNING id INTO _id; - - CALL vibetype_test.set_local_superuser(); - - RETURN _id; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.account_block_remove ( - _created_by UUID, - _blocked_account_id UUID -) RETURNS VOID AS $$ -DECLARE - _id UUID; -BEGIN - DELETE FROM vibetype.account_block - WHERE created_by = _created_by and blocked_account_id = _blocked_account_id; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.event_test ( - _test_case TEXT, - _account_id UUID, - _expected_result UUID[] -) RETURNS VOID AS $$ -BEGIN - IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; - SET LOCAL jwt.claims.account_id = ''; - ELSE - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; - END IF; - - IF EXISTS (SELECT id FROM vibetype.event EXCEPT SELECT * FROM unnest(_expected_result)) THEN - RAISE EXCEPTION 'some event should not appear in the query result'; - END IF; - - IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT id FROM vibetype.event) THEN - RAISE EXCEPTION 'some event is missing in the query result'; - END IF; - - CALL vibetype_test.set_local_superuser(); -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.event_category_mapping_test ( - _test_case TEXT, - _account_id UUID, - _expected_result UUID[] -) RETURNS VOID AS $$ -BEGIN - IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; - SET LOCAL jwt.claims.account_id = ''; - ELSE - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; - END IF; - - IF EXISTS (SELECT event_id FROM vibetype.event_category_mapping EXCEPT SELECT * FROM unnest(_expected_result)) THEN - RAISE EXCEPTION 'some event_category_mappings should not appear in the query result'; - END IF; - - IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT event_id FROM vibetype.event_category_mapping) THEN - RAISE EXCEPTION 'some event_category_mappings is missing in the query result'; - END IF; - - CALL vibetype_test.set_local_superuser(); -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.contact_test ( - _test_case TEXT, - _account_id UUID, - _expected_result UUID[] -) RETURNS VOID AS $$ -DECLARE - rec RECORD; -BEGIN - IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; - SET LOCAL jwt.claims.account_id = ''; - ELSE - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; - END IF; - - IF EXISTS (SELECT id FROM vibetype.contact EXCEPT SELECT * FROM unnest(_expected_result)) THEN - RAISE EXCEPTION 'some contact should not appear in the query result'; - END IF; - - IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT id FROM vibetype.contact) THEN - RAISE EXCEPTION 'some contact is missing in the query result'; - END IF; - - CALL vibetype_test.set_local_superuser(); -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.guest_test ( - _test_case TEXT, - _account_id UUID, - _expected_result UUID[] -) RETURNS VOID AS $$ -BEGIN - IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; - SET LOCAL jwt.claims.account_id = ''; - ELSE - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; - END IF; - - IF EXISTS (SELECT id FROM vibetype.guest EXCEPT SELECT * FROM unnest(_expected_result)) THEN - RAISE EXCEPTION 'some guest should not appear in the query result'; - END IF; - - IF EXISTS (SELECT * FROM unnest(_expected_result) EXCEPT SELECT id FROM vibetype.guest) THEN - RAISE EXCEPTION 'some guest is missing in the query result'; - END IF; - - CALL vibetype_test.set_local_superuser(); -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.guest_claim_from_account_guest ( - _account_id UUID -) -RETURNS UUID[] AS $$ -DECLARE - _guest vibetype.guest; - _result UUID[] := ARRAY[]::UUID[]; - _text TEXT := ''; -BEGIN - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; - - -- reads all guests where _account_id is invited, - -- sets jwt.claims.guests to a string representation of these guests - -- and returns an array of these guests. - - FOR _guest IN - SELECT g.id - FROM vibetype.guest g JOIN vibetype.contact c - ON g.contact_id = c.id - WHERE c.account_id = _account_id - LOOP - _text := _text || ',"' || _guest.id || '"'; - _result := array_append(_result, _guest.id); - END LOOP; - - IF LENGTH(_text) > 0 THEN - _text := SUBSTR(_text, 2); - END IF; - - EXECUTE 'SET LOCAL jwt.claims.guests = ''[' || _text || ']'''; - - CALL vibetype_test.set_local_superuser(); - - RETURN _result; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.invoker_set ( - _invoker_id UUID -) -RETURNS VOID AS $$ -BEGIN - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _invoker_id || ''''; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.invoker_unset () -RETURNS VOID AS $$ -BEGIN - CALL vibetype_test.set_local_superuser(); - EXECUTE 'SET LOCAL jwt.claims.account_id = '''''; -END $$ LANGUAGE plpgsql; - -CREATE FUNCTION vibetype_test.uuid_array_test ( - _test_case TEXT, - _array UUID[], - _expected_array UUID[] -) -RETURNS VOID AS $$ -BEGIN - IF EXISTS (SELECT * FROM unnest(_array) EXCEPT SELECT * FROM unnest(_expected_array)) THEN - RAISE EXCEPTION 'some uuid should not appear in the array'; - END IF; - - IF EXISTS (SELECT * FROM unnest(_expected_array) EXCEPT SELECT * FROM unnest(_array)) THEN - RAISE EXCEPTION 'some expected uuid is missing in the array'; - END IF; -END $$ LANGUAGE plpgsql; - -GRANT EXECUTE ON FUNCTION vibetype_test.account_block_create(UUID, UUID) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.account_block_remove(UUID, UUID) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.account_create(TEXT, TEXT) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.account_remove(TEXT) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.contact_create(UUID, TEXT) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.contact_select_by_account_id(UUID) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.contact_test(TEXT, UUID, UUID[]) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.event_category_create(TEXT) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.event_category_mapping_create(UUID, UUID, TEXT) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.event_category_mapping_test(TEXT, UUID, UUID[]) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.event_create(UUID, TEXT, TEXT, TEXT, TEXT) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.event_test(TEXT, UUID, UUID[]) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.guest_create(UUID, UUID, UUID) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.guest_test(TEXT, UUID, UUID[]) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.guest_claim_from_account_guest(UUID) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.invoker_set(UUID) TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.invoker_unset() TO vibetype_account; -GRANT EXECUTE ON FUNCTION vibetype_test.uuid_array_test(TEXT, UUID[], UUID[]) TO vibetype_account; +-- nothing defined here; tests will use functions from function_test_utilities.sql COMMIT; diff --git a/src/deploy/test_invitation.sql b/src/deploy/test_invitation.sql new file mode 100644 index 00000000..1004a402 --- /dev/null +++ b/src/deploy/test_invitation.sql @@ -0,0 +1,5 @@ +BEGIN; + +-- nothing defined here; see also function_test_utilities.sql + +COMMIT; diff --git a/src/revert/function_test_utilities.sql b/src/revert/function_test_utilities.sql new file mode 100644 index 00000000..cc1ff0e8 --- /dev/null +++ b/src/revert/function_test_utilities.sql @@ -0,0 +1,23 @@ +BEGIN; + +DROP PROCEDURE vibetype_test.set_local_superuser(); +DROP FUNCTION vibetype_test.account_block_create(UUID, UUID); +DROP FUNCTION vibetype_test.account_block_remove(UUID, UUID); +DROP FUNCTION vibetype_test.account_create(TEXT, TEXT); +DROP FUNCTION vibetype_test.account_remove(TEXT); +DROP FUNCTION vibetype_test.contact_create(UUID, TEXT); +DROP FUNCTION vibetype_test.contact_select_by_account_id(UUID); +DROP FUNCTION vibetype_test.contact_test(TEXT, UUID, UUID[]); +DROP FUNCTION vibetype_test.event_category_create(TEXT); +DROP FUNCTION vibetype_test.event_category_mapping_create(UUID, UUID, TEXT); +DROP FUNCTION vibetype_test.event_category_mapping_test(TEXT, UUID, UUID[]); +DROP FUNCTION vibetype_test.event_create(UUID, TEXT, TEXT, TEXT, TEXT); +DROP FUNCTION vibetype_test.event_test(TEXT, UUID, UUID[]); +DROP FUNCTION vibetype_test.guest_create(UUID, UUID, UUID); +DROP FUNCTION vibetype_test.guest_test(TEXT, UUID, UUID[]); +DROP FUNCTION vibetype_test.guest_claim_from_account_guest(UUID); +DROP FUNCTION vibetype_test.invoker_set(UUID); +DROP FUNCTION vibetype_test.invoker_unset(); +DROP FUNCTION vibetype_test.uuid_array_test(TEXT, UUID[], UUID[]); + +COMMIT; diff --git a/src/revert/table_invitation.sql b/src/revert/table_invitation.sql new file mode 100644 index 00000000..31d314d0 --- /dev/null +++ b/src/revert/table_invitation.sql @@ -0,0 +1,5 @@ +BEGIN; + +DROP TABLE vibetype.invitation; + +COMMIT; diff --git a/src/revert/table_invitation_policy.sql b/src/revert/table_invitation_policy.sql new file mode 100644 index 00000000..aead7aa2 --- /dev/null +++ b/src/revert/table_invitation_policy.sql @@ -0,0 +1,6 @@ +BEGIN; + +DROP POLICY invitation_insert ON vibetype.invitation; +DROP POLICY invitation_select ON vibetype.invitation; + +COMMIT; diff --git a/src/revert/table_notification.sql b/src/revert/table_notification.sql index 1c1b3517..bb85e7c9 100644 --- a/src/revert/table_notification.sql +++ b/src/revert/table_notification.sql @@ -1,5 +1,5 @@ BEGIN; -DROP TABLE vibetype_private.notification; +DROP TABLE vibetype.notification; COMMIT; diff --git a/src/revert/test_account_block.sql b/src/revert/test_account_block.sql index 66b65676..316a70a8 100644 --- a/src/revert/test_account_block.sql +++ b/src/revert/test_account_block.sql @@ -1,23 +1,5 @@ BEGIN; -DROP FUNCTION vibetype_test.account_block_create(UUID, UUID); -DROP FUNCTION vibetype_test.account_block_remove(UUID, UUID); -DROP FUNCTION vibetype_test.account_create(TEXT, TEXT); -DROP FUNCTION vibetype_test.account_remove(TEXT); -DROP FUNCTION vibetype_test.contact_create(UUID, TEXT); -DROP FUNCTION vibetype_test.contact_select_by_account_id(UUID); -DROP FUNCTION vibetype_test.contact_test(TEXT, UUID, UUID[]); -DROP FUNCTION vibetype_test.event_category_create(TEXT); -DROP FUNCTION vibetype_test.event_category_mapping_create(UUID, UUID, TEXT); -DROP FUNCTION vibetype_test.event_category_mapping_test(TEXT, UUID, UUID[]); -DROP FUNCTION vibetype_test.event_create(UUID, TEXT, TEXT, TEXT, TEXT); -DROP FUNCTION vibetype_test.event_test(TEXT, UUID, UUID[]); -DROP FUNCTION vibetype_test.guest_create(UUID, UUID, UUID); -DROP FUNCTION vibetype_test.guest_test(TEXT, UUID, UUID[]); -DROP FUNCTION vibetype_test.guest_claim_from_account_guest(UUID); -DROP FUNCTION vibetype_test.invoker_set(UUID); -DROP FUNCTION vibetype_test.invoker_unset(); -DROP FUNCTION vibetype_test.uuid_array_test(TEXT, UUID[], UUID[]); -DROP PROCEDURE vibetype_test.set_local_superuser(); +-- nothing to be done here; tests used functions from function_test_utilities.sql COMMIT; diff --git a/src/revert/test_invitation.sql b/src/revert/test_invitation.sql new file mode 100644 index 00000000..2035c361 --- /dev/null +++ b/src/revert/test_invitation.sql @@ -0,0 +1,3 @@ +BEGIN; + +COMMIT; \ No newline at end of file diff --git a/src/sqitch.plan b/src/sqitch.plan index 78a81cfa..a40efd45 100644 --- a/src/sqitch.plan +++ b/src/sqitch.plan @@ -91,8 +91,9 @@ table_event_recommendation [schema_public table_account_public table_event] 1970 table_event_recommendation_policy [schema_public table_event_recommendation role_account] 1970-01-01T00:00:00Z marlon # Row level security policies for table event_recommendation. table_event_favorite [schema_public table_account_public table_event] 1970-01-01T00:00:00Z Sven Thelemann # A table for the user accounts' favorite events. table_event_favorite_policy [schema_public table_account_public table_event role_account] 1970-01-01T00:00:00Z Sven Thelemann # Policy for table event_favorite. +function_test_utilities [schema_test schema_public schema_private role_account table_account_private table_account_public function_account_delete function_account_registration function_account_email_address_verification table_contact table_event table_event_category table_event_category_mapping table_guest table_account_block ] 1970-01-01T00:00:00Z Sven Thelemann # A collection of utility functions to be used across al tests. function_guest_create_multiple [schema_public table_guest role_account] 1970-01-01T00:00:00Z Sven Thelemann # Function for inserting multiple guest records. -test_account_block [schema_test] 1970-01-01T00:00:00Z Sven Thelemann # Test cases for account blocking. +test_account_block [schema_test function_test_utilities] 1970-01-01T00:00:00Z Sven Thelemann # Test cases for account blocking. function_event_search [privilege_execute_revoke schema_public enum_language schema_private function_language_iso_full_text_search table_event role_account role_anonymous] 1970-01-01T00:00:00Z Jonas Thelemann # Full-text search on events. test_location [schema_public schema_private table_account_private table_event role_anonymous role_account] 1970-01-01T00:00:00Z Sven Thelemann # A collection of location related functions. table_device [schema_public table_account_public function_trigger_metadata_update] 1970-01-01T00:00:00Z Jonas Thelemann # A device that's assigned to an account. @@ -101,3 +102,6 @@ enum_friendship_status [schema_public] 1970-01-01T00:00:00Z Sven Thelemann # A friend relation together with its status. table_friendship_policy [schema_public table_friendship role_account] 1970-01-01T00:00:00Z Sven Thelemann # Policy for table friend. test_friendship [schema_test] 1970-01-01T00:00:00Z Sven Thelemann # Test cases for friendship. +table_invitation [schema_public table_guest table_account_public] 1970-01-01T00:00:00Z Sven Thelemann # A table for tracking actions around invitations. +table_invitation_policy [schema_public table_invitation role_account function_invoker_account_id table_guest table_event] 1970-01-01T00:00:00Z Sven Thelemann # Stores invitations and their statuses. +test_invitation [schema_test function_test_utilities] 1970-01-01T00:00:00Z Sven Thelemann # Invitation related tests. diff --git a/src/verify/function_account_registration.sql b/src/verify/function_account_registration.sql index 514c48c2..557d792e 100644 --- a/src/verify/function_account_registration.sql +++ b/src/verify/function_account_registration.sql @@ -78,7 +78,7 @@ BEGIN PERFORM vibetype.account_registration('username-8b973f', 'email@example.com', 'password', 'en'); IF NOT EXISTS ( - SELECT 1 FROM vibetype_private.notification + SELECT 1 FROM vibetype.notification WHERE channel = 'account_registration' AND payload::jsonb -> 'account' ->> 'username' = 'username-8b973f' ) THEN diff --git a/src/verify/function_test_utilities.sql b/src/verify/function_test_utilities.sql new file mode 100644 index 00000000..0140ec04 --- /dev/null +++ b/src/verify/function_test_utilities.sql @@ -0,0 +1,28 @@ +BEGIN; + +DO $$ +BEGIN + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.set_local_superuser()', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_anonymous', 'vibetype_test.set_local_superuser()', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_create(TEXT, TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_remove(TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.contact_select_by_account_id(UUID)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.contact_create(UUID, TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_create(UUID, TEXT, TEXT, TEXT, TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.guest_create(UUID, UUID, UUID)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_category_create(TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_category_mapping_create(UUID, UUID, TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_block_create(UUID, UUID)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_block_remove(UUID, UUID)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_test(TEXT, UUID, UUID[])', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_category_mapping_test(TEXT, UUID, UUID[])', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.contact_test(TEXT, UUID, UUID[])', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.guest_test(TEXT, UUID, UUID[])', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.guest_claim_from_account_guest(UUID)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.invoker_set(UUID)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.invoker_unset()', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.uuid_array_test(TEXT, UUID[], UUID[])', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.uuid_array_test(TEXT, UUID[], UUID[])', 'EXECUTE')); +END $$; + +ROLLBACK; \ No newline at end of file diff --git a/src/verify/table_invitation.sql b/src/verify/table_invitation.sql new file mode 100644 index 00000000..4a6ec4e2 --- /dev/null +++ b/src/verify/table_invitation.sql @@ -0,0 +1,16 @@ +BEGIN; + +SELECT + -- inherited from vibetype.notification + id, + channel, + is_acknowledged, + payload, + created_at, + -- columns specific for vibetype.invitation + guest_id, + created_by +FROM vibetype.invitation +WHERE FALSE; + +ROLLBACK; diff --git a/src/verify/table_invitation_policy.sql b/src/verify/table_invitation_policy.sql new file mode 100644 index 00000000..5da8d583 --- /dev/null +++ b/src/verify/table_invitation_policy.sql @@ -0,0 +1,15 @@ +BEGIN; + +DO $$ +BEGIN + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'SELECT')); + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'DELETE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'SELECT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'DELETE')); +END $$; + +ROLLBACK; diff --git a/src/verify/table_notification.sql b/src/verify/table_notification.sql index dce56cd3..8123fed8 100644 --- a/src/verify/table_notification.sql +++ b/src/verify/table_notification.sql @@ -5,6 +5,6 @@ SELECT id, is_acknowledged, payload, created_at -FROM vibetype_private.notification WHERE FALSE; +FROM vibetype.notification WHERE FALSE; ROLLBACK; diff --git a/src/verify/test_invitation.sql b/src/verify/test_invitation.sql new file mode 100644 index 00000000..179bc940 --- /dev/null +++ b/src/verify/test_invitation.sql @@ -0,0 +1,38 @@ +BEGIN; + +DO $$ +DECLARE + accountA UUID; + accountB UUID; + + contactAB UUID; + eventA UUID; + guestAB UUID; + + invitationId UUID; + + rec RECORD; + +BEGIN + + accountA := vibetype_test.account_create('a', 'a@example.com'); + accountB := vibetype_test.account_create('b', 'b@example.com'); + contactAB := vibetype_test.contact_create(accountA, 'b@example.com'); + eventA := vibetype_test.event_create(accountA, 'Event by A', 'event-by-a', '2025-06-01 20:00', 'public'); + guestAB := vibetype_test.guest_create(accountA, eventA, contactAB); + + PERFORM vibetype_test.invoker_set(accountA); + + invitationId := vibetype.invite(guestAB, 'de'); + + SELECT guest_id, created_by, channel INTO rec + FROM vibetype.invitation + WHERE id = invitationId; + + IF rec.guest_id != guestAB or rec.created_by != accountA or rec.channel != 'event_invitation' THEN + RAISE EXCEPTION 'The invitation was not correctly created'; + END IF; + +END $$; + +ROLLBACK; \ No newline at end of file diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index 4608b568..8f905cb5 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -550,7 +550,7 @@ BEGIN IF (_notify_data IS NULL) THEN -- noop ELSE - INSERT INTO vibetype_private.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload) VALUES ( 'account_password_reset_request', jsonb_pretty(jsonb_build_object( 'account', _notify_data, @@ -612,7 +612,7 @@ BEGIN INSERT INTO vibetype.contact(account_id, created_by) VALUES (_new_account_private.id, _new_account_private.id); - INSERT INTO vibetype_private.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), @@ -660,11 +660,10 @@ BEGIN updated.email_address, updated.email_address_verification, updated.email_address_verification_valid_until - FROM updated, vibetype.account - WHERE updated.id = account.id + FROM updated JOIN vibetype.account ON updated.id = account.id INTO _new_account_notify; - INSERT INTO vibetype_private.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), @@ -1478,20 +1477,22 @@ COMMENT ON FUNCTION vibetype.guest_count(event_id uuid) IS 'Returns the guest co -- Name: invite(uuid, text); Type: FUNCTION; Schema: vibetype; Owner: ci -- -CREATE FUNCTION vibetype.invite(guest_id uuid, language text) RETURNS void +CREATE FUNCTION vibetype.invite(guest_id uuid, language text) RETURNS uuid LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ DECLARE _contact RECORD; _email_address TEXT; _event RECORD; - _event_creator_profile_picture_upload_id UUID; _event_creator_profile_picture_upload_storage_key TEXT; _event_creator_username TEXT; _guest RECORD; + _id UUID; BEGIN -- Guest UUID - SELECT * FROM vibetype.guest INTO _guest WHERE guest.id = invite.guest_id; + SELECT * INTO _guest + FROM vibetype.guest + WHERE guest.id = invite.guest_id; IF ( _guest IS NULL @@ -1502,30 +1503,16 @@ BEGIN END IF; -- Event - SELECT - id, - address_id, - description, - "end", - guest_count_maximum, - is_archived, - is_in_person, - is_remote, - name, - slug, - start, - url, - visibility, - created_at, - created_by - FROM vibetype.event INTO _event WHERE "event".id = _guest.event_id; + SELECT * INTO _event FROM vibetype.event WHERE id = _guest.event_id; IF (_event IS NULL) THEN RAISE 'Event not accessible!' USING ERRCODE = 'no_data_found'; END IF; -- Contact - SELECT account_id, email_address FROM vibetype.contact INTO _contact WHERE contact.id = _guest.contact_id; + SELECT account_id, email_address INTO _contact + FROM vibetype.contact + WHERE id = _guest.contact_id; IF (_contact IS NULL) THEN RAISE 'Contact not accessible!' USING ERRCODE = 'no_data_found'; @@ -1539,7 +1526,9 @@ BEGIN END IF; ELSE -- Account - SELECT email_address FROM vibetype_private.account INTO _email_address WHERE account.id = _contact.account_id; + SELECT email_address INTO _email_address + FROM vibetype_private.account + WHERE id = _contact.account_id; IF (_email_address IS NULL) THEN RAISE 'Account email address not accessible!' USING ERRCODE = 'no_data_found'; @@ -1547,14 +1536,19 @@ BEGIN END IF; -- Event creator username - SELECT username FROM vibetype.account INTO _event_creator_username WHERE account.id = _event.created_by; + SELECT username INTO _event_creator_username + FROM vibetype.account + WHERE id = _event.created_by; -- Event creator profile picture storage key - SELECT upload_id FROM vibetype.profile_picture INTO _event_creator_profile_picture_upload_id WHERE profile_picture.account_id = _event.created_by; - SELECT storage_key FROM vibetype.upload INTO _event_creator_profile_picture_upload_storage_key WHERE upload.id = _event_creator_profile_picture_upload_id; + SELECT u.storage_key INTO _event_creator_profile_picture_upload_storage_key + FROM vibetype.profile_picture p + JOIN vibetype.upload u ON p.upload_id = u.id + WHERE p.account_id = _event.created_by; - INSERT INTO vibetype_private.notification (channel, payload) + INSERT INTO vibetype.invitation (guest_id, channel, payload, created_by) VALUES ( + invite.guest_id, 'event_invitation', jsonb_pretty(jsonb_build_object( 'data', jsonb_build_object( @@ -1565,8 +1559,12 @@ BEGIN 'guestId', _guest.id ), 'template', jsonb_build_object('language', invite.language) - )) - ); + )), + vibetype.invoker_account_id() + ) + RETURNING id INTO _id; + + RETURN _id; END; $$; @@ -1577,7 +1575,7 @@ ALTER FUNCTION vibetype.invite(guest_id uuid, language text) OWNER TO ci; -- Name: FUNCTION invite(guest_id uuid, language text); Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON FUNCTION vibetype.invite(guest_id uuid, language text) IS 'Adds a notification for the invitation channel.'; +COMMENT ON FUNCTION vibetype.invite(guest_id uuid, language text) IS 'Adds an invitation and a notification.'; -- @@ -1724,10 +1722,15 @@ ALTER FUNCTION vibetype.legal_term_change() OWNER TO ci; CREATE FUNCTION vibetype.notification_acknowledge(id uuid, is_acknowledged boolean) RETURNS void LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ +DECLARE + update_count INTEGER; BEGIN - IF (EXISTS (SELECT 1 FROM vibetype_private.notification WHERE "notification".id = notification_acknowledge.id)) THEN - UPDATE vibetype_private.notification SET is_acknowledged = notification_acknowledge.is_acknowledged WHERE "notification".id = notification_acknowledge.id; - ELSE + UPDATE vibetype.notification SET + is_acknowledged = notification_acknowledge.is_acknowledged + WHERE id = notification_acknowledge.id; + + GET DIAGNOSTICS update_count = ROW_COUNT; + IF update_count = 0 THEN RAISE 'Notification with given id not found!' USING ERRCODE = 'no_data_found'; END IF; END; @@ -2222,7 +2225,7 @@ BEGIN VALUES (_created_by, _blocked_Account_id) RETURNING id INTO _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; RETURN _id; END $$; @@ -2420,7 +2423,7 @@ BEGIN PERFORM vibetype.account_delete('password'); - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; END IF; END $$; @@ -2451,7 +2454,7 @@ BEGIN UPDATE vibetype.contact SET account_id = _account_id WHERE id = _id; END IF; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; RETURN _id; END $$; @@ -2505,7 +2508,7 @@ BEGIN RAISE EXCEPTION 'some contact is missing in the query result'; END IF; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; END $$; @@ -2539,7 +2542,7 @@ BEGIN INSERT INTO vibetype.event_category_mapping(event_id, category) VALUES (_event_id, _category); - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; END $$; @@ -2569,7 +2572,7 @@ BEGIN RAISE EXCEPTION 'some event_category_mappings is missing in the query result'; END IF; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; END $$; @@ -2592,7 +2595,7 @@ BEGIN VALUES (_created_by, _name, _slug, _start::TIMESTAMP WITH TIME ZONE, _visibility::vibetype.event_visibility) RETURNING id INTO _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; RETURN _id; END $$; @@ -2730,7 +2733,7 @@ BEGIN RAISE EXCEPTION 'some event is missing in the query result'; END IF; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; END $$; @@ -2952,7 +2955,7 @@ BEGIN EXECUTE 'SET LOCAL jwt.claims.guests = ''[' || _text || ']'''; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; RETURN _result; END $$; @@ -2977,7 +2980,7 @@ BEGIN VALUES (_contact_id, _event_id) RETURNING id INTO _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; RETURN _id; END $$; @@ -3009,7 +3012,7 @@ BEGIN RAISE EXCEPTION 'some guest is missing in the query result'; END IF; - CALL vibetype_test.set_local_superuser(); + SET LOCAL role = 'ci'; END $$; @@ -4698,6 +4701,99 @@ ALTER VIEW vibetype.guest_flat OWNER TO ci; COMMENT ON VIEW vibetype.guest_flat IS 'View returning flattened guests.'; +-- +-- Name: notification; Type: TABLE; Schema: vibetype; Owner: ci +-- + +CREATE TABLE vibetype.notification ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + channel text NOT NULL, + is_acknowledged boolean, + payload text NOT NULL, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT notification_payload_check CHECK ((octet_length(payload) <= 8000)) +); + + +ALTER TABLE vibetype.notification OWNER TO ci; + +-- +-- Name: TABLE notification; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON TABLE vibetype.notification IS 'A notification.'; + + +-- +-- Name: COLUMN notification.id; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.notification.id IS 'The notification''s internal id.'; + + +-- +-- Name: COLUMN notification.channel; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.notification.channel IS 'The notification''s channel.'; + + +-- +-- Name: COLUMN notification.is_acknowledged; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; + + +-- +-- Name: COLUMN notification.payload; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload.'; + + +-- +-- Name: COLUMN notification.created_at; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; + + +-- +-- Name: invitation; Type: TABLE; Schema: vibetype; Owner: ci +-- + +CREATE TABLE vibetype.invitation ( + guest_id uuid NOT NULL, + created_by uuid NOT NULL +) +INHERITS (vibetype.notification); + + +ALTER TABLE vibetype.invitation OWNER TO ci; + +-- +-- Name: TABLE invitation; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON TABLE vibetype.invitation IS '@omit update,delete\nStores invitations and their statuses.'; + + +-- +-- Name: COLUMN invitation.guest_id; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.invitation.guest_id IS 'The ID of the guest associated with this invitation.'; + + +-- +-- Name: COLUMN invitation.created_by; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.invitation.created_by IS '@omit create +Reference to the account that created the invitation.'; + + -- -- Name: legal_term; Type: TABLE; Schema: vibetype; Owner: ci -- @@ -5134,61 +5230,17 @@ COMMENT ON COLUMN vibetype_private.jwt.token IS 'The token.'; -- --- Name: notification; Type: TABLE; Schema: vibetype_private; Owner: ci --- - -CREATE TABLE vibetype_private.notification ( - id uuid DEFAULT gen_random_uuid() NOT NULL, - channel text NOT NULL, - is_acknowledged boolean, - payload text NOT NULL, - created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, - CONSTRAINT notification_payload_check CHECK ((octet_length(payload) <= 8000)) -); - - -ALTER TABLE vibetype_private.notification OWNER TO ci; - --- --- Name: TABLE notification; Type: COMMENT; Schema: vibetype_private; Owner: ci --- - -COMMENT ON TABLE vibetype_private.notification IS 'A notification.'; - - --- --- Name: COLUMN notification.id; Type: COMMENT; Schema: vibetype_private; Owner: ci +-- Name: invitation id; Type: DEFAULT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype_private.notification.id IS 'The notification''s internal id.'; +ALTER TABLE ONLY vibetype.invitation ALTER COLUMN id SET DEFAULT gen_random_uuid(); -- --- Name: COLUMN notification.channel; Type: COMMENT; Schema: vibetype_private; Owner: ci +-- Name: invitation created_at; Type: DEFAULT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype_private.notification.channel IS 'The notification''s channel.'; - - --- --- Name: COLUMN notification.is_acknowledged; Type: COMMENT; Schema: vibetype_private; Owner: ci --- - -COMMENT ON COLUMN vibetype_private.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; - - --- --- Name: COLUMN notification.payload; Type: COMMENT; Schema: vibetype_private; Owner: ci --- - -COMMENT ON COLUMN vibetype_private.notification.payload IS 'The notification''s payload.'; - - --- --- Name: COLUMN notification.created_at; Type: COMMENT; Schema: vibetype_private; Owner: ci --- - -COMMENT ON COLUMN vibetype_private.notification.created_at IS 'The timestamp of the notification''s creation.'; +ALTER TABLE ONLY vibetype.invitation ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP; -- @@ -5563,6 +5615,14 @@ ALTER TABLE ONLY vibetype.legal_term ADD CONSTRAINT legal_term_pkey PRIMARY KEY (id); +-- +-- Name: notification notification_pkey; Type: CONSTRAINT; Schema: vibetype; Owner: ci +-- + +ALTER TABLE ONLY vibetype.notification + ADD CONSTRAINT notification_pkey PRIMARY KEY (id); + + -- -- Name: profile_picture profile_picture_account_id_key; Type: CONSTRAINT; Schema: vibetype; Owner: ci -- @@ -5666,14 +5726,6 @@ ALTER TABLE ONLY vibetype_private.jwt ADD CONSTRAINT jwt_token_key UNIQUE (token); --- --- Name: notification notification_pkey; Type: CONSTRAINT; Schema: vibetype_private; Owner: ci --- - -ALTER TABLE ONLY vibetype_private.notification - ADD CONSTRAINT notification_pkey PRIMARY KEY (id); - - -- -- Name: idx_address_created_by; Type: INDEX; Schema: vibetype; Owner: ci -- @@ -5800,6 +5852,13 @@ CREATE INDEX idx_guest_updated_by ON vibetype.guest USING btree (updated_by); COMMENT ON INDEX vibetype.idx_guest_updated_by IS 'B-Tree index to optimize lookups by updater.'; +-- +-- Name: idx_invitation_guest_id; Type: INDEX; Schema: vibetype; Owner: ci +-- + +CREATE INDEX idx_invitation_guest_id ON vibetype.invitation USING btree (guest_id); + + -- -- Name: idx_account_private_location; Type: INDEX; Schema: vibetype_private; Owner: ci -- @@ -6219,6 +6278,22 @@ ALTER TABLE ONLY vibetype.guest ADD CONSTRAINT guest_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES vibetype.account(id); +-- +-- Name: invitation invitation_created_by_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci +-- + +ALTER TABLE ONLY vibetype.invitation + ADD CONSTRAINT invitation_created_by_fkey FOREIGN KEY (created_by) REFERENCES vibetype.account(id); + + +-- +-- Name: invitation invitation_guest_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci +-- + +ALTER TABLE ONLY vibetype.invitation + ADD CONSTRAINT invitation_guest_id_fkey FOREIGN KEY (guest_id) REFERENCES vibetype.guest(id); + + -- -- Name: legal_term_acceptance legal_term_acceptance_account_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci -- @@ -6733,6 +6808,29 @@ EXCEPT FROM vibetype_private.account_block_ids() account_block_ids(id))))))))))); +-- +-- Name: invitation; Type: ROW SECURITY; Schema: vibetype; Owner: ci +-- + +ALTER TABLE vibetype.invitation ENABLE ROW LEVEL SECURITY; + +-- +-- Name: invitation invitation_insert; Type: POLICY; Schema: vibetype; Owner: ci +-- + +CREATE POLICY invitation_insert ON vibetype.invitation FOR INSERT WITH CHECK (((created_by = vibetype.invoker_account_id()) AND (vibetype.invoker_account_id() = ( SELECT e.created_by + FROM (vibetype.guest g + JOIN vibetype.event e ON ((g.event_id = e.id))) + WHERE (g.id = invitation.guest_id))))); + + +-- +-- Name: invitation invitation_select; Type: POLICY; Schema: vibetype; Owner: ci +-- + +CREATE POLICY invitation_select ON vibetype.invitation FOR SELECT USING ((created_by = vibetype.invoker_account_id())); + + -- -- Name: legal_term; Type: ROW SECURITY; Schema: vibetype; Owner: ci -- @@ -7858,6 +7956,13 @@ GRANT SELECT ON TABLE vibetype.guest_flat TO vibetype_account; GRANT SELECT ON TABLE vibetype.guest_flat TO vibetype_anonymous; +-- +-- Name: TABLE invitation; Type: ACL; Schema: vibetype; Owner: ci +-- + +GRANT SELECT,INSERT ON TABLE vibetype.invitation TO vibetype_account; + + -- -- Name: TABLE legal_term; Type: ACL; Schema: vibetype; Owner: ci -- From 6adb11599af02f60001d3fa496ec13d2112dc952 Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Tue, 25 Mar 2025 18:33:04 +0100 Subject: [PATCH 02/12] fix(role): make role reset independent of user name In `function_test_utilities.sql' many functions set the role back to the session user at their end. We use `SET LOCAL ROLE NONE`to be independent of any real user name. --- src/deploy/function_test_utilities.sql | 22 +++++++++++----------- test/schema/schema.definition.sql | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/deploy/function_test_utilities.sql b/src/deploy/function_test_utilities.sql index 4170c9d5..fe7dc755 100644 --- a/src/deploy/function_test_utilities.sql +++ b/src/deploy/function_test_utilities.sql @@ -60,7 +60,7 @@ BEGIN PERFORM vibetype.account_delete('password'); - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END IF; END $$ LANGUAGE plpgsql; @@ -104,7 +104,7 @@ BEGIN UPDATE vibetype.contact SET account_id = _account_id WHERE id = _id; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$ LANGUAGE plpgsql; @@ -129,7 +129,7 @@ BEGIN VALUES (_created_by, _name, _slug, _start::TIMESTAMP WITH TIME ZONE, _visibility::vibetype.event_visibility) RETURNING id INTO _id; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$ LANGUAGE plpgsql; @@ -152,7 +152,7 @@ BEGIN VALUES (_contact_id, _event_id) RETURNING id INTO _id; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$ LANGUAGE plpgsql; @@ -182,7 +182,7 @@ BEGIN INSERT INTO vibetype.event_category_mapping(event_id, category) VALUES (_event_id, _category); - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.event_category_mapping_create(UUID, UUID, TEXT) TO vibetype_account; @@ -202,7 +202,7 @@ BEGIN VALUES (_created_by, _blocked_Account_id) RETURNING id INTO _id; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$ LANGUAGE plpgsql; @@ -246,7 +246,7 @@ BEGIN RAISE EXCEPTION 'some event is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.event_test(TEXT, UUID, UUID[]) TO vibetype_account; @@ -274,7 +274,7 @@ BEGIN RAISE EXCEPTION 'some event_category_mappings is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.event_category_mapping_test(TEXT, UUID, UUID[]) TO vibetype_account; @@ -304,7 +304,7 @@ BEGIN RAISE EXCEPTION 'some contact is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.contact_test(TEXT, UUID, UUID[]) TO vibetype_account; @@ -332,7 +332,7 @@ BEGIN RAISE EXCEPTION 'some guest is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.guest_test(TEXT, UUID, UUID[]) TO vibetype_account; @@ -370,7 +370,7 @@ BEGIN EXECUTE 'SET LOCAL jwt.claims.guests = ''[' || _text || ']'''; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _result; END $$ LANGUAGE plpgsql; diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index 8f905cb5..7516acd2 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -2225,7 +2225,7 @@ BEGIN VALUES (_created_by, _blocked_Account_id) RETURNING id INTO _id; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$; @@ -2423,7 +2423,7 @@ BEGIN PERFORM vibetype.account_delete('password'); - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END IF; END $$; @@ -2454,7 +2454,7 @@ BEGIN UPDATE vibetype.contact SET account_id = _account_id WHERE id = _id; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$; @@ -2508,7 +2508,7 @@ BEGIN RAISE EXCEPTION 'some contact is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$; @@ -2542,7 +2542,7 @@ BEGIN INSERT INTO vibetype.event_category_mapping(event_id, category) VALUES (_event_id, _category); - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$; @@ -2572,7 +2572,7 @@ BEGIN RAISE EXCEPTION 'some event_category_mappings is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$; @@ -2595,7 +2595,7 @@ BEGIN VALUES (_created_by, _name, _slug, _start::TIMESTAMP WITH TIME ZONE, _visibility::vibetype.event_visibility) RETURNING id INTO _id; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$; @@ -2733,7 +2733,7 @@ BEGIN RAISE EXCEPTION 'some event is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$; @@ -2955,7 +2955,7 @@ BEGIN EXECUTE 'SET LOCAL jwt.claims.guests = ''[' || _text || ']'''; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _result; END $$; @@ -2980,7 +2980,7 @@ BEGIN VALUES (_contact_id, _event_id) RETURNING id INTO _id; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; RETURN _id; END $$; @@ -3012,7 +3012,7 @@ BEGIN RAISE EXCEPTION 'some guest is missing in the query result'; END IF; - SET LOCAL role = 'ci'; + SET LOCAL ROLE NONE; END $$; From 1ce8e75a49a2f32189fa656594b5820e1ce3ce73 Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Sun, 30 Mar 2025 00:17:47 +0100 Subject: [PATCH 03/12] chore(role): remove function `set_local_superuser` Switching back to the login role can be done with ` SET LOCAL ROLE NONE`. --- src/deploy/function_test_utilities.sql | 23 +------------ src/deploy/test_friendship.sql | 8 ++--- src/revert/function_test_utilities.sql | 1 - src/verify/function_test_utilities.sql | 2 -- test/schema/schema.definition.sql | 45 +++----------------------- 5 files changed, 10 insertions(+), 69 deletions(-) diff --git a/src/deploy/function_test_utilities.sql b/src/deploy/function_test_utilities.sql index fe7dc755..14f4818f 100644 --- a/src/deploy/function_test_utilities.sql +++ b/src/deploy/function_test_utilities.sql @@ -1,26 +1,5 @@ BEGIN; -CREATE PROCEDURE vibetype_test.set_local_superuser() -AS $$ -DECLARE - _superuser_name TEXT; -BEGIN - SELECT usename INTO _superuser_name - FROM pg_user - WHERE usesuper = true - ORDER BY usename - LIMIT 1; - - IF _superuser_name IS NOT NULL THEN - EXECUTE format('SET LOCAL role = %I', _superuser_name); - ELSE - RAISE NOTICE 'No superuser found!'; - END IF; -END $$ LANGUAGE plpgsql; - -GRANT EXECUTE ON PROCEDURE vibetype_test.set_local_superuser() TO vibetype_anonymous, vibetype_account; - - CREATE FUNCTION vibetype_test.account_create ( _username TEXT, _email TEXT @@ -393,7 +372,7 @@ GRANT EXECUTE ON FUNCTION vibetype_test.invoker_set(UUID) TO vibetype_account; CREATE FUNCTION vibetype_test.invoker_unset () RETURNS VOID AS $$ BEGIN - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; EXECUTE 'SET LOCAL jwt.claims.account_id = '''''; END $$ LANGUAGE plpgsql; diff --git a/src/deploy/test_friendship.sql b/src/deploy/test_friendship.sql index d835b4ec..d0cf1d71 100644 --- a/src/deploy/test_friendship.sql +++ b/src/deploy/test_friendship.sql @@ -15,7 +15,7 @@ BEGIN SET "status" = 'accepted'::vibetype.friendship_status WHERE id = _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; CREATE FUNCTION vibetype_test.friendship_reject ( @@ -29,7 +29,7 @@ BEGIN DELETE FROM vibetype.friendship WHERE id = _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; CREATE FUNCTION vibetype_test.friendship_request ( @@ -56,7 +56,7 @@ BEGIN VALUES (_a_account_id, _b_account_id, _invoker_account_id) RETURNING id INTO _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; RETURN _id; END $$ LANGUAGE plpgsql; @@ -94,7 +94,7 @@ BEGIN RAISE EXCEPTION 'some account is missing in the query result'; END IF; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; END $$ LANGUAGE plpgsql; CREATE FUNCTION vibetype_test.friendship_account_ids_test ( diff --git a/src/revert/function_test_utilities.sql b/src/revert/function_test_utilities.sql index cc1ff0e8..0d3a4c85 100644 --- a/src/revert/function_test_utilities.sql +++ b/src/revert/function_test_utilities.sql @@ -1,6 +1,5 @@ BEGIN; -DROP PROCEDURE vibetype_test.set_local_superuser(); DROP FUNCTION vibetype_test.account_block_create(UUID, UUID); DROP FUNCTION vibetype_test.account_block_remove(UUID, UUID); DROP FUNCTION vibetype_test.account_create(TEXT, TEXT); diff --git a/src/verify/function_test_utilities.sql b/src/verify/function_test_utilities.sql index 0140ec04..2f6b841a 100644 --- a/src/verify/function_test_utilities.sql +++ b/src/verify/function_test_utilities.sql @@ -2,8 +2,6 @@ BEGIN; DO $$ BEGIN - ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.set_local_superuser()', 'EXECUTE')); - ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_anonymous', 'vibetype_test.set_local_superuser()', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_create(TEXT, TEXT)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_remove(TEXT)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.contact_select_by_account_id(UUID)', 'EXECUTE')); diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index 7516acd2..7fbb6c82 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -2757,7 +2757,7 @@ BEGIN SET "status" = 'accepted'::vibetype.friendship_status WHERE id = _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; END $$; @@ -2839,7 +2839,7 @@ BEGIN DELETE FROM vibetype.friendship WHERE id = _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; END $$; @@ -2872,7 +2872,7 @@ BEGIN VALUES (_a_account_id, _b_account_id, _invoker_account_id) RETURNING id INTO _id; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; RETURN _id; END $$; @@ -2914,7 +2914,7 @@ BEGIN RAISE EXCEPTION 'some account is missing in the query result'; END IF; - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; END $$; @@ -3075,39 +3075,13 @@ CREATE FUNCTION vibetype_test.invoker_unset() RETURNS void LANGUAGE plpgsql AS $$ BEGIN - CALL vibetype_test.set_local_superuser(); + SET LOCAL ROLE NONE; EXECUTE 'SET LOCAL jwt.claims.account_id = '''''; END $$; ALTER FUNCTION vibetype_test.invoker_unset() OWNER TO ci; --- --- Name: set_local_superuser(); Type: PROCEDURE; Schema: vibetype_test; Owner: ci --- - -CREATE PROCEDURE vibetype_test.set_local_superuser() - LANGUAGE plpgsql - AS $$ -DECLARE - _superuser_name TEXT; -BEGIN - SELECT usename INTO _superuser_name - FROM pg_user - WHERE usesuper = true - ORDER BY usename - LIMIT 1; - - IF _superuser_name IS NOT NULL THEN - EXECUTE format('SET LOCAL role = %I', _superuser_name); - ELSE - RAISE NOTICE 'No superuser found!'; - END IF; -END $$; - - -ALTER PROCEDURE vibetype_test.set_local_superuser() OWNER TO ci; - -- -- Name: uuid_array_test(text, uuid[], uuid[]); Type: FUNCTION; Schema: vibetype_test; Owner: ci -- @@ -7801,15 +7775,6 @@ REVOKE ALL ON FUNCTION vibetype_test.invoker_unset() FROM PUBLIC; GRANT ALL ON FUNCTION vibetype_test.invoker_unset() TO vibetype_account; --- --- Name: PROCEDURE set_local_superuser(); Type: ACL; Schema: vibetype_test; Owner: ci --- - -REVOKE ALL ON PROCEDURE vibetype_test.set_local_superuser() FROM PUBLIC; -GRANT ALL ON PROCEDURE vibetype_test.set_local_superuser() TO vibetype_anonymous; -GRANT ALL ON PROCEDURE vibetype_test.set_local_superuser() TO vibetype_account; - - -- -- Name: FUNCTION uuid_array_test(_test_case text, _array uuid[], _expected_array uuid[]); Type: ACL; Schema: vibetype_test; Owner: ci -- From d5917f35056254f3dcf4db480a403601005542d0 Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Sun, 30 Mar 2025 16:10:44 +0200 Subject: [PATCH 04/12] chore(renaming): rename table `invitation` Table `invitation` has been renamed to `notification_invitation`. --- src/deploy/function_invite.sql | 2 +- src/deploy/table_invitation.sql | 16 -- src/deploy/table_invitation_policy.sql | 22 -- src/deploy/table_notification_invitation.sql | 16 ++ .../table_notification_invitation_policy.sql | 22 ++ src/revert/table_invitation.sql | 5 - src/revert/table_invitation_policy.sql | 6 - src/revert/table_notification_invitation.sql | 5 + .../table_notification_invitation_policy.sql | 6 + src/sqitch.plan | 4 +- ....sql => table_notification_invitation.sql} | 4 +- .../table_notification_invitation_policy.sql | 15 ++ src/verify/test_invitation.sql | 2 +- test/schema/schema.definition.sql | 252 +++++++++--------- 14 files changed, 196 insertions(+), 181 deletions(-) delete mode 100644 src/deploy/table_invitation.sql delete mode 100644 src/deploy/table_invitation_policy.sql create mode 100644 src/deploy/table_notification_invitation.sql create mode 100644 src/deploy/table_notification_invitation_policy.sql delete mode 100644 src/revert/table_invitation.sql delete mode 100644 src/revert/table_invitation_policy.sql create mode 100644 src/revert/table_notification_invitation.sql create mode 100644 src/revert/table_notification_invitation_policy.sql rename src/verify/{table_invitation.sql => table_notification_invitation.sql} (63%) create mode 100644 src/verify/table_notification_invitation_policy.sql diff --git a/src/deploy/function_invite.sql b/src/deploy/function_invite.sql index b8e99261..cb194a4d 100644 --- a/src/deploy/function_invite.sql +++ b/src/deploy/function_invite.sql @@ -70,7 +70,7 @@ BEGIN JOIN vibetype.upload u ON p.upload_id = u.id WHERE p.account_id = _event.created_by; - INSERT INTO vibetype.invitation (guest_id, channel, payload, created_by) + INSERT INTO vibetype.notification_invitation (guest_id, channel, payload, created_by) VALUES ( invite.guest_id, 'event_invitation', diff --git a/src/deploy/table_invitation.sql b/src/deploy/table_invitation.sql deleted file mode 100644 index 8a21cc1d..00000000 --- a/src/deploy/table_invitation.sql +++ /dev/null @@ -1,16 +0,0 @@ -BEGIN; - -CREATE TABLE vibetype.invitation ( - guest_id UUID NOT NULL REFERENCES vibetype.guest(id), - -- created_at is already column of table notification - created_by UUID NOT NULL REFERENCES vibetype.account(id) -) -INHERITS (vibetype.notification); - -COMMENT ON TABLE vibetype.invitation IS '@omit update,delete\nStores invitations and their statuses.'; -COMMENT ON COLUMN vibetype.invitation.guest_id IS 'The ID of the guest associated with this invitation.'; -COMMENT ON COLUMN vibetype.invitation.created_by IS E'@omit create\nReference to the account that created the invitation.'; - -CREATE INDEX idx_invitation_guest_id ON vibetype.invitation USING btree (guest_id); - -COMMIT; diff --git a/src/deploy/table_invitation_policy.sql b/src/deploy/table_invitation_policy.sql deleted file mode 100644 index ece1b928..00000000 --- a/src/deploy/table_invitation_policy.sql +++ /dev/null @@ -1,22 +0,0 @@ -BEGIN; - -GRANT SELECT, INSERT ON vibetype.invitation TO vibetype_account; - -ALTER TABLE vibetype.invitation ENABLE ROW LEVEL SECURITY; - -CREATE POLICY invitation_select ON vibetype.invitation FOR SELECT USING ( - created_by = vibetype.invoker_account_id() -); - -CREATE POLICY invitation_insert ON vibetype.invitation FOR INSERT WITH CHECK ( - created_by = vibetype.invoker_account_id() - AND - vibetype.invoker_account_id() = ( - SELECT e.created_by - FROM vibetype.guest g - JOIN vibetype.event e ON g.event_id = e.id - WHERE g.id = guest_id - ) -); - -COMMIT; diff --git a/src/deploy/table_notification_invitation.sql b/src/deploy/table_notification_invitation.sql new file mode 100644 index 00000000..a16c29f9 --- /dev/null +++ b/src/deploy/table_notification_invitation.sql @@ -0,0 +1,16 @@ +BEGIN; + +CREATE TABLE vibetype.notification_invitation ( + guest_id UUID NOT NULL REFERENCES vibetype.guest(id), + -- created_at is already column of table notification + created_by UUID NOT NULL REFERENCES vibetype.account(id) +) +INHERITS (vibetype.notification); + +COMMENT ON TABLE vibetype.notification_invitation IS '@omit update,delete\nStores invitations and their statuses.'; +COMMENT ON COLUMN vibetype.notification_invitation.guest_id IS 'The ID of the guest associated with this invitation.'; +COMMENT ON COLUMN vibetype.notification_invitation.created_by IS E'@omit create\nReference to the account that created the invitation.'; + +CREATE INDEX idx_invitation_guest_id ON vibetype.notification_invitation USING btree (guest_id); + +COMMIT; diff --git a/src/deploy/table_notification_invitation_policy.sql b/src/deploy/table_notification_invitation_policy.sql new file mode 100644 index 00000000..ff794207 --- /dev/null +++ b/src/deploy/table_notification_invitation_policy.sql @@ -0,0 +1,22 @@ +BEGIN; + +GRANT SELECT, INSERT ON vibetype.notification_invitation TO vibetype_account; + +ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; + +CREATE POLICY notification_invitation_select ON vibetype.notification_invitation FOR SELECT USING ( + created_by = vibetype.invoker_account_id() +); + +CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT WITH CHECK ( + created_by = vibetype.invoker_account_id() + AND + vibetype.invoker_account_id() = ( + SELECT e.created_by + FROM vibetype.guest g + JOIN vibetype.event e ON g.event_id = e.id + WHERE g.id = guest_id + ) +); + +COMMIT; diff --git a/src/revert/table_invitation.sql b/src/revert/table_invitation.sql deleted file mode 100644 index 31d314d0..00000000 --- a/src/revert/table_invitation.sql +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN; - -DROP TABLE vibetype.invitation; - -COMMIT; diff --git a/src/revert/table_invitation_policy.sql b/src/revert/table_invitation_policy.sql deleted file mode 100644 index aead7aa2..00000000 --- a/src/revert/table_invitation_policy.sql +++ /dev/null @@ -1,6 +0,0 @@ -BEGIN; - -DROP POLICY invitation_insert ON vibetype.invitation; -DROP POLICY invitation_select ON vibetype.invitation; - -COMMIT; diff --git a/src/revert/table_notification_invitation.sql b/src/revert/table_notification_invitation.sql new file mode 100644 index 00000000..0e1c2a70 --- /dev/null +++ b/src/revert/table_notification_invitation.sql @@ -0,0 +1,5 @@ +BEGIN; + +DROP TABLE vibetype.notification_invitation; + +COMMIT; diff --git a/src/revert/table_notification_invitation_policy.sql b/src/revert/table_notification_invitation_policy.sql new file mode 100644 index 00000000..b016f766 --- /dev/null +++ b/src/revert/table_notification_invitation_policy.sql @@ -0,0 +1,6 @@ +BEGIN; + +DROP POLICY notification_invitation_insert ON vibetype.notification_invitation; +DROP POLICY notification_invitation_select ON vibetype.notification_invitation; + +COMMIT; diff --git a/src/sqitch.plan b/src/sqitch.plan index a40efd45..1a957396 100644 --- a/src/sqitch.plan +++ b/src/sqitch.plan @@ -102,6 +102,6 @@ enum_friendship_status [schema_public] 1970-01-01T00:00:00Z Sven Thelemann # A friend relation together with its status. table_friendship_policy [schema_public table_friendship role_account] 1970-01-01T00:00:00Z Sven Thelemann # Policy for table friend. test_friendship [schema_test] 1970-01-01T00:00:00Z Sven Thelemann # Test cases for friendship. -table_invitation [schema_public table_guest table_account_public] 1970-01-01T00:00:00Z Sven Thelemann # A table for tracking actions around invitations. -table_invitation_policy [schema_public table_invitation role_account function_invoker_account_id table_guest table_event] 1970-01-01T00:00:00Z Sven Thelemann # Stores invitations and their statuses. +table_notification_invitation [schema_public table_guest table_account_public] 1970-01-01T00:00:00Z Sven Thelemann # A table for tracking actions around invitations. +table_notification_invitation_policy [schema_public table_notification_invitation role_account function_invoker_account_id table_guest table_event] 1970-01-01T00:00:00Z Sven Thelemann # Stores invitations and their statuses. test_invitation [schema_test function_test_utilities] 1970-01-01T00:00:00Z Sven Thelemann # Invitation related tests. diff --git a/src/verify/table_invitation.sql b/src/verify/table_notification_invitation.sql similarity index 63% rename from src/verify/table_invitation.sql rename to src/verify/table_notification_invitation.sql index 4a6ec4e2..9b9638e9 100644 --- a/src/verify/table_invitation.sql +++ b/src/verify/table_notification_invitation.sql @@ -7,10 +7,10 @@ SELECT is_acknowledged, payload, created_at, - -- columns specific for vibetype.invitation + -- columns specific for vibetype.notification_invitation guest_id, created_by -FROM vibetype.invitation +FROM vibetype.notification_invitation WHERE FALSE; ROLLBACK; diff --git a/src/verify/table_notification_invitation_policy.sql b/src/verify/table_notification_invitation_policy.sql new file mode 100644 index 00000000..961ecb09 --- /dev/null +++ b/src/verify/table_notification_invitation_policy.sql @@ -0,0 +1,15 @@ +BEGIN; + +DO $$ +BEGIN + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'SELECT')); + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'DELETE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'SELECT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'DELETE')); +END $$; + +ROLLBACK; diff --git a/src/verify/test_invitation.sql b/src/verify/test_invitation.sql index 179bc940..5c343a5e 100644 --- a/src/verify/test_invitation.sql +++ b/src/verify/test_invitation.sql @@ -26,7 +26,7 @@ BEGIN invitationId := vibetype.invite(guestAB, 'de'); SELECT guest_id, created_by, channel INTO rec - FROM vibetype.invitation + FROM vibetype.notification_invitation WHERE id = invitationId; IF rec.guest_id != guestAB or rec.created_by != accountA or rec.channel != 'event_invitation' THEN diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index 7fbb6c82..8d151bf6 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -1546,7 +1546,7 @@ BEGIN JOIN vibetype.upload u ON p.upload_id = u.id WHERE p.account_id = _event.created_by; - INSERT INTO vibetype.invitation (guest_id, channel, payload, created_by) + INSERT INTO vibetype.notification_invitation (guest_id, channel, payload, created_by) VALUES ( invite.guest_id, 'event_invitation', @@ -4676,208 +4676,208 @@ COMMENT ON VIEW vibetype.guest_flat IS 'View returning flattened guests.'; -- --- Name: notification; Type: TABLE; Schema: vibetype; Owner: ci +-- Name: legal_term; Type: TABLE; Schema: vibetype; Owner: ci -- -CREATE TABLE vibetype.notification ( +CREATE TABLE vibetype.legal_term ( id uuid DEFAULT gen_random_uuid() NOT NULL, - channel text NOT NULL, - is_acknowledged boolean, - payload text NOT NULL, + language character varying(5) DEFAULT 'en'::character varying NOT NULL, + term text NOT NULL, + version character varying(20) NOT NULL, created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, - CONSTRAINT notification_payload_check CHECK ((octet_length(payload) <= 8000)) + CONSTRAINT legal_term_language_check CHECK (((language)::text ~ '^[a-z]{2}(_[A-Z]{2})?$'::text)), + CONSTRAINT legal_term_term_check CHECK (((char_length(term) > 0) AND (char_length(term) <= 500000))), + CONSTRAINT legal_term_version_check CHECK (((version)::text ~ '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$'::text)) ); -ALTER TABLE vibetype.notification OWNER TO ci; +ALTER TABLE vibetype.legal_term OWNER TO ci; -- --- Name: TABLE notification; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: TABLE legal_term; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON TABLE vibetype.notification IS 'A notification.'; +COMMENT ON TABLE vibetype.legal_term IS '@omit create,update,delete +Legal terms like privacy policies or terms of service.'; -- --- Name: COLUMN notification.id; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term.id; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.notification.id IS 'The notification''s internal id.'; +COMMENT ON COLUMN vibetype.legal_term.id IS 'Unique identifier for each legal term.'; -- --- Name: COLUMN notification.channel; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term.language; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.notification.channel IS 'The notification''s channel.'; +COMMENT ON COLUMN vibetype.legal_term.language IS 'Language code in ISO 639-1 format with optional region (e.g., `en` for English, `en_GB` for British English)'; -- --- Name: COLUMN notification.is_acknowledged; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term.term; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; +COMMENT ON COLUMN vibetype.legal_term.term IS 'Text of the legal term. Markdown is expected to be used. It must be non-empty and cannot exceed 500,000 characters.'; -- --- Name: COLUMN notification.payload; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term.version; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload.'; +COMMENT ON COLUMN vibetype.legal_term.version IS 'Semantic versioning string to track changes to the legal terms (format: `X.Y.Z`).'; -- --- Name: COLUMN notification.created_at; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term.created_at; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; +COMMENT ON COLUMN vibetype.legal_term.created_at IS 'Timestamp when the term was created. Set to the current time by default.'; -- --- Name: invitation; Type: TABLE; Schema: vibetype; Owner: ci +-- Name: legal_term_acceptance; Type: TABLE; Schema: vibetype; Owner: ci -- -CREATE TABLE vibetype.invitation ( - guest_id uuid NOT NULL, - created_by uuid NOT NULL -) -INHERITS (vibetype.notification); +CREATE TABLE vibetype.legal_term_acceptance ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + account_id uuid NOT NULL, + legal_term_id uuid NOT NULL, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); -ALTER TABLE vibetype.invitation OWNER TO ci; +ALTER TABLE vibetype.legal_term_acceptance OWNER TO ci; -- --- Name: TABLE invitation; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: TABLE legal_term_acceptance; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON TABLE vibetype.invitation IS '@omit update,delete\nStores invitations and their statuses.'; +COMMENT ON TABLE vibetype.legal_term_acceptance IS '@omit update,delete\nTracks each user account''s acceptance of legal terms and conditions.'; -- --- Name: COLUMN invitation.guest_id; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term_acceptance.id; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.invitation.guest_id IS 'The ID of the guest associated with this invitation.'; +COMMENT ON COLUMN vibetype.legal_term_acceptance.id IS '@omit create +Unique identifier for this legal term acceptance record. Automatically generated for each new acceptance.'; -- --- Name: COLUMN invitation.created_by; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term_acceptance.account_id; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.invitation.created_by IS '@omit create -Reference to the account that created the invitation.'; +COMMENT ON COLUMN vibetype.legal_term_acceptance.account_id IS 'The user account ID that accepted the legal terms. If the account is deleted, this acceptance record will also be deleted.'; -- --- Name: legal_term; Type: TABLE; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term_acceptance.legal_term_id; Type: COMMENT; Schema: vibetype; Owner: ci -- -CREATE TABLE vibetype.legal_term ( - id uuid DEFAULT gen_random_uuid() NOT NULL, - language character varying(5) DEFAULT 'en'::character varying NOT NULL, - term text NOT NULL, - version character varying(20) NOT NULL, - created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, - CONSTRAINT legal_term_language_check CHECK (((language)::text ~ '^[a-z]{2}(_[A-Z]{2})?$'::text)), - CONSTRAINT legal_term_term_check CHECK (((char_length(term) > 0) AND (char_length(term) <= 500000))), - CONSTRAINT legal_term_version_check CHECK (((version)::text ~ '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$'::text)) -); - +COMMENT ON COLUMN vibetype.legal_term_acceptance.legal_term_id IS 'The ID of the legal terms that were accepted. Deletion of these legal terms is restricted while they are still referenced in this table.'; -ALTER TABLE vibetype.legal_term OWNER TO ci; -- --- Name: TABLE legal_term; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN legal_term_acceptance.created_at; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON TABLE vibetype.legal_term IS '@omit create,update,delete -Legal terms like privacy policies or terms of service.'; +COMMENT ON COLUMN vibetype.legal_term_acceptance.created_at IS '@omit create +Timestamp showing when the legal terms were accepted, set automatically at the time of acceptance.'; -- --- Name: COLUMN legal_term.id; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: notification; Type: TABLE; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term.id IS 'Unique identifier for each legal term.'; +CREATE TABLE vibetype.notification ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + channel text NOT NULL, + is_acknowledged boolean, + payload text NOT NULL, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT notification_payload_check CHECK ((octet_length(payload) <= 8000)) +); +ALTER TABLE vibetype.notification OWNER TO ci; + -- --- Name: COLUMN legal_term.language; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: TABLE notification; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term.language IS 'Language code in ISO 639-1 format with optional region (e.g., `en` for English, `en_GB` for British English)'; +COMMENT ON TABLE vibetype.notification IS 'A notification.'; -- --- Name: COLUMN legal_term.term; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification.id; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term.term IS 'Text of the legal term. Markdown is expected to be used. It must be non-empty and cannot exceed 500,000 characters.'; +COMMENT ON COLUMN vibetype.notification.id IS 'The notification''s internal id.'; -- --- Name: COLUMN legal_term.version; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification.channel; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term.version IS 'Semantic versioning string to track changes to the legal terms (format: `X.Y.Z`).'; +COMMENT ON COLUMN vibetype.notification.channel IS 'The notification''s channel.'; -- --- Name: COLUMN legal_term.created_at; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification.is_acknowledged; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term.created_at IS 'Timestamp when the term was created. Set to the current time by default.'; +COMMENT ON COLUMN vibetype.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; -- --- Name: legal_term_acceptance; Type: TABLE; Schema: vibetype; Owner: ci +-- Name: COLUMN notification.payload; Type: COMMENT; Schema: vibetype; Owner: ci -- -CREATE TABLE vibetype.legal_term_acceptance ( - id uuid DEFAULT gen_random_uuid() NOT NULL, - account_id uuid NOT NULL, - legal_term_id uuid NOT NULL, - created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL -); - +COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload.'; -ALTER TABLE vibetype.legal_term_acceptance OWNER TO ci; -- --- Name: TABLE legal_term_acceptance; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification.created_at; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON TABLE vibetype.legal_term_acceptance IS '@omit update,delete\nTracks each user account''s acceptance of legal terms and conditions.'; +COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; -- --- Name: COLUMN legal_term_acceptance.id; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: notification_invitation; Type: TABLE; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term_acceptance.id IS '@omit create -Unique identifier for this legal term acceptance record. Automatically generated for each new acceptance.'; +CREATE TABLE vibetype.notification_invitation ( + guest_id uuid NOT NULL, + created_by uuid NOT NULL +) +INHERITS (vibetype.notification); + +ALTER TABLE vibetype.notification_invitation OWNER TO ci; -- --- Name: COLUMN legal_term_acceptance.account_id; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: TABLE notification_invitation; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term_acceptance.account_id IS 'The user account ID that accepted the legal terms. If the account is deleted, this acceptance record will also be deleted.'; +COMMENT ON TABLE vibetype.notification_invitation IS '@omit update,delete\nStores invitations and their statuses.'; -- --- Name: COLUMN legal_term_acceptance.legal_term_id; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification_invitation.guest_id; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term_acceptance.legal_term_id IS 'The ID of the legal terms that were accepted. Deletion of these legal terms is restricted while they are still referenced in this table.'; +COMMENT ON COLUMN vibetype.notification_invitation.guest_id IS 'The ID of the guest associated with this invitation.'; -- --- Name: COLUMN legal_term_acceptance.created_at; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification_invitation.created_by; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.legal_term_acceptance.created_at IS '@omit create -Timestamp showing when the legal terms were accepted, set automatically at the time of acceptance.'; +COMMENT ON COLUMN vibetype.notification_invitation.created_by IS '@omit create +Reference to the account that created the invitation.'; -- @@ -5204,17 +5204,17 @@ COMMENT ON COLUMN vibetype_private.jwt.token IS 'The token.'; -- --- Name: invitation id; Type: DEFAULT; Schema: vibetype; Owner: ci +-- Name: notification_invitation id; Type: DEFAULT; Schema: vibetype; Owner: ci -- -ALTER TABLE ONLY vibetype.invitation ALTER COLUMN id SET DEFAULT gen_random_uuid(); +ALTER TABLE ONLY vibetype.notification_invitation ALTER COLUMN id SET DEFAULT gen_random_uuid(); -- --- Name: invitation created_at; Type: DEFAULT; Schema: vibetype; Owner: ci +-- Name: notification_invitation created_at; Type: DEFAULT; Schema: vibetype; Owner: ci -- -ALTER TABLE ONLY vibetype.invitation ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP; +ALTER TABLE ONLY vibetype.notification_invitation ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP; -- @@ -5830,7 +5830,7 @@ COMMENT ON INDEX vibetype.idx_guest_updated_by IS 'B-Tree index to optimize look -- Name: idx_invitation_guest_id; Type: INDEX; Schema: vibetype; Owner: ci -- -CREATE INDEX idx_invitation_guest_id ON vibetype.invitation USING btree (guest_id); +CREATE INDEX idx_invitation_guest_id ON vibetype.notification_invitation USING btree (guest_id); -- @@ -6253,35 +6253,35 @@ ALTER TABLE ONLY vibetype.guest -- --- Name: invitation invitation_created_by_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci +-- Name: legal_term_acceptance legal_term_acceptance_account_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci -- -ALTER TABLE ONLY vibetype.invitation - ADD CONSTRAINT invitation_created_by_fkey FOREIGN KEY (created_by) REFERENCES vibetype.account(id); +ALTER TABLE ONLY vibetype.legal_term_acceptance + ADD CONSTRAINT legal_term_acceptance_account_id_fkey FOREIGN KEY (account_id) REFERENCES vibetype.account(id) ON DELETE CASCADE; -- --- Name: invitation invitation_guest_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci +-- Name: legal_term_acceptance legal_term_acceptance_legal_term_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci -- -ALTER TABLE ONLY vibetype.invitation - ADD CONSTRAINT invitation_guest_id_fkey FOREIGN KEY (guest_id) REFERENCES vibetype.guest(id); +ALTER TABLE ONLY vibetype.legal_term_acceptance + ADD CONSTRAINT legal_term_acceptance_legal_term_id_fkey FOREIGN KEY (legal_term_id) REFERENCES vibetype.legal_term(id) ON DELETE RESTRICT; -- --- Name: legal_term_acceptance legal_term_acceptance_account_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci +-- Name: notification_invitation notification_invitation_created_by_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci -- -ALTER TABLE ONLY vibetype.legal_term_acceptance - ADD CONSTRAINT legal_term_acceptance_account_id_fkey FOREIGN KEY (account_id) REFERENCES vibetype.account(id) ON DELETE CASCADE; +ALTER TABLE ONLY vibetype.notification_invitation + ADD CONSTRAINT notification_invitation_created_by_fkey FOREIGN KEY (created_by) REFERENCES vibetype.account(id); -- --- Name: legal_term_acceptance legal_term_acceptance_legal_term_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci +-- Name: notification_invitation notification_invitation_guest_id_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci -- -ALTER TABLE ONLY vibetype.legal_term_acceptance - ADD CONSTRAINT legal_term_acceptance_legal_term_id_fkey FOREIGN KEY (legal_term_id) REFERENCES vibetype.legal_term(id) ON DELETE RESTRICT; +ALTER TABLE ONLY vibetype.notification_invitation + ADD CONSTRAINT notification_invitation_guest_id_fkey FOREIGN KEY (guest_id) REFERENCES vibetype.guest(id); -- @@ -6783,59 +6783,59 @@ EXCEPT -- --- Name: invitation; Type: ROW SECURITY; Schema: vibetype; Owner: ci +-- Name: legal_term; Type: ROW SECURITY; Schema: vibetype; Owner: ci -- -ALTER TABLE vibetype.invitation ENABLE ROW LEVEL SECURITY; +ALTER TABLE vibetype.legal_term ENABLE ROW LEVEL SECURITY; -- --- Name: invitation invitation_insert; Type: POLICY; Schema: vibetype; Owner: ci +-- Name: legal_term_acceptance; Type: ROW SECURITY; Schema: vibetype; Owner: ci -- -CREATE POLICY invitation_insert ON vibetype.invitation FOR INSERT WITH CHECK (((created_by = vibetype.invoker_account_id()) AND (vibetype.invoker_account_id() = ( SELECT e.created_by - FROM (vibetype.guest g - JOIN vibetype.event e ON ((g.event_id = e.id))) - WHERE (g.id = invitation.guest_id))))); - +ALTER TABLE vibetype.legal_term_acceptance ENABLE ROW LEVEL SECURITY; -- --- Name: invitation invitation_select; Type: POLICY; Schema: vibetype; Owner: ci +-- Name: legal_term_acceptance legal_term_acceptance_insert; Type: POLICY; Schema: vibetype; Owner: ci -- -CREATE POLICY invitation_select ON vibetype.invitation FOR SELECT USING ((created_by = vibetype.invoker_account_id())); +CREATE POLICY legal_term_acceptance_insert ON vibetype.legal_term_acceptance FOR INSERT WITH CHECK (((vibetype.invoker_account_id() IS NOT NULL) AND (account_id = vibetype.invoker_account_id()))); -- --- Name: legal_term; Type: ROW SECURITY; Schema: vibetype; Owner: ci +-- Name: legal_term_acceptance legal_term_acceptance_select; Type: POLICY; Schema: vibetype; Owner: ci -- -ALTER TABLE vibetype.legal_term ENABLE ROW LEVEL SECURITY; +CREATE POLICY legal_term_acceptance_select ON vibetype.legal_term_acceptance FOR SELECT USING (((vibetype.invoker_account_id() IS NOT NULL) AND (account_id = vibetype.invoker_account_id()))); + -- --- Name: legal_term_acceptance; Type: ROW SECURITY; Schema: vibetype; Owner: ci +-- Name: legal_term legal_term_select; Type: POLICY; Schema: vibetype; Owner: ci -- -ALTER TABLE vibetype.legal_term_acceptance ENABLE ROW LEVEL SECURITY; +CREATE POLICY legal_term_select ON vibetype.legal_term FOR SELECT USING (true); + -- --- Name: legal_term_acceptance legal_term_acceptance_insert; Type: POLICY; Schema: vibetype; Owner: ci +-- Name: notification_invitation; Type: ROW SECURITY; Schema: vibetype; Owner: ci -- -CREATE POLICY legal_term_acceptance_insert ON vibetype.legal_term_acceptance FOR INSERT WITH CHECK (((vibetype.invoker_account_id() IS NOT NULL) AND (account_id = vibetype.invoker_account_id()))); - +ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; -- --- Name: legal_term_acceptance legal_term_acceptance_select; Type: POLICY; Schema: vibetype; Owner: ci +-- Name: notification_invitation notification_invitation_insert; Type: POLICY; Schema: vibetype; Owner: ci -- -CREATE POLICY legal_term_acceptance_select ON vibetype.legal_term_acceptance FOR SELECT USING (((vibetype.invoker_account_id() IS NOT NULL) AND (account_id = vibetype.invoker_account_id()))); +CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT WITH CHECK (((created_by = vibetype.invoker_account_id()) AND (vibetype.invoker_account_id() = ( SELECT e.created_by + FROM (vibetype.guest g + JOIN vibetype.event e ON ((g.event_id = e.id))) + WHERE (g.id = notification_invitation.guest_id))))); -- --- Name: legal_term legal_term_select; Type: POLICY; Schema: vibetype; Owner: ci +-- Name: notification_invitation notification_invitation_select; Type: POLICY; Schema: vibetype; Owner: ci -- -CREATE POLICY legal_term_select ON vibetype.legal_term FOR SELECT USING (true); +CREATE POLICY notification_invitation_select ON vibetype.notification_invitation FOR SELECT USING ((created_by = vibetype.invoker_account_id())); -- @@ -7921,13 +7921,6 @@ GRANT SELECT ON TABLE vibetype.guest_flat TO vibetype_account; GRANT SELECT ON TABLE vibetype.guest_flat TO vibetype_anonymous; --- --- Name: TABLE invitation; Type: ACL; Schema: vibetype; Owner: ci --- - -GRANT SELECT,INSERT ON TABLE vibetype.invitation TO vibetype_account; - - -- -- Name: TABLE legal_term; Type: ACL; Schema: vibetype; Owner: ci -- @@ -7943,6 +7936,13 @@ GRANT SELECT ON TABLE vibetype.legal_term TO vibetype_anonymous; GRANT SELECT,INSERT ON TABLE vibetype.legal_term_acceptance TO vibetype_account; +-- +-- Name: TABLE notification_invitation; Type: ACL; Schema: vibetype; Owner: ci +-- + +GRANT SELECT,INSERT ON TABLE vibetype.notification_invitation TO vibetype_account; + + -- -- Name: TABLE profile_picture; Type: ACL; Schema: vibetype; Owner: ci -- From 6d6715e68ee568ff603e34202adb02abcb5b7796 Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Sun, 30 Mar 2025 23:21:35 +0200 Subject: [PATCH 05/12] fix(test): modify security mode for two functions The functions `vibetype_test.account_create` and `vibetype_test.contact_create` must run in mode SECURITY DEFINER in order to work when not logged in as the owner of the database objects. --- src/deploy/function_test_utilities.sql | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/deploy/function_test_utilities.sql b/src/deploy/function_test_utilities.sql index 14f4818f..9aeb6f45 100644 --- a/src/deploy/function_test_utilities.sql +++ b/src/deploy/function_test_utilities.sql @@ -17,7 +17,7 @@ BEGIN PERFORM vibetype.account_email_address_verification(_verification); RETURN _id; -END $$ LANGUAGE plpgsql; +END $$ LANGUAGE plpgsql STRICT SECURITY DEFINER; GRANT EXECUTE ON FUNCTION vibetype_test.account_create(TEXT, TEXT) TO vibetype_account; @@ -70,11 +70,14 @@ DECLARE _id UUID; _account_id UUID; BEGIN - SELECT id FROM vibetype_private.account WHERE email_address = _email_address INTO _account_id; - SET LOCAL role = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + SELECT id + INTO _account_id + FROM vibetype_private.account + WHERE email_address = _email_address; + INSERT INTO vibetype.contact(created_by, email_address) VALUES (_created_by, _email_address) RETURNING id INTO _id; @@ -83,10 +86,8 @@ BEGIN UPDATE vibetype.contact SET account_id = _account_id WHERE id = _id; END IF; - SET LOCAL ROLE NONE; - RETURN _id; -END $$ LANGUAGE plpgsql; +END $$ LANGUAGE plpgsql STRICT SECURITY DEFINER; GRANT EXECUTE ON FUNCTION vibetype_test.contact_create(UUID, TEXT) TO vibetype_account; From 280ae3c28ca98c648bd8962dabfd1dc1d166251b Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Wed, 2 Apr 2025 23:11:43 +0200 Subject: [PATCH 06/12] fix(notification): modify table `notification` Column `created_by` was moved from table `notification_invitation` to table `notification`. Row level security was added to table `notification`. Some modifications were made to functions in `function_test_utilities.sql`. --- ...unction_account_password_reset_request.sql | 37 +++-- src/deploy/function_account_registration.sql | 9 +- .../function_account_registration_refresh.sql | 5 +- src/deploy/function_test_utilities.sql | 130 ++++++++------- src/deploy/table_notification.sql | 2 + src/deploy/table_notification_invitation.sql | 5 +- src/deploy/table_notification_policy.sql | 15 ++ src/revert/function_test_utilities.sql | 5 +- src/revert/table_notification_policy.sql | 6 + src/sqitch.plan | 4 +- src/verify/function_test_utilities.sql | 5 +- src/verify/table_notification.sql | 1 + src/verify/table_notification_invitation.sql | 4 +- ...licy.sql => table_notification_policy.sql} | 18 +-- test/schema/schema.definition.sql | 148 +++++++++++------- 15 files changed, 240 insertions(+), 154 deletions(-) create mode 100644 src/deploy/table_notification_policy.sql create mode 100644 src/revert/table_notification_policy.sql rename src/verify/{table_invitation_policy.sql => table_notification_policy.sql} (59%) diff --git a/src/deploy/function_account_password_reset_request.sql b/src/deploy/function_account_password_reset_request.sql index 0ae861a2..4096c27b 100644 --- a/src/deploy/function_account_password_reset_request.sql +++ b/src/deploy/function_account_password_reset_request.sql @@ -5,31 +5,36 @@ CREATE FUNCTION vibetype.account_password_reset_request( language TEXT ) RETURNS VOID AS $$ DECLARE - _notify_data RECORD; + _rec_account vibetype_private.account%ROWTYPE; + _notify_data RECORD := NULL; BEGIN - WITH updated AS ( - UPDATE vibetype_private.account - SET password_reset_verification = gen_random_uuid() - WHERE account.email_address = account_password_reset_request.email_address - RETURNING * - ) SELECT - account.username, - updated.email_address, - updated.password_reset_verification, - updated.password_reset_verification_valid_until - FROM updated, vibetype.account - WHERE updated.id = account.id - INTO _notify_data; + + UPDATE vibetype_private.account + SET password_reset_verification = gen_random_uuid() + WHERE account.email_address = account_password_reset_request.email_address + RETURNING * INTO _rec_account; + + IF _rec_account IS NOT NULL THEN + SELECT + username, + _rec_account.email_address, + _rec_account.password_reset_verification, + _rec_account.password_reset_verification_valid_until + INTO _notify_data + FROM vibetype.account + WHERE id = _rec_account.id; + END IF; IF (_notify_data IS NULL) THEN -- noop ELSE - INSERT INTO vibetype.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload, created_by) VALUES ( 'account_password_reset_request', jsonb_pretty(jsonb_build_object( 'account', _notify_data, 'template', jsonb_build_object('language', account_password_reset_request.language) - )) + )), + _rec_account.id ); END IF; END; diff --git a/src/deploy/function_account_registration.sql b/src/deploy/function_account_registration.sql index c469ce75..cc26be2e 100644 --- a/src/deploy/function_account_registration.sql +++ b/src/deploy/function_account_registration.sql @@ -7,8 +7,8 @@ CREATE FUNCTION vibetype.account_registration( language TEXT ) RETURNS UUID AS $$ DECLARE - _new_account_private vibetype_private.account; - _new_account_public vibetype.account; + _new_account_private vibetype_private.account%ROWTYPE; + _new_account_public vibetype.account%ROWTYPE; _new_account_notify RECORD; BEGIN IF (char_length(account_registration.password) < 8) THEN @@ -40,12 +40,13 @@ BEGIN INSERT INTO vibetype.contact(account_id, created_by) VALUES (_new_account_private.id, _new_account_private.id); - INSERT INTO vibetype.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload, created_by) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), 'template', jsonb_build_object('language', account_registration.language) - )) + )), + _new_account_private.id ); RETURN _new_account_public.id; diff --git a/src/deploy/function_account_registration_refresh.sql b/src/deploy/function_account_registration_refresh.sql index ca822d51..7bda51c2 100644 --- a/src/deploy/function_account_registration_refresh.sql +++ b/src/deploy/function_account_registration_refresh.sql @@ -26,12 +26,13 @@ BEGIN FROM updated JOIN vibetype.account ON updated.id = account.id INTO _new_account_notify; - INSERT INTO vibetype.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload, created_by) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), 'template', jsonb_build_object('language', account_registration_refresh.language) - )) + )), + account_registration_refresh.account_id ); END; $$ LANGUAGE PLPGSQL STRICT SECURITY DEFINER; diff --git a/src/deploy/function_test_utilities.sql b/src/deploy/function_test_utilities.sql index 9aeb6f45..38919315 100644 --- a/src/deploy/function_test_utilities.sql +++ b/src/deploy/function_test_utilities.sql @@ -1,5 +1,6 @@ BEGIN; + CREATE FUNCTION vibetype_test.account_create ( _username TEXT, _email TEXT @@ -32,7 +33,7 @@ BEGIN IF _id IS NOT NULL THEN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _id || ''''; DELETE FROM vibetype.event WHERE created_by = _id; @@ -46,6 +47,59 @@ END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.account_remove(TEXT) TO vibetype_account; +CREATE OR REPLACE FUNCTION vibetype_test.account_select_by_email_address(_email_address text) +RETURNS UUID AS $$ +DECLARE + _account_id UUID; +BEGIN + SELECT id + INTO _account_id + FROM vibetype_private.account + WHERE email_address = _email_address; + + RETURN _account_id; +END; +$$ LANGUAGE plpgsql STRICT SECURITY DEFINER; + +GRANT EXECUTE ON FUNCTION vibetype_test.account_select_by_email_address(TEXT) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.account_block_create ( + _created_by UUID, + _blocked_account_id UUID +) RETURNS UUID AS $$ +DECLARE + _id UUID; +BEGIN + SET LOCAL ROLE = 'vibetype_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + + INSERT INTO vibetype.account_block(created_by, blocked_account_id) + VALUES (_created_by, _blocked_Account_id) + RETURNING id INTO _id; + + SET LOCAL ROLE NONE; + + RETURN _id; +END $$ LANGUAGE plpgsql; + +GRANT EXECUTE ON FUNCTION vibetype_test.account_block_create(UUID, UUID) TO vibetype_account; + + +CREATE FUNCTION vibetype_test.account_block_remove ( + _created_by UUID, + _blocked_account_id UUID +) RETURNS VOID AS $$ +DECLARE + _id UUID; +BEGIN + DELETE FROM vibetype.account_block + WHERE created_by = _created_by and blocked_account_id = _blocked_account_id; +END $$ LANGUAGE plpgsql STRICT SECURITY DEFINER; + +GRANT EXECUTE ON FUNCTION vibetype_test.account_block_remove(UUID, UUID) TO vibetype_account; + + CREATE FUNCTION vibetype_test.contact_select_by_account_id ( _account_id UUID ) RETURNS UUID AS $$ @@ -71,12 +125,10 @@ DECLARE _account_id UUID; BEGIN + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; - SELECT id - INTO _account_id - FROM vibetype_private.account - WHERE email_address = _email_address; + _account_id := vibetype_test.account_select_by_email_address(_email_address); INSERT INTO vibetype.contact(created_by, email_address) VALUES (_created_by, _email_address) @@ -86,8 +138,10 @@ BEGIN UPDATE vibetype.contact SET account_id = _account_id WHERE id = _id; END IF; + SET LOCAL ROLE NONE; + RETURN _id; -END $$ LANGUAGE plpgsql STRICT SECURITY DEFINER; +END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.contact_create(UUID, TEXT) TO vibetype_account; @@ -102,7 +156,7 @@ CREATE FUNCTION vibetype_test.event_create ( DECLARE _id UUID; BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; INSERT INTO vibetype.event(created_by, name, slug, start, visibility) @@ -125,7 +179,7 @@ CREATE FUNCTION vibetype_test.guest_create ( DECLARE _id UUID; BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; INSERT INTO vibetype.guest(contact_id, event_id) @@ -145,7 +199,7 @@ CREATE FUNCTION vibetype_test.event_category_create ( ) RETURNS VOID AS $$ BEGIN INSERT INTO vibetype.event_category(category) VALUES (_category); -END $$ LANGUAGE plpgsql; +END $$ LANGUAGE plpgsql STRICT sECURITY DEFINER; GRANT EXECUTE ON FUNCTION vibetype_test.event_category_create(TEXT) TO vibetype_account; @@ -156,7 +210,7 @@ CREATE FUNCTION vibetype_test.event_category_mapping_create ( _category TEXT ) RETURNS VOID AS $$ BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; INSERT INTO vibetype.event_category_mapping(event_id, category) @@ -168,42 +222,6 @@ END $$ LANGUAGE plpgsql; GRANT EXECUTE ON FUNCTION vibetype_test.event_category_mapping_create(UUID, UUID, TEXT) TO vibetype_account; -CREATE FUNCTION vibetype_test.account_block_create ( - _created_by UUID, - _blocked_account_id UUID -) RETURNS UUID AS $$ -DECLARE - _id UUID; -BEGIN - SET LOCAL role = 'vibetype_account'; - EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; - - INSERT INTO vibetype.account_block(created_by, blocked_account_id) - VALUES (_created_by, _blocked_Account_id) - RETURNING id INTO _id; - - SET LOCAL ROLE NONE; - - RETURN _id; -END $$ LANGUAGE plpgsql; - -GRANT EXECUTE ON FUNCTION vibetype_test.account_block_create(UUID, UUID) TO vibetype_account; - - -CREATE FUNCTION vibetype_test.account_block_remove ( - _created_by UUID, - _blocked_account_id UUID -) RETURNS VOID AS $$ -DECLARE - _id UUID; -BEGIN - DELETE FROM vibetype.account_block - WHERE created_by = _created_by and blocked_account_id = _blocked_account_id; -END $$ LANGUAGE plpgsql; - -GRANT EXECUTE ON FUNCTION vibetype_test.account_block_remove(UUID, UUID) TO vibetype_account; - - CREATE FUNCTION vibetype_test.event_test ( _test_case TEXT, _account_id UUID, @@ -211,10 +229,10 @@ CREATE FUNCTION vibetype_test.event_test ( ) RETURNS VOID AS $$ BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -239,10 +257,10 @@ CREATE FUNCTION vibetype_test.event_category_mapping_test ( ) RETURNS VOID AS $$ BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -269,10 +287,10 @@ DECLARE rec RECORD; BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -297,10 +315,10 @@ CREATE FUNCTION vibetype_test.guest_test ( ) RETURNS VOID AS $$ BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -327,7 +345,7 @@ DECLARE _result UUID[] := ARRAY[]::UUID[]; _text TEXT := ''; BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; -- reads all guests where _account_id is invited, @@ -363,7 +381,7 @@ CREATE FUNCTION vibetype_test.invoker_set ( ) RETURNS VOID AS $$ BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _invoker_id || ''''; END $$ LANGUAGE plpgsql; diff --git a/src/deploy/table_notification.sql b/src/deploy/table_notification.sql index 31a646b0..929a3abe 100644 --- a/src/deploy/table_notification.sql +++ b/src/deploy/table_notification.sql @@ -7,6 +7,7 @@ CREATE TABLE vibetype.notification ( is_acknowledged BOOLEAN, payload TEXT NOT NULL CHECK (octet_length(payload) <= 8000), + created_by UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -15,6 +16,7 @@ COMMENT ON COLUMN vibetype.notification.id IS 'The notification''s internal id.' COMMENT ON COLUMN vibetype.notification.channel IS 'The notification''s channel.'; COMMENT ON COLUMN vibetype.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload.'; +COMMENT ON COLUMN vibetype.notification.created_by IS E'@omit create\nReference to the account that created the notification.'; COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; COMMIT; diff --git a/src/deploy/table_notification_invitation.sql b/src/deploy/table_notification_invitation.sql index a16c29f9..cbc648b6 100644 --- a/src/deploy/table_notification_invitation.sql +++ b/src/deploy/table_notification_invitation.sql @@ -1,15 +1,12 @@ BEGIN; CREATE TABLE vibetype.notification_invitation ( - guest_id UUID NOT NULL REFERENCES vibetype.guest(id), - -- created_at is already column of table notification - created_by UUID NOT NULL REFERENCES vibetype.account(id) + guest_id UUID NOT NULL REFERENCES vibetype.guest(id) ) INHERITS (vibetype.notification); COMMENT ON TABLE vibetype.notification_invitation IS '@omit update,delete\nStores invitations and their statuses.'; COMMENT ON COLUMN vibetype.notification_invitation.guest_id IS 'The ID of the guest associated with this invitation.'; -COMMENT ON COLUMN vibetype.notification_invitation.created_by IS E'@omit create\nReference to the account that created the invitation.'; CREATE INDEX idx_invitation_guest_id ON vibetype.notification_invitation USING btree (guest_id); diff --git a/src/deploy/table_notification_policy.sql b/src/deploy/table_notification_policy.sql new file mode 100644 index 00000000..4bbb22c5 --- /dev/null +++ b/src/deploy/table_notification_policy.sql @@ -0,0 +1,15 @@ +BEGIN; + +GRANT SELECT, INSERT ON vibetype.notification TO vibetype_account; + +ALTER TABLE vibetype.notification ENABLE ROW LEVEL SECURITY; + +CREATE POLICY notification_select ON vibetype.notification FOR SELECT USING ( + created_by = vibetype.invoker_account_id() +); + +CREATE POLICY notification_insert ON vibetype.notification FOR INSERT WITH CHECK ( + created_by = vibetype.invoker_account_id() +); + +COMMIT; \ No newline at end of file diff --git a/src/revert/function_test_utilities.sql b/src/revert/function_test_utilities.sql index 0d3a4c85..63833522 100644 --- a/src/revert/function_test_utilities.sql +++ b/src/revert/function_test_utilities.sql @@ -1,9 +1,10 @@ BEGIN; -DROP FUNCTION vibetype_test.account_block_create(UUID, UUID); -DROP FUNCTION vibetype_test.account_block_remove(UUID, UUID); DROP FUNCTION vibetype_test.account_create(TEXT, TEXT); DROP FUNCTION vibetype_test.account_remove(TEXT); +DROP FUNCTION vibetype_test.account_select_by_email_address(TEXT); +DROP FUNCTION vibetype_test.account_block_create(UUID, UUID); +DROP FUNCTION vibetype_test.account_block_remove(UUID, UUID); DROP FUNCTION vibetype_test.contact_create(UUID, TEXT); DROP FUNCTION vibetype_test.contact_select_by_account_id(UUID); DROP FUNCTION vibetype_test.contact_test(TEXT, UUID, UUID[]); diff --git a/src/revert/table_notification_policy.sql b/src/revert/table_notification_policy.sql new file mode 100644 index 00000000..b9616b5e --- /dev/null +++ b/src/revert/table_notification_policy.sql @@ -0,0 +1,6 @@ +BEGIN; + +DROP POLICY notification_insert ON vibetype.notification; +DROP POLICY notification_select ON vibetype.notification; + +COMMIT; \ No newline at end of file diff --git a/src/sqitch.plan b/src/sqitch.plan index 1a957396..712904c8 100644 --- a/src/sqitch.plan +++ b/src/sqitch.plan @@ -15,10 +15,10 @@ function_invoker_account_id [privilege_execute_revoke schema_public role_account enum_invitation_feedback [schema_public] 1970-01-01T00:00:00Z Jonas Thelemann # Possible answers to an invitation: accepted, canceled. enum_event_visibility [schema_public] 1970-01-01T00:00:00Z Jonas Thelemann # Possible visibilities of events and event groups: public, private and unlisted. function_trigger_metadata_update [privilege_execute_revoke schema_public function_invoker_account_id role_account] 1970-01-01T00:00:00Z Jonas Thelemann # Trigger function to automatically update metadata fields `updated_at` and `updated_by` when a row is modified. Sets `updated_at` to the current timestamp and `updated_by` to the account ID of the invoker. -table_notification [schema_private] 1970-01-01T00:00:00Z Jonas Thelemann # Notifications that are sent via pg_notify. test_postgres [schema_test] 1970-01-01T00:00:00Z Jonas Thelemann # Checks whether the given indexes exist in the specified schema. table_account_private [schema_private test_postgres] 1970-01-01T00:00:00Z Jonas Thelemann # Add private table account. table_account_public [schema_public schema_private table_account_private] 1970-01-01T00:00:00Z Jonas Thelemann # Add public table account. +table_notification [schema_private table_account_public] 1970-01-01T00:00:00Z Jonas Thelemann # Notifications that are sent via pg_notify. table_account_block [schema_public table_account_public test_postgres] 1970-01-01T00:00:00Z Sven Thelemann # Blocking of an account by another account. table_account_block_policy [schema_public table_account_block role_account role_anonymous function_invoker_account_id] 1970-01-01T00:00:00Z Sven Thelemann # Policy for table account block. function_account_block_ids [schema_public table_account_block role_account role_anonymous function_invoker_account_id] 1970-01-01T00:00:00Z Sven Thelemann # Function returning all account id's involved in blocking. @@ -102,6 +102,6 @@ enum_friendship_status [schema_public] 1970-01-01T00:00:00Z Sven Thelemann # A friend relation together with its status. table_friendship_policy [schema_public table_friendship role_account] 1970-01-01T00:00:00Z Sven Thelemann # Policy for table friend. test_friendship [schema_test] 1970-01-01T00:00:00Z Sven Thelemann # Test cases for friendship. -table_notification_invitation [schema_public table_guest table_account_public] 1970-01-01T00:00:00Z Sven Thelemann # A table for tracking actions around invitations. +table_notification_invitation [schema_public table_guest] 1970-01-01T00:00:00Z Sven Thelemann # A table for tracking actions around invitations. table_notification_invitation_policy [schema_public table_notification_invitation role_account function_invoker_account_id table_guest table_event] 1970-01-01T00:00:00Z Sven Thelemann # Stores invitations and their statuses. test_invitation [schema_test function_test_utilities] 1970-01-01T00:00:00Z Sven Thelemann # Invitation related tests. diff --git a/src/verify/function_test_utilities.sql b/src/verify/function_test_utilities.sql index 2f6b841a..95ff724c 100644 --- a/src/verify/function_test_utilities.sql +++ b/src/verify/function_test_utilities.sql @@ -4,14 +4,15 @@ DO $$ BEGIN ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_create(TEXT, TEXT)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_remove(TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_select_by_email_address(TEXT)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_block_create(UUID, UUID)', 'EXECUTE')); + ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_block_remove(UUID, UUID)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.contact_select_by_account_id(UUID)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.contact_create(UUID, TEXT)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_create(UUID, TEXT, TEXT, TEXT, TEXT)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.guest_create(UUID, UUID, UUID)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_category_create(TEXT)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_category_mapping_create(UUID, UUID, TEXT)', 'EXECUTE')); - ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_block_create(UUID, UUID)', 'EXECUTE')); - ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.account_block_remove(UUID, UUID)', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_test(TEXT, UUID, UUID[])', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.event_category_mapping_test(TEXT, UUID, UUID[])', 'EXECUTE')); ASSERT (SELECT pg_catalog.has_function_privilege('vibetype_account', 'vibetype_test.contact_test(TEXT, UUID, UUID[])', 'EXECUTE')); diff --git a/src/verify/table_notification.sql b/src/verify/table_notification.sql index 8123fed8..9d775c8e 100644 --- a/src/verify/table_notification.sql +++ b/src/verify/table_notification.sql @@ -4,6 +4,7 @@ SELECT id, channel, is_acknowledged, payload, + created_by, created_at FROM vibetype.notification WHERE FALSE; diff --git a/src/verify/table_notification_invitation.sql b/src/verify/table_notification_invitation.sql index 9b9638e9..4cc17fbe 100644 --- a/src/verify/table_notification_invitation.sql +++ b/src/verify/table_notification_invitation.sql @@ -6,10 +6,10 @@ SELECT channel, is_acknowledged, payload, + created_by, created_at, -- columns specific for vibetype.notification_invitation - guest_id, - created_by + guest_id FROM vibetype.notification_invitation WHERE FALSE; diff --git a/src/verify/table_invitation_policy.sql b/src/verify/table_notification_policy.sql similarity index 59% rename from src/verify/table_invitation_policy.sql rename to src/verify/table_notification_policy.sql index 5da8d583..8bcf13d1 100644 --- a/src/verify/table_invitation_policy.sql +++ b/src/verify/table_notification_policy.sql @@ -2,14 +2,14 @@ BEGIN; DO $$ BEGIN - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'SELECT')); - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'INSERT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'UPDATE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.invitation', 'DELETE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'SELECT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'INSERT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'UPDATE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.invitation', 'DELETE')); + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'SELECT')); + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'DELETE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'SELECT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'DELETE')); END $$; -ROLLBACK; +ROLLBACK; \ No newline at end of file diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index 8d151bf6..6b91aa4a 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -531,31 +531,36 @@ CREATE FUNCTION vibetype.account_password_reset_request(email_address text, lang LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ DECLARE - _notify_data RECORD; + _rec_account vibetype_private.account%ROWTYPE; + _notify_data RECORD := NULL; BEGIN - WITH updated AS ( - UPDATE vibetype_private.account - SET password_reset_verification = gen_random_uuid() - WHERE account.email_address = account_password_reset_request.email_address - RETURNING * - ) SELECT - account.username, - updated.email_address, - updated.password_reset_verification, - updated.password_reset_verification_valid_until - FROM updated, vibetype.account - WHERE updated.id = account.id - INTO _notify_data; + + UPDATE vibetype_private.account + SET password_reset_verification = gen_random_uuid() + WHERE account.email_address = account_password_reset_request.email_address + RETURNING * INTO _rec_account; + + IF _rec_account IS NOT NULL THEN + SELECT + username, + _rec_account.email_address, + _rec_account.password_reset_verification, + _rec_account.password_reset_verification_valid_until + INTO _notify_data + FROM vibetype.account + WHERE id = _rec_account.id; + END IF; IF (_notify_data IS NULL) THEN -- noop ELSE - INSERT INTO vibetype.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload, created_by) VALUES ( 'account_password_reset_request', jsonb_pretty(jsonb_build_object( 'account', _notify_data, 'template', jsonb_build_object('language', account_password_reset_request.language) - )) + )), + _rec_account.id ); END IF; END; @@ -579,8 +584,8 @@ CREATE FUNCTION vibetype.account_registration(username text, email_address text, LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ DECLARE - _new_account_private vibetype_private.account; - _new_account_public vibetype.account; + _new_account_private vibetype_private.account%ROWTYPE; + _new_account_public vibetype.account%ROWTYPE; _new_account_notify RECORD; BEGIN IF (char_length(account_registration.password) < 8) THEN @@ -612,12 +617,13 @@ BEGIN INSERT INTO vibetype.contact(account_id, created_by) VALUES (_new_account_private.id, _new_account_private.id); - INSERT INTO vibetype.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload, created_by) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), 'template', jsonb_build_object('language', account_registration.language) - )) + )), + _new_account_private.id ); RETURN _new_account_public.id; @@ -663,12 +669,13 @@ BEGIN FROM updated JOIN vibetype.account ON updated.id = account.id INTO _new_account_notify; - INSERT INTO vibetype.notification (channel, payload) VALUES ( + INSERT INTO vibetype.notification (channel, payload, created_by) VALUES ( 'account_registration', jsonb_pretty(jsonb_build_object( 'account', row_to_json(_new_account_notify), 'template', jsonb_build_object('language', account_registration_refresh.language) - )) + )), + account_registration_refresh.account_id ); END; $$; @@ -2218,7 +2225,7 @@ CREATE FUNCTION vibetype_test.account_block_create(_created_by uuid, _blocked_ac DECLARE _id UUID; BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; INSERT INTO vibetype.account_block(created_by, blocked_account_id) @@ -2238,7 +2245,7 @@ ALTER FUNCTION vibetype_test.account_block_create(_created_by uuid, _blocked_acc -- CREATE FUNCTION vibetype_test.account_block_remove(_created_by uuid, _blocked_account_id uuid) RETURNS void - LANGUAGE plpgsql + LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ DECLARE _id UUID; @@ -2255,7 +2262,7 @@ ALTER FUNCTION vibetype_test.account_block_remove(_created_by uuid, _blocked_acc -- CREATE FUNCTION vibetype_test.account_create(_username text, _email text) RETURNS uuid - LANGUAGE plpgsql + LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ DECLARE _id UUID; @@ -2416,7 +2423,7 @@ BEGIN IF _id IS NOT NULL THEN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _id || ''''; DELETE FROM vibetype.event WHERE created_by = _id; @@ -2430,6 +2437,28 @@ END $$; ALTER FUNCTION vibetype_test.account_remove(_username text) OWNER TO ci; +-- +-- Name: account_select_by_email_address(text); Type: FUNCTION; Schema: vibetype_test; Owner: ci +-- + +CREATE FUNCTION vibetype_test.account_select_by_email_address(_email_address text) RETURNS uuid + LANGUAGE plpgsql STRICT SECURITY DEFINER + AS $$ +DECLARE + _account_id UUID; +BEGIN + SELECT id + INTO _account_id + FROM vibetype_private.account + WHERE email_address = _email_address; + + RETURN _account_id; +END; +$$; + + +ALTER FUNCTION vibetype_test.account_select_by_email_address(_email_address text) OWNER TO ci; + -- -- Name: contact_create(uuid, text); Type: FUNCTION; Schema: vibetype_test; Owner: ci -- @@ -2441,11 +2470,12 @@ DECLARE _id UUID; _account_id UUID; BEGIN - SELECT id FROM vibetype_private.account WHERE email_address = _email_address INTO _account_id; - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; + _account_id := vibetype_test.account_select_by_email_address(_email_address); + INSERT INTO vibetype.contact(created_by, email_address) VALUES (_created_by, _email_address) RETURNING id INTO _id; @@ -2493,10 +2523,10 @@ DECLARE rec RECORD; BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -2519,7 +2549,7 @@ ALTER FUNCTION vibetype_test.contact_test(_test_case text, _account_id uuid, _ex -- CREATE FUNCTION vibetype_test.event_category_create(_category text) RETURNS void - LANGUAGE plpgsql + LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ BEGIN INSERT INTO vibetype.event_category(category) VALUES (_category); @@ -2536,7 +2566,7 @@ CREATE FUNCTION vibetype_test.event_category_mapping_create(_created_by uuid, _e LANGUAGE plpgsql AS $$ BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; INSERT INTO vibetype.event_category_mapping(event_id, category) @@ -2557,10 +2587,10 @@ CREATE FUNCTION vibetype_test.event_category_mapping_test(_test_case text, _acco AS $$ BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -2588,7 +2618,7 @@ CREATE FUNCTION vibetype_test.event_create(_created_by uuid, _name text, _slug t DECLARE _id UUID; BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; INSERT INTO vibetype.event(created_by, name, slug, start, visibility) @@ -2718,10 +2748,10 @@ CREATE FUNCTION vibetype_test.event_test(_test_case text, _account_id uuid, _exp AS $$ BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -2932,7 +2962,7 @@ DECLARE _result UUID[] := ARRAY[]::UUID[]; _text TEXT := ''; BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; -- reads all guests where _account_id is invited, @@ -2973,7 +3003,7 @@ CREATE FUNCTION vibetype_test.guest_create(_created_by uuid, _event_id uuid, _co DECLARE _id UUID; BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _created_by || ''''; INSERT INTO vibetype.guest(contact_id, event_id) @@ -2997,10 +3027,10 @@ CREATE FUNCTION vibetype_test.guest_test(_test_case text, _account_id uuid, _exp AS $$ BEGIN IF _account_id IS NULL THEN - SET LOCAL role = 'vibetype_anonymous'; + SET LOCAL ROLE = 'vibetype_anonymous'; SET LOCAL jwt.claims.account_id = ''; ELSE - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; END IF; @@ -3060,7 +3090,7 @@ CREATE FUNCTION vibetype_test.invoker_set(_invoker_id uuid) RETURNS void LANGUAGE plpgsql AS $$ BEGIN - SET LOCAL role = 'vibetype_account'; + SET LOCAL ROLE = 'vibetype_account'; EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _invoker_id || ''''; END $$; @@ -4796,6 +4826,7 @@ CREATE TABLE vibetype.notification ( channel text NOT NULL, is_acknowledged boolean, payload text NOT NULL, + created_by uuid NOT NULL, created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT notification_payload_check CHECK ((octet_length(payload) <= 8000)) ); @@ -4838,6 +4869,14 @@ COMMENT ON COLUMN vibetype.notification.is_acknowledged IS 'Whether the notifica COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload.'; +-- +-- Name: COLUMN notification.created_by; Type: COMMENT; Schema: vibetype; Owner: ci +-- + +COMMENT ON COLUMN vibetype.notification.created_by IS '@omit create +Reference to the account that created the notification.'; + + -- -- Name: COLUMN notification.created_at; Type: COMMENT; Schema: vibetype; Owner: ci -- @@ -4850,8 +4889,7 @@ COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the noti -- CREATE TABLE vibetype.notification_invitation ( - guest_id uuid NOT NULL, - created_by uuid NOT NULL + guest_id uuid NOT NULL ) INHERITS (vibetype.notification); @@ -4872,14 +4910,6 @@ COMMENT ON TABLE vibetype.notification_invitation IS '@omit update,delete\nStore COMMENT ON COLUMN vibetype.notification_invitation.guest_id IS 'The ID of the guest associated with this invitation.'; --- --- Name: COLUMN notification_invitation.created_by; Type: COMMENT; Schema: vibetype; Owner: ci --- - -COMMENT ON COLUMN vibetype.notification_invitation.created_by IS '@omit create -Reference to the account that created the invitation.'; - - -- -- Name: profile_picture; Type: TABLE; Schema: vibetype; Owner: ci -- @@ -6269,11 +6299,11 @@ ALTER TABLE ONLY vibetype.legal_term_acceptance -- --- Name: notification_invitation notification_invitation_created_by_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci +-- Name: notification notification_created_by_fkey; Type: FK CONSTRAINT; Schema: vibetype; Owner: ci -- -ALTER TABLE ONLY vibetype.notification_invitation - ADD CONSTRAINT notification_invitation_created_by_fkey FOREIGN KEY (created_by) REFERENCES vibetype.account(id); +ALTER TABLE ONLY vibetype.notification + ADD CONSTRAINT notification_created_by_fkey FOREIGN KEY (created_by) REFERENCES vibetype.account(id) ON DELETE CASCADE; -- @@ -7605,6 +7635,14 @@ REVOKE ALL ON FUNCTION vibetype_test.account_remove(_username text) FROM PUBLIC; GRANT ALL ON FUNCTION vibetype_test.account_remove(_username text) TO vibetype_account; +-- +-- Name: FUNCTION account_select_by_email_address(_email_address text); Type: ACL; Schema: vibetype_test; Owner: ci +-- + +REVOKE ALL ON FUNCTION vibetype_test.account_select_by_email_address(_email_address text) FROM PUBLIC; +GRANT ALL ON FUNCTION vibetype_test.account_select_by_email_address(_email_address text) TO vibetype_account; + + -- -- Name: FUNCTION contact_create(_created_by uuid, _email_address text); Type: ACL; Schema: vibetype_test; Owner: ci -- From 748758a568d69e15725a67a1c80a02c9dc3c9614 Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Thu, 10 Apr 2025 22:11:19 +0200 Subject: [PATCH 07/12] chore(policy): simplify policies The policies for tables `notification` and `notification_invitation` were updated to make use of `FOR ALL` policies. --- .../table_notification_invitation_policy.sql | 8 ++++---- src/deploy/table_notification_policy.sql | 9 +++------ .../table_notification_invitation_policy.sql | 2 +- src/revert/table_notification_policy.sql | 3 +-- test/schema/schema.definition.sql | 14 +++++++------- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/deploy/table_notification_invitation_policy.sql b/src/deploy/table_notification_invitation_policy.sql index ff794207..88b8d626 100644 --- a/src/deploy/table_notification_invitation_policy.sql +++ b/src/deploy/table_notification_invitation_policy.sql @@ -4,13 +4,13 @@ GRANT SELECT, INSERT ON vibetype.notification_invitation TO vibetype_account; ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; -CREATE POLICY notification_invitation_select ON vibetype.notification_invitation FOR SELECT USING ( +CREATE POLICY notification_invitation_all ON vibetype.notification_invitation FOR ALL +USING ( created_by = vibetype.invoker_account_id() ); -CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT WITH CHECK ( - created_by = vibetype.invoker_account_id() - AND +CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT +WITH CHECK ( vibetype.invoker_account_id() = ( SELECT e.created_by FROM vibetype.guest g diff --git a/src/deploy/table_notification_policy.sql b/src/deploy/table_notification_policy.sql index 4bbb22c5..b2bb6a92 100644 --- a/src/deploy/table_notification_policy.sql +++ b/src/deploy/table_notification_policy.sql @@ -4,12 +4,9 @@ GRANT SELECT, INSERT ON vibetype.notification TO vibetype_account; ALTER TABLE vibetype.notification ENABLE ROW LEVEL SECURITY; -CREATE POLICY notification_select ON vibetype.notification FOR SELECT USING ( - created_by = vibetype.invoker_account_id() -); - -CREATE POLICY notification_insert ON vibetype.notification FOR INSERT WITH CHECK ( - created_by = vibetype.invoker_account_id() +CREATE POLICY notification_all ON vibetype.legal_term_acceptance FOR ALL +USING ( + account_id = vibetype.invoker_account_id() ); COMMIT; \ No newline at end of file diff --git a/src/revert/table_notification_invitation_policy.sql b/src/revert/table_notification_invitation_policy.sql index b016f766..4ad707be 100644 --- a/src/revert/table_notification_invitation_policy.sql +++ b/src/revert/table_notification_invitation_policy.sql @@ -1,6 +1,6 @@ BEGIN; +DROP POLICY notification_invitation_all ON vibetype.notification_invitation; DROP POLICY notification_invitation_insert ON vibetype.notification_invitation; -DROP POLICY notification_invitation_select ON vibetype.notification_invitation; COMMIT; diff --git a/src/revert/table_notification_policy.sql b/src/revert/table_notification_policy.sql index b9616b5e..bed4258f 100644 --- a/src/revert/table_notification_policy.sql +++ b/src/revert/table_notification_policy.sql @@ -1,6 +1,5 @@ BEGIN; -DROP POLICY notification_insert ON vibetype.notification; -DROP POLICY notification_select ON vibetype.notification; +DROP POLICY notification_all ON vibetype.notification; COMMIT; \ No newline at end of file diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index 729e20f8..9feb3d58 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -7723,20 +7723,20 @@ CREATE POLICY legal_term_select ON vibetype.legal_term FOR SELECT USING (true); ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; -- --- Name: notification_invitation notification_invitation_insert; Type: POLICY; Schema: vibetype; Owner: ci +-- Name: notification_invitation notification_invitation_all; Type: POLICY; Schema: vibetype; Owner: ci -- -CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT WITH CHECK (((created_by = vibetype.invoker_account_id()) AND (vibetype.invoker_account_id() = ( SELECT e.created_by - FROM (vibetype.guest g - JOIN vibetype.event e ON ((g.event_id = e.id))) - WHERE (g.id = notification_invitation.guest_id))))); +CREATE POLICY notification_invitation_all ON vibetype.notification_invitation USING ((created_by = vibetype.invoker_account_id())); -- --- Name: notification_invitation notification_invitation_select; Type: POLICY; Schema: vibetype; Owner: ci +-- Name: notification_invitation notification_invitation_insert; Type: POLICY; Schema: vibetype; Owner: ci -- -CREATE POLICY notification_invitation_select ON vibetype.notification_invitation FOR SELECT USING ((created_by = vibetype.invoker_account_id())); +CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT WITH CHECK ((vibetype.invoker_account_id() = ( SELECT e.created_by + FROM (vibetype.guest g + JOIN vibetype.event e ON ((g.event_id = e.id))) + WHERE (g.id = notification_invitation.guest_id)))); -- From 9bdd042991914c5eb6219f21807ea77d8a4ad0ab Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Tue, 29 Apr 2025 22:54:51 +0200 Subject: [PATCH 08/12] chore: define policies in table definition files For tables `vibetype.notification` and `vibetype.notification_invitation` the policies were moved to the table files, making the policy files obsolete. --- src/deploy/table_notification.sql | 9 ++++++++ src/deploy/table_notification_invitation.sql | 19 ++++++++++++++++ .../table_notification_invitation_policy.sql | 22 ------------------- src/deploy/table_notification_policy.sql | 12 ---------- src/revert/table_notification.sql | 2 ++ src/revert/table_notification_invitation.sql | 3 +++ .../table_notification_invitation_policy.sql | 6 ----- src/revert/table_notification_policy.sql | 5 ----- src/sqitch.plan | 1 - src/verify/table_notification.sql | 12 ++++++++++ src/verify/table_notification_invitation.sql | 12 ++++++++++ .../table_notification_invitation_policy.sql | 15 ------------- src/verify/table_notification_policy.sql | 15 ------------- test/schema/schema.definition.sql | 20 +++++++++++++++++ 14 files changed, 77 insertions(+), 76 deletions(-) delete mode 100644 src/deploy/table_notification_invitation_policy.sql delete mode 100644 src/deploy/table_notification_policy.sql delete mode 100644 src/revert/table_notification_invitation_policy.sql delete mode 100644 src/revert/table_notification_policy.sql delete mode 100644 src/verify/table_notification_invitation_policy.sql delete mode 100644 src/verify/table_notification_policy.sql diff --git a/src/deploy/table_notification.sql b/src/deploy/table_notification.sql index 929a3abe..5a41064c 100644 --- a/src/deploy/table_notification.sql +++ b/src/deploy/table_notification.sql @@ -19,4 +19,13 @@ COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload. COMMENT ON COLUMN vibetype.notification.created_by IS E'@omit create\nReference to the account that created the notification.'; COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; +GRANT SELECT, INSERT ON vibetype.notification TO vibetype_account; + +ALTER TABLE vibetype.notification ENABLE ROW LEVEL SECURITY; + +CREATE POLICY notification_all ON vibetype.notification FOR ALL +USING ( + created_by = vibetype.invoker_account_id() +); + COMMIT; diff --git a/src/deploy/table_notification_invitation.sql b/src/deploy/table_notification_invitation.sql index cbc648b6..7be3ce00 100644 --- a/src/deploy/table_notification_invitation.sql +++ b/src/deploy/table_notification_invitation.sql @@ -10,4 +10,23 @@ COMMENT ON COLUMN vibetype.notification_invitation.guest_id IS 'The ID of the gu CREATE INDEX idx_invitation_guest_id ON vibetype.notification_invitation USING btree (guest_id); +GRANT SELECT, INSERT ON vibetype.notification_invitation TO vibetype_account; + +ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; + +CREATE POLICY notification_invitation_all ON vibetype.notification_invitation FOR ALL +USING ( + created_by = vibetype.invoker_account_id() +); + +CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT +WITH CHECK ( + vibetype.invoker_account_id() = ( + SELECT e.created_by + FROM vibetype.guest g + JOIN vibetype.event e ON g.event_id = e.id + WHERE g.id = guest_id + ) +); + COMMIT; diff --git a/src/deploy/table_notification_invitation_policy.sql b/src/deploy/table_notification_invitation_policy.sql deleted file mode 100644 index 88b8d626..00000000 --- a/src/deploy/table_notification_invitation_policy.sql +++ /dev/null @@ -1,22 +0,0 @@ -BEGIN; - -GRANT SELECT, INSERT ON vibetype.notification_invitation TO vibetype_account; - -ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; - -CREATE POLICY notification_invitation_all ON vibetype.notification_invitation FOR ALL -USING ( - created_by = vibetype.invoker_account_id() -); - -CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT -WITH CHECK ( - vibetype.invoker_account_id() = ( - SELECT e.created_by - FROM vibetype.guest g - JOIN vibetype.event e ON g.event_id = e.id - WHERE g.id = guest_id - ) -); - -COMMIT; diff --git a/src/deploy/table_notification_policy.sql b/src/deploy/table_notification_policy.sql deleted file mode 100644 index b2bb6a92..00000000 --- a/src/deploy/table_notification_policy.sql +++ /dev/null @@ -1,12 +0,0 @@ -BEGIN; - -GRANT SELECT, INSERT ON vibetype.notification TO vibetype_account; - -ALTER TABLE vibetype.notification ENABLE ROW LEVEL SECURITY; - -CREATE POLICY notification_all ON vibetype.legal_term_acceptance FOR ALL -USING ( - account_id = vibetype.invoker_account_id() -); - -COMMIT; \ No newline at end of file diff --git a/src/revert/table_notification.sql b/src/revert/table_notification.sql index bb85e7c9..f1d545f3 100644 --- a/src/revert/table_notification.sql +++ b/src/revert/table_notification.sql @@ -1,5 +1,7 @@ BEGIN; +DROP POLICY notification_all ON vibetype.notification; + DROP TABLE vibetype.notification; COMMIT; diff --git a/src/revert/table_notification_invitation.sql b/src/revert/table_notification_invitation.sql index 0e1c2a70..f420e857 100644 --- a/src/revert/table_notification_invitation.sql +++ b/src/revert/table_notification_invitation.sql @@ -1,5 +1,8 @@ BEGIN; +DROP POLICY notification_invitation_all ON vibetype.notification_invitation; +DROP POLICY notification_invitation_insert ON vibetype.notification_invitation; + DROP TABLE vibetype.notification_invitation; COMMIT; diff --git a/src/revert/table_notification_invitation_policy.sql b/src/revert/table_notification_invitation_policy.sql deleted file mode 100644 index 4ad707be..00000000 --- a/src/revert/table_notification_invitation_policy.sql +++ /dev/null @@ -1,6 +0,0 @@ -BEGIN; - -DROP POLICY notification_invitation_all ON vibetype.notification_invitation; -DROP POLICY notification_invitation_insert ON vibetype.notification_invitation; - -COMMIT; diff --git a/src/revert/table_notification_policy.sql b/src/revert/table_notification_policy.sql deleted file mode 100644 index bed4258f..00000000 --- a/src/revert/table_notification_policy.sql +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN; - -DROP POLICY notification_all ON vibetype.notification; - -COMMIT; \ No newline at end of file diff --git a/src/sqitch.plan b/src/sqitch.plan index 4838aaa1..3993786a 100644 --- a/src/sqitch.plan +++ b/src/sqitch.plan @@ -110,5 +110,4 @@ function_audit_log [schema_private table_audit_log view_audit_log_trigger] 1970- table_account_preference_event_format [schema_public table_account_public table_event_category] 1970-01-01T00:00:00Z Sven Thelemann # Event formats a user account is interested in (M:N relationship). table_account_preference_event_format_policy [schema_public table_account_preference_event_format role_account] 1970-01-01T00:00:00Z Sven Thelemann # Security policies for the accounts' event format preferences. table_notification_invitation [schema_public table_guest] 1970-01-01T00:00:00Z Sven Thelemann # A table for tracking actions around invitations. -table_notification_invitation_policy [schema_public table_notification_invitation role_account function_invoker_account_id table_guest table_event] 1970-01-01T00:00:00Z Sven Thelemann # Stores invitations and their statuses. test_invitation [schema_test function_test_utilities table_legal_term function_account_email_address_verification] 1970-01-01T00:00:00Z Sven Thelemann # Invitation related tests. diff --git a/src/verify/table_notification.sql b/src/verify/table_notification.sql index 9d775c8e..5bafae37 100644 --- a/src/verify/table_notification.sql +++ b/src/verify/table_notification.sql @@ -8,4 +8,16 @@ SELECT id, created_at FROM vibetype.notification WHERE FALSE; +DO $$ +BEGIN + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'SELECT')); + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'DELETE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'SELECT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'DELETE')); +END $$; + ROLLBACK; diff --git a/src/verify/table_notification_invitation.sql b/src/verify/table_notification_invitation.sql index 4cc17fbe..eae1cb55 100644 --- a/src/verify/table_notification_invitation.sql +++ b/src/verify/table_notification_invitation.sql @@ -13,4 +13,16 @@ SELECT FROM vibetype.notification_invitation WHERE FALSE; +DO $$ +BEGIN + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'SELECT')); + ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'DELETE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'SELECT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'UPDATE')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'DELETE')); +END $$; + ROLLBACK; diff --git a/src/verify/table_notification_invitation_policy.sql b/src/verify/table_notification_invitation_policy.sql deleted file mode 100644 index 961ecb09..00000000 --- a/src/verify/table_notification_invitation_policy.sql +++ /dev/null @@ -1,15 +0,0 @@ -BEGIN; - -DO $$ -BEGIN - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'SELECT')); - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'INSERT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'UPDATE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'DELETE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'SELECT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'INSERT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'UPDATE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'DELETE')); -END $$; - -ROLLBACK; diff --git a/src/verify/table_notification_policy.sql b/src/verify/table_notification_policy.sql deleted file mode 100644 index 8bcf13d1..00000000 --- a/src/verify/table_notification_policy.sql +++ /dev/null @@ -1,15 +0,0 @@ -BEGIN; - -DO $$ -BEGIN - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'SELECT')); - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'INSERT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'UPDATE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'DELETE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'SELECT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'INSERT')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'UPDATE')); - ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'DELETE')); -END $$; - -ROLLBACK; \ No newline at end of file diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index 9feb3d58..452fca42 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -7716,6 +7716,19 @@ CREATE POLICY legal_term_acceptance_select ON vibetype.legal_term_acceptance FOR CREATE POLICY legal_term_select ON vibetype.legal_term FOR SELECT USING (true); +-- +-- Name: notification; Type: ROW SECURITY; Schema: vibetype; Owner: ci +-- + +ALTER TABLE vibetype.notification ENABLE ROW LEVEL SECURITY; + +-- +-- Name: notification notification_all; Type: POLICY; Schema: vibetype; Owner: ci +-- + +CREATE POLICY notification_all ON vibetype.notification USING ((created_by = vibetype.invoker_account_id())); + + -- -- Name: notification_invitation; Type: ROW SECURITY; Schema: vibetype; Owner: ci -- @@ -8945,6 +8958,13 @@ GRANT SELECT ON TABLE vibetype.legal_term TO vibetype_anonymous; GRANT SELECT,INSERT ON TABLE vibetype.legal_term_acceptance TO vibetype_account; +-- +-- Name: TABLE notification; Type: ACL; Schema: vibetype; Owner: ci +-- + +GRANT SELECT,INSERT ON TABLE vibetype.notification TO vibetype_account; + + -- -- Name: TABLE notification_invitation; Type: ACL; Schema: vibetype; Owner: ci -- From b7adc45eb14519cdd611c75d24d5417d6dbfbede Mon Sep 17 00:00:00 2001 From: Jonas Thelemann Date: Fri, 2 May 2025 12:21:31 +0200 Subject: [PATCH 09/12] chore: review --- ...unction_account_password_reset_request.sql | 16 +++--- src/deploy/function_invite.sql | 6 +-- src/deploy/table_notification.sql | 10 ++-- src/deploy/table_notification_invitation.sql | 16 ++---- src/revert/table_notification_invitation.sql | 1 - src/verify/table_notification.sql | 2 +- src/verify/table_notification_invitation.sql | 2 +- test/fixture/schema.definition.sql | 52 +++++++++---------- 8 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/deploy/function_account_password_reset_request.sql b/src/deploy/function_account_password_reset_request.sql index 4096c27b..abf5efde 100644 --- a/src/deploy/function_account_password_reset_request.sql +++ b/src/deploy/function_account_password_reset_request.sql @@ -5,24 +5,24 @@ CREATE FUNCTION vibetype.account_password_reset_request( language TEXT ) RETURNS VOID AS $$ DECLARE - _rec_account vibetype_private.account%ROWTYPE; + _account vibetype_private.account%ROWTYPE; _notify_data RECORD := NULL; BEGIN UPDATE vibetype_private.account SET password_reset_verification = gen_random_uuid() WHERE account.email_address = account_password_reset_request.email_address - RETURNING * INTO _rec_account; + RETURNING * INTO _account; - IF _rec_account IS NOT NULL THEN + IF _account IS NOT NULL THEN SELECT username, - _rec_account.email_address, - _rec_account.password_reset_verification, - _rec_account.password_reset_verification_valid_until + _account.email_address, + _account.password_reset_verification, + _account.password_reset_verification_valid_until INTO _notify_data FROM vibetype.account - WHERE id = _rec_account.id; + WHERE id = _account.id; END IF; IF (_notify_data IS NULL) THEN @@ -34,7 +34,7 @@ BEGIN 'account', _notify_data, 'template', jsonb_build_object('language', account_password_reset_request.language) )), - _rec_account.id + _account.id ); END IF; END; diff --git a/src/deploy/function_invite.sql b/src/deploy/function_invite.sql index cb194a4d..0ede9442 100644 --- a/src/deploy/function_invite.sql +++ b/src/deploy/function_invite.sql @@ -2,7 +2,7 @@ BEGIN; CREATE FUNCTION vibetype.invite( guest_id UUID, - "language" TEXT + language TEXT ) RETURNS UUID AS $$ DECLARE _contact RECORD; @@ -88,11 +88,11 @@ BEGIN ) RETURNING id INTO _id; - RETURN _id; + RETURN _id; END; $$ LANGUAGE PLPGSQL STRICT SECURITY DEFINER; -COMMENT ON FUNCTION vibetype.invite(UUID, TEXT) IS 'Adds an invitation and a notification.'; +COMMENT ON FUNCTION vibetype.invite(UUID, TEXT) IS 'Adds a notification for the invitation channel.'; GRANT EXECUTE ON FUNCTION vibetype.invite(UUID, TEXT) TO vibetype_account; diff --git a/src/deploy/table_notification.sql b/src/deploy/table_notification.sql index 5a41064c..f541d70b 100644 --- a/src/deploy/table_notification.sql +++ b/src/deploy/table_notification.sql @@ -7,19 +7,21 @@ CREATE TABLE vibetype.notification ( is_acknowledged BOOLEAN, payload TEXT NOT NULL CHECK (octet_length(payload) <= 8000), - created_by UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_by UUID NOT NULL REFERENCES vibetype.account(id) ON DELETE CASCADE ); +CREATE INDEX idx_notification_created_by ON vibetype.notification USING btree (created_by); + COMMENT ON TABLE vibetype.notification IS 'A notification.'; COMMENT ON COLUMN vibetype.notification.id IS 'The notification''s internal id.'; COMMENT ON COLUMN vibetype.notification.channel IS 'The notification''s channel.'; COMMENT ON COLUMN vibetype.notification.is_acknowledged IS 'Whether the notification was acknowledged.'; COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload.'; -COMMENT ON COLUMN vibetype.notification.created_by IS E'@omit create\nReference to the account that created the notification.'; COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; +COMMENT ON COLUMN vibetype.notification.created_by IS 'Reference to the account that created the notification.'; -GRANT SELECT, INSERT ON vibetype.notification TO vibetype_account; +GRANT SELECT ON vibetype.notification TO vibetype_account; ALTER TABLE vibetype.notification ENABLE ROW LEVEL SECURITY; diff --git a/src/deploy/table_notification_invitation.sql b/src/deploy/table_notification_invitation.sql index 7be3ce00..5b51eff6 100644 --- a/src/deploy/table_notification_invitation.sql +++ b/src/deploy/table_notification_invitation.sql @@ -5,12 +5,12 @@ CREATE TABLE vibetype.notification_invitation ( ) INHERITS (vibetype.notification); +CREATE INDEX idx_invitation_guest_id ON vibetype.notification_invitation USING btree (guest_id); + COMMENT ON TABLE vibetype.notification_invitation IS '@omit update,delete\nStores invitations and their statuses.'; COMMENT ON COLUMN vibetype.notification_invitation.guest_id IS 'The ID of the guest associated with this invitation.'; -CREATE INDEX idx_invitation_guest_id ON vibetype.notification_invitation USING btree (guest_id); - -GRANT SELECT, INSERT ON vibetype.notification_invitation TO vibetype_account; +GRANT SELECT ON vibetype.notification_invitation TO vibetype_account; ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; @@ -19,14 +19,4 @@ USING ( created_by = vibetype.invoker_account_id() ); -CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT -WITH CHECK ( - vibetype.invoker_account_id() = ( - SELECT e.created_by - FROM vibetype.guest g - JOIN vibetype.event e ON g.event_id = e.id - WHERE g.id = guest_id - ) -); - COMMIT; diff --git a/src/revert/table_notification_invitation.sql b/src/revert/table_notification_invitation.sql index f420e857..6d94fcc4 100644 --- a/src/revert/table_notification_invitation.sql +++ b/src/revert/table_notification_invitation.sql @@ -1,7 +1,6 @@ BEGIN; DROP POLICY notification_invitation_all ON vibetype.notification_invitation; -DROP POLICY notification_invitation_insert ON vibetype.notification_invitation; DROP TABLE vibetype.notification_invitation; diff --git a/src/verify/table_notification.sql b/src/verify/table_notification.sql index 5bafae37..d5c8f157 100644 --- a/src/verify/table_notification.sql +++ b/src/verify/table_notification.sql @@ -11,7 +11,7 @@ FROM vibetype.notification WHERE FALSE; DO $$ BEGIN ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'SELECT')); - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'INSERT')); ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'UPDATE')); ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification', 'DELETE')); ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification', 'SELECT')); diff --git a/src/verify/table_notification_invitation.sql b/src/verify/table_notification_invitation.sql index eae1cb55..07dab49d 100644 --- a/src/verify/table_notification_invitation.sql +++ b/src/verify/table_notification_invitation.sql @@ -16,7 +16,7 @@ WHERE FALSE; DO $$ BEGIN ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'SELECT')); - ASSERT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'INSERT')); + ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'INSERT')); ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'UPDATE')); ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_account', 'vibetype.notification_invitation', 'DELETE')); ASSERT NOT (SELECT pg_catalog.has_table_privilege('vibetype_anonymous', 'vibetype.notification_invitation', 'SELECT')); diff --git a/test/fixture/schema.definition.sql b/test/fixture/schema.definition.sql index 3a422bf0..fda051ef 100644 --- a/test/fixture/schema.definition.sql +++ b/test/fixture/schema.definition.sql @@ -515,24 +515,24 @@ CREATE FUNCTION vibetype.account_password_reset_request(email_address text, lang LANGUAGE plpgsql STRICT SECURITY DEFINER AS $$ DECLARE - _rec_account vibetype_private.account%ROWTYPE; + _account vibetype_private.account%ROWTYPE; _notify_data RECORD := NULL; BEGIN UPDATE vibetype_private.account SET password_reset_verification = gen_random_uuid() WHERE account.email_address = account_password_reset_request.email_address - RETURNING * INTO _rec_account; + RETURNING * INTO _account; - IF _rec_account IS NOT NULL THEN + IF _account IS NOT NULL THEN SELECT username, - _rec_account.email_address, - _rec_account.password_reset_verification, - _rec_account.password_reset_verification_valid_until + _account.email_address, + _account.password_reset_verification, + _account.password_reset_verification_valid_until INTO _notify_data FROM vibetype.account - WHERE id = _rec_account.id; + WHERE id = _account.id; END IF; IF (_notify_data IS NULL) THEN @@ -544,7 +544,7 @@ BEGIN 'account', _notify_data, 'template', jsonb_build_object('language', account_password_reset_request.language) )), - _rec_account.id + _account.id ); END IF; END; @@ -1530,7 +1530,7 @@ BEGIN ) RETURNING id INTO _id; - RETURN _id; + RETURN _id; END; $$; @@ -1541,7 +1541,7 @@ ALTER FUNCTION vibetype.invite(guest_id uuid, language text) OWNER TO ci; -- Name: FUNCTION invite(guest_id uuid, language text); Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON FUNCTION vibetype.invite(guest_id uuid, language text) IS 'Adds an invitation and a notification.'; +COMMENT ON FUNCTION vibetype.invite(guest_id uuid, language text) IS 'Adds a notification for the invitation channel.'; -- @@ -4410,8 +4410,8 @@ CREATE TABLE vibetype.notification ( channel text NOT NULL, is_acknowledged boolean, payload text NOT NULL, - created_by uuid NOT NULL, created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + created_by uuid NOT NULL, CONSTRAINT notification_payload_check CHECK ((octet_length(payload) <= 8000)) ); @@ -4454,18 +4454,17 @@ COMMENT ON COLUMN vibetype.notification.payload IS 'The notification''s payload. -- --- Name: COLUMN notification.created_by; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification.created_at; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.notification.created_by IS '@omit create -Reference to the account that created the notification.'; +COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; -- --- Name: COLUMN notification.created_at; Type: COMMENT; Schema: vibetype; Owner: ci +-- Name: COLUMN notification.created_by; Type: COMMENT; Schema: vibetype; Owner: ci -- -COMMENT ON COLUMN vibetype.notification.created_at IS 'The timestamp of the notification''s creation.'; +COMMENT ON COLUMN vibetype.notification.created_by IS 'Reference to the account that created the notification.'; -- @@ -5674,6 +5673,13 @@ COMMENT ON INDEX vibetype.idx_guest_updated_by IS 'B-Tree index to optimize look CREATE INDEX idx_invitation_guest_id ON vibetype.notification_invitation USING btree (guest_id); +-- +-- Name: idx_notification_created_by; Type: INDEX; Schema: vibetype; Owner: ci +-- + +CREATE INDEX idx_notification_created_by ON vibetype.notification USING btree (created_by); + + -- -- Name: idx_account_private_location; Type: INDEX; Schema: vibetype_private; Owner: ci -- @@ -6675,16 +6681,6 @@ ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; CREATE POLICY notification_invitation_all ON vibetype.notification_invitation USING ((created_by = vibetype.invoker_account_id())); --- --- Name: notification_invitation notification_invitation_insert; Type: POLICY; Schema: vibetype; Owner: ci --- - -CREATE POLICY notification_invitation_insert ON vibetype.notification_invitation FOR INSERT WITH CHECK ((vibetype.invoker_account_id() = ( SELECT e.created_by - FROM (vibetype.guest g - JOIN vibetype.event e ON ((g.event_id = e.id))) - WHERE (g.id = notification_invitation.guest_id)))); - - -- -- Name: profile_picture; Type: ROW SECURITY; Schema: vibetype; Owner: ci -- @@ -7601,14 +7597,14 @@ GRANT SELECT,INSERT ON TABLE vibetype.legal_term_acceptance TO vibetype_account; -- Name: TABLE notification; Type: ACL; Schema: vibetype; Owner: ci -- -GRANT SELECT,INSERT ON TABLE vibetype.notification TO vibetype_account; +GRANT SELECT ON TABLE vibetype.notification TO vibetype_account; -- -- Name: TABLE notification_invitation; Type: ACL; Schema: vibetype; Owner: ci -- -GRANT SELECT,INSERT ON TABLE vibetype.notification_invitation TO vibetype_account; +GRANT SELECT ON TABLE vibetype.notification_invitation TO vibetype_account; -- From 82b9b4a4226d93dd33f1cbd395ea5993770f2a3c Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Tue, 6 May 2025 22:25:39 +0200 Subject: [PATCH 10/12] feat(invitation): modify function `invite` The function `invite` has been changed to only store the event id in the payload of the notification record. --- src/deploy/function_invite.sql | 13 +++++++------ test/fixture/schema.definition.sql | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/deploy/function_invite.sql b/src/deploy/function_invite.sql index 0ede9442..3e5ba85d 100644 --- a/src/deploy/function_invite.sql +++ b/src/deploy/function_invite.sql @@ -7,7 +7,8 @@ CREATE FUNCTION vibetype.invite( DECLARE _contact RECORD; _email_address TEXT; - _event RECORD; + _event_id UUID; + _event_created_by UUID; _event_creator_profile_picture_upload_storage_key TEXT; _event_creator_username TEXT; _guest RECORD; @@ -27,9 +28,9 @@ BEGIN END IF; -- Event - SELECT * INTO _event FROM vibetype.event WHERE id = _guest.event_id; + SELECT id, created_by INTO _event_id, _event_created_by FROM vibetype.event WHERE id = _guest.event_id; - IF (_event IS NULL) THEN + IF (_event_id IS NULL) THEN RAISE 'Event not accessible!' USING ERRCODE = 'no_data_found'; END IF; @@ -62,13 +63,13 @@ BEGIN -- Event creator username SELECT username INTO _event_creator_username FROM vibetype.account - WHERE id = _event.created_by; + WHERE id = _event_created_by; -- Event creator profile picture storage key SELECT u.storage_key INTO _event_creator_profile_picture_upload_storage_key FROM vibetype.profile_picture p JOIN vibetype.upload u ON p.upload_id = u.id - WHERE p.account_id = _event.created_by; + WHERE p.account_id = _event_created_by; INSERT INTO vibetype.notification_invitation (guest_id, channel, payload, created_by) VALUES ( @@ -77,7 +78,7 @@ BEGIN jsonb_pretty(jsonb_build_object( 'data', jsonb_build_object( 'emailAddress', _email_address, - 'event', _event, + 'eventId', _event_id, 'eventCreatorProfilePictureUploadStorageKey', _event_creator_profile_picture_upload_storage_key, 'eventCreatorUsername', _event_creator_username, 'guestId', _guest.id diff --git a/test/fixture/schema.definition.sql b/test/fixture/schema.definition.sql index fda051ef..0e053d5c 100644 --- a/test/fixture/schema.definition.sql +++ b/test/fixture/schema.definition.sql @@ -1449,7 +1449,8 @@ CREATE FUNCTION vibetype.invite(guest_id uuid, language text) RETURNS uuid DECLARE _contact RECORD; _email_address TEXT; - _event RECORD; + _event_id UUID; + _event_created_by UUID; _event_creator_profile_picture_upload_storage_key TEXT; _event_creator_username TEXT; _guest RECORD; @@ -1469,9 +1470,9 @@ BEGIN END IF; -- Event - SELECT * INTO _event FROM vibetype.event WHERE id = _guest.event_id; + SELECT id, created_by INTO _event_id, _event_created_by FROM vibetype.event WHERE id = _guest.event_id; - IF (_event IS NULL) THEN + IF (_event_id IS NULL) THEN RAISE 'Event not accessible!' USING ERRCODE = 'no_data_found'; END IF; @@ -1504,13 +1505,13 @@ BEGIN -- Event creator username SELECT username INTO _event_creator_username FROM vibetype.account - WHERE id = _event.created_by; + WHERE id = _event_created_by; -- Event creator profile picture storage key SELECT u.storage_key INTO _event_creator_profile_picture_upload_storage_key FROM vibetype.profile_picture p JOIN vibetype.upload u ON p.upload_id = u.id - WHERE p.account_id = _event.created_by; + WHERE p.account_id = _event_created_by; INSERT INTO vibetype.notification_invitation (guest_id, channel, payload, created_by) VALUES ( @@ -1519,7 +1520,7 @@ BEGIN jsonb_pretty(jsonb_build_object( 'data', jsonb_build_object( 'emailAddress', _email_address, - 'event', _event, + 'eventId', _event_id, 'eventCreatorProfilePictureUploadStorageKey', _event_creator_profile_picture_upload_storage_key, 'eventCreatorUsername', _event_creator_username, 'guestId', _guest.id From 051766dc0141a2949daee3b067beafb0e05185e3 Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Tue, 6 May 2025 22:41:37 +0200 Subject: [PATCH 11/12] feat(invitation): cleanup policies The policy for table `notification_invitation` has been removed because it is already present for the parent table `notification`. --- src/deploy/table_notification_invitation.sql | 5 ----- src/revert/table_notification_invitation.sql | 2 -- test/fixture/schema.definition.sql | 7 ------- 3 files changed, 14 deletions(-) diff --git a/src/deploy/table_notification_invitation.sql b/src/deploy/table_notification_invitation.sql index 5b51eff6..5fb5022a 100644 --- a/src/deploy/table_notification_invitation.sql +++ b/src/deploy/table_notification_invitation.sql @@ -14,9 +14,4 @@ GRANT SELECT ON vibetype.notification_invitation TO vibetype_account; ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; -CREATE POLICY notification_invitation_all ON vibetype.notification_invitation FOR ALL -USING ( - created_by = vibetype.invoker_account_id() -); - COMMIT; diff --git a/src/revert/table_notification_invitation.sql b/src/revert/table_notification_invitation.sql index 6d94fcc4..0e1c2a70 100644 --- a/src/revert/table_notification_invitation.sql +++ b/src/revert/table_notification_invitation.sql @@ -1,7 +1,5 @@ BEGIN; -DROP POLICY notification_invitation_all ON vibetype.notification_invitation; - DROP TABLE vibetype.notification_invitation; COMMIT; diff --git a/test/fixture/schema.definition.sql b/test/fixture/schema.definition.sql index 0e053d5c..4eff83e4 100644 --- a/test/fixture/schema.definition.sql +++ b/test/fixture/schema.definition.sql @@ -6675,13 +6675,6 @@ CREATE POLICY notification_all ON vibetype.notification USING ((created_by = vib ALTER TABLE vibetype.notification_invitation ENABLE ROW LEVEL SECURITY; --- --- Name: notification_invitation notification_invitation_all; Type: POLICY; Schema: vibetype; Owner: ci --- - -CREATE POLICY notification_invitation_all ON vibetype.notification_invitation USING ((created_by = vibetype.invoker_account_id())); - - -- -- Name: profile_picture; Type: ROW SECURITY; Schema: vibetype; Owner: ci -- From 4981600b60ad734fb12952b089e638217b4ab9eb Mon Sep 17 00:00:00 2001 From: Sven Thelemann Date: Fri, 9 May 2025 12:19:47 +0200 Subject: [PATCH 12/12] feat(invitation): undo changes from previous commits Function `invite`has been restored to its previous implementation regarding JSON payload. --- src/deploy/function_invite.sql | 30 +++++++++++++++++++++++------- test/fixture/schema.definition.sql | 30 +++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/deploy/function_invite.sql b/src/deploy/function_invite.sql index 3e5ba85d..794cf8cc 100644 --- a/src/deploy/function_invite.sql +++ b/src/deploy/function_invite.sql @@ -7,8 +7,7 @@ CREATE FUNCTION vibetype.invite( DECLARE _contact RECORD; _email_address TEXT; - _event_id UUID; - _event_created_by UUID; + _event RECORD; _event_creator_profile_picture_upload_storage_key TEXT; _event_creator_username TEXT; _guest RECORD; @@ -28,9 +27,26 @@ BEGIN END IF; -- Event - SELECT id, created_by INTO _event_id, _event_created_by FROM vibetype.event WHERE id = _guest.event_id; + SELECT + id, + address_id, + description, + "end", + guest_count_maximum, + is_archived, + is_in_person, + is_remote, + name, + slug, + start, + url, + visibility, + created_at, + created_by + INTO _event + FROM vibetype.event WHERE id = _guest.event_id; - IF (_event_id IS NULL) THEN + IF (_event IS NULL) THEN RAISE 'Event not accessible!' USING ERRCODE = 'no_data_found'; END IF; @@ -63,13 +79,13 @@ BEGIN -- Event creator username SELECT username INTO _event_creator_username FROM vibetype.account - WHERE id = _event_created_by; + WHERE id = _event.created_by; -- Event creator profile picture storage key SELECT u.storage_key INTO _event_creator_profile_picture_upload_storage_key FROM vibetype.profile_picture p JOIN vibetype.upload u ON p.upload_id = u.id - WHERE p.account_id = _event_created_by; + WHERE p.account_id = _event.created_by; INSERT INTO vibetype.notification_invitation (guest_id, channel, payload, created_by) VALUES ( @@ -78,7 +94,7 @@ BEGIN jsonb_pretty(jsonb_build_object( 'data', jsonb_build_object( 'emailAddress', _email_address, - 'eventId', _event_id, + 'event', _event, 'eventCreatorProfilePictureUploadStorageKey', _event_creator_profile_picture_upload_storage_key, 'eventCreatorUsername', _event_creator_username, 'guestId', _guest.id diff --git a/test/fixture/schema.definition.sql b/test/fixture/schema.definition.sql index 4eff83e4..cb6bba1d 100644 --- a/test/fixture/schema.definition.sql +++ b/test/fixture/schema.definition.sql @@ -1449,8 +1449,7 @@ CREATE FUNCTION vibetype.invite(guest_id uuid, language text) RETURNS uuid DECLARE _contact RECORD; _email_address TEXT; - _event_id UUID; - _event_created_by UUID; + _event RECORD; _event_creator_profile_picture_upload_storage_key TEXT; _event_creator_username TEXT; _guest RECORD; @@ -1470,9 +1469,26 @@ BEGIN END IF; -- Event - SELECT id, created_by INTO _event_id, _event_created_by FROM vibetype.event WHERE id = _guest.event_id; + SELECT + id, + address_id, + description, + "end", + guest_count_maximum, + is_archived, + is_in_person, + is_remote, + name, + slug, + start, + url, + visibility, + created_at, + created_by + INTO _event + FROM vibetype.event WHERE id = _guest.event_id; - IF (_event_id IS NULL) THEN + IF (_event IS NULL) THEN RAISE 'Event not accessible!' USING ERRCODE = 'no_data_found'; END IF; @@ -1505,13 +1521,13 @@ BEGIN -- Event creator username SELECT username INTO _event_creator_username FROM vibetype.account - WHERE id = _event_created_by; + WHERE id = _event.created_by; -- Event creator profile picture storage key SELECT u.storage_key INTO _event_creator_profile_picture_upload_storage_key FROM vibetype.profile_picture p JOIN vibetype.upload u ON p.upload_id = u.id - WHERE p.account_id = _event_created_by; + WHERE p.account_id = _event.created_by; INSERT INTO vibetype.notification_invitation (guest_id, channel, payload, created_by) VALUES ( @@ -1520,7 +1536,7 @@ BEGIN jsonb_pretty(jsonb_build_object( 'data', jsonb_build_object( 'emailAddress', _email_address, - 'eventId', _event_id, + 'event', _event, 'eventCreatorProfilePictureUploadStorageKey', _event_creator_profile_picture_upload_storage_key, 'eventCreatorUsername', _event_creator_username, 'guestId', _guest.id