Rename Postgres tables for better recognition and add additional routes in web app

This commit is contained in:
Thilo Hohlt
2024-07-31 10:29:46 +02:00
parent a7f2fdebf5
commit d21e00a0c3
13 changed files with 601 additions and 243 deletions

View File

@@ -20,57 +20,57 @@ CREATE TABLE internal.user (
role NAME NOT NULL DEFAULT 'authenticated_user' role NAME NOT NULL DEFAULT 'authenticated_user'
); );
CREATE TABLE internal.cms_content ( CREATE TABLE internal.website (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id UUID REFERENCES internal.user(id) ON DELETE CASCADE NOT NULL DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID, owner_id UUID REFERENCES internal.user(id) ON DELETE CASCADE NOT NULL DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID,
content_type VARCHAR(10) CHECK (content_type IN ('Blog', 'Docs')) NOT NULL, content_type VARCHAR(10) CHECK (content_type IN ('Blog', 'Docs')) NOT NULL,
project_name VARCHAR(50) NOT NULL, title VARCHAR(50) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ, last_modified_at TIMESTAMPTZ,
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.cms_media ( CREATE TABLE internal.media (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content_id UUID REFERENCES internal.cms_content(id) ON DELETE CASCADE NOT NULL, website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE NOT NULL,
user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE NOT NULL DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID, user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE NOT NULL DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID,
original_name TEXT NOT NULL, original_name TEXT NOT NULL,
file_system_path TEXT NOT NULL, file_system_path TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP() created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP()
); );
CREATE TABLE internal.cms_settings ( CREATE TABLE internal.settings (
content_id UUID PRIMARY KEY REFERENCES internal.cms_content(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
accent_color_light_theme CHAR(7) CHECK (accent_color_light_theme ~ '^#[a-fA-F0-9]{6}$') NOT NULL DEFAULT '#a5d8ff', accent_color_light_theme CHAR(7) CHECK (accent_color_light_theme ~ '^#[a-fA-F0-9]{6}$') NOT NULL DEFAULT '#a5d8ff',
accent_color_dark_theme CHAR(7) CHECK (accent_color_dark_theme ~ '^#[a-fA-F0-9]{6}$') NOT NULL DEFAULT '#114678', accent_color_dark_theme CHAR(7) CHECK (accent_color_dark_theme ~ '^#[a-fA-F0-9]{6}$') NOT NULL DEFAULT '#114678',
favicon_image UUID REFERENCES internal.cms_media(id) ON DELETE SET NULL, favicon_image UUID REFERENCES internal.media(id) ON DELETE SET NULL,
last_modified_at TIMESTAMPTZ, last_modified_at TIMESTAMPTZ,
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.cms_header ( CREATE TABLE internal.header (
content_id UUID PRIMARY KEY REFERENCES internal.cms_content(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
logo_type TEXT CHECK (logo_type IN ('text', 'image')) NOT NULL DEFAULT 'text', logo_type TEXT CHECK (logo_type IN ('text', 'image')) NOT NULL DEFAULT 'text',
logo_text VARCHAR(255), logo_text VARCHAR(255),
logo_image UUID REFERENCES internal.cms_media(id) ON DELETE SET NULL, logo_image UUID REFERENCES internal.media(id) ON DELETE SET NULL,
last_modified_at TIMESTAMPTZ, last_modified_at TIMESTAMPTZ,
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.cms_home ( CREATE TABLE internal.home (
content_id UUID PRIMARY KEY REFERENCES internal.cms_content(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
main_content TEXT, main_content TEXT,
last_modified_at TIMESTAMPTZ, last_modified_at TIMESTAMPTZ,
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.cms_article ( CREATE TABLE internal.article (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content_id UUID REFERENCES internal.cms_content(id) ON DELETE CASCADE NOT NULL, website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE NOT NULL,
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
meta_description VARCHAR(500), meta_description VARCHAR(500),
meta_author VARCHAR(255), meta_author VARCHAR(255),
cover_image UUID REFERENCES internal.cms_media(id) ON DELETE SET NULL, cover_image UUID REFERENCES internal.media(id) ON DELETE SET NULL,
publication_date DATE NOT NULL DEFAULT CURRENT_DATE, publication_date DATE NOT NULL DEFAULT CURRENT_DATE,
main_content TEXT, main_content TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
@@ -78,43 +78,43 @@ CREATE TABLE internal.cms_article (
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.cms_footer ( CREATE TABLE internal.footer (
content_id UUID PRIMARY KEY REFERENCES internal.cms_content(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
additional_text VARCHAR(255), additional_text VARCHAR(255),
last_modified_at TIMESTAMPTZ, last_modified_at TIMESTAMPTZ,
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.cms_collab ( CREATE TABLE internal.collab (
content_id UUID REFERENCES internal.cms_content(id) ON DELETE CASCADE, website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE,
user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE, user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE,
permission_level INTEGER CHECK (permission_level IN (10, 20, 30)) NOT NULL DEFAULT 10, permission_level INTEGER CHECK (permission_level IN (10, 20, 30)) NOT NULL DEFAULT 10,
added_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), added_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_at TIMESTAMPTZ, last_modified_at TIMESTAMPTZ,
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL, last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL,
PRIMARY KEY (content_id, user_id) PRIMARY KEY (website_id, user_id)
); );
CREATE TABLE internal.cms_change_log ( CREATE TABLE internal.change_log (
content_id UUID REFERENCES internal.cms_content(id) ON DELETE CASCADE, website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE,
user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID, user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE DEFAULT (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID,
change_summary VARCHAR(255) NOT NULL, change_summary VARCHAR(255) NOT NULL,
previous_value JSONB, previous_value JSONB,
new_value JSONB, new_value JSONB,
timestamp TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), timestamp TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
PRIMARY KEY (content_id, user_id, timestamp) PRIMARY KEY (website_id, user_id, timestamp)
); );
-- migrate:down -- migrate:down
DROP TABLE internal.cms_change_log; DROP TABLE internal.change_log;
DROP TABLE internal.cms_collab; DROP TABLE internal.collab;
DROP TABLE internal.cms_footer; DROP TABLE internal.footer;
DROP TABLE internal.cms_article; DROP TABLE internal.article;
DROP TABLE internal.cms_home; DROP TABLE internal.home;
DROP TABLE internal.cms_header; DROP TABLE internal.header;
DROP TABLE internal.cms_settings; DROP TABLE internal.settings;
DROP TABLE internal.cms_media; DROP TABLE internal.media;
DROP TABLE internal.cms_content; DROP TABLE internal.website;
DROP SCHEMA api; DROP SCHEMA api;
DROP TABLE internal.user; DROP TABLE internal.user;

View File

@@ -5,136 +5,136 @@ AS
SELECT id, username SELECT id, username
FROM internal.user; FROM internal.user;
CREATE VIEW api.cms_content CREATE VIEW api.website
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_content; FROM internal.website;
CREATE VIEW api.cms_media CREATE VIEW api.media
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_media; FROM internal.media;
CREATE VIEW api.cms_settings CREATE VIEW api.settings
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_settings; FROM internal.settings;
CREATE VIEW api.cms_header CREATE VIEW api.header
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_header; FROM internal.header;
CREATE view api.cms_home CREATE view api.home
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_home; FROM internal.home;
CREATE VIEW api.cms_article CREATE VIEW api.article
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_article; FROM internal.article;
CREATE VIEW api.cms_footer CREATE VIEW api.footer
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_footer; FROM internal.footer;
CREATE VIEW api.cms_collab CREATE VIEW api.collab
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_collab; FROM internal.collab;
CREATE VIEW api.cms_change_log CREATE VIEW api.change_log
WITH (security_invoker = on) WITH (security_invoker = on)
AS AS
SELECT * SELECT *
FROM internal.cms_change_log; FROM internal.change_log;
CREATE FUNCTION CREATE FUNCTION
api.create_project(content_type VARCHAR(10), project_name VARCHAR(50), OUT content_id UUID) AS $$ api.create_website(content_type VARCHAR(10), title VARCHAR(50), OUT website_id UUID) AS $$
DECLARE DECLARE
_content_id UUID; _website_id UUID;
BEGIN BEGIN
INSERT INTO internal.cms_content (content_type, project_name) INSERT INTO internal.website (content_type, title)
VALUES (create_project.content_type, create_project.project_name) VALUES (create_website.content_type, create_website.title)
RETURNING id INTO _content_id; RETURNING id INTO _website_id;
INSERT INTO internal.cms_settings (content_id) INSERT INTO internal.settings (website_id)
VALUES (_content_id); VALUES (_website_id);
INSERT INTO internal.cms_header (content_id, logo_text) INSERT INTO internal.header (website_id, logo_text)
VALUES (_content_id, 'archtika ' || create_project.content_type); VALUES (_website_id, 'archtika ' || create_website.content_type);
INSERT INTO internal.cms_home (content_id, main_content) INSERT INTO internal.home (website_id, main_content)
VALUES VALUES
(_content_id, '## Main content comes in here'); (_website_id, '## Main content comes in here');
INSERT INTO internal.cms_article (content_id, title, meta_description, meta_author, main_content) INSERT INTO internal.article (website_id, title, meta_description, meta_author, main_content)
VALUES VALUES
(_content_id, 'First article', 'This is the first sample article', 'Author Name', '## First article'), (_website_id, 'First article', 'This is the first sample article', 'Author Name', '## First article'),
(_content_id, 'Second article', 'This is the second sample article', 'Author Name', '## Second article'); (_website_id, 'Second article', 'This is the second sample article', 'Author Name', '## Second article');
INSERT INTO internal.cms_footer (content_id, additional_text) INSERT INTO internal.footer (website_id, additional_text)
VALUES (_content_id, 'This website was created with archtika'); VALUES (_website_id, 'This website was created with archtika');
content_id := _content_id; website_id := _website_id;
END; END;
$$ LANGUAGE plpgsql SECURITY DEFINER; $$ LANGUAGE plpgsql SECURITY DEFINER;
GRANT EXECUTE ON FUNCTION api.create_project(VARCHAR(10), VARCHAR(50)) TO authenticated_user; GRANT EXECUTE ON FUNCTION api.create_website(VARCHAR(10), VARCHAR(50)) TO authenticated_user;
-- Security invoker only works on views if the user has access to the underlying table -- Security invoker only works on views if the user has access to the underlying table
GRANT SELECT ON internal.user TO authenticated_user; GRANT SELECT ON internal.user TO authenticated_user;
GRANT SELECT ON api.user TO authenticated_user; GRANT SELECT ON api.user TO authenticated_user;
GRANT SELECT, UPDATE, DELETE ON internal.cms_content TO authenticated_user; GRANT SELECT, UPDATE, DELETE ON internal.website TO authenticated_user;
GRANT SELECT, UPDATE, DELETE ON api.cms_content TO authenticated_user; GRANT SELECT, UPDATE, DELETE ON api.website TO authenticated_user;
GRANT SELECT, INSERT ON internal.cms_media TO authenticated_user; GRANT SELECT, INSERT ON internal.media TO authenticated_user;
GRANT SELECT, INSERT ON api.cms_media TO authenticated_user; GRANT SELECT, INSERT ON api.media TO authenticated_user;
GRANT SELECT, UPDATE ON internal.cms_settings TO authenticated_user; GRANT SELECT, UPDATE ON internal.settings TO authenticated_user;
GRANT SELECT, UPDATE ON api.cms_settings TO authenticated_user; GRANT SELECT, UPDATE ON api.settings TO authenticated_user;
GRANT SELECT, UPDATE ON internal.cms_header TO authenticated_user; GRANT SELECT, UPDATE ON internal.header TO authenticated_user;
GRANT SELECT, UPDATE ON api.cms_header TO authenticated_user; GRANT SELECT, UPDATE ON api.header TO authenticated_user;
GRANT SELECT, UPDATE ON internal.cms_home TO authenticated_user; GRANT SELECT, UPDATE ON internal.home TO authenticated_user;
GRANT SELECT, UPDATE ON api.cms_home TO authenticated_user; GRANT SELECT, UPDATE ON api.home TO authenticated_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON internal.cms_article TO authenticated_user; GRANT SELECT, INSERT, UPDATE, DELETE ON internal.article TO authenticated_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON api.cms_article TO authenticated_user; GRANT SELECT, INSERT, UPDATE, DELETE ON api.article TO authenticated_user;
GRANT SELECT, UPDATE ON internal.cms_footer TO authenticated_user; GRANT SELECT, UPDATE ON internal.footer TO authenticated_user;
GRANT SELECT, UPDATE ON api.cms_footer TO authenticated_user; GRANT SELECT, UPDATE ON api.footer TO authenticated_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON internal.cms_collab TO authenticated_user; GRANT SELECT, INSERT, UPDATE, DELETE ON internal.collab TO authenticated_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON api.cms_collab TO authenticated_user; GRANT SELECT, INSERT, UPDATE, DELETE ON api.collab TO authenticated_user;
GRANT SELECT ON internal.cms_change_log TO authenticated_user; GRANT SELECT ON internal.change_log TO authenticated_user;
GRANT SELECT ON api.cms_change_log TO authenticated_user; GRANT SELECT ON api.change_log TO authenticated_user;
-- migrate:down -- migrate:down
REVOKE SELECT ON internal.user FROM authenticated_user; REVOKE SELECT ON internal.user FROM authenticated_user;
REVOKE SELECT, UPDATE, DELETE ON internal.cms_content FROM authenticated_user; REVOKE SELECT, UPDATE, DELETE ON internal.website FROM authenticated_user;
REVOKE SELECT, INSERT ON internal.cms_media FROM authenticated_user; REVOKE SELECT, INSERT ON internal.media FROM authenticated_user;
REVOKE SELECT, UPDATE ON internal.cms_settings FROM authenticated_user; REVOKE SELECT, UPDATE ON internal.settings FROM authenticated_user;
REVOKE SELECT, UPDATE ON internal.cms_header FROM authenticated_user; REVOKE SELECT, UPDATE ON internal.header FROM authenticated_user;
REVOKE SELECT, INSERT, UPDATE, DELETE ON internal.cms_article FROM authenticated_user; REVOKE SELECT, INSERT, UPDATE, DELETE ON internal.article FROM authenticated_user;
REVOKE SELECT, UPDATE ON internal.cms_footer FROM authenticated_user; REVOKE SELECT, UPDATE ON internal.footer FROM authenticated_user;
REVOKE SELECT, INSERT, UPDATE, DELETE ON internal.cms_collab FROM authenticated_user; REVOKE SELECT, INSERT, UPDATE, DELETE ON internal.collab FROM authenticated_user;
REVOKE SELECT ON internal.cms_change_log FROM authenticated_user; REVOKE SELECT ON internal.change_log FROM authenticated_user;
DROP FUNCTION api.create_project(VARCHAR(10), VARCHAR(50)); DROP FUNCTION api.create_website(VARCHAR(10), VARCHAR(50));
DROP VIEW api.cms_change_log; DROP VIEW api.change_log;
DROP VIEW api.cms_collab; DROP VIEW api.collab;
DROP VIEW api.cms_footer; DROP VIEW api.footer;
DROP VIEW api.cms_home; DROP VIEW api.home;
DROP VIEW api.cms_article; DROP VIEW api.article;
DROP VIEW api.cms_header; DROP VIEW api.header;
DROP VIEW api.cms_settings; DROP VIEW api.settings;
DROP VIEW api.cms_media; DROP VIEW api.media;
DROP VIEW api.cms_content; DROP VIEW api.website;
DROP VIEW api.user; DROP VIEW api.user;

View File

@@ -1,208 +1,208 @@
-- migrate:up -- migrate:up
ALTER TABLE internal.user ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.user ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_content ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.website ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_media ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.media ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_settings ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.settings ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_header ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.header ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_home ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.home ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_article ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.article ENABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_footer ENABLE ROW LEVEL SECURITY; ALTER TABLE internal.footer ENABLE ROW LEVEL SECURITY;
CREATE POLICY view_own_user ON internal.user CREATE POLICY view_own_user ON internal.user
FOR SELECT FOR SELECT
USING (id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID);
CREATE POLICY view_own_projects ON internal.cms_content CREATE POLICY view_own_websites ON internal.website
FOR SELECT FOR SELECT
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID);
CREATE POLICY update_own_project ON internal.cms_content CREATE POLICY update_own_website ON internal.website
FOR UPDATE FOR UPDATE
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID);
CREATE POLICY delete_own_project ON internal.cms_content CREATE POLICY delete_own_website ON internal.website
FOR DELETE FOR DELETE
USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID);
CREATE POLICY view_own_media ON internal.cms_media CREATE POLICY view_own_media ON internal.media
FOR SELECT FOR SELECT
USING (user_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID); USING (user_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID);
CREATE POLICY insert_own_media ON internal.cms_media CREATE POLICY insert_own_media ON internal.media
FOR INSERT FOR INSERT
WITH CHECK ( WITH CHECK (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_media.content_id WHERE internal.website.id = internal.media.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY view_own_settings ON internal.cms_settings CREATE POLICY view_own_settings ON internal.settings
FOR SELECT FOR SELECT
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_settings.content_id WHERE internal.website.id = internal.settings.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY update_own_settings ON internal.cms_settings CREATE POLICY update_own_settings ON internal.settings
FOR UPDATE FOR UPDATE
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_settings.content_id WHERE internal.website.id = internal.settings.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY view_own_header ON internal.cms_header CREATE POLICY view_own_header ON internal.header
FOR SELECT FOR SELECT
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_header.content_id WHERE internal.website.id = internal.header.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY update_own_header ON internal.cms_header CREATE POLICY update_own_header ON internal.header
FOR UPDATE FOR UPDATE
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_header.content_id WHERE internal.website.id = internal.header.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY view_own_home ON internal.cms_home CREATE POLICY view_own_home ON internal.home
FOR SELECT FOR SELECT
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_home.content_id WHERE internal.website.id = internal.home.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY update_own_home ON internal.cms_home CREATE POLICY update_own_home ON internal.home
FOR UPDATE FOR UPDATE
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_home.content_id WHERE internal.website.id = internal.home.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY view_own_articles ON internal.cms_article CREATE POLICY view_own_articles ON internal.article
FOR SELECT FOR SELECT
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_article.content_id WHERE internal.website.id = internal.article.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY update_own_article ON internal.cms_article CREATE POLICY update_own_article ON internal.article
FOR UPDATE FOR UPDATE
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_article.content_id WHERE internal.website.id = internal.article.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY delete_own_article ON internal.cms_article CREATE POLICY delete_own_article ON internal.article
FOR DELETE FOR DELETE
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_article.content_id WHERE internal.website.id = internal.article.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY insert_own_article ON internal.cms_article CREATE POLICY insert_own_article ON internal.article
FOR INSERT FOR INSERT
WITH CHECK ( WITH CHECK (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_article.content_id WHERE internal.website.id = internal.article.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY view_own_footer ON internal.cms_footer CREATE POLICY view_own_footer ON internal.footer
FOR SELECT FOR SELECT
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_footer.content_id WHERE internal.website.id = internal.footer.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
CREATE POLICY update_own_footer ON internal.cms_footer CREATE POLICY update_own_footer ON internal.footer
FOR UPDATE FOR UPDATE
USING ( USING (
EXISTS ( EXISTS (
SELECT 1 SELECT 1
FROM internal.cms_content FROM internal.website
WHERE internal.cms_content.id = internal.cms_footer.content_id WHERE internal.website.id = internal.footer.website_id
AND internal.cms_content.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID AND internal.website.owner_id = (current_setting('request.jwt.claims', true)::json->>'user_id')::UUID
) )
); );
-- migrate:down -- migrate:down
DROP POLICY view_own_user ON internal.user; DROP POLICY view_own_user ON internal.user;
DROP POLICY view_own_projects ON internal.cms_content; DROP POLICY view_own_websites ON internal.website;
DROP POLICY delete_own_project ON internal.cms_content; DROP POLICY delete_own_website ON internal.website;
DROP POLICY update_own_project ON internal.cms_content; DROP POLICY update_own_website ON internal.website;
DROP POLICY view_own_media ON internal.cms_media; DROP POLICY view_own_media ON internal.media;
DROP POLICY insert_own_media ON internal.cms_media; DROP POLICY insert_own_media ON internal.media;
DROP POLICY view_own_settings ON internal.cms_settings; DROP POLICY view_own_settings ON internal.settings;
DROP POLICY update_own_settings ON internal.cms_settings; DROP POLICY update_own_settings ON internal.settings;
DROP POLICY view_own_header ON internal.cms_header; DROP POLICY view_own_header ON internal.header;
DROP POLICY update_own_header ON internal.cms_header; DROP POLICY update_own_header ON internal.header;
DROP POLICY view_own_home ON internal.cms_home; DROP POLICY view_own_home ON internal.home;
DROP POLICY update_own_home ON internal.cms_home; DROP POLICY update_own_home ON internal.home;
DROP POLICY view_own_articles ON internal.cms_article; DROP POLICY view_own_articles ON internal.article;
DROP POLICY update_own_article ON internal.cms_article; DROP POLICY update_own_article ON internal.article;
DROP POLICY delete_own_article ON internal.cms_article; DROP POLICY delete_own_article ON internal.article;
DROP POLICY insert_own_article ON internal.cms_article; DROP POLICY insert_own_article ON internal.article;
DROP POLICY view_own_footer ON internal.cms_footer; DROP POLICY view_own_footer ON internal.footer;
DROP POLICY update_own_footer ON internal.cms_footer; DROP POLICY update_own_footer ON internal.footer;
ALTER TABLE internal.user DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.user DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_content DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.website DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_media DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.media DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_settings DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.settings DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_header DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.header DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_home DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.home DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_article DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.article DISABLE ROW LEVEL SECURITY;
ALTER TABLE internal.cms_footer DISABLE ROW LEVEL SECURITY; ALTER TABLE internal.footer DISABLE ROW LEVEL SECURITY;

View File

@@ -1 +1,26 @@
<form action=""></form> <script lang="ts">
import { enhance } from "$app/forms";
const { form } = $props();
</script>
<form method="POST" use:enhance>
{#if form?.success}
<p>Successfully logged in</p>
{/if}
{#if form?.success === false}
<p>{form.message}</p>
{/if}
<label>
Username
<input type="text" name="username" required />
</label>
<label>
Password
<input type="password" name="password" required />
</label>
<button type="submit">Submit</button>
</form>

View File

@@ -1 +1,26 @@
<form action=""></form> <script lang="ts">
import { enhance } from "$app/forms";
const { form } = $props();
</script>
<form method="POST" use:enhance>
{#if form?.success}
<p>Successfully registered</p>
{/if}
{#if form?.success === false}
<p>{form.message}</p>
{/if}
<label>
Username
<input type="text" name="username" minlength="3" maxlength="16" required />
</label>
<label>
Password
<input type="password" name="password" minlength="12" maxlength="128" required />
</label>
<button type="submit">Submit</button>
</form>

View File

@@ -0,0 +1,107 @@
export const load = async ({ fetch, cookies, url }) => {
const searchQuery = url.searchParams.get("website_search_query");
const sortBy = url.searchParams.get("website_sort");
const params = new URLSearchParams();
const baseFetchUrl = "http://localhost:3000/website";
if (searchQuery) {
params.append("website_title", `ilike.*${searchQuery}*`);
}
switch (sortBy) {
case "creation-time":
params.append("order", "created_at.desc");
break;
case "last-modified":
params.append("order", "last_modified_at.desc");
break;
case "title-a-to-z":
params.append("order", "title.asc");
break;
case "title-z-to-a":
params.append("order", "title.desc");
break;
}
const constructedFetchUrl = `${baseFetchUrl}?${params.toString()}`;
const websiteData = await fetch(constructedFetchUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${cookies.get("session_token")}`
}
});
const websites = await websiteData.json();
return {
websites
};
};
export const actions = {
createWebsite: async ({ request, fetch, cookies }) => {
const data = await request.formData();
const res = await fetch("http://localhost:3000/rpc/create_website", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${cookies.get("session_token")}`
},
body: JSON.stringify({
content_type: data.get("content-type"),
title: data.get("title")
})
});
if (!res.ok) {
const response = await res.json();
return { createWebsite: { success: false, message: response.message } };
}
return { createWebsite: { success: true, operation: "created" } };
},
updateWebsite: async ({ request, cookies, fetch }) => {
const data = await request.formData();
const res = await fetch(`http://localhost:3000/website?id=eq.${data.get("id")}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${cookies.get("session_token")}`
},
body: JSON.stringify({
title: data.get("title")
})
});
if (!res.ok) {
const response = await res.json();
return { updateWebsite: { success: false, message: response.message } };
}
return { updateWebsite: { success: true, operation: "updated" } };
},
deleteWebsite: async ({ request, cookies, fetch }) => {
const data = await request.formData();
const res = await fetch(`http://localhost:3000/website?id=eq.${data.get("id")}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${cookies.get("session_token")}`
}
});
if (!res.ok) {
const response = await res.json();
return { deleteWebsite: { success: false, message: response.message } };
}
return { deleteWebsite: { success: true, operation: "deleted" } };
}
};

View File

@@ -0,0 +1,103 @@
<script lang="ts">
import { enhance } from "$app/forms";
import DateTime from "$lib/components/DateTime.svelte";
const { form, data } = $props();
</script>
<section>
<h2>Create website</h2>
<form method="POST" action="?/createWebsite" use:enhance>
{#if form?.createWebsite?.success}
<p>Successfully created website</p>
{/if}
{#if form?.createWebsite?.success === false}
<p>{form.createWebsite.message}</p>
{/if}
<label>
Type
<select name="content-type">
<option value="Blog">Blog</option>
<option value="Docs">Docs</option>
</select>
</label>
<label>
Title
<input type="text" name="title" />
</label>
<button type="submit">Submit</button>
</form>
</section>
<section>
<h2>Your websites</h2>
{#if form?.deleteWebsite?.success}
<p>Successfully deleted website</p>
{/if}
{#if form?.deleteWebsite?.success === false}
<p>{form.deleteWebsite.message}</p>
{/if}
{#each data.websites as { id, content_type, title, created_at }}
<article>
<h3>
<a href="/website/{id}">{title}</a>
</h3>
<p>
<strong>Type:</strong>
{content_type}
</p>
<p>
<strong>Created at:</strong>
<DateTime date={created_at} />
</p>
<details>
<summary>Update</summary>
<form
method="POST"
action="?/updateWebsite"
use:enhance={() => {
return async ({ update }) => {
await update({ reset: false });
};
}}
>
{#if form?.updateWebsite?.success}
<p>Successfully updated website</p>
{/if}
{#if form?.updateWebsite?.success === false}
<p>{form.updateWebsite.message}</p>
{/if}
<input type="hidden" name="id" value={id} />
<label>
Title
<input type="text" name="title" value={title} />
</label>
<button type="submit">Submit</button>
</form>
</details>
<details>
<summary>Delete</summary>
<!-- TODO: Needs to be password protected -->
<form method="POST" action="?/deleteWebsite" use:enhance>
<input type="hidden" name="id" value={id} />
<button type="submit">Delete</button>
</form>
</details>
</article>
{/each}
</section>
<section>
<h2>Shared with you</h2>
</section>

View File

@@ -0,0 +1,36 @@
export const load = async ({ locals }) => {
return {
user: locals.user
};
};
export const actions = {
logout: async ({ cookies }) => {
cookies.delete("session_token", { path: "/" });
return { logout: { success: true } };
},
deleteAccount: async ({ request, fetch, cookies }) => {
const data = await request.formData();
const res = await fetch("http://localhost:3000/rpc/delete_account", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${cookies.get("session_token")}`
},
body: JSON.stringify({
password: data.get("password")
})
});
const response = await res.json();
if (!res.ok) {
return { deleteAccount: { success: false, message: response.message } };
}
cookies.delete("session_token", { path: "/" });
return { deleteAccount: { success: true } };
}
};

View File

@@ -0,0 +1,51 @@
<script lang="ts">
import { enhance } from "$app/forms";
const { data, form } = $props();
</script>
<section>
<h2>Overview</h2>
<p>
<strong>Username:</strong>
{data.user.username}
</p>
<p>
<strong>ID:</strong>
{data.user.id}
</p>
</section>
<section>
<h2>Logout</h2>
{#if form?.logout?.success}
<p>Successfully logged out</p>
{/if}
<form method="POST" action="?/logout" use:enhance>
<button type="submit">Logout</button>
</form>
</section>
<section>
<h2>Delete account</h2>
{#if form?.deleteAccount?.success}
<p>Account was deleted</p>
{/if}
{#if form?.deleteAccount?.success === false}
<p>{form.deleteAccount.message}</p>
{/if}
<form method="POST" action="?/deleteAccount" use:enhance>
<label>
Password
<input type="password" name="password" required />
</label>
<button type="submit">Delete account</button>
</form>
</section>

View File

@@ -1,5 +1,5 @@
export const load = async ({ params, fetch, cookies }) => { export const load = async ({ params, fetch, cookies }) => {
const websiteData = await fetch(`http://localhost:3000/cms_content?id=eq.${params.websiteId}`, { const websiteData = await fetch(`http://localhost:3000/content?id=eq.${params.websiteId}`, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -5,7 +5,7 @@ import { ALLOWED_MIME_TYPES } from "$lib/utils.js";
export const load = async ({ params, fetch, cookies, url }) => { export const load = async ({ params, fetch, cookies, url }) => {
const globalSettingsData = await fetch( const globalSettingsData = await fetch(
`http://localhost:3000/cms_settings?content_id=eq.${params.websiteId}&select=*,cms_media(*)`, `http://localhost:3000/settings?website_id=eq.${params.websiteId}&select=*,media(*)`,
{ {
method: "GET", method: "GET",
headers: { headers: {
@@ -17,7 +17,7 @@ export const load = async ({ params, fetch, cookies, url }) => {
); );
const headerData = await fetch( const headerData = await fetch(
`http://localhost:3000/cms_header?content_id=eq.${params.websiteId}&select=*,cms_media(*)`, `http://localhost:3000/header?website_id=eq.${params.websiteId}&select=*,media(*)`,
{ {
method: "GET", method: "GET",
headers: { headers: {
@@ -28,7 +28,7 @@ export const load = async ({ params, fetch, cookies, url }) => {
} }
); );
const homeData = await fetch(`http://localhost:3000/cms_home?content_id=eq.${params.websiteId}`, { const homeData = await fetch(`http://localhost:3000/home?website_id=eq.${params.websiteId}`, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -37,24 +37,21 @@ export const load = async ({ params, fetch, cookies, url }) => {
} }
}); });
const footerData = await fetch( const footerData = await fetch(`http://localhost:3000/footer?website_id=eq.${params.websiteId}`, {
`http://localhost:3000/cms_footer?content_id=eq.${params.websiteId}`, method: "GET",
{ headers: {
method: "GET", "Content-Type": "application/json",
headers: { Authorization: `Bearer ${cookies.get("session_token")}`,
"Content-Type": "application/json", Accept: "application/vnd.pgrst.object+json"
Authorization: `Bearer ${cookies.get("session_token")}`,
Accept: "application/vnd.pgrst.object+json"
}
} }
); });
const searchQuery = url.searchParams.get("article_search_query"); const searchQuery = url.searchParams.get("article_search_query");
const sortBy = url.searchParams.get("article_sort"); const sortBy = url.searchParams.get("article_sort");
const parameters = new URLSearchParams(); const parameters = new URLSearchParams();
const baseFetchUrl = `http://localhost:3000/cms_article?content_id=eq.${params.websiteId}&select=*,cms_media(*)`; const baseFetchUrl = `http://localhost:3000/article?website_id=eq.${params.websiteId}&select=*,media(*)`;
if (searchQuery) { if (searchQuery) {
parameters.append("title", `ilike.*${searchQuery}*`); parameters.append("title", `ilike.*${searchQuery}*`);
@@ -117,21 +114,18 @@ export const actions = {
return favicon; return favicon;
} }
const res = await fetch( const res = await fetch(`http://localhost:3000/settings?website_id=eq.${params.websiteId}`, {
`http://localhost:3000/cms_settings?content_id=eq.${params.websiteId}`, method: "PATCH",
{ headers: {
method: "PATCH", "Content-Type": "application/json",
headers: { Authorization: `Bearer ${cookies.get("session_token")}`
"Content-Type": "application/json", },
Authorization: `Bearer ${cookies.get("session_token")}` body: JSON.stringify({
}, accent_color_light_theme: data.get("accent-color-light"),
body: JSON.stringify({ accent_color_dark_theme: data.get("accent-color-dark"),
accent_color_light_theme: data.get("accent-color-light"), favicon_image: favicon?.content
accent_color_dark_theme: data.get("accent-color-dark"), })
favicon_image: favicon?.content });
})
}
);
if (!res.ok) { if (!res.ok) {
const response = await res.json(); const response = await res.json();
@@ -160,7 +154,7 @@ export const actions = {
return logo; return logo;
} }
const res = await fetch(`http://localhost:3000/cms_header?content_id=eq.${params.websiteId}`, { const res = await fetch(`http://localhost:3000/header?website_id=eq.${params.websiteId}`, {
method: "PATCH", method: "PATCH",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -187,7 +181,7 @@ export const actions = {
updateHome: async ({ request, fetch, cookies, params }) => { updateHome: async ({ request, fetch, cookies, params }) => {
const data = await request.formData(); const data = await request.formData();
const res = await fetch(`http://localhost:3000/cms_home?content_id=eq.${params.websiteId}`, { const res = await fetch(`http://localhost:3000/home?website_id=eq.${params.websiteId}`, {
method: "PATCH", method: "PATCH",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -208,7 +202,7 @@ export const actions = {
updateFooter: async ({ request, fetch, cookies, params }) => { updateFooter: async ({ request, fetch, cookies, params }) => {
const data = await request.formData(); const data = await request.formData();
const res = await fetch(`http://localhost:3000/cms_footer?content_id=eq.${params.websiteId}`, { const res = await fetch(`http://localhost:3000/footer?website_id=eq.${params.websiteId}`, {
method: "PATCH", method: "PATCH",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -233,14 +227,14 @@ export const actions = {
createArticle: async ({ request, fetch, cookies, params }) => { createArticle: async ({ request, fetch, cookies, params }) => {
const data = await request.formData(); const data = await request.formData();
const res = await fetch("http://localhost:3000/cms_article", { const res = await fetch("http://localhost:3000/article", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${cookies.get("session_token")}` Authorization: `Bearer ${cookies.get("session_token")}`
}, },
body: JSON.stringify({ body: JSON.stringify({
content_id: params.websiteId, website_id: params.websiteId,
title: data.get("title") title: data.get("title")
}) })
}); });
@@ -268,7 +262,7 @@ export const actions = {
return cover; return cover;
} }
const res = await fetch(`http://localhost:3000/cms_article?id=eq.${data.get("article-id")}`, { const res = await fetch(`http://localhost:3000/article?id=eq.${data.get("article-id")}`, {
method: "PATCH", method: "PATCH",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -294,7 +288,7 @@ export const actions = {
deleteArticle: async ({ request, fetch, cookies }) => { deleteArticle: async ({ request, fetch, cookies }) => {
const data = await request.formData(); const data = await request.formData();
const res = await fetch(`http://localhost:3000/cms_article?id=eq.${data.get("article-id")}`, { const res = await fetch(`http://localhost:3000/article?id=eq.${data.get("article-id")}`, {
method: "DELETE", method: "DELETE",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -348,7 +342,7 @@ const handleFileUpload = async (
const relativePath = relative(join(process.cwd(), "static"), filepath); const relativePath = relative(join(process.cwd(), "static"), filepath);
const res = await customFetch("http://localhost:3000/cms_media", { const res = await customFetch("http://localhost:3000/media", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -357,7 +351,7 @@ const handleFileUpload = async (
Accept: "application/vnd.pgrst.object+json" Accept: "application/vnd.pgrst.object+json"
}, },
body: JSON.stringify({ body: JSON.stringify({
content_id: contentId, website_id: contentId,
user_id: userId, user_id: userId,
original_name: file.name, original_name: file.name,
file_system_path: relativePath file_system_path: relativePath

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { page } from "$app/stores";
const { data, children } = $props();
</script>
<nav>
<a href="/">archtika</a>
{#if data.user}
<a href="/account">Account</a>
{:else}
<a href="/register">Register</a>
<a href="/login">Login</a>
{/if}
</nav>
<header>
<h1>{$page.url.pathname}</h1>
</header>
<main>
{@render children()}
</main>
<footer>
<p>
<small>archtika</small>
</p>
</footer>

View File

@@ -1,11 +0,0 @@
<section>
<h2>Create website</h2>
</section>
<section>
<h2>Your websites</h2>
</section>
<section>
<h2>Shared with you</h2>
</section>