diff --git a/src/deploy/extension_postgis.sql b/src/deploy/extension_postgis.sql index 2048ea18..36c25266 100644 --- a/src/deploy/extension_postgis.sql +++ b/src/deploy/extension_postgis.sql @@ -4,4 +4,15 @@ CREATE EXTENSION postgis WITH SCHEMA public; COMMENT ON EXTENSION postgis IS 'Functions to work with geospatial data.'; +GRANT EXECUTE ON FUNCTION + geography(geometry), + geometry(text), + geometrytype(geography), + postgis_type_name(character varying, integer, boolean), + st_asgeojson(geography, integer, integer), + st_coorddim(geometry), + st_geomfromgeojson(text), + st_srid(geography) +TO maevsi_anonymous, maevsi_account; + COMMIT; diff --git a/src/deploy/index_account_private_location.sql b/src/deploy/index_account_private_location.sql new file mode 100644 index 00000000..e78e911d --- /dev/null +++ b/src/deploy/index_account_private_location.sql @@ -0,0 +1,7 @@ +BEGIN; + +CREATE INDEX idx_account_private_location ON maevsi_private.account USING GIST (location); + +COMMENT ON INDEX maevsi_private.idx_account_private_location IS 'Spatial index on column location in maevsi_private.account.'; + +COMMIT; diff --git a/src/deploy/index_event_location.sql b/src/deploy/index_event_location.sql new file mode 100644 index 00000000..2c221e3f --- /dev/null +++ b/src/deploy/index_event_location.sql @@ -0,0 +1,7 @@ +BEGIN; + +CREATE INDEX idx_event_location ON maevsi.event USING GIST (location_geography); + +COMMENT ON INDEX maevsi.idx_event_location IS 'Spatial index on column location in maevsi.event.'; + +COMMIT; diff --git a/src/deploy/table_account_private.sql b/src/deploy/table_account_private.sql index 6306e8cb..c5e05abd 100644 --- a/src/deploy/table_account_private.sql +++ b/src/deploy/table_account_private.sql @@ -7,6 +7,7 @@ CREATE TABLE maevsi_private.account ( email_address TEXT NOT NULL CHECK (char_length(email_address) < 255) UNIQUE, -- no regex check as "a valid email address is one that you can send emails to" (http://www.dominicsayers.com/isemail/) email_address_verification UUID DEFAULT gen_random_uuid(), email_address_verification_valid_until TIMESTAMP WITH TIME ZONE, + location GEOGRAPHY(Point, 4326), password_hash TEXT NOT NULL, password_reset_verification UUID, password_reset_verification_valid_until TIMESTAMP WITH TIME ZONE, @@ -22,6 +23,7 @@ COMMENT ON COLUMN maevsi_private.account.birth_date IS 'The account owner''s dat COMMENT ON COLUMN maevsi_private.account.email_address IS 'The account''s email address for account related information.'; COMMENT ON COLUMN maevsi_private.account.email_address_verification IS 'The UUID used to verify an email address, or null if already verified.'; COMMENT ON COLUMN maevsi_private.account.email_address_verification_valid_until IS 'The timestamp until which an email address verification is valid.'; +COMMENT ON COLUMN maevsi_private.account.location IS 'The account''s geometric location.'; COMMENT ON COLUMN maevsi_private.account.password_hash IS 'The account''s password, hashed and salted.'; COMMENT ON COLUMN maevsi_private.account.password_reset_verification IS 'The UUID used to reset a password, or null if there is no pending reset request.'; COMMENT ON COLUMN maevsi_private.account.password_reset_verification_valid_until IS 'The timestamp until which a password reset is valid.'; diff --git a/src/deploy/table_event.sql b/src/deploy/table_event.sql index 645b03fb..cdbc98cf 100644 --- a/src/deploy/table_event.sql +++ b/src/deploy/table_event.sql @@ -12,6 +12,7 @@ CREATE TABLE maevsi.event ( is_remote BOOLEAN, language maevsi.language, location TEXT CHECK (char_length("location") > 0 AND char_length("location") < 300), + location_geography GEOGRAPHY(Point, 4326), name TEXT NOT NULL CHECK (char_length("name") > 0 AND char_length("name") < 100), slug TEXT NOT NULL CHECK (char_length(slug) < 100 AND slug ~ '^[-A-Za-z0-9]+$'), start TIMESTAMP WITH TIME ZONE NOT NULL, @@ -34,6 +35,7 @@ COMMENT ON COLUMN maevsi.event.is_archived IS 'Indicates whether the event is ar COMMENT ON COLUMN maevsi.event.is_in_person IS 'Indicates whether the event takes place in person.'; COMMENT ON COLUMN maevsi.event.is_remote IS 'Indicates whether the event takes place remotely.'; COMMENT ON COLUMN maevsi.event.location IS 'The event''s location as it can be shown on a map.'; +COMMENT ON COLUMN maevsi.event.location_geography IS 'The event''s geographic location.'; COMMENT ON COLUMN maevsi.event.name IS 'The event''s name.'; COMMENT ON COLUMN maevsi.event.slug IS 'The event''s name, slugified.'; COMMENT ON COLUMN maevsi.event.start IS 'The event''s start date and time, with timezone.'; diff --git a/src/deploy/test_location.sql b/src/deploy/test_location.sql new file mode 100644 index 00000000..1e9590fa --- /dev/null +++ b/src/deploy/test_location.sql @@ -0,0 +1,162 @@ +BEGIN; + + +CREATE FUNCTION maevsi.account_filter_radius_event( + _event_id UUID, + _distance_max DOUBLE PRECISION +) +RETURNS TABLE ( + account_id UUID, + distance DOUBLE PRECISION +) AS $$ +BEGIN + RETURN QUERY + WITH event AS ( + SELECT location_geography + FROM maevsi.event + WHERE id = _event_id + ) + SELECT + a.id AS account_id, + ST_Distance(e.location_geography, a.location) AS distance + FROM + event e, + maevsi_private.account a + WHERE + ST_DWithin(e.location_geography, a.location, _distance_max * 1000); +END; +$$ LANGUAGE PLPGSQL STRICT STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION maevsi.account_filter_radius_event(UUID, DOUBLE PRECISION) IS 'Returns account locations within a given radius around the location of an event.'; + +GRANT EXECUTE ON FUNCTION maevsi.account_filter_radius_event(UUID, DOUBLE PRECISION) TO maevsi_account; + + +CREATE FUNCTION maevsi.event_filter_radius_account( + _account_id UUID, + _distance_max DOUBLE PRECISION +) +RETURNS TABLE ( + event_id UUID, + distance DOUBLE PRECISION +) AS $$ +BEGIN + RETURN QUERY + WITH account AS ( + SELECT location + FROM maevsi_private.account + WHERE id = _account_id + ) + SELECT + e.id AS event_id, + ST_Distance(a.location, e.location_geography) AS distance + FROM + account a, + maevsi.event e + WHERE + ST_DWithin(a.location, e.location_geography, _distance_max * 1000); +END; +$$ LANGUAGE PLPGSQL STRICT STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION maevsi.event_filter_radius_account(UUID, DOUBLE PRECISION) IS 'Returns event locations within a given radius around the location of an account.'; + +GRANT EXECUTE ON FUNCTION maevsi.event_filter_radius_account(UUID, DOUBLE PRECISION) TO maevsi_account; + + +CREATE FUNCTION maevsi.account_location_update( + _account_id UUID, + _latitude DOUBLE PRECISION, + _longitude DOUBLE PRECISION +) +RETURNS VOID AS $$ +BEGIN + UPDATE maevsi_private.account + SET + location = ST_Point(_longitude, _latitude, 4326) + WHERE + id = _account_id; +END; +$$ LANGUAGE PLPGSQL STRICT SECURITY DEFINER; + +COMMENT ON FUNCTION maevsi.account_location_update(UUID, DOUBLE PRECISION, DOUBLE PRECISION) IS 'Updates an account''s location based on latitude and longitude (GPS coordinates).'; + +GRANT EXECUTE ON FUNCTION maevsi.account_location_update(UUID, DOUBLE PRECISION, DOUBLE PRECISION) TO maevsi_account; + + +CREATE FUNCTION maevsi.event_location_update( + _event_id UUID, + _latitude DOUBLE PRECISION, + _longitude DOUBLE PRECISION +) +RETURNS VOID AS $$ +BEGIN + UPDATE maevsi.event + SET + location_geography = ST_Point(_longitude, _latitude, 4326) + WHERE + id = _event_id; +END; +$$ LANGUAGE PLPGSQL STRICT SECURITY DEFINER; + +COMMENT ON FUNCTION maevsi.event_location_update(UUID, DOUBLE PRECISION, DOUBLE PRECISION) IS 'Updates an event''s location based on latitude and longitude (GPS coordinates).'; + +GRANT EXECUTE ON FUNCTION maevsi.event_location_update(UUID, DOUBLE PRECISION, DOUBLE PRECISION) TO maevsi_account; + + +CREATE FUNCTION maevsi.account_location_coordinates( + _account_id UUID +) +RETURNS DOUBLE PRECISION[] AS $$ +DECLARE + _latitude DOUBLE PRECISION; + _longitude DOUBLE PRECISION; +BEGIN + SELECT + ST_Y(location::geometry), + ST_X(location::geometry) + INTO + _latitude, + _longitude + FROM + maevsi_private.account + WHERE + id = _account_id; + + RETURN ARRAY[_latitude, _longitude]; +END; +$$ LANGUAGE PLPGSQL STRICT STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION maevsi.account_location_coordinates(UUID) IS 'Returns an array with latitude and longitude of the account''s current location data'; + +GRANT EXECUTE ON FUNCTION maevsi.account_location_coordinates(UUID) TO maevsi_account; + + +CREATE FUNCTION maevsi.event_location_coordinates( + _event_id UUID +) +RETURNS DOUBLE PRECISION[] AS $$ +DECLARE + _latitude DOUBLE PRECISION; + _longitude DOUBLE PRECISION; +BEGIN + SELECT + ST_Y(location_geography::geometry), + ST_X(location_geography::geometry) + INTO + _latitude, + _longitude + FROM + maevsi.event + WHERE + id = _event_id; + + RETURN ARRAY[_latitude, _longitude]; +END; +$$ LANGUAGE PLPGSQL STRICT STABLE SECURITY DEFINER; + +COMMENT ON FUNCTION maevsi.event_location_coordinates(UUID) IS 'Returns an array with latitude and longitude of the event''s current location data.'; + +GRANT EXECUTE ON FUNCTION maevsi.event_location_coordinates(UUID) TO maevsi_account; + + +COMMIT; diff --git a/src/revert/extension_postgis.sql b/src/revert/extension_postgis.sql index 7e80813b..aa435f50 100644 --- a/src/revert/extension_postgis.sql +++ b/src/revert/extension_postgis.sql @@ -1,5 +1,16 @@ BEGIN; +REVOKE EXECUTE ON FUNCTION + st_srid(geography), + st_geomfromgeojson(text), + st_coorddim(geometry), + st_asgeojson(geography, integer, integer), + postgis_type_name(character varying, integer, boolean), + geometrytype(geography), + geometry(text), + geography(geometry) +FROM maevsi_anonymous, maevsi_account; + DROP EXTENSION postgis; COMMIT; diff --git a/src/revert/index_account_private_location.sql b/src/revert/index_account_private_location.sql new file mode 100644 index 00000000..3ebd791a --- /dev/null +++ b/src/revert/index_account_private_location.sql @@ -0,0 +1,5 @@ +BEGIN; + +DROP INDEX maevsi_private.idx_account_private_location; + +COMMIT; diff --git a/src/revert/index_event_location.sql b/src/revert/index_event_location.sql new file mode 100644 index 00000000..3ad46c6c --- /dev/null +++ b/src/revert/index_event_location.sql @@ -0,0 +1,5 @@ +BEGIN; + +DROP INDEX maevsi.idx_event_location; + +COMMIT; diff --git a/src/revert/test_location.sql b/src/revert/test_location.sql new file mode 100644 index 00000000..67a1d508 --- /dev/null +++ b/src/revert/test_location.sql @@ -0,0 +1,12 @@ +BEGIN; + +DROP FUNCTION maevsi.account_filter_radius_event(UUID, DOUBLE PRECISION); +DROP FUNCTION maevsi.event_filter_radius_account(UUID, DOUBLE PRECISION); + +DROP FUNCTION maevsi.account_location_update(UUID, DOUBLE PRECISION, DOUBLE PRECISION); +DROP FUNCTION maevsi.event_location_update(UUID, DOUBLE PRECISION, DOUBLE PRECISION); + +DROP FUNCTION maevsi.account_location_coordinates(UUID); +DROP FUNCTION maevsi.event_location_coordinates(UUID); + +COMMIT; diff --git a/src/sqitch.plan b/src/sqitch.plan index e3ed1d26..f35d2d03 100644 --- a/src/sqitch.plan +++ b/src/sqitch.plan @@ -15,7 +15,8 @@ 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. table_notification [schema_private] 1970-01-01T00:00:00Z Jonas Thelemann # Notifications that are sent via pg_notify. -table_account_private [schema_private schema_public] 1970-01-01T00:00:00Z Jonas Thelemann # Add private table account. +extension_postgis [schema_public] 1970-01-01T00:00:00Z Jonas Thelemann # Add functions to work with geospatial data. +table_account_private [schema_private extension_postgis] 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_account_block [schema_public table_account_public] 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. @@ -23,7 +24,7 @@ table_event_group [schema_public role_account role_anonymous table_account_publi index_event_group_author_username [table_event_group] 1970-01-01T00:00:00Z Jonas Thelemann # Add an index to the event group table's author_username field. enum_language [schema_public] 1970-01-01T00:00:00Z Jonas Thelemann # Supported ISO 639 language codes. function_language_iso_full_text_search [privilege_execute_revoke schema_public enum_language] 1970-01-01T00:00:00Z Jonas Thelemann # ISO language code to full text search language conversion. -table_event [schema_public table_account_public enum_language enum_event_visibility function_language_iso_full_text_search role_account role_anonymous] 1970-01-01T00:00:00Z Jonas Thelemann # Add table event. +table_event [schema_public table_account_public enum_language extension_postgis enum_event_visibility function_language_iso_full_text_search role_account role_anonymous] 1970-01-01T00:00:00Z Jonas Thelemann # Add table event. function_events_organized [privilege_execute_revoke schema_public function_invoker_account_id table_event role_account role_anonymous] 1970-01-01T00:00:00Z Jonas Thelemann # Add a function that returns all event ids for which the invoker is the author. index_event_author_username [table_event] 1970-01-01T00:00:00Z Jonas Thelemann # Add an index to the event table's username field. enum_invitation_feedback_paper 1970-01-01T00:00:00Z Jonas Thelemann # Possible choices on how to receive a paper invitation: paper, digital. @@ -93,5 +94,7 @@ table_event_recommendation_policy [schema_public table_event_recommendation role table_event_favourite [schema_public table_account_public table_event] 1970-01-01T00:00:00Z Sven Thelemann # A table for the user accounts' favourite events. table_event_favourite_policy [schema_public table_account_public table_event role_account] 1970-01-01T00:00:00Z Sven Thelemann # Policy for table event_favourite. test_account_blocking [schema_test] 1970-01-01T00:00:00Z Sven Thelemann # Test cases for account blocking. -extension_postgis [schema_public] 1970-01-01T00:00:00Z Jonas Thelemann # Add functions to work with geospatial data. 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. +index_account_private_location [schema_private table_account_private] 1970-01-01T00:00:00Z Sven Thelemann # Spacial index on location column in table maevsi_private.account. +index_event_location [schema_public table_event] 1970-01-01T00:00:00Z Sven Thelemann # Spacial index on location_geography column in table maevsi.event. +test_location [schema_public schema_private extension_postgis table_account_private table_event role_anonymous role_account] 1970-01-01T00:00:00Z Sven Thelemann # A collection of location related functions. diff --git a/src/verify/extension_postgis.sql b/src/verify/extension_postgis.sql index 0433a859..74691917 100644 --- a/src/verify/extension_postgis.sql +++ b/src/verify/extension_postgis.sql @@ -1,6 +1,33 @@ BEGIN; SELECT 1/count(*) FROM pg_extension WHERE extname = 'postgis'; -SELECT has_function_privilege('public.ST_DWithin(public.geometry, public.geometry, double precision)', 'EXECUTE'); +SELECT has_function_privilege('ST_DWithin(geometry, geometry, double precision)', 'EXECUTE'); + +SAVEPOINT function_privileges_for_roles; +DO $$ +DECLARE + functions TEXT[] := ARRAY[ + 'geography(geometry)', + 'geometry(TEXT)', + 'geometrytype(GEOGRAPHY)', + 'postgis_type_name(CHARACTER VARYING, INTEGER, BOOLEAN)', + 'st_asgeojson(GEOGRAPHY, INTEGER, INTEGER)', + 'st_coorddim(GEOMETRY)', + 'st_geomfromgeojson(TEXT)', + 'st_srid(GEOGRAPHY)' + ]; + roles TEXT[] := ARRAY['maevsi_account', 'maevsi_anonymous']; + function TEXT; + role TEXT; +BEGIN + FOREACH role IN ARRAY roles LOOP + FOREACH function IN ARRAY functions LOOP + IF NOT (SELECT pg_catalog.has_function_privilege(role, function, 'EXECUTE')) THEN + RAISE EXCEPTION 'Test function_privileges_for_roles failed: % does not have EXECUTE privilege on function %', role, function; + END IF; + END LOOP; + END LOOP; +END $$; +ROLLBACK TO SAVEPOINT function_privileges_for_roles; ROLLBACK; diff --git a/src/verify/index_account_private_location.sql b/src/verify/index_account_private_location.sql new file mode 100644 index 00000000..16c05d09 --- /dev/null +++ b/src/verify/index_account_private_location.sql @@ -0,0 +1,9 @@ +BEGIN; + +SELECT 1/COUNT(*) +FROM pg_class c +JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE c.relname = 'idx_account_private_location' +AND n.nspname = 'maevsi_private'; + +ROLLBACK; diff --git a/src/verify/index_event_location.sql b/src/verify/index_event_location.sql new file mode 100644 index 00000000..90251554 --- /dev/null +++ b/src/verify/index_event_location.sql @@ -0,0 +1,9 @@ +BEGIN; + +SELECT 1/COUNT(*) +FROM pg_class c +JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE c.relname = 'idx_event_location' +AND n.nspname = 'maevsi'; + +ROLLBACK; diff --git a/src/verify/table_account_private.sql b/src/verify/table_account_private.sql index 0e5520e0..d18b3b07 100644 --- a/src/verify/table_account_private.sql +++ b/src/verify/table_account_private.sql @@ -7,6 +7,7 @@ SELECT id, email_address_verification, email_address_verification_valid_until, last_activity, + location, password_hash, password_reset_verification, password_reset_verification_valid_until, diff --git a/src/verify/table_event.sql b/src/verify/table_event.sql index 58f89ce2..cf4ace85 100644 --- a/src/verify/table_event.sql +++ b/src/verify/table_event.sql @@ -10,6 +10,7 @@ SELECT id, is_in_person, is_remote, location, + location_geography, name, slug, start, diff --git a/src/verify/test_location.sql b/src/verify/test_location.sql new file mode 100644 index 00000000..bde9573e --- /dev/null +++ b/src/verify/test_location.sql @@ -0,0 +1,73 @@ +BEGIN; + +DO $$ +DECLARE + _account_id UUID; + _event_id UUID; + _coordinates DOUBLE PRECISION[]; + _id UUID; +BEGIN + -- Register account + _account_id := maevsi.account_registration('username', 'email@example.com', 'password', 'en'); + PERFORM maevsi.account_email_address_verification( + (SELECT email_address_verification FROM maevsi_private.account WHERE id = _account_id) + ); + + -- Set account-specific context + SET LOCAL role = 'maevsi_account'; + EXECUTE 'SET LOCAL jwt.claims.account_id = ''' || _account_id || ''''; + + -- Create event + INSERT INTO maevsi.event(author_account_id, name, slug, start, visibility) + VALUES (_account_id, 'event', 'event', CURRENT_TIMESTAMP, 'public'::maevsi.event_visibility) + RETURNING id INTO _event_id; + + -- Update and validate account location + PERFORM maevsi.account_location_update(_account_id, 51.304, 9.476); -- Somewhere in Kassel + _coordinates := maevsi.account_location_coordinates(_account_id); + + IF NOT (round(_coordinates[1]::numeric, 3) = 51.304 AND round(_coordinates[2]::numeric, 3) = 9.476) THEN + RAISE EXCEPTION 'Wrong account coordinates'; + END IF; + + -- Update and validate event location + PERFORM maevsi.event_location_update(_event_id, 50.113, 8.650); -- Somewhere in Frankfurt + _coordinates := maevsi.event_location_coordinates(_event_id); + + IF NOT (round(_coordinates[1]::numeric, 3) = 50.113 AND round(_coordinates[2]::numeric, 3) = 8.650) THEN + RAISE EXCEPTION 'Wrong event coordinates'; + END IF; + + -- Test event filtering by radius from account + SELECT event_id INTO _id + FROM maevsi.maevsi.event_filter_radius_account(_account_id, 100); + + IF _id IS NOT NULL THEN + RAISE EXCEPTION 'Function `event_filter_radius_account` with radius 100 km should have returned an empty result'; + END IF; + + SELECT event_id INTO _id + FROM maevsi.maevsi.event_filter_radius_account(_account_id, 250); + + IF _id != _event_id THEN + RAISE EXCEPTION 'Function `event_filter_radius_account` with radius 250 km should have returned `_event_id`'; + END IF; + + -- Test account filtering by radius from event + SELECT account_id INTO _id + FROM maevsi.maevsi.account_filter_radius_event(_event_id, 100); + + IF _id IS NOT NULL THEN + RAISE EXCEPTION 'Function `account_filter_radius_event` with radius 100 km should have returned an empty result'; + END IF; + + SELECT account_id INTO _id + FROM maevsi.maevsi.account_filter_radius_event(_event_id, 250); + + IF _id != _account_id THEN + RAISE EXCEPTION 'Function `account_filter_radius_event` with radius 250 km should have returned `_account_id`'; + END IF; +END; +$$ LANGUAGE plpgsql; + +ROLLBACK; diff --git a/test/schema/schema.definition.sql b/test/schema/schema.definition.sql index c776e3e2..e526f526 100644 --- a/test/schema/schema.definition.sql +++ b/test/schema/schema.definition.sql @@ -348,6 +348,103 @@ ALTER FUNCTION maevsi.account_email_address_verification(code uuid) OWNER TO pos COMMENT ON FUNCTION maevsi.account_email_address_verification(code uuid) IS 'Sets the account''s email address verification code to `NULL` for which the email address verification code equals the one passed and is up to date.'; +-- +-- Name: account_filter_radius_event(uuid, double precision); Type: FUNCTION; Schema: maevsi; Owner: postgres +-- + +CREATE FUNCTION maevsi.account_filter_radius_event(_event_id uuid, _distance_max double precision) RETURNS TABLE(account_id uuid, distance double precision) + LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER + AS $$ +BEGIN + RETURN QUERY + WITH event AS ( + SELECT location_geography + FROM maevsi.event + WHERE id = _event_id + ) + SELECT + a.id AS account_id, + ST_Distance(e.location_geography, a.location) AS distance + FROM + event e, + maevsi_private.account a + WHERE + ST_DWithin(e.location_geography, a.location, _distance_max * 1000); +END; +$$; + + +ALTER FUNCTION maevsi.account_filter_radius_event(_event_id uuid, _distance_max double precision) OWNER TO postgres; + +-- +-- Name: FUNCTION account_filter_radius_event(_event_id uuid, _distance_max double precision); Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON FUNCTION maevsi.account_filter_radius_event(_event_id uuid, _distance_max double precision) IS 'Returns account locations within a given radius around the location of an event.'; + + +-- +-- Name: account_location_coordinates(uuid); Type: FUNCTION; Schema: maevsi; Owner: postgres +-- + +CREATE FUNCTION maevsi.account_location_coordinates(_account_id uuid) RETURNS double precision[] + LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER + AS $$ +DECLARE + _latitude DOUBLE PRECISION; + _longitude DOUBLE PRECISION; +BEGIN + SELECT + ST_Y(location::geometry), + ST_X(location::geometry) + INTO + _latitude, + _longitude + FROM + maevsi_private.account + WHERE + id = _account_id; + + RETURN ARRAY[_latitude, _longitude]; +END; +$$; + + +ALTER FUNCTION maevsi.account_location_coordinates(_account_id uuid) OWNER TO postgres; + +-- +-- Name: FUNCTION account_location_coordinates(_account_id uuid); Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON FUNCTION maevsi.account_location_coordinates(_account_id uuid) IS 'Returns an array with latitude and longitude of the account''s current location data'; + + +-- +-- Name: account_location_update(uuid, double precision, double precision); Type: FUNCTION; Schema: maevsi; Owner: postgres +-- + +CREATE FUNCTION maevsi.account_location_update(_account_id uuid, _latitude double precision, _longitude double precision) RETURNS void + LANGUAGE plpgsql STRICT SECURITY DEFINER + AS $$ +BEGIN + UPDATE maevsi_private.account + SET + location = ST_Point(_longitude, _latitude, 4326) + WHERE + id = _account_id; +END; +$$; + + +ALTER FUNCTION maevsi.account_location_update(_account_id uuid, _latitude double precision, _longitude double precision) OWNER TO postgres; + +-- +-- Name: FUNCTION account_location_update(_account_id uuid, _latitude double precision, _longitude double precision); Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON FUNCTION maevsi.account_location_update(_account_id uuid, _latitude double precision, _longitude double precision) IS 'Updates an account''s location based on latitude and longitude (GPS coordinates).'; + + -- -- Name: account_password_change(text, text); Type: FUNCTION; Schema: maevsi; Owner: postgres -- @@ -754,6 +851,7 @@ CREATE TABLE maevsi.event ( is_remote boolean, language maevsi.language, location text, + location_geography public.geography(Point,4326), name text NOT NULL, slug text NOT NULL, start timestamp with time zone NOT NULL, @@ -843,6 +941,13 @@ COMMENT ON COLUMN maevsi.event.is_remote IS 'Indicates whether the event takes p COMMENT ON COLUMN maevsi.event.location IS 'The event''s location as it can be shown on a map.'; +-- +-- Name: COLUMN event.location_geography; Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON COLUMN maevsi.event.location_geography IS 'The event''s geographic location.'; + + -- -- Name: COLUMN event.name; Type: COMMENT; Schema: maevsi; Owner: postgres -- @@ -936,6 +1041,41 @@ ALTER FUNCTION maevsi.event_delete(id uuid, password text) OWNER TO postgres; COMMENT ON FUNCTION maevsi.event_delete(id uuid, password text) IS 'Allows to delete an event.'; +-- +-- Name: event_filter_radius_account(uuid, double precision); Type: FUNCTION; Schema: maevsi; Owner: postgres +-- + +CREATE FUNCTION maevsi.event_filter_radius_account(_account_id uuid, _distance_max double precision) RETURNS TABLE(event_id uuid, distance double precision) + LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER + AS $$ +BEGIN + RETURN QUERY + WITH account AS ( + SELECT location + FROM maevsi_private.account + WHERE id = _account_id + ) + SELECT + e.id AS event_id, + ST_Distance(a.location, e.location_geography) AS distance + FROM + account a, + maevsi.event e + WHERE + ST_DWithin(a.location, e.location_geography, _distance_max * 1000); +END; +$$; + + +ALTER FUNCTION maevsi.event_filter_radius_account(_account_id uuid, _distance_max double precision) OWNER TO postgres; + +-- +-- Name: FUNCTION event_filter_radius_account(_account_id uuid, _distance_max double precision); Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON FUNCTION maevsi.event_filter_radius_account(_account_id uuid, _distance_max double precision) IS 'Returns event locations within a given radius around the location of an account.'; + + -- -- Name: event_invitee_count_maximum(uuid); Type: FUNCTION; Schema: maevsi; Owner: postgres -- @@ -1006,6 +1146,68 @@ ALTER FUNCTION maevsi.event_is_existing(author_account_id uuid, slug text) OWNER COMMENT ON FUNCTION maevsi.event_is_existing(author_account_id uuid, slug text) IS 'Shows if an event exists.'; +-- +-- Name: event_location_coordinates(uuid); Type: FUNCTION; Schema: maevsi; Owner: postgres +-- + +CREATE FUNCTION maevsi.event_location_coordinates(_event_id uuid) RETURNS double precision[] + LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER + AS $$ +DECLARE + _latitude DOUBLE PRECISION; + _longitude DOUBLE PRECISION; +BEGIN + SELECT + ST_Y(location_geography::geometry), + ST_X(location_geography::geometry) + INTO + _latitude, + _longitude + FROM + maevsi.event + WHERE + id = _event_id; + + RETURN ARRAY[_latitude, _longitude]; +END; +$$; + + +ALTER FUNCTION maevsi.event_location_coordinates(_event_id uuid) OWNER TO postgres; + +-- +-- Name: FUNCTION event_location_coordinates(_event_id uuid); Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON FUNCTION maevsi.event_location_coordinates(_event_id uuid) IS 'Returns an array with latitude and longitude of the event''s current location data.'; + + +-- +-- Name: event_location_update(uuid, double precision, double precision); Type: FUNCTION; Schema: maevsi; Owner: postgres +-- + +CREATE FUNCTION maevsi.event_location_update(_event_id uuid, _latitude double precision, _longitude double precision) RETURNS void + LANGUAGE plpgsql STRICT SECURITY DEFINER + AS $$ +BEGIN + UPDATE maevsi.event + SET + location_geography = ST_Point(_longitude, _latitude, 4326) + WHERE + id = _event_id; +END; +$$; + + +ALTER FUNCTION maevsi.event_location_update(_event_id uuid, _latitude double precision, _longitude double precision) OWNER TO postgres; + +-- +-- Name: FUNCTION event_location_update(_event_id uuid, _latitude double precision, _longitude double precision); Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON FUNCTION maevsi.event_location_update(_event_id uuid, _latitude double precision, _longitude double precision) IS 'Updates an event''s location based on latitude and longitude (GPS coordinates).'; + + -- -- Name: event_search(text, maevsi.language); Type: FUNCTION; Schema: maevsi; Owner: postgres -- @@ -3346,6 +3548,7 @@ CREATE TABLE maevsi_private.account ( email_address text NOT NULL, email_address_verification uuid DEFAULT gen_random_uuid(), email_address_verification_valid_until timestamp with time zone, + location public.geography(Point,4326), password_hash text NOT NULL, password_reset_verification uuid, password_reset_verification_valid_until timestamp with time zone, @@ -3400,6 +3603,13 @@ COMMENT ON COLUMN maevsi_private.account.email_address_verification IS 'The UUID COMMENT ON COLUMN maevsi_private.account.email_address_verification_valid_until IS 'The timestamp until which an email address verification is valid.'; +-- +-- Name: COLUMN account.location; Type: COMMENT; Schema: maevsi_private; Owner: postgres +-- + +COMMENT ON COLUMN maevsi_private.account.location IS 'The account''s geometric location.'; + + -- -- Name: COLUMN account.password_hash; Type: COMMENT; Schema: maevsi_private; Owner: postgres -- @@ -4541,6 +4751,20 @@ CREATE INDEX idx_event_grouping_event_id ON maevsi.event_grouping USING btree (e COMMENT ON INDEX maevsi.idx_event_grouping_event_id IS 'Speeds up reverse foreign key lookups.'; +-- +-- Name: idx_event_location; Type: INDEX; Schema: maevsi; Owner: postgres +-- + +CREATE INDEX idx_event_location ON maevsi.event USING gist (location_geography); + + +-- +-- Name: INDEX idx_event_location; Type: COMMENT; Schema: maevsi; Owner: postgres +-- + +COMMENT ON INDEX maevsi.idx_event_location IS 'Spatial index on column location in maevsi.event.'; + + -- -- Name: idx_event_search_vector; Type: INDEX; Schema: maevsi; Owner: postgres -- @@ -4576,6 +4800,20 @@ CREATE INDEX idx_invitation_event_id ON maevsi.invitation USING btree (event_id) COMMENT ON INDEX maevsi.idx_invitation_event_id IS 'Speeds up reverse foreign key lookups.'; +-- +-- Name: idx_account_private_location; Type: INDEX; Schema: maevsi_private; Owner: postgres +-- + +CREATE INDEX idx_account_private_location ON maevsi_private.account USING gist (location); + + +-- +-- Name: INDEX idx_account_private_location; Type: COMMENT; Schema: maevsi_private; Owner: postgres +-- + +COMMENT ON INDEX maevsi_private.idx_account_private_location IS 'Spatial index on column location in maevsi_private.account.'; + + -- -- Name: invitation maevsi_invitation_update; Type: TRIGGER; Schema: maevsi; Owner: postgres -- @@ -5749,6 +5987,8 @@ REVOKE ALL ON FUNCTION public.bytea(public.geometry) FROM PUBLIC; -- REVOKE ALL ON FUNCTION public.geography(public.geometry) FROM PUBLIC; +GRANT ALL ON FUNCTION public.geography(public.geometry) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.geography(public.geometry) TO maevsi_account; -- @@ -5826,6 +6066,8 @@ REVOKE ALL ON FUNCTION public.geometry(polygon) FROM PUBLIC; -- REVOKE ALL ON FUNCTION public.geometry(text) FROM PUBLIC; +GRANT ALL ON FUNCTION public.geometry(text) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.geometry(text) TO maevsi_account; -- @@ -5845,6 +6087,30 @@ GRANT ALL ON FUNCTION maevsi.account_email_address_verification(code uuid) TO ma GRANT ALL ON FUNCTION maevsi.account_email_address_verification(code uuid) TO maevsi_anonymous; +-- +-- Name: FUNCTION account_filter_radius_event(_event_id uuid, _distance_max double precision); Type: ACL; Schema: maevsi; Owner: postgres +-- + +REVOKE ALL ON FUNCTION maevsi.account_filter_radius_event(_event_id uuid, _distance_max double precision) FROM PUBLIC; +GRANT ALL ON FUNCTION maevsi.account_filter_radius_event(_event_id uuid, _distance_max double precision) TO maevsi_account; + + +-- +-- Name: FUNCTION account_location_coordinates(_account_id uuid); Type: ACL; Schema: maevsi; Owner: postgres +-- + +REVOKE ALL ON FUNCTION maevsi.account_location_coordinates(_account_id uuid) FROM PUBLIC; +GRANT ALL ON FUNCTION maevsi.account_location_coordinates(_account_id uuid) TO maevsi_account; + + +-- +-- Name: FUNCTION account_location_update(_account_id uuid, _latitude double precision, _longitude double precision); Type: ACL; Schema: maevsi; Owner: postgres +-- + +REVOKE ALL ON FUNCTION maevsi.account_location_update(_account_id uuid, _latitude double precision, _longitude double precision) FROM PUBLIC; +GRANT ALL ON FUNCTION maevsi.account_location_update(_account_id uuid, _latitude double precision, _longitude double precision) TO maevsi_account; + + -- -- Name: FUNCTION account_password_change(password_current text, password_new text); Type: ACL; Schema: maevsi; Owner: postgres -- @@ -5929,6 +6195,14 @@ REVOKE ALL ON FUNCTION maevsi.event_delete(id uuid, password text) FROM PUBLIC; GRANT ALL ON FUNCTION maevsi.event_delete(id uuid, password text) TO maevsi_account; +-- +-- Name: FUNCTION event_filter_radius_account(_account_id uuid, _distance_max double precision); Type: ACL; Schema: maevsi; Owner: postgres +-- + +REVOKE ALL ON FUNCTION maevsi.event_filter_radius_account(_account_id uuid, _distance_max double precision) FROM PUBLIC; +GRANT ALL ON FUNCTION maevsi.event_filter_radius_account(_account_id uuid, _distance_max double precision) TO maevsi_account; + + -- -- Name: FUNCTION event_invitee_count_maximum(event_id uuid); Type: ACL; Schema: maevsi; Owner: postgres -- @@ -5947,6 +6221,22 @@ GRANT ALL ON FUNCTION maevsi.event_is_existing(author_account_id uuid, slug text GRANT ALL ON FUNCTION maevsi.event_is_existing(author_account_id uuid, slug text) TO maevsi_anonymous; +-- +-- Name: FUNCTION event_location_coordinates(_event_id uuid); Type: ACL; Schema: maevsi; Owner: postgres +-- + +REVOKE ALL ON FUNCTION maevsi.event_location_coordinates(_event_id uuid) FROM PUBLIC; +GRANT ALL ON FUNCTION maevsi.event_location_coordinates(_event_id uuid) TO maevsi_account; + + +-- +-- Name: FUNCTION event_location_update(_event_id uuid, _latitude double precision, _longitude double precision); Type: ACL; Schema: maevsi; Owner: postgres +-- + +REVOKE ALL ON FUNCTION maevsi.event_location_update(_event_id uuid, _latitude double precision, _longitude double precision) FROM PUBLIC; +GRANT ALL ON FUNCTION maevsi.event_location_update(_event_id uuid, _latitude double precision, _longitude double precision) TO maevsi_account; + + -- -- Name: FUNCTION event_search(query text, language maevsi.language); Type: ACL; Schema: maevsi; Owner: postgres -- @@ -7442,6 +7732,8 @@ REVOKE ALL ON FUNCTION public.geometry_within_nd(public.geometry, public.geometr -- REVOKE ALL ON FUNCTION public.geometrytype(public.geography) FROM PUBLIC; +GRANT ALL ON FUNCTION public.geometrytype(public.geography) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.geometrytype(public.geography) TO maevsi_account; -- @@ -8212,6 +8504,8 @@ REVOKE ALL ON FUNCTION public.postgis_transform_pipeline_geometry(geom public.ge -- REVOKE ALL ON FUNCTION public.postgis_type_name(geomname character varying, coord_dimension integer, use_new_name boolean) FROM PUBLIC; +GRANT ALL ON FUNCTION public.postgis_type_name(geomname character varying, coord_dimension integer, use_new_name boolean) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.postgis_type_name(geomname character varying, coord_dimension integer, use_new_name boolean) TO maevsi_account; -- @@ -8506,6 +8800,8 @@ REVOKE ALL ON FUNCTION public.st_asgeojson(text) FROM PUBLIC; -- REVOKE ALL ON FUNCTION public.st_asgeojson(geog public.geography, maxdecimaldigits integer, options integer) FROM PUBLIC; +GRANT ALL ON FUNCTION public.st_asgeojson(geog public.geography, maxdecimaldigits integer, options integer) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.st_asgeojson(geog public.geography, maxdecimaldigits integer, options integer) TO maevsi_account; -- @@ -9003,6 +9299,8 @@ REVOKE ALL ON FUNCTION public.st_convexhull(public.geometry) FROM PUBLIC; -- REVOKE ALL ON FUNCTION public.st_coorddim(geometry public.geometry) FROM PUBLIC; +GRANT ALL ON FUNCTION public.st_coorddim(geometry public.geometry) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.st_coorddim(geometry public.geometry) TO maevsi_account; -- @@ -9612,6 +9910,8 @@ REVOKE ALL ON FUNCTION public.st_geomfromgeojson(jsonb) FROM PUBLIC; -- REVOKE ALL ON FUNCTION public.st_geomfromgeojson(text) FROM PUBLIC; +GRANT ALL ON FUNCTION public.st_geomfromgeojson(text) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.st_geomfromgeojson(text) TO maevsi_account; -- @@ -11110,6 +11410,8 @@ REVOKE ALL ON FUNCTION public.st_squaregrid(size double precision, bounds public -- REVOKE ALL ON FUNCTION public.st_srid(geog public.geography) FROM PUBLIC; +GRANT ALL ON FUNCTION public.st_srid(geog public.geography) TO maevsi_anonymous; +GRANT ALL ON FUNCTION public.st_srid(geog public.geography) TO maevsi_account; --