diff --git a/nix/dev-vm.nix b/nix/dev-vm.nix index 773664a..48bacd4 100644 --- a/nix/dev-vm.nix +++ b/nix/dev-vm.nix @@ -68,7 +68,7 @@ ]; locations = { "/" = { - root = "/var/www/archtika-websites"; + root = "/var/www/archtika-websites/"; index = "index.html"; tryFiles = "$uri $uri/ $uri.html $uri/index.html index.html =404"; extraConfig = '' diff --git a/rest-api/db/migrations/20240908104458_extra_page_table.sql b/rest-api/db/migrations/20240908104458_extra_page_table.sql new file mode 100644 index 0000000..287562f --- /dev/null +++ b/rest-api/db/migrations/20240908104458_extra_page_table.sql @@ -0,0 +1,55 @@ +-- migrate:up +CREATE TABLE internal.legal_information ( + website_id UUID PRIMARY KEY REFERENCES internal.website (id) ON DELETE CASCADE, + main_content TEXT NOT NULL CHECK (TRIM(main_content) != ''), + last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), + last_modified_by UUID REFERENCES internal.user (id) ON DELETE SET NULL +); + +CREATE VIEW api.legal_information WITH ( security_invoker = ON +) AS +SELECT + website_id, + main_content, + last_modified_at, + last_modified_by +FROM + internal.legal_information; + +GRANT SELECT, INSERT, UPDATE, DELETE ON internal.legal_information TO authenticated_user; + +GRANT SELECT, INSERT, UPDATE, DELETE ON api.legal_information TO authenticated_user; + +ALTER TABLE internal.legal_information ENABLE ROW LEVEL SECURITY; + +CREATE POLICY view_legal_information ON internal.legal_information + FOR SELECT + USING (internal.user_has_website_access (website_id, 10)); + +CREATE POLICY update_legal_information ON internal.legal_information + FOR UPDATE + USING (internal.user_has_website_access (website_id, 30)); + +CREATE POLICY delete_legal_information ON internal.legal_information + FOR DELETE + USING (internal.user_has_website_access (website_id, 30)); + +CREATE POLICY insert_legal_information ON internal.legal_information + FOR INSERT + WITH CHECK (internal.user_has_website_access (website_id, 30)); + +-- migrate:down +DROP POLICY insert_legal_information ON internal.legal_information; + +DROP POLICY delete_legal_information ON internal.legal_information; + +DROP POLICY update_legal_information ON internal.legal_information; + +DROP POLICY view_legal_information ON internal.legal_information; + +ALTER TABLE internal.legal_information DISABLE ROW LEVEL SECURITY; + +DROP VIEW api.legal_information; + +DROP TABLE internal.legal_information; + diff --git a/rest-api/db/migrations/20240908115706_adjust_website_overview_extra_page.sql b/rest-api/db/migrations/20240908115706_adjust_website_overview_extra_page.sql new file mode 100644 index 0000000..127813c --- /dev/null +++ b/rest-api/db/migrations/20240908115706_adjust_website_overview_extra_page.sql @@ -0,0 +1,142 @@ +-- migrate:up +CREATE OR REPLACE VIEW api.website_overview WITH ( security_invoker = ON +) AS +SELECT + w.id, + w.user_id, + w.content_type, + w.title, + s.accent_color_light_theme, + s.accent_color_dark_theme, + s.favicon_image, + h.logo_type, + h.logo_text, + h.logo_image, + ho.main_content, + f.additional_text, + ( + SELECT + JSON_AGG( + JSON_BUILD_OBJECT( + 'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at +) +) + FROM + internal.article a + WHERE + a.website_id = w.id +) AS articles, + CASE WHEN w.content_type = 'Docs' THEN + ( + SELECT + JSON_OBJECT_AGG( + COALESCE( + category_name, 'Uncategorized' +), articles +) + FROM ( + SELECT + dc.category_name, + dc.category_weight AS category_weight, + JSON_AGG( + JSON_BUILD_OBJECT( + 'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at +) ORDER BY a.article_weight DESC NULLS LAST +) AS articles + FROM + internal.article a + LEFT JOIN internal.docs_category dc ON a.category = dc.id + WHERE + a.website_id = w.id + GROUP BY + dc.id, + dc.category_name, + dc.category_weight + ORDER BY + category_weight DESC NULLS LAST +) AS categorized_articles) +ELSE + NULL + END AS categorized_articles, + li.main_content legal_information_main_content +FROM + internal.website w + JOIN internal.settings s ON w.id = s.website_id + JOIN internal.header h ON w.id = h.website_id + JOIN internal.home ho ON w.id = ho.website_id + JOIN internal.footer f ON w.id = f.website_id + LEFT JOIN internal.legal_information li ON w.id = li.website_id; + +GRANT SELECT ON api.website_overview TO authenticated_user; + +-- migrate:down +DROP VIEW api.website_overview; + +CREATE VIEW api.website_overview WITH ( security_invoker = ON +) AS +SELECT + w.id, + w.user_id, + w.content_type, + w.title, + s.accent_color_light_theme, + s.accent_color_dark_theme, + s.favicon_image, + h.logo_type, + h.logo_text, + h.logo_image, + ho.main_content, + f.additional_text, + ( + SELECT + JSON_AGG( + JSON_BUILD_OBJECT( + 'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at +) +) + FROM + internal.article a + WHERE + a.website_id = w.id +) AS articles, + CASE WHEN w.content_type = 'Docs' THEN + ( + SELECT + JSON_OBJECT_AGG( + COALESCE( + category_name, 'Uncategorized' +), articles +) + FROM ( + SELECT + dc.category_name, + dc.category_weight AS category_weight, + JSON_AGG( + JSON_BUILD_OBJECT( + 'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at +) ORDER BY a.article_weight DESC NULLS LAST +) AS articles + FROM + internal.article a + LEFT JOIN internal.docs_category dc ON a.category = dc.id + WHERE + a.website_id = w.id + GROUP BY + dc.id, + dc.category_name, + dc.category_weight + ORDER BY + category_weight DESC NULLS LAST +) AS categorized_articles) +ELSE + NULL + END AS categorized_articles +FROM + internal.website w + JOIN internal.settings s ON w.id = s.website_id + JOIN internal.header h ON w.id = h.website_id + JOIN internal.home ho ON w.id = ho.website_id + JOIN internal.footer f ON w.id = f.website_id; + +GRANT SELECT ON api.website_overview TO authenticated_user; + diff --git a/web-app/src/lib/components/WebsiteEditor.svelte b/web-app/src/lib/components/WebsiteEditor.svelte index 9715784..6280bd6 100644 --- a/web-app/src/lib/components/WebsiteEditor.svelte +++ b/web-app/src/lib/components/WebsiteEditor.svelte @@ -49,6 +49,9 @@
  • Collaborators
  • +
  • + Legal information +
  • Publish
  • diff --git a/web-app/src/lib/templates/blog/BlogArticle.svelte b/web-app/src/lib/templates/blog/BlogArticle.svelte index e3a8adb..cc9b3d9 100644 --- a/web-app/src/lib/templates/blog/BlogArticle.svelte +++ b/web-app/src/lib/templates/blog/BlogArticle.svelte @@ -50,4 +50,4 @@ {/if} -