diff --git a/rest-api/db/migrations/20240719071602_main_tables.sql b/rest-api/db/migrations/20240719071602_main_tables.sql index 987297c..3d45994 100644 --- a/rest-api/db/migrations/20240719071602_main_tables.sql +++ b/rest-api/db/migrations/20240719071602_main_tables.sql @@ -24,9 +24,9 @@ CREATE TABLE internal.website ( 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, content_type VARCHAR(10) CHECK (content_type IN ('Blog', 'Docs')) NOT NULL, - title VARCHAR(50) NOT NULL, + title VARCHAR(50) NOT NULL CHECK (trim(title) <> ''), created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), - last_modified_at TIMESTAMPTZ, + last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL ); @@ -44,44 +44,48 @@ CREATE TABLE internal.settings ( 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', favicon_image UUID REFERENCES internal.media(id) ON DELETE SET NULL, - last_modified_at TIMESTAMPTZ, + last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL ); CREATE TABLE internal.header ( 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_text VARCHAR(255), + logo_text VARCHAR(50), logo_image UUID REFERENCES internal.media(id) ON DELETE SET NULL, - last_modified_at TIMESTAMPTZ, - last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL + last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), + last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL, + CONSTRAINT logo_content_check CHECK ( + (logo_type = 'text' AND logo_text IS NOT NULL AND trim(logo_text) <> '') OR + (logo_type = 'image' AND logo_image IS NOT NULL) + ) ); CREATE TABLE internal.home ( website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE, - main_content TEXT, - last_modified_at TIMESTAMPTZ, + 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 TABLE internal.article ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), website_id UUID REFERENCES internal.website(id) ON DELETE CASCADE NOT NULL, - title VARCHAR(255) NOT NULL, - meta_description VARCHAR(500), - meta_author VARCHAR(255), + title VARCHAR(100) NOT NULL CHECK (trim(title) <> ''), + meta_description VARCHAR(250) NOT NULL CHECK (trim(meta_description) <> ''), + meta_author VARCHAR(100) NOT NULL CHECK (trim(meta_author) <> ''), cover_image UUID REFERENCES internal.media(id) ON DELETE SET NULL, publication_date DATE NOT NULL DEFAULT CURRENT_DATE, - main_content TEXT, + main_content TEXT NOT NULL CHECK (trim(main_content) <> ''), created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), - last_modified_at TIMESTAMPTZ, + last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL ); CREATE TABLE internal.footer ( website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE, - additional_text VARCHAR(255), - last_modified_at TIMESTAMPTZ, + additional_text VARCHAR(250) NOT NULL CHECK (trim(additional_text) <> ''), + last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL ); @@ -90,7 +94,7 @@ CREATE TABLE internal.collab ( user_id UUID REFERENCES internal.user(id) ON DELETE CASCADE, permission_level INTEGER CHECK (permission_level IN (10, 20, 30)) NOT NULL DEFAULT 10, added_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), - last_modified_at TIMESTAMPTZ, + last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL, PRIMARY KEY (website_id, user_id) ); diff --git a/rest-api/db/migrations/20240805132306_last_modified_triggers.sql b/rest-api/db/migrations/20240805132306_last_modified_triggers.sql new file mode 100644 index 0000000..98a1cea --- /dev/null +++ b/rest-api/db/migrations/20240805132306_last_modified_triggers.sql @@ -0,0 +1,55 @@ +-- migrate:up +CREATE FUNCTION update_last_modified() +RETURNS TRIGGER AS $$ +BEGIN + NEW.last_modified_at = CLOCK_TIMESTAMP(); + NEW.last_modified_by = (current_setting('request.jwt.claims', true)::JSON->>'user_id')::UUID; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER update_website_last_modified +BEFORE UPDATE ON internal.website +FOR EACH ROW +EXECUTE FUNCTION update_last_modified(); + +CREATE TRIGGER update_settings_last_modified +BEFORE UPDATE ON internal.settings +FOR EACH ROW +EXECUTE FUNCTION update_last_modified(); + +CREATE TRIGGER update_header_last_modified +BEFORE UPDATE ON internal.header +FOR EACH ROW +EXECUTE FUNCTION update_last_modified(); + +CREATE TRIGGER update_home_last_modified +BEFORE UPDATE ON internal.home +FOR EACH ROW +EXECUTE FUNCTION update_last_modified(); + +CREATE TRIGGER update_article_last_modified +BEFORE UPDATE ON internal.article +FOR EACH ROW +EXECUTE FUNCTION update_last_modified(); + +CREATE TRIGGER update_footer_last_modified +BEFORE UPDATE ON internal.footer +FOR EACH ROW +EXECUTE FUNCTION update_last_modified(); + +CREATE TRIGGER update_collab_last_modified +BEFORE UPDATE ON internal.collab +FOR EACH ROW +EXECUTE FUNCTION update_last_modified(); + +-- migrate:down +DROP TRIGGER update_website_last_modified ON internal.website; +DROP TRIGGER update_settings_last_modified ON internal.settings; +DROP TRIGGER update_header_last_modified ON internal.header; +DROP TRIGGER update_home_last_modified ON internal.home; +DROP TRIGGER update_article_last_modified ON internal.article; +DROP TRIGGER update_footer_last_modified ON internal.footer; +DROP TRIGGER update_collab_last_modified ON internal.collab; + +DROP FUNCTION update_last_modified(); \ No newline at end of file diff --git a/web-app/src/routes/(authenticated)/+page.svelte b/web-app/src/routes/(authenticated)/+page.svelte index 8c5a54a..8ec8870 100644 --- a/web-app/src/routes/(authenticated)/+page.svelte +++ b/web-app/src/routes/(authenticated)/+page.svelte @@ -37,7 +37,7 @@ @@ -112,7 +112,14 @@ diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte index 0b8ae74..6769e80 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte @@ -33,6 +33,8 @@ type="color" name="accent-color-light" value={data.globalSettings.accent_color_light_theme} + pattern="\S(.*\S)?" + required /> @@ -99,7 +114,7 @@ > @@ -120,7 +135,9 @@ > diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte index 2de4e75..8be8554 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/+page.svelte @@ -35,7 +35,7 @@ > diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte index c173d95..d81f73a 100644 --- a/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte +++ b/web-app/src/routes/(authenticated)/website/[websiteId]/articles/[articleId]/+page.svelte @@ -30,19 +30,35 @@ >