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 @@
>