diff --git a/SQL Scripts/functions/accept_join_request_rpc.sql b/SQL Scripts/functions/accept_join_request_rpc.sql index d257316..a02a0bd 100644 --- a/SQL Scripts/functions/accept_join_request_rpc.sql +++ b/SQL Scripts/functions/accept_join_request_rpc.sql @@ -29,6 +29,9 @@ BEGIN -- Delete the request DELETE FROM public.join_requests WHERE id = _request_id; + -- Check for assign_all contexts + PERFORM do_assign_all_check_for_user(_project_id, _request.user_id); + RETURN TRUE; END $body$ LANGUAGE plpgsql SECURITY DEFINER; \ No newline at end of file diff --git a/SQL Scripts/functions/accept_project_invite.sql b/SQL Scripts/functions/accept_project_invite.sql index 138b4c9..4257bf9 100644 --- a/SQL Scripts/functions/accept_project_invite.sql +++ b/SQL Scripts/functions/accept_project_invite.sql @@ -6,6 +6,8 @@ BEGIN INSERT INTO public.group_users (group_type, user_id, type_id) VALUES ('project', auth.uid(), NEW.project_group_id); + + PERFORM do_assign_all_check_for_user(NEW.project_id, auth.uid()); END IF; RETURN NEW; END; diff --git a/SQL Scripts/functions/check_action_policy_layer_from_context.sql b/SQL Scripts/functions/check_action_policy_layer_from_context.sql index 75c5a59..b98341b 100644 --- a/SQL Scripts/functions/check_action_policy_layer_from_context.sql +++ b/SQL Scripts/functions/check_action_policy_layer_from_context.sql @@ -8,7 +8,7 @@ BEGIN _exists = EXISTS(SELECT 1 FROM public.profiles pr - INNER JOIN public.layer_context lc ON lc.context_id = $4 AND lc.is_active_layer = TRUE + INNER JOIN public.layer_contexts lc ON lc.context_id = $4 AND lc.is_active_layer = TRUE INNER JOIN public.context_users cu ON cu.context_id = lc.context_id AND cu.user_id = $1 INNER JOIN public.roles r ON cu.role_id = r.id INNER JOIN public.role_policies rp ON r.id = rp.role_id diff --git a/SQL Scripts/functions/do_assign_all_check_for_user.sql b/SQL Scripts/functions/do_assign_all_check_for_user.sql new file mode 100644 index 0000000..d36ca36 --- /dev/null +++ b/SQL Scripts/functions/do_assign_all_check_for_user.sql @@ -0,0 +1,29 @@ +CREATE +OR REPLACE FUNCTION do_assign_all_check_for_user (_project_id UUID, _user_id UUID) RETURNS VOID AS $body$ +DECLARE + _context public.contexts % rowtype; + _role_id uuid; +BEGIN + -- Get the default group + SELECT g.role_id INTO _role_id + FROM public.default_groups g + WHERE g.group_type = 'layer' AND g.is_default = TRUE; + + -- Iterate all context in the project and check for the assign_all_members flag + FOR _context IN SELECT * FROM public.contexts c WHERE c.project_id = _project_id + LOOP + IF _context.assign_all_members IS TRUE + THEN + IF NOT EXISTS(SELECT 1 FROM public.context_users cu WHERE cu.context_id = _context.id AND cu.user_id = _user_id) + THEN + INSERT INTO public.context_users + (context_id, user_id, role_id) + VALUES + (_context.id, _user_id, _role_id); + END IF; + END IF; + END LOOP; + + RETURN; +END +$body$ LANGUAGE plpgsql SECURITY DEFINER; \ No newline at end of file diff --git a/SQL Scripts/functions/join_project_rpc.sql b/SQL Scripts/functions/join_project_rpc.sql index ae88ddb..4d9d908 100644 --- a/SQL Scripts/functions/join_project_rpc.sql +++ b/SQL Scripts/functions/join_project_rpc.sql @@ -2,6 +2,7 @@ CREATE OR REPLACE FUNCTION join_project_rpc (_project_id UUID) RETURNS BOOLEAN AS $body$ DECLARE _is_open_join BOOLEAN; + _context BOOLEAN; _project_group_id uuid; BEGIN @@ -20,6 +21,9 @@ BEGIN VALUES ('project', auth.uid(), _project_group_id); + -- Check for assign_all contexts + PERFORM do_assign_all_check_for_user(_project_id, auth.uid()); + RETURN TRUE; END $body$ LANGUAGE plpgsql SECURITY DEFINER; \ No newline at end of file diff --git a/SQL Scripts/functions/set_context_to_all_members.sql b/SQL Scripts/functions/set_context_to_all_members.sql new file mode 100644 index 0000000..227b490 --- /dev/null +++ b/SQL Scripts/functions/set_context_to_all_members.sql @@ -0,0 +1,60 @@ +CREATE +OR REPLACE FUNCTION set_context_to_all_members ( + _context_id uuid, + _is_all_members BOOLEAN +) RETURNS BOOLEAN AS $body$ +DECLARE + _project_id uuid; + _project_group_id uuid; + _role_id uuid; + _record RECORD; +BEGIN + + -- Find the project for this context + SELECT p.id INTO _project_id FROM public.projects p + INNER JOIN public.contexts c ON c.id = _context_id + WHERE p.id = c.project_id; + + -- Check user has the right policy + IF NOT (check_action_policy_organization(auth.uid(), 'contexts', 'UPDATE') + OR check_action_policy_project(auth.uid(), 'contexts', 'INSERT', _project_id)) + THEN + RETURN FALSE; + END IF; + + -- Update the context + UPDATE public.contexts c + SET assign_all_members = _is_all_members + WHERE c.id = _context_id; + + -- If we are setting assign_all_members to TRUE + IF _is_all_members + THEN + + -- Get the default group + SELECT g.role_id INTO _role_id + FROM public.default_groups g + WHERE g.group_type = 'layer' AND g.is_default = TRUE; + + -- Get the project group + SELECT pg.id INTO _project_group_id + FROM public.project_groups pg + WHERE pg.project_id = _project_id AND pg.is_default = TRUE; + + -- Iterate all team members and add to context + FOR _record IN SELECT * + FROM public.group_users + WHERE group_type = 'project' AND type_id = _project_group_id + LOOP + IF NOT EXISTS(SELECT 1 FROM public.context_users cu WHERE cu.context_id = _context_id AND cu.user_id = _record.user_id) + THEN + INSERT INTO public.context_users + (context_id, user_id, role_id) + VALUES(_context_id, _record.user_id, _role_id); + END IF; + END LOOP; + END IF; + + RETURN TRUE; +END +$body$ LANGUAGE plpgsql SECURITY DEFINER; \ No newline at end of file diff --git a/SQL Scripts/tables/contexts.sql b/SQL Scripts/tables/contexts.sql index 461ac83..d782d12 100644 --- a/SQL Scripts/tables/contexts.sql +++ b/SQL Scripts/tables/contexts.sql @@ -40,4 +40,8 @@ ADD COLUMN description VARCHAR; -- Changes 1/25/24 -- ALTER TABLE public.contexts -ADD COLUMN is_project_default BOOLEAN DEFAULT FALSE; \ No newline at end of file +ADD COLUMN is_project_default BOOLEAN DEFAULT FALSE; + +-- Changes 11/26/24 +ALTER TABLE public.contexts +ADD COLUMN assign_all_members BOOLEAN DEFAULT FALSE; \ No newline at end of file diff --git a/jest/tests/projects.test.ts b/jest/tests/projects.test.ts index 912001d..0c0e529 100644 --- a/jest/tests/projects.test.ts +++ b/jest/tests/projects.test.ts @@ -953,7 +953,7 @@ async function processInvite( } } - // console.log("Invite Error: ", result.error); + console.log('Invite Error: ', result.error); return false; } diff --git a/package-lock.json b/package-lock.json index aecf4dd..812ad75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1848,20 +1848,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/typical": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", @@ -2319,8 +2305,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "ajv": { "version": "6.12.6", @@ -3264,8 +3249,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "dev": true, - "requires": {} + "dev": true }, "type": { "version": "1.2.0", @@ -3295,13 +3279,6 @@ "is-typedarray": "^1.0.0" } }, - "typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "peer": true - }, "typical": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", diff --git a/supabase/config.toml b/supabase/config.toml index da1d5b7..07e47e8 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -74,3 +74,6 @@ redirect_uri = "" # Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, # or any other third-party OIDC providers. url = "" + +[analytics] +enabled = false diff --git a/supabase/migrations/20241202140529_assign_all_to_context_support.sql b/supabase/migrations/20241202140529_assign_all_to_context_support.sql new file mode 100644 index 0000000..7c65b6b --- /dev/null +++ b/supabase/migrations/20241202140529_assign_all_to_context_support.sql @@ -0,0 +1,250 @@ +alter table "public"."contexts" add column "assign_all_members" boolean default false; + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.do_assign_all_check_for_user(_project_id uuid, _user_id uuid) + RETURNS void + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +DECLARE + _context public.contexts % rowtype; + _role_id uuid; +BEGIN + -- Get the default group + SELECT g.role_id INTO _role_id + FROM public.default_groups g + WHERE g.group_type = 'layer' AND g.is_default = TRUE; + + -- Iterate all context in the project and check for the assign_all_members flag + FOR _context IN SELECT * FROM public.contexts c WHERE c.project_id = _project_id + LOOP + IF _context.assign_all_members IS TRUE + THEN + IF NOT EXISTS(SELECT 1 FROM public.context_users cu WHERE cu.context_id = _context.id AND cu.user_id = _user_id) + THEN + INSERT INTO public.context_users + (context_id, user_id, role_id) + VALUES + (_context.id, _user_id, _role_id); + END IF; + END IF; + END LOOP; + + RETURN; +END +$function$ +; + +CREATE OR REPLACE FUNCTION public.set_context_to_all_members(_context_id uuid, _is_all_members boolean) + RETURNS boolean + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +DECLARE + _project_id uuid; + _project_group_id uuid; + _role_id uuid; + _record RECORD; +BEGIN + + -- Find the project for this context + SELECT p.id INTO _project_id FROM public.projects p + INNER JOIN public.contexts c ON c.id = _context_id + WHERE p.id = c.project_id; + + -- Check user has the right policy + IF NOT (check_action_policy_organization(auth.uid(), 'contexts', 'UPDATE') + OR check_action_policy_project(auth.uid(), 'contexts', 'INSERT', _project_id)) + THEN + RETURN FALSE; + END IF; + + -- Update the context + UPDATE public.contexts c + SET assign_all_members = _is_all_members + WHERE c.id = _context_id; + + -- If we are setting assign_all_members to TRUE + IF _is_all_members + THEN + + -- Get the default group + SELECT g.role_id INTO _role_id + FROM public.default_groups g + WHERE g.group_type = 'layer' AND g.is_default = TRUE; + + -- Get the project group + SELECT pg.id INTO _project_group_id + FROM public.project_groups pg + WHERE pg.project_id = _project_id AND pg.is_default = TRUE; + + -- Iterate all team members and add to context + FOR _record IN SELECT * + FROM public.group_users + WHERE group_type = 'project' AND type_id = _project_group_id + LOOP + IF NOT EXISTS(SELECT 1 FROM public.context_users cu WHERE cu.context_id = _context_id AND cu.user_id = _record.user_id) + THEN + INSERT INTO public.context_users + (context_id, user_id, role_id) + VALUES(_context_id, _record.user_id, _role_id); + END IF; + END LOOP; + END IF; + + RETURN TRUE; +END +$function$ +; + +CREATE OR REPLACE FUNCTION public.update_project_documents_sort_rpc(_project_id uuid, _document_ids uuid[]) + RETURNS boolean + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +DECLARE + _current_index integer = 0; + _document_id uuid; +BEGIN + -- Check project policy that project documents can be updated by this user + IF NOT (check_action_policy_organization(auth.uid(), 'project_documents', 'UPDATE') + OR check_action_policy_project(auth.uid(), 'project_documents', 'UPDATE', _project_id)) + THEN + RETURN FALSE; + END IF; + + FOREACH _document_id IN ARRAY _document_ids + LOOP + + UPDATE public.project_documents pd + SET sort = _current_index + WHERE pd.document_id = _document_id + AND pd.project_id = _project_id; + + _current_index := _current_index + 1; + + END LOOP; + + RETURN TRUE; +END +$function$ +; + +CREATE OR REPLACE FUNCTION public.accept_join_request_rpc(_project_id uuid, _request_id uuid) + RETURNS boolean + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +DECLARE + _default_group_id uuid; + _request public.join_requests % rowtype; +BEGIN + -- Check project policy that contexts can be updated by this user + IF NOT (check_action_policy_organization(auth.uid(), 'projects', 'UPDATE') + OR check_action_policy_project(auth.uid(), 'projects', 'UPDATE', _project_id)) + THEN + RETURN FALSE; + END IF; + + -- Get the request + SELECT * INTO _request FROM public.join_requests jr WHERE jr.id = _request_id LIMIT 1; + + -- Get the group id + SELECT g.id INTO _default_group_id FROM public.project_groups g WHERE g.project_id = _project_id AND g.is_default = TRUE; + + -- Add the user to the project + INSERT INTO public.group_users + (group_type, type_id, user_id) + VALUES('project', _default_group_id, _request.user_id); + + -- Delete the request + DELETE FROM public.join_requests WHERE id = _request_id; + + -- Check for assign_all contexts + PERFORM do_assign_all_check_for_user(_project_id, _request.user_id); + + RETURN TRUE; +END +$function$ +; + +CREATE OR REPLACE FUNCTION public.accept_project_invite() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +BEGIN + IF NEW.accepted IS TRUE THEN + INSERT INTO public.group_users + (group_type, user_id, type_id) + VALUES ('project', auth.uid(), NEW.project_group_id); + + PERFORM do_assign_all_check_for_user(NEW.project_id, auth.uid()); + END IF; + RETURN NEW; +END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.check_action_policy_layer_from_context(user_id uuid, table_name character varying, operation operation_types, context_id uuid) + RETURNS boolean + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +DECLARE + _exists BOOLEAN; +BEGIN + _exists = EXISTS(SELECT 1 + + FROM public.profiles pr + INNER JOIN public.layer_contexts lc ON lc.context_id = $4 AND lc.is_active_layer = TRUE + INNER JOIN public.context_users cu ON cu.context_id = lc.context_id AND cu.user_id = $1 + INNER JOIN public.roles r ON cu.role_id = r.id + INNER JOIN public.role_policies rp ON r.id = rp.role_id + INNER JOIN public.policies p ON rp.policy_id = p.id + + WHERE p.table_name = $2 + AND p.operation = $3); + -- RAISE LOG 'Policy for layer from context % is %', $4, _exists; + + RETURN _exists; +END; +$function$ +; + +CREATE OR REPLACE FUNCTION public.join_project_rpc(_project_id uuid) + RETURNS boolean + LANGUAGE plpgsql + SECURITY DEFINER +AS $function$ +DECLARE + _is_open_join BOOLEAN; + _context BOOLEAN; + _project_group_id uuid; +BEGIN + + + SELECT (is_open_join) INTO _is_open_join FROM public.projects WHERE id = _project_id; + + -- They at least have to be authenticated + IF NOT check_action_policy_organization(auth.uid(), 'documents', 'SELECT') OR _is_open_join IS FALSE THEN + RETURN FALSE; + END IF; + + SELECT (id) INTO _project_group_id FROM public.project_groups WHERE project_id = _project_id AND is_default IS TRUE; + + INSERT INTO public.group_users + (group_type, user_id, type_id) + VALUES + ('project', auth.uid(), _project_group_id); + + -- Check for assign_all contexts + PERFORM do_assign_all_check_for_user(_project_id, auth.uid()); + + RETURN TRUE; +END +$function$ +; + + diff --git a/yarn.lock b/yarn.lock index 329c735..dcbc70e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -187,7 +187,7 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.0.0 || ^6.0.0-alpha", "@typescript-eslint/parser@^6.15.0": +"@typescript-eslint/parser@^6.15.0": version "6.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz" integrity sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA== @@ -265,7 +265,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: +acorn@^8.9.0: version "8.11.2" resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== @@ -480,7 +480,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.56.0: +eslint@^8.56.0: version "8.56.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz" integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== @@ -1091,11 +1091,6 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@>=4.2.0: - version "5.3.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz" - integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== - typical@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz"