Skip to content

Commit

Permalink
Merge pull request #1315 from research-software-directory/1294-catego…
Browse files Browse the repository at this point in the history
…ries-for-projects-organisations

1294 categories for organisations
  • Loading branch information
dmijatovic authored Oct 24, 2024
2 parents 2a1b6e2 + 50b7c67 commit 851b8c7
Show file tree
Hide file tree
Showing 70 changed files with 1,865 additions and 473 deletions.
114 changes: 91 additions & 23 deletions data-generation/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,44 +308,72 @@ function generateKeywordsForEntity(idsEntity, idsKeyword, nameEntity) {
return result;
}

async function generateCategories(idsCommunities, maxDepth = 3) {
const communityPromises = [];
const categoriesPerCommunity = new Map();
const categoriesPerOrganisation = new Map();
const globalCategories = [];
async function generateCategories(idsCommunities, idsOrganisations, maxDepth = 3) {
const promises = [];

for (const commId of idsCommunities) {
communityPromises.push(generateAndSaveCategoriesForCommunity(commId, maxDepth));
promises.push(
generateAndSaveCategoriesForEntity(commId, null, maxDepth).then(ids =>
categoriesPerCommunity.set(commId, ids),
),
);
}
for (const orgId of idsOrganisations) {
promises.push(
generateAndSaveCategoriesForEntity(null, orgId, maxDepth).then(ids =>
categoriesPerOrganisation.set(orgId, ids),
),
);
}
communityPromises.push(generateAndSaveCategoriesForCommunity(null, maxDepth));
promises.push(generateAndSaveCategoriesForEntity(null, null, maxDepth).then(ids => globalCategories.push(...ids)));

return await Promise.all(communityPromises);
return await Promise.all(promises);
}

async function generateAndSaveCategoriesForCommunity(idCommunity, maxDepth) {
async function generateAndSaveCategoriesForEntity(idCommunity, idOrganisation, maxDepth) {
return new Promise(async res => {
let parentIds = [null];
let parentIdsAndFlags = [
{id: null, forSoftware: faker.datatype.boolean(), forProjects: faker.datatype.boolean()},
];
const idsAndFlags = [];
for (let level = 1; level <= maxDepth; level++) {
const newParentIds = [];
for (const parent of parentIds) {
const newParentIdsAndFlags = [];
for (const parent of parentIdsAndFlags) {
let toGenerateCount = faker.number.int(4);
if (idCommunity === null && level === 1) {
if (idCommunity === null && idOrganisation === null && level === 1) {
toGenerateCount += 1;
}
for (let i = 0; i < toGenerateCount; i++) {
const name = `Parent ${parent}, level ${level}, item ${i + 1}`;
const name = `Parent ${parent.id}, level ${level}, item ${i + 1}`;
const shortName = `Level ${level}, item ${i + 1}`;
const body = {
community: idCommunity,
parent: parent,
organisation: idOrganisation,
parent: parent.id,
short_name: shortName,
name: name,
allow_software: parent.forSoftware,
allow_projects: parent.forProjects,
};
await postToBackend('/category', body)
.then(resp => resp.json())
.then(json => json[0].id)
.then(id => newParentIds.push(id));
.then(json => ({
id: json[0].id,
forSoftware: parent.forSoftware,
forProjects: parent.forProjects,
}))
.then(data => {
newParentIdsAndFlags.push(data);
idsAndFlags.push(data);
});
}
}
parentIds = newParentIds;
parentIdsAndFlags = newParentIdsAndFlags;
}
res();
res(idsAndFlags);
});
}

Expand Down Expand Up @@ -1060,7 +1088,8 @@ const communityPromise = postToBackend('/community', generateCommunities())
.then(async commArray => {
idsCommunities = commArray.map(comm => comm['id']);
postToBackend('/keyword_for_community', generateKeywordsForEntity(idsCommunities, idsKeywords, 'community'));
generateCategories(idsCommunities);
await organisationPromise;
await generateCategories(idsCommunities, idsOrganisations);
});

await postToBackend('/meta_pages', generateMetaPages()).then(() => console.log('meta pages done'));
Expand All @@ -1079,13 +1108,52 @@ await postToBackend(
'/software_for_project',
generateRelationsForDifferingEntities(idsSoftware, idsProjects, 'software', 'project'),
).then(() => console.log('sw-pj done'));
await postToBackend(
'/software_for_organisation',
generateRelationsForDifferingEntities(idsSoftware, idsOrganisations, 'software', 'organisation'),
).then(() => console.log('sw-org done'));
await postToBackend('/project_for_organisation', generateProjectForOrganisation(idsProjects, idsOrganisations)).then(
() => console.log('pj-org done'),
const softwareForOrganisation = generateRelationsForDifferingEntities(
idsSoftware,
idsOrganisations,
'software',
'organisation',
);
await postToBackend('/software_for_organisation', softwareForOrganisation)
.then(async () => {
const allCategoriesForSoftware = [];
for (const entry of softwareForOrganisation) {
const orgId = entry.organisation;
const relations = generateRelationsForDifferingEntities(
[entry.software],
categoriesPerOrganisation
.get(orgId)
.filter(data => data.forSoftware)
.map(data => data.id),
'software_id',
'category_id',
);
allCategoriesForSoftware.push(...relations);
}
// console.log(allCategoriesForSoftware);
await postToBackend('/category_for_software', allCategoriesForSoftware);
})
.then(() => console.log('sw-org done'));
const projectForOrganisation = generateProjectForOrganisation(idsProjects, idsOrganisations);
await postToBackend('/project_for_organisation', generateProjectForOrganisation(idsProjects, idsOrganisations))
.then(async () => {
const allCategoriesForProjects = [];
for (const entry of projectForOrganisation) {
const orgId = entry.organisation;
const relations = generateRelationsForDifferingEntities(
[entry.project],
categoriesPerOrganisation
.get(orgId)
.filter(data => data.forProjects)
.map(data => data.id),
'project_id',
'category_id',
);
allCategoriesForProjects.push(...relations);
}
await postToBackend('/category_for_project', allCategoriesForProjects);
})
.then(() => console.log('pj-org done'));
await postToBackend('/software_for_community', generateSoftwareForCommunity(idsSoftware, idsCommunities)).then(() =>
console.log('sw-comm done'),
);
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
-- SPDX-FileCopyrightText: 2023 - 2024 Felix Mühlbauer (GFZ) <felix.muehlbauer@gfz-potsdam.de>
-- SPDX-FileCopyrightText: 2023 - 2024 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
-- SPDX-FileCopyrightText: 2024 Christian Meeßen (GFZ) <christian.meessen@gfz-potsdam.de>
-- SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
--
-- SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -88,29 +89,24 @@ CREATE TABLE category (
id UUID PRIMARY KEY,
parent UUID REFERENCES category DEFAULT NULL,
community UUID REFERENCES community(id) DEFAULT NULL,
organisation UUID REFERENCES organisation(id) DEFAULT NULL,
allow_software BOOLEAN NOT NULL DEFAULT FALSE,
allow_projects BOOLEAN NOT NULL DEFAULT FALSE,
short_name VARCHAR(100) NOT NULL,
name VARCHAR(250) NOT NULL,
properties JSONB NOT NULL DEFAULT '{}'::jsonb,
provenance_iri VARCHAR(250) DEFAULT NULL, -- e.g. https://www.w3.org/TR/skos-reference/#mapping

CONSTRAINT unique_short_name UNIQUE NULLS NOT DISTINCT (parent, short_name, community),
CONSTRAINT unique_name UNIQUE NULLS NOT DISTINCT (parent, name, community),
CONSTRAINT only_one_entity CHECK (community IS NULL OR organisation IS NULL),
CONSTRAINT unique_short_name UNIQUE NULLS NOT DISTINCT (parent, short_name, community, organisation),
CONSTRAINT unique_name UNIQUE NULLS NOT DISTINCT (parent, name, community, organisation),
CONSTRAINT invalid_value_for_properties CHECK (properties - '{icon, is_highlight, description, subtitle, tree_level_labels}'::text[] = '{}'::jsonb),
CONSTRAINT highlight_must_be_top_level_category CHECK (NOT ((properties->>'is_highlight')::boolean AND parent IS NOT NULL))
);

CREATE INDEX category_parent_idx ON category(parent);
CREATE INDEX category_community_idx ON category(community);


CREATE TABLE category_for_software (
software_id UUID REFERENCES software (id),
category_id UUID REFERENCES category (id),
PRIMARY KEY (software_id, category_id)
);

CREATE INDEX category_for_software_category_id_idx ON category_for_software(category_id);

CREATE INDEX category_organisation_idx ON category(organisation);

-- sanitize categories

Expand All @@ -126,6 +122,9 @@ BEGIN
IF NEW.parent IS NOT NULL AND (SELECT community FROM category WHERE id = NEW.parent) IS DISTINCT FROM NEW.community THEN
RAISE EXCEPTION USING MESSAGE = 'The community must be the same as of its parent.';
END IF;
IF NEW.parent IS NOT NULL AND (SELECT organisation FROM category WHERE id = NEW.parent) IS DISTINCT FROM NEW.organisation THEN
RAISE EXCEPTION USING MESSAGE = 'The organisation must be the same as of its parent.';
END IF;
NEW.id = gen_random_uuid();
RETURN NEW;
END
Expand All @@ -151,6 +150,12 @@ BEGIN
IF NEW.parent IS NOT NULL AND (SELECT community FROM category WHERE id = NEW.parent) IS DISTINCT FROM NEW.community THEN
RAISE EXCEPTION USING MESSAGE = 'The community must be the same as of its parent.';
END IF;
IF NEW.organisation IS DISTINCT FROM OLD.organisation THEN
RAISE EXCEPTION USING MESSAGE = 'The organisation this category belongs to may not be changed.';
END IF;
IF NEW.parent IS NOT NULL AND (SELECT organisation FROM category WHERE id = NEW.parent) IS DISTINCT FROM NEW.organisation THEN
RAISE EXCEPTION USING MESSAGE = 'The organisation must be the same as of its parent.';
END IF;
RETURN NEW;
END
$$;
Expand Down Expand Up @@ -224,6 +229,19 @@ $$
$$;


-- TABLE FOR software categories
-- includes organisation, community and general categories
-- Note! to filter specific categories of an community or organisation use join with community table

CREATE TABLE category_for_software (
software_id UUID REFERENCES software (id),
category_id UUID REFERENCES category (id),
PRIMARY KEY (software_id, category_id)
);

CREATE INDEX category_for_software_category_id_idx ON category_for_software(category_id);

-- RPC for software page to show all software categories
CREATE FUNCTION category_paths_by_software_expanded(software_id UUID)
RETURNS JSON
LANGUAGE SQL STABLE AS
Expand All @@ -238,3 +256,45 @@ $$
ELSE '[]'::json
END AS result
$$;


-- TABLE FOR project categories
-- currently used only for organisation categories
CREATE TABLE category_for_project (
project_id UUID REFERENCES project (id),
category_id UUID REFERENCES category (id),
PRIMARY KEY (project_id, category_id)
);

CREATE INDEX category_for_project_category_id_idx ON category_for_project(category_id);

-- RPC for project page to show all project categories
CREATE FUNCTION category_paths_by_project_expanded(project_id UUID)
RETURNS JSON
LANGUAGE SQL STABLE AS
$$
WITH
cat_ids AS
(SELECT
category_id
FROM
category_for_project
WHERE
category_for_project.project_id = category_paths_by_project_expanded.project_id
),
paths AS
(
SELECT
category_path_expanded(category_id) AS path
FROM cat_ids
)
SELECT
CASE
WHEN EXISTS(
SELECT 1 FROM cat_ids
) THEN (
SELECT json_agg(path) FROM paths
)
ELSE '[]'::json
END AS result
$$;
30 changes: 26 additions & 4 deletions database/020-row-level-security.sql
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ CREATE POLICY admin_all_rights ON testimonial TO rsd_admin


-- categories

ALTER TABLE category ENABLE ROW LEVEL SECURITY;

-- allow everybody to read
Expand All @@ -290,7 +289,11 @@ CREATE POLICY anyone_can_read ON category

CREATE POLICY maintainer_all_rights ON category
TO rsd_user
USING (community IN (SELECT * FROM communities_of_current_maintainer()));
USING (
(community IS NOT NULL AND community IN (SELECT * FROM communities_of_current_maintainer()))
OR
(organisation IS NOT NULL AND organisation IN (SELECT * FROM organisations_of_current_maintainer()))
);

-- allow admins to have full read/write access
CREATE POLICY admin_all_rights ON category
Expand All @@ -299,14 +302,13 @@ CREATE POLICY admin_all_rights ON category


-- categories for software

ALTER TABLE category_for_software ENABLE ROW LEVEL SECURITY;

-- allow everybody to read metadata of published software
CREATE POLICY anyone_can_read ON category_for_software
FOR SELECT
TO rsd_web_anon, rsd_user
USING (EXISTS(SELECT 1 FROM software WHERE id = software_id));
USING (software_id IN (SELECT id FROM software));

-- allow software maintainers to have read/write access to their software
CREATE POLICY maintainer_all_rights ON category_for_software
Expand All @@ -319,6 +321,26 @@ CREATE POLICY admin_all_rights ON category_for_software
USING (TRUE);


-- categories for project
ALTER TABLE category_for_project ENABLE ROW LEVEL SECURITY;

-- allow everybody to read metadata of published projects
CREATE POLICY anyone_can_read ON category_for_project
FOR SELECT
TO rsd_web_anon, rsd_user
USING (project_id IN (SELECT id FROM project));

-- allow software maintainers to have read/write access to their project
CREATE POLICY maintainer_all_rights ON category_for_project
TO rsd_user
USING (project_id IN (SELECT * FROM projects_of_current_maintainer()));

-- allow admins to have full read/write access
CREATE POLICY admin_all_rights ON category_for_project
TO rsd_admin
USING (TRUE);


-- keywords
ALTER TABLE keyword ENABLE ROW LEVEL SECURITY;

Expand Down
Loading

0 comments on commit 851b8c7

Please sign in to comment.