mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 19:01:35 +01:00
Add ability to export articles, track publications in json file on NGINX, fix vulnerabilities and refactor
This commit is contained in:
@@ -4,6 +4,12 @@
|
||||
const dateObject = new Date(date);
|
||||
|
||||
const calcTimeAgo = (date: Date) => {
|
||||
const secondsElapsed = (date.getTime() - Date.now()) / 1000;
|
||||
|
||||
if (Math.abs(secondsElapsed) < 1) {
|
||||
return "Just now";
|
||||
}
|
||||
|
||||
const formatter = new Intl.RelativeTimeFormat("en");
|
||||
const ranges = [
|
||||
["years", 60 * 60 * 24 * 365],
|
||||
@@ -14,7 +20,6 @@
|
||||
["minutes", 60],
|
||||
["seconds", 1]
|
||||
] as const;
|
||||
const secondsElapsed = (date.getTime() - Date.now()) / 1000;
|
||||
|
||||
for (const [rangeType, rangeVal] of ranges) {
|
||||
if (rangeVal < Math.abs(secondsElapsed)) {
|
||||
|
||||
@@ -25,15 +25,7 @@
|
||||
previewElement.scrollTop = (textareaScrollTop.value / 100) * scrollHeight;
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
"settings",
|
||||
"articles",
|
||||
"categories",
|
||||
"collaborators",
|
||||
"legal-information",
|
||||
"publish",
|
||||
"logs"
|
||||
];
|
||||
const tabs = ["settings", "articles", "categories", "collaborators", "publish", "logs"];
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="toggle-mobile-preview" hidden />
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
*
|
||||
* This file was automatically generated by pg-to-ts v.4.1.1
|
||||
* $ pg-to-ts generate -c postgres://username:password@localhost:15432/archtika -t article -t change_log -t collab -t docs_category -t domain_prefix -t footer -t header -t home -t legal_information -t media -t settings -t user -t website -s internal
|
||||
* $ pg-to-ts generate -c postgres://username:password@localhost:15432/archtika -t article -t change_log -t collab -t docs_category -t footer -t header -t home -t media -t settings -t user -t website -s internal
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -206,34 +206,6 @@ const docs_category = {
|
||||
$input: null as unknown as DocsCategoryInput
|
||||
} as const;
|
||||
|
||||
// Table domain_prefix
|
||||
export interface DomainPrefix {
|
||||
website_id: string;
|
||||
prefix: string;
|
||||
created_at: string;
|
||||
last_modified_at: string;
|
||||
last_modified_by: string | null;
|
||||
}
|
||||
export interface DomainPrefixInput {
|
||||
website_id: string;
|
||||
prefix: string;
|
||||
created_at?: string;
|
||||
last_modified_at?: string;
|
||||
last_modified_by?: string | null;
|
||||
}
|
||||
const domain_prefix = {
|
||||
tableName: "domain_prefix",
|
||||
columns: ["website_id", "prefix", "created_at", "last_modified_at", "last_modified_by"],
|
||||
requiredForInsert: ["website_id", "prefix"],
|
||||
primaryKey: "website_id",
|
||||
foreignKeys: {
|
||||
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
||||
last_modified_by: { table: "user", column: "id", $type: null as unknown as User }
|
||||
},
|
||||
$type: null as unknown as DomainPrefix,
|
||||
$input: null as unknown as DomainPrefixInput
|
||||
} as const;
|
||||
|
||||
// Table footer
|
||||
export interface Footer {
|
||||
website_id: string;
|
||||
@@ -332,34 +304,6 @@ const home = {
|
||||
$input: null as unknown as HomeInput
|
||||
} as const;
|
||||
|
||||
// Table legal_information
|
||||
export interface LegalInformation {
|
||||
website_id: string;
|
||||
main_content: string;
|
||||
created_at: string;
|
||||
last_modified_at: string;
|
||||
last_modified_by: string | null;
|
||||
}
|
||||
export interface LegalInformationInput {
|
||||
website_id: string;
|
||||
main_content: string;
|
||||
created_at?: string;
|
||||
last_modified_at?: string;
|
||||
last_modified_by?: string | null;
|
||||
}
|
||||
const legal_information = {
|
||||
tableName: "legal_information",
|
||||
columns: ["website_id", "main_content", "created_at", "last_modified_at", "last_modified_by"],
|
||||
requiredForInsert: ["website_id", "main_content"],
|
||||
primaryKey: "website_id",
|
||||
foreignKeys: {
|
||||
website_id: { table: "website", column: "id", $type: null as unknown as Website },
|
||||
last_modified_by: { table: "user", column: "id", $type: null as unknown as User }
|
||||
},
|
||||
$type: null as unknown as LegalInformation,
|
||||
$input: null as unknown as LegalInformationInput
|
||||
} as const;
|
||||
|
||||
// Table media
|
||||
export interface Media {
|
||||
id: string;
|
||||
@@ -469,8 +413,8 @@ export interface Website {
|
||||
user_id: string;
|
||||
content_type: string;
|
||||
title: string;
|
||||
slug: string | null;
|
||||
max_storage_size: number;
|
||||
is_published: boolean;
|
||||
created_at: string;
|
||||
last_modified_at: string;
|
||||
last_modified_by: string | null;
|
||||
@@ -480,8 +424,8 @@ export interface WebsiteInput {
|
||||
user_id?: string;
|
||||
content_type: string;
|
||||
title: string;
|
||||
slug?: string | null;
|
||||
max_storage_size?: number;
|
||||
is_published?: boolean;
|
||||
created_at?: string;
|
||||
last_modified_at?: string;
|
||||
last_modified_by?: string | null;
|
||||
@@ -493,8 +437,8 @@ const website = {
|
||||
"user_id",
|
||||
"content_type",
|
||||
"title",
|
||||
"slug",
|
||||
"max_storage_size",
|
||||
"is_published",
|
||||
"created_at",
|
||||
"last_modified_at",
|
||||
"last_modified_by"
|
||||
@@ -526,10 +470,6 @@ export interface TableTypes {
|
||||
select: DocsCategory;
|
||||
input: DocsCategoryInput;
|
||||
};
|
||||
domain_prefix: {
|
||||
select: DomainPrefix;
|
||||
input: DomainPrefixInput;
|
||||
};
|
||||
footer: {
|
||||
select: Footer;
|
||||
input: FooterInput;
|
||||
@@ -542,10 +482,6 @@ export interface TableTypes {
|
||||
select: Home;
|
||||
input: HomeInput;
|
||||
};
|
||||
legal_information: {
|
||||
select: LegalInformation;
|
||||
input: LegalInformationInput;
|
||||
};
|
||||
media: {
|
||||
select: Media;
|
||||
input: MediaInput;
|
||||
@@ -569,11 +505,9 @@ export const tables = {
|
||||
change_log,
|
||||
collab,
|
||||
docs_category,
|
||||
domain_prefix,
|
||||
footer,
|
||||
header,
|
||||
home,
|
||||
legal_information,
|
||||
media,
|
||||
settings,
|
||||
user,
|
||||
|
||||
@@ -19,11 +19,13 @@ export const apiRequest = async (
|
||||
body?: any;
|
||||
successMessage?: string;
|
||||
returnData?: boolean;
|
||||
noJSONTransform?: boolean;
|
||||
} = {
|
||||
headers: {},
|
||||
body: undefined,
|
||||
successMessage: "Operation was successful",
|
||||
returnData: false
|
||||
returnData: false,
|
||||
noJSONTransform: false
|
||||
}
|
||||
) => {
|
||||
const headers = {
|
||||
@@ -48,7 +50,7 @@ export const apiRequest = async (
|
||||
return {
|
||||
success: true,
|
||||
message: options.successMessage,
|
||||
data: method === "HEAD" ? response : await response.json()
|
||||
data: method === "HEAD" || options.noJSONTransform ? response : await response.json()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
64
web-app/src/lib/templates/Article.svelte
Normal file
64
web-app/src/lib/templates/Article.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { md, type WebsiteOverview } from "$lib/utils";
|
||||
import type { Article } from "$lib/db-schema";
|
||||
import Head from "$lib/templates/Head.svelte";
|
||||
import Nav from "$lib/templates/Nav.svelte";
|
||||
import Footer from "$lib/templates/Footer.svelte";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
article,
|
||||
apiUrl,
|
||||
websiteUrl
|
||||
}: {
|
||||
websiteOverview: WebsiteOverview;
|
||||
article: Article;
|
||||
apiUrl: string;
|
||||
websiteUrl: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Head
|
||||
{websiteOverview}
|
||||
nestingLevel={1}
|
||||
{apiUrl}
|
||||
title={article.title}
|
||||
slug={article.slug as string}
|
||||
metaDescription={article.meta_description}
|
||||
{websiteUrl}
|
||||
/>
|
||||
|
||||
<Nav
|
||||
{websiteOverview}
|
||||
isDocsTemplate={websiteOverview.content_type === "Docs"}
|
||||
isIndexPage={false}
|
||||
{apiUrl}
|
||||
/>
|
||||
|
||||
<header>
|
||||
<div class="container">
|
||||
{#if websiteOverview.content_type === "Blog"}
|
||||
<hgroup>
|
||||
{#if article.publication_date}
|
||||
<p>{article.publication_date}</p>
|
||||
{/if}
|
||||
<h1>{article.title}</h1>
|
||||
</hgroup>
|
||||
{#if article.cover_image}
|
||||
<img src="{apiUrl}/rpc/retrieve_file?id={article.cover_image}" alt="" />
|
||||
{/if}
|
||||
{:else}
|
||||
<h1>{article.title}</h1>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if article.main_content}
|
||||
<main>
|
||||
<div class="container">
|
||||
{@html md(article.main_content)}
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<Footer {websiteOverview} />
|
||||
11
web-app/src/lib/templates/Footer.svelte
Normal file
11
web-app/src/lib/templates/Footer.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { type WebsiteOverview, md } from "../utils";
|
||||
|
||||
const { websiteOverview }: { websiteOverview: WebsiteOverview } = $props();
|
||||
</script>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
{@html md(websiteOverview.footer.additional_text, false)}
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { type WebsiteOverview } from "../../utils";
|
||||
import { type WebsiteOverview } from "../utils";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
@@ -22,7 +22,7 @@
|
||||
const constructedTitle =
|
||||
websiteOverview.title === title ? title : `${websiteOverview.title} | ${title}`;
|
||||
|
||||
let ogUrl = `${websiteUrl.replace(/\/$/, "")}${nestingLevel === 0 ? (websiteOverview.title === title ? "" : `/${slug}`) : `/articles/${slug}`}`;
|
||||
const ogUrl = `${websiteUrl.replace(/\/$/, "")}${nestingLevel === 0 ? (websiteOverview.title === title ? "" : `/${slug}`) : `/articles/${slug}`}`;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -1,18 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Head from "../common/Head.svelte";
|
||||
import Nav from "../common/Nav.svelte";
|
||||
import Footer from "../common/Footer.svelte";
|
||||
import { md, type WebsiteOverview } from "$lib/utils";
|
||||
import Head from "$lib/templates/Head.svelte";
|
||||
import Nav from "$lib/templates/Nav.svelte";
|
||||
import Footer from "$lib/templates/Footer.svelte";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
apiUrl,
|
||||
isLegalPage,
|
||||
websiteUrl
|
||||
}: {
|
||||
websiteOverview: WebsiteOverview;
|
||||
apiUrl: string;
|
||||
isLegalPage: boolean;
|
||||
websiteUrl: string;
|
||||
} = $props();
|
||||
|
||||
@@ -27,28 +25,29 @@
|
||||
{websiteOverview}
|
||||
nestingLevel={0}
|
||||
{apiUrl}
|
||||
title={isLegalPage ? "Legal information" : websiteOverview.title}
|
||||
title={websiteOverview.title}
|
||||
metaDescription={websiteOverview.home.meta_description}
|
||||
{websiteUrl}
|
||||
/>
|
||||
|
||||
<Nav {websiteOverview} isDocsTemplate={false} isIndexPage={true} {isLegalPage} {apiUrl} />
|
||||
<Nav
|
||||
{websiteOverview}
|
||||
isDocsTemplate={websiteOverview.content_type === "Docs"}
|
||||
isIndexPage={true}
|
||||
{apiUrl}
|
||||
/>
|
||||
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>{isLegalPage ? "Legal information" : websiteOverview.title}</h1>
|
||||
<h1>{websiteOverview.title}</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="container">
|
||||
{@html md(
|
||||
isLegalPage
|
||||
? (websiteOverview.legal_information?.main_content ?? "")
|
||||
: websiteOverview.home.main_content,
|
||||
false
|
||||
)}
|
||||
{#if websiteOverview.article.length > 0 && !isLegalPage}
|
||||
{@html md(websiteOverview.home.main_content, false)}
|
||||
|
||||
{#if websiteOverview.article.length > 0 && websiteOverview.content_type === "Blog"}
|
||||
<section class="articles" id="articles">
|
||||
<h2>
|
||||
<a href="#articles">Articles</a>
|
||||
@@ -76,4 +75,4 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer {websiteOverview} isIndexPage={true} />
|
||||
<Footer {websiteOverview} />
|
||||
@@ -1,19 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { type WebsiteOverview } from "../../utils";
|
||||
import type { Article } from "../../db-schema";
|
||||
import { type WebsiteOverview } from "../utils";
|
||||
import type { Article } from "../db-schema";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
isDocsTemplate,
|
||||
isIndexPage,
|
||||
apiUrl,
|
||||
isLegalPage
|
||||
apiUrl
|
||||
}: {
|
||||
websiteOverview: WebsiteOverview;
|
||||
isDocsTemplate: boolean;
|
||||
isIndexPage: boolean;
|
||||
apiUrl: string;
|
||||
isLegalPage?: boolean;
|
||||
} = $props();
|
||||
|
||||
const categorizedArticles = Object.fromEntries(
|
||||
@@ -72,17 +70,14 @@
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
<svelte:element
|
||||
this={isIndexPage && !isLegalPage ? "span" : "a"}
|
||||
href={`${isLegalPage ? "./" : "../"}`}
|
||||
>
|
||||
<svelte:element this={isIndexPage ? "span" : "a"} href={`${isIndexPage ? "./" : "../"}`}>
|
||||
{#if websiteOverview.header.logo_type === "text"}
|
||||
<strong>{websiteOverview.header.logo_text}</strong>
|
||||
{:else}
|
||||
<img
|
||||
src="{apiUrl}/rpc/retrieve_file?id={websiteOverview.header.logo_image}"
|
||||
width="24"
|
||||
height="24"
|
||||
width="32"
|
||||
height="32"
|
||||
alt=""
|
||||
/>
|
||||
{/if}
|
||||
@@ -1,51 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Head from "../common/Head.svelte";
|
||||
import Nav from "../common/Nav.svelte";
|
||||
import Footer from "../common/Footer.svelte";
|
||||
import { type WebsiteOverview, md } from "../../utils";
|
||||
import type { Article } from "../../db-schema";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
article,
|
||||
apiUrl,
|
||||
websiteUrl
|
||||
}: { websiteOverview: WebsiteOverview; article: Article; apiUrl: string; websiteUrl: string } =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<Head
|
||||
{websiteOverview}
|
||||
nestingLevel={1}
|
||||
{apiUrl}
|
||||
title={article.title}
|
||||
slug={article.slug as string}
|
||||
metaDescription={article.meta_description}
|
||||
{websiteUrl}
|
||||
/>
|
||||
|
||||
<Nav {websiteOverview} isDocsTemplate={false} isIndexPage={false} {apiUrl} />
|
||||
|
||||
<header>
|
||||
<div class="container">
|
||||
<hgroup>
|
||||
{#if article.publication_date}
|
||||
<p>{article.publication_date}</p>
|
||||
{/if}
|
||||
<h1>{article.title}</h1>
|
||||
</hgroup>
|
||||
{#if article.cover_image}
|
||||
<img src="{apiUrl}/rpc/retrieve_file?id={article.cover_image}" alt="" />
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if article.main_content}
|
||||
<main>
|
||||
<div class="container">
|
||||
{@html md(article.main_content)}
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<Footer {websiteOverview} isIndexPage={false} />
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { type WebsiteOverview, md } from "../../utils";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
isIndexPage
|
||||
}: { websiteOverview: WebsiteOverview; isIndexPage: boolean } = $props();
|
||||
</script>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<small>
|
||||
{@html md(websiteOverview.footer.additional_text, false).replace(
|
||||
"!!legal",
|
||||
`<a href="${isIndexPage ? "./legal-information" : "../legal-information"}">Legal information</a>`
|
||||
)}
|
||||
</small>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Head from "../common/Head.svelte";
|
||||
import Nav from "../common/Nav.svelte";
|
||||
import Footer from "../common/Footer.svelte";
|
||||
import { md, type WebsiteOverview } from "../../utils";
|
||||
import type { Article } from "../../db-schema";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
article,
|
||||
apiUrl,
|
||||
websiteUrl
|
||||
}: { websiteOverview: WebsiteOverview; article: Article; apiUrl: string; websiteUrl: string } =
|
||||
$props();
|
||||
</script>
|
||||
|
||||
<Head
|
||||
{websiteOverview}
|
||||
nestingLevel={1}
|
||||
{apiUrl}
|
||||
title={article.title}
|
||||
slug={article.slug as string}
|
||||
metaDescription={article.meta_description}
|
||||
{websiteUrl}
|
||||
/>
|
||||
|
||||
<Nav {websiteOverview} isDocsTemplate={true} isIndexPage={false} {apiUrl} />
|
||||
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>{article.title}</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if article.main_content}
|
||||
<main>
|
||||
<div class="container">
|
||||
{@html md(article.main_content)}
|
||||
</div>
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<Footer {websiteOverview} isIndexPage={false} />
|
||||
@@ -1,48 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Head from "../common/Head.svelte";
|
||||
import Nav from "../common/Nav.svelte";
|
||||
import Footer from "../common/Footer.svelte";
|
||||
import { md, type WebsiteOverview } from "../../utils";
|
||||
|
||||
const {
|
||||
websiteOverview,
|
||||
apiUrl,
|
||||
isLegalPage,
|
||||
websiteUrl
|
||||
}: {
|
||||
websiteOverview: WebsiteOverview;
|
||||
apiUrl: string;
|
||||
isLegalPage: boolean;
|
||||
websiteUrl: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Head
|
||||
{websiteOverview}
|
||||
nestingLevel={0}
|
||||
{apiUrl}
|
||||
title={isLegalPage ? "Legal information" : websiteOverview.title}
|
||||
metaDescription={websiteOverview.home.meta_description}
|
||||
{websiteUrl}
|
||||
/>
|
||||
|
||||
<Nav {websiteOverview} isDocsTemplate={true} isIndexPage={true} {isLegalPage} {apiUrl} />
|
||||
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>{isLegalPage ? "Legal information" : websiteOverview.title}</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="container">
|
||||
{@html md(
|
||||
isLegalPage
|
||||
? (websiteOverview.legal_information?.main_content ?? "")
|
||||
: websiteOverview.home.main_content,
|
||||
false
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer {websiteOverview} isIndexPage={true} />
|
||||
@@ -11,8 +11,7 @@ import type {
|
||||
Footer,
|
||||
Article,
|
||||
DocsCategory,
|
||||
LegalInformation,
|
||||
DomainPrefix
|
||||
User
|
||||
} from "$lib/db-schema";
|
||||
import type { SubmitFunction } from "@sveltejs/kit";
|
||||
import { sending } from "./runes.svelte";
|
||||
@@ -221,6 +220,5 @@ export interface WebsiteOverview extends Website {
|
||||
home: Home;
|
||||
footer: Footer;
|
||||
article: (Article & { docs_category: DocsCategory | null })[];
|
||||
legal_information?: LegalInformation;
|
||||
domain_prefix?: DomainPrefix;
|
||||
user: User;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user