Add additional validation measures and create triggers for last modified columns

This commit is contained in:
Thilo Hohlt
2024-08-05 16:03:07 +02:00
parent fa500cf376
commit 62db2776a7
6 changed files with 127 additions and 28 deletions

View File

@@ -24,9 +24,9 @@ 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,
title VARCHAR(50) NOT NULL, title VARCHAR(50) NOT NULL CHECK (trim(title) <> ''),
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), 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 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_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.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 NOT NULL DEFAULT CLOCK_TIMESTAMP(),
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.header ( CREATE TABLE internal.header (
website_id UUID PRIMARY KEY REFERENCES internal.website(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(50),
logo_image UUID REFERENCES internal.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 NOT NULL DEFAULT CLOCK_TIMESTAMP(),
last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL 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 ( CREATE TABLE internal.home (
website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
main_content TEXT, main_content TEXT NOT NULL CHECK (trim(main_content) <> ''),
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 last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.article ( CREATE TABLE internal.article (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
website_id UUID REFERENCES internal.website(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(100) NOT NULL CHECK (trim(title) <> ''),
meta_description VARCHAR(500), meta_description VARCHAR(250) NOT NULL CHECK (trim(meta_description) <> ''),
meta_author VARCHAR(255), meta_author VARCHAR(100) NOT NULL CHECK (trim(meta_author) <> ''),
cover_image UUID REFERENCES internal.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 NOT NULL CHECK (trim(main_content) <> ''),
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(), 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 last_modified_by UUID REFERENCES internal.user(id) ON DELETE SET NULL
); );
CREATE TABLE internal.footer ( CREATE TABLE internal.footer (
website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE, website_id UUID PRIMARY KEY REFERENCES internal.website(id) ON DELETE CASCADE,
additional_text VARCHAR(255), additional_text VARCHAR(250) NOT NULL CHECK (trim(additional_text) <> ''),
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 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, 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 NOT NULL DEFAULT CLOCK_TIMESTAMP(),
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 (website_id, user_id) PRIMARY KEY (website_id, user_id)
); );

View File

@@ -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();

View File

@@ -37,7 +37,7 @@
</label> </label>
<label> <label>
Title: Title:
<input type="text" name="title" /> <input type="text" name="title" maxlength="50" pattern="\S(.*\S)?" required />
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>
@@ -112,7 +112,14 @@
<input type="hidden" name="id" value={id} /> <input type="hidden" name="id" value={id} />
<label> <label>
Title Title
<input type="text" name="title" value={title} /> <input
type="text"
name="title"
value={title}
maxlength="50"
pattern="\S(.*\S)?"
required
/>
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>

View File

@@ -33,6 +33,8 @@
type="color" type="color"
name="accent-color-light" name="accent-color-light"
value={data.globalSettings.accent_color_light_theme} value={data.globalSettings.accent_color_light_theme}
pattern="\S(.*\S)?"
required
/> />
</label> </label>
<label> <label>
@@ -41,6 +43,8 @@
type="color" type="color"
name="accent-color-dark" name="accent-color-dark"
value={data.globalSettings.accent_color_dark_theme} value={data.globalSettings.accent_color_dark_theme}
pattern="\S(.*\S)?"
required
/> />
</label> </label>
<label> <label>
@@ -74,11 +78,22 @@
</label> </label>
<label> <label>
Logo text: Logo text:
<input type="text" name="logo-text" value={data.header.logo_text} /> <input
type="text"
name="logo-text"
value={data.header.logo_text}
pattern="\S(.*\S)?"
required={data.header.logo_type === "text"}
/>
</label> </label>
<label> <label>
Logo image: Logo image:
<input type="file" name="logo-image" accept={ALLOWED_MIME_TYPES.join(", ")} /> <input
type="file"
name="logo-image"
accept={ALLOWED_MIME_TYPES.join(", ")}
required={data.header.logo_type === "image"}
/>
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>
@@ -99,7 +114,7 @@
> >
<label> <label>
Main content: Main content:
<textarea name="main-content" rows="20">{data.home.main_content}</textarea> <textarea name="main-content" rows="20" required>{data.home.main_content}</textarea>
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>
@@ -120,7 +135,9 @@
> >
<label> <label>
Additional text: Additional text:
<textarea name="additional-text" rows="5">{data.footer.additional_text}</textarea> <textarea name="additional-text" rows="5" maxlength="250" required
>{data.footer.additional_text}</textarea
>
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>

View File

@@ -35,7 +35,7 @@
> >
<label> <label>
Title: Title:
<input type="text" name="title" /> <input type="text" name="title" pattern="\S(.*\S)?" maxlength="100" required />
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>

View File

@@ -30,19 +30,35 @@
> >
<label> <label>
Title: Title:
<input type="text" name="title" value={data.article.title} /> <input
type="text"
name="title"
value={data.article.title}
pattern="\S(.*\S)?"
maxlength="100"
required
/>
</label> </label>
<label> <label>
Description: Description:
<textarea name="description" rows="5">{data.article.meta_description}</textarea> <textarea name="description" rows="5" maxlength="250" required
>{data.article.meta_description}</textarea
>
</label> </label>
<label> <label>
Author: Author:
<input type="text" name="author" value={data.article.meta_author} /> <input
type="text"
name="author"
value={data.article.meta_author}
pattern="\S(.*\S)?"
maxlength="100"
required
/>
</label> </label>
<label> <label>
Publication date: Publication date:
<input type="date" name="publication-date" value={data.article.publication_date} /> <input type="date" name="publication-date" value={data.article.publication_date} required />
</label> </label>
<label> <label>
Cover image: Cover image:
@@ -50,7 +66,7 @@
</label> </label>
<label> <label>
Main content: Main content:
<textarea name="main-content" rows="20">{data.article.main_content}</textarea> <textarea name="main-content" rows="20" required>{data.article.main_content}</textarea>
</label> </label>
<button type="submit">Submit</button> <button type="submit">Submit</button>