mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 02:41:35 +01:00
Add custom domain prefixes and option to disable user registration
This commit is contained in:
@@ -15,5 +15,6 @@
|
||||
acmeEmail = "thilo.hohlt@tutanota.com";
|
||||
dnsProvider = "porkbun";
|
||||
dnsEnvironmentFile = /var/lib/porkbun.env;
|
||||
disableRegistration = true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +75,12 @@ in
|
||||
default = null;
|
||||
description = "API secrets for the DNS-01 challenge (required for wildcard domains).";
|
||||
};
|
||||
|
||||
disableRegistration = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "By default any user can create an account. That behavior can be disabled by using this option.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
@@ -125,7 +131,7 @@ in
|
||||
};
|
||||
|
||||
script = ''
|
||||
BODY_SIZE_LIMIT=Infinity ORIGIN=https://${cfg.domain} PORT=${toString cfg.webAppPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app
|
||||
REGISTRATION_IS_DISABLED=${toString cfg.disableRegistration} BODY_SIZE_LIMIT=Infinity ORIGIN=https://${cfg.domain} PORT=${toString cfg.webAppPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -165,10 +171,13 @@ in
|
||||
"/api/" = {
|
||||
proxyPass = "http://localhost:${toString cfg.apiPort}/";
|
||||
extraConfig = ''
|
||||
default_type application/json;
|
||||
default_type application/json;
|
||||
proxy_set_header Connection "";
|
||||
proxy_http_version 1.1;
|
||||
allow 127.0.0.1;
|
||||
'';
|
||||
};
|
||||
"/api/rpc/register" = mkIf cfg.disableRegistration {
|
||||
extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -68,7 +68,7 @@ CREATE TRIGGER update_footer_last_modified
|
||||
EXECUTE FUNCTION internal.update_last_modified ();
|
||||
|
||||
CREATE TRIGGER update_legal_information_last_modified
|
||||
BEFORE INSERT OR DELETE ON internal.legal_information
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON internal.legal_information
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION internal.update_last_modified ();
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
-- migrate:up
|
||||
CREATE TABLE internal.domain_prefix (
|
||||
website_id UUID PRIMARY KEY REFERENCES internal.website (id) ON DELETE CASCADE,
|
||||
prefix VARCHAR(16) UNIQUE NOT NULL CHECK (LENGTH(prefix) >= 3 AND prefix ~ '^[a-z]+(-[a-z]+)*$'),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
|
||||
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
|
||||
last_modified_by UUID REFERENCES internal.user (id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE VIEW api.domain_prefix WITH ( security_invoker = ON
|
||||
) AS
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
internal.domain_prefix;
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON internal.domain_prefix TO authenticated_user;
|
||||
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON api.domain_prefix TO authenticated_user;
|
||||
|
||||
ALTER TABLE internal.domain_prefix ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY view_domain_prefix ON internal.domain_prefix
|
||||
FOR SELECT
|
||||
USING (internal.user_has_website_access (website_id, 10));
|
||||
|
||||
CREATE POLICY update_domain_prefix ON internal.domain_prefix
|
||||
FOR UPDATE
|
||||
USING (internal.user_has_website_access (website_id, 30));
|
||||
|
||||
CREATE POLICY delete_domain_prefix ON internal.domain_prefix
|
||||
FOR DELETE
|
||||
USING (internal.user_has_website_access (website_id, 30));
|
||||
|
||||
CREATE POLICY insert_domain_prefix ON internal.domain_prefix
|
||||
FOR INSERT
|
||||
WITH CHECK (internal.user_has_website_access (website_id, 30));
|
||||
|
||||
CREATE TRIGGER update_domain_prefix_last_modified
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON internal.domain_prefix
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION internal.update_last_modified ();
|
||||
|
||||
CREATE TRIGGER domain_prefix_track_changes
|
||||
AFTER INSERT OR UPDATE OR DELETE ON internal.domain_prefix
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION internal.track_changes ();
|
||||
|
||||
-- migrate:down
|
||||
DROP TRIGGER domain_prefix_track_changes ON internal.domain_prefix;
|
||||
|
||||
DROP TRIGGER update_domain_prefix_last_modified ON internal.domain_prefix;
|
||||
|
||||
DROP VIEW api.domain_prefix;
|
||||
|
||||
DROP TABLE internal.domain_prefix;
|
||||
|
||||
@@ -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 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 domain_prefix -t footer -t header -t home -t legal_information -t media -t settings -t user -t website -s internal
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -169,6 +169,7 @@ export interface DocsCategory {
|
||||
user_id: string | null;
|
||||
category_name: string;
|
||||
category_weight: number;
|
||||
created_at: Date;
|
||||
last_modified_at: Date;
|
||||
last_modified_by: string | null;
|
||||
}
|
||||
@@ -178,6 +179,7 @@ export interface DocsCategoryInput {
|
||||
user_id?: string | null;
|
||||
category_name: string;
|
||||
category_weight: number;
|
||||
created_at?: Date;
|
||||
last_modified_at?: Date;
|
||||
last_modified_by?: string | null;
|
||||
}
|
||||
@@ -189,6 +191,7 @@ const docs_category = {
|
||||
"user_id",
|
||||
"category_name",
|
||||
"category_weight",
|
||||
"created_at",
|
||||
"last_modified_at",
|
||||
"last_modified_by"
|
||||
],
|
||||
@@ -203,6 +206,34 @@ const docs_category = {
|
||||
$input: null as unknown as DocsCategoryInput
|
||||
} as const;
|
||||
|
||||
// Table domain_prefix
|
||||
export interface DomainPrefix {
|
||||
website_id: string;
|
||||
prefix: string;
|
||||
created_at: Date;
|
||||
last_modified_at: Date;
|
||||
last_modified_by: string | null;
|
||||
}
|
||||
export interface DomainPrefixInput {
|
||||
website_id: string;
|
||||
prefix: string;
|
||||
created_at?: Date;
|
||||
last_modified_at?: Date;
|
||||
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;
|
||||
@@ -297,18 +328,20 @@ const home = {
|
||||
export interface LegalInformation {
|
||||
website_id: string;
|
||||
main_content: string;
|
||||
created_at: Date;
|
||||
last_modified_at: Date;
|
||||
last_modified_by: string | null;
|
||||
}
|
||||
export interface LegalInformationInput {
|
||||
website_id: string;
|
||||
main_content: string;
|
||||
created_at?: Date;
|
||||
last_modified_at?: Date;
|
||||
last_modified_by?: string | null;
|
||||
}
|
||||
const legal_information = {
|
||||
tableName: "legal_information",
|
||||
columns: ["website_id", "main_content", "last_modified_at", "last_modified_by"],
|
||||
columns: ["website_id", "main_content", "created_at", "last_modified_at", "last_modified_by"],
|
||||
requiredForInsert: ["website_id", "main_content"],
|
||||
primaryKey: "website_id",
|
||||
foreignKeys: {
|
||||
@@ -395,16 +428,18 @@ export interface User {
|
||||
username: string;
|
||||
password_hash: string;
|
||||
role: string;
|
||||
created_at: Date;
|
||||
}
|
||||
export interface UserInput {
|
||||
id?: string;
|
||||
username: string;
|
||||
password_hash: string;
|
||||
role?: string;
|
||||
created_at?: Date;
|
||||
}
|
||||
const user = {
|
||||
tableName: "user",
|
||||
columns: ["id", "username", "password_hash", "role"],
|
||||
columns: ["id", "username", "password_hash", "role", "created_at"],
|
||||
requiredForInsert: ["username", "password_hash"],
|
||||
primaryKey: "id",
|
||||
foreignKeys: {},
|
||||
@@ -418,8 +453,8 @@ export interface Website {
|
||||
user_id: string;
|
||||
content_type: string;
|
||||
title: string;
|
||||
created_at: Date;
|
||||
is_published: boolean;
|
||||
created_at: Date;
|
||||
last_modified_at: Date;
|
||||
last_modified_by: string | null;
|
||||
title_search: any | null;
|
||||
@@ -429,8 +464,8 @@ export interface WebsiteInput {
|
||||
user_id?: string;
|
||||
content_type: string;
|
||||
title: string;
|
||||
created_at?: Date;
|
||||
is_published?: boolean;
|
||||
created_at?: Date;
|
||||
last_modified_at?: Date;
|
||||
last_modified_by?: string | null;
|
||||
title_search?: any | null;
|
||||
@@ -442,8 +477,8 @@ const website = {
|
||||
"user_id",
|
||||
"content_type",
|
||||
"title",
|
||||
"created_at",
|
||||
"is_published",
|
||||
"created_at",
|
||||
"last_modified_at",
|
||||
"last_modified_by",
|
||||
"title_search"
|
||||
@@ -475,6 +510,10 @@ export interface TableTypes {
|
||||
select: DocsCategory;
|
||||
input: DocsCategoryInput;
|
||||
};
|
||||
domain_prefix: {
|
||||
select: DomainPrefix;
|
||||
input: DomainPrefixInput;
|
||||
};
|
||||
footer: {
|
||||
select: Footer;
|
||||
input: FooterInput;
|
||||
@@ -514,6 +553,7 @@ export const tables = {
|
||||
change_log,
|
||||
collab,
|
||||
docs_category,
|
||||
domain_prefix,
|
||||
footer,
|
||||
header,
|
||||
home,
|
||||
|
||||
@@ -3,3 +3,9 @@ import { dev } from "$app/environment";
|
||||
export const API_BASE_PREFIX = dev
|
||||
? "http://localhost:3000"
|
||||
: `${process.env.ORIGIN ? `${process.env.ORIGIN}/api` : "http://localhost:3000"}`;
|
||||
|
||||
export const REGISTRATION_IS_DISABLED = dev
|
||||
? false
|
||||
: process.env.REGISTRATION_IS_DISABLED
|
||||
? JSON.parse(process.env.REGISTRATION_IS_DISABLED)
|
||||
: false;
|
||||
|
||||
@@ -12,7 +12,8 @@ import type {
|
||||
Footer,
|
||||
Article,
|
||||
DocsCategory,
|
||||
LegalInformation
|
||||
LegalInformation,
|
||||
DomainPrefix
|
||||
} from "$lib/db-schema";
|
||||
|
||||
export const ALLOWED_MIME_TYPES = [
|
||||
@@ -198,4 +199,5 @@ export interface WebsiteOverview extends Website {
|
||||
footer: Footer;
|
||||
article: (Article & { docs_category: DocsCategory | null })[];
|
||||
legal_information?: LegalInformation;
|
||||
domain_prefix?: DomainPrefix;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import type { Actions } from "./$types";
|
||||
import { API_BASE_PREFIX } from "$lib/server/utils";
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
import { API_BASE_PREFIX, REGISTRATION_IS_DISABLED } from "$lib/server/utils";
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
REGISTRATION_IS_DISABLED
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async ({ request, fetch }) => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
|
||||
import type { ActionData } from "./$types";
|
||||
import type { ActionData, PageServerData } from "./$types";
|
||||
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
|
||||
|
||||
const { form }: { form: ActionData } = $props();
|
||||
const { data, form }: { data: PageServerData; form: ActionData } = $props();
|
||||
|
||||
let sending = $state(false);
|
||||
</script>
|
||||
@@ -15,24 +15,52 @@
|
||||
<LoadingSpinner />
|
||||
{/if}
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
use:enhance={() => {
|
||||
sending = true;
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
sending = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<label>
|
||||
Username:
|
||||
<input type="text" name="username" minlength="3" maxlength="16" required />
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" name="password" minlength="12" maxlength="128" required />
|
||||
</label>
|
||||
{#if data.REGISTRATION_IS_DISABLED}
|
||||
<p class="registration-disabled">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
width="20"
|
||||
height="20"
|
||||
color="var(--color-error)"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 1a4.5 4.5 0 0 0-4.5 4.5V9H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2h-.5V5.5A4.5 4.5 0 0 0 10 1Zm3 8V5.5a3 3 0 1 0-6 0V9h6Z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
Account registration is disabled on this instance
|
||||
</p>
|
||||
{:else}
|
||||
<form
|
||||
method="POST"
|
||||
use:enhance={() => {
|
||||
sending = true;
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
sending = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<label>
|
||||
Username:
|
||||
<input type="text" name="username" minlength="3" maxlength="16" required />
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" name="password" minlength="12" maxlength="128" required />
|
||||
</label>
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.registration-disabled {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -103,6 +103,19 @@ export const actions: Actions = {
|
||||
deleteWebsite: async ({ request, cookies, fetch }) => {
|
||||
const data = await request.formData();
|
||||
|
||||
const oldDomainPrefixData = await fetch(
|
||||
`${API_BASE_PREFIX}/domain_prefix?website_id=eq.${data.get("id")}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||
Accept: "application/vnd.pgrst.object+json"
|
||||
}
|
||||
}
|
||||
);
|
||||
const oldDomainPrefix = await oldDomainPrefixData.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_PREFIX}/website?id=eq.${data.get("id")}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
@@ -116,11 +129,19 @@ export const actions: Actions = {
|
||||
return { success: false, message: response.message };
|
||||
}
|
||||
|
||||
await rm(join("/", "var", "www", "archtika-websites", data.get("id") as string), {
|
||||
await rm(join("/", "var", "www", "archtika-websites", "previews", data.get("id") as string), {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
|
||||
await rm(
|
||||
join("/", "var", "www", "archtika-websites", oldDomainPrefix.prefix ?? data.get("id")),
|
||||
{
|
||||
recursive: true,
|
||||
force: true
|
||||
}
|
||||
);
|
||||
|
||||
return { success: true, message: "Successfully deleted website" };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
||||
import { readFile, mkdir, writeFile, rename } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { type WebsiteOverview, slugify } from "$lib/utils";
|
||||
import type { Actions, PageServerLoad } from "./$types";
|
||||
@@ -9,10 +9,11 @@ import BlogArticle from "$lib/templates/blog/BlogArticle.svelte";
|
||||
import DocsIndex from "$lib/templates/docs/DocsIndex.svelte";
|
||||
import DocsArticle from "$lib/templates/docs/DocsArticle.svelte";
|
||||
import { dev } from "$app/environment";
|
||||
import type { DomainPrefixInput } from "$lib/db-schema";
|
||||
|
||||
export const load: PageServerLoad = async ({ params, fetch, cookies }) => {
|
||||
const websiteOverviewData = await fetch(
|
||||
`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}&select=*,settings(*),header(*),home(*),footer(*),article(*,docs_category(*)),legal_information(*)`,
|
||||
`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}&select=*,settings(*),header(*),home(*),footer(*),article(*,docs_category(*)),legal_information(*),domain_prefix(*)`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -36,10 +37,13 @@ export const load: PageServerLoad = async ({ params, fetch, cookies }) => {
|
||||
}/previews/${websiteOverview.id}/`;
|
||||
|
||||
const websiteProdUrl = dev
|
||||
? `http://localhost:18000/${websiteOverview.id}/`
|
||||
? `http://localhost:18000/${websiteOverview.domain_prefix?.prefix ?? websiteOverview.id}/`
|
||||
: process.env.ORIGIN
|
||||
? process.env.ORIGIN.replace("//", `//${websiteOverview.id}.`)
|
||||
: `http://localhost:18000/${websiteOverview.id}/`;
|
||||
? process.env.ORIGIN.replace(
|
||||
"//",
|
||||
`//${websiteOverview.domain_prefix?.prefix ?? websiteOverview.id}.`
|
||||
)
|
||||
: `http://localhost:18000/${websiteOverview.domain_prefix?.prefix ?? websiteOverview.id}/`;
|
||||
|
||||
return {
|
||||
websiteOverview,
|
||||
@@ -51,7 +55,7 @@ export const load: PageServerLoad = async ({ params, fetch, cookies }) => {
|
||||
export const actions: Actions = {
|
||||
publishWebsite: async ({ fetch, params, cookies }) => {
|
||||
const websiteOverviewData = await fetch(
|
||||
`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}&select=*,settings(*),header(*),home(*),footer(*),article(*,docs_category(*)),legal_information(*)`,
|
||||
`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}&select=*,settings(*),header(*),home(*),footer(*),article(*,docs_category(*)),legal_information(*),domain_prefix(*)`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -82,10 +86,85 @@ export const actions: Actions = {
|
||||
}
|
||||
|
||||
return { success: true, message: "Successfully published website" };
|
||||
},
|
||||
createUpdateCustomDomainPrefix: async ({ request, fetch, params, cookies }) => {
|
||||
const data = await request.formData();
|
||||
|
||||
const oldDomainPrefixData = await fetch(
|
||||
`${API_BASE_PREFIX}/domain_prefix?website_id=eq.${params.websiteId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||
Accept: "application/vnd.pgrst.object+json"
|
||||
}
|
||||
}
|
||||
);
|
||||
const oldDomainPrefix = await oldDomainPrefixData.json();
|
||||
|
||||
const res = await fetch(`${API_BASE_PREFIX}/domain_prefix`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||
Prefer: "resolution=merge-duplicates",
|
||||
Accept: "application/vnd.pgrst.object+json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
website_id: params.websiteId,
|
||||
prefix: data.get("domain-prefix") as string
|
||||
} satisfies DomainPrefixInput)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const response = await res.json();
|
||||
return { success: false, message: response.message };
|
||||
}
|
||||
|
||||
await rename(
|
||||
join(
|
||||
"/",
|
||||
"var",
|
||||
"www",
|
||||
"archtika-websites",
|
||||
res.status === 201 ? params.websiteId : oldDomainPrefix.prefix
|
||||
),
|
||||
join("/", "var", "www", "archtika-websites", data.get("domain-prefix") as string)
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully ${res.status === 201 ? "created" : "updated"} domain prefix`
|
||||
};
|
||||
},
|
||||
deleteCustomDomainPrefix: async ({ fetch, params, cookies }) => {
|
||||
const res = await fetch(`${API_BASE_PREFIX}/domain_prefix?website_id=eq.${params.websiteId}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||
Prefer: "return=representation",
|
||||
Accept: "application/vnd.pgrst.object+json"
|
||||
}
|
||||
});
|
||||
|
||||
const response = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
return { success: false, message: response.message };
|
||||
}
|
||||
|
||||
await rename(
|
||||
join("/", "var", "www", "archtika-websites", response.prefix),
|
||||
join("/", "var", "www", "archtika-websites", params.websiteId)
|
||||
);
|
||||
|
||||
return { success: true, message: `Successfully deleted domain prefix` };
|
||||
}
|
||||
};
|
||||
|
||||
const generateStaticFiles = async (websiteData: WebsiteOverview, isPreview: boolean = true) => {
|
||||
const generateStaticFiles = async (websiteData: WebsiteOverview, isPreview = true) => {
|
||||
const fileContents = (head: string, body: string) => {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
@@ -112,7 +191,13 @@ const generateStaticFiles = async (websiteData: WebsiteOverview, isPreview: bool
|
||||
if (isPreview) {
|
||||
uploadDir = join("/", "var", "www", "archtika-websites", "previews", websiteData.id);
|
||||
} else {
|
||||
uploadDir = join("/", "var", "www", "archtika-websites", websiteData.id);
|
||||
uploadDir = join(
|
||||
"/",
|
||||
"var",
|
||||
"www",
|
||||
"archtika-websites",
|
||||
websiteData.domain_prefix?.prefix ?? websiteData.id
|
||||
);
|
||||
}
|
||||
|
||||
await mkdir(uploadDir, { recursive: true });
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
|
||||
import type { ActionData, PageServerData } from "./$types";
|
||||
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
|
||||
import Modal from "$lib/components/Modal.svelte";
|
||||
|
||||
const { data, form }: { data: PageServerData; form: ActionData } = $props();
|
||||
|
||||
@@ -45,18 +46,73 @@
|
||||
>
|
||||
<button type="submit">Publish</button>
|
||||
</form>
|
||||
|
||||
{#if data.websiteOverview.is_published}
|
||||
<section id="publication-status">
|
||||
<h3>
|
||||
<a href="#publication-status">Publication status</a>
|
||||
</h3>
|
||||
<p>
|
||||
Your website is published at:
|
||||
<br />
|
||||
<a href={data.websiteProdUrl}>{data.websiteProdUrl}</a>
|
||||
</p>
|
||||
</section>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if data.websiteOverview.is_published}
|
||||
<section id="publication-status">
|
||||
<h2>
|
||||
<a href="#publication-status">Publication status</a>
|
||||
</h2>
|
||||
<p>
|
||||
Your website is published at:
|
||||
<br />
|
||||
<a href={data.websiteProdUrl}>{data.websiteProdUrl}</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="custom-domain-prefix">
|
||||
<h2>
|
||||
<a href="#custom-domain-prefix">Custom domain prefix</a>
|
||||
</h2>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/createUpdateCustomDomainPrefix"
|
||||
use:enhance={() => {
|
||||
sending = true;
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
sending = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<label>
|
||||
Prefix:
|
||||
<input
|
||||
type="text"
|
||||
name="domain-prefix"
|
||||
value={data.websiteOverview.domain_prefix?.prefix ?? ""}
|
||||
placeholder="my-blog"
|
||||
minlength="3"
|
||||
maxlength="16"
|
||||
pattern="^[a-z]+(-[a-z]+)*$"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
{#if data.websiteOverview.domain_prefix?.prefix}
|
||||
<Modal id="delete-domain-prefix" text="Delete">
|
||||
<form
|
||||
action="?/deleteCustomDomainPrefix"
|
||||
method="post"
|
||||
use:enhance={() => {
|
||||
sending = true;
|
||||
return async ({ update }) => {
|
||||
await update();
|
||||
window.location.hash = "!";
|
||||
sending = false;
|
||||
};
|
||||
}}
|
||||
>
|
||||
<h3>Delete domain prefix</h3>
|
||||
<p>
|
||||
<strong>Caution!</strong>
|
||||
This action will remove the domain prefix and reset it to its initial value.
|
||||
</p>
|
||||
<button type="submit">Delete domain prefix</button>
|
||||
</form>
|
||||
</Modal>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
</WebsiteEditor>
|
||||
|
||||
Reference in New Issue
Block a user