mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 10:51:36 +01:00
Add additional validation measures and create triggers for last modified columns
This commit is contained in:
@@ -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)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user