mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 02:41:35 +01:00
Remove NGINX website directories from API and fix some minor issues
This commit is contained in:
@@ -15,6 +15,10 @@
|
|||||||
acmeEmail = "thilo.hohlt@tutanota.com";
|
acmeEmail = "thilo.hohlt@tutanota.com";
|
||||||
dnsProvider = "porkbun";
|
dnsProvider = "porkbun";
|
||||||
dnsEnvironmentFile = /var/lib/porkbun.env;
|
dnsEnvironmentFile = /var/lib/porkbun.env;
|
||||||
|
settings = {
|
||||||
disableRegistration = true;
|
disableRegistration = true;
|
||||||
|
maxWebsiteStorageSize = 250;
|
||||||
|
maxUserWebsites = 3;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,15 @@
|
|||||||
postgresql = {
|
postgresql = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.postgresql_16;
|
package = pkgs.postgresql_16;
|
||||||
|
/*
|
||||||
|
PL/Perl:
|
||||||
|
overrideAttrs (
|
||||||
|
finalAttrs: previousAttrs: {
|
||||||
|
buildInputs = previousAttrs.buildInputs ++ [ pkgs.perl ];
|
||||||
|
configureFlags = previousAttrs.configureFlags ++ [ "--with-perl" ];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
*/
|
||||||
ensureDatabases = [ "archtika" ];
|
ensureDatabases = [ "archtika" ];
|
||||||
authentication = lib.mkForce ''
|
authentication = lib.mkForce ''
|
||||||
local all all trust
|
local all all trust
|
||||||
|
|||||||
@@ -76,11 +76,27 @@ in
|
|||||||
description = "API secrets for the DNS-01 challenge (required for wildcard domains).";
|
description = "API secrets for the DNS-01 challenge (required for wildcard domains).";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
disableRegistration = mkOption {
|
disableRegistration = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = "By default any user can create an account. That behavior can be disabled by using this option.";
|
description = "By default any user can create an account. That behavior can be disabled by using this option.";
|
||||||
};
|
};
|
||||||
|
maxUserWebsites = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
description = "Maximum number of websites allowed per user by default.";
|
||||||
|
};
|
||||||
|
maxWebsiteStorageSize = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 500;
|
||||||
|
description = "Maximum amount of disk space in MB allowed per user website by default.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
@@ -91,7 +107,7 @@ in
|
|||||||
|
|
||||||
users.groups.${cfg.group} = { };
|
users.groups.${cfg.group} = { };
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [ "d /var/www/archtika-websites 0755 ${cfg.user} ${cfg.group} -" ];
|
systemd.tmpfiles.rules = [ "d /var/www/archtika-websites 0777 ${cfg.user} ${cfg.group} -" ];
|
||||||
|
|
||||||
systemd.services.archtika-api = {
|
systemd.services.archtika-api = {
|
||||||
description = "archtika API service";
|
description = "archtika API service";
|
||||||
@@ -112,6 +128,8 @@ in
|
|||||||
JWT_SECRET=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c64)
|
JWT_SECRET=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c64)
|
||||||
|
|
||||||
${pkgs.postgresql_16}/bin/psql postgres://postgres@localhost:5432/${cfg.databaseName} -c "ALTER DATABASE ${cfg.databaseName} SET \"app.jwt_secret\" TO '$JWT_SECRET'"
|
${pkgs.postgresql_16}/bin/psql postgres://postgres@localhost:5432/${cfg.databaseName} -c "ALTER DATABASE ${cfg.databaseName} SET \"app.jwt_secret\" TO '$JWT_SECRET'"
|
||||||
|
${pkgs.postgresql_16}/bin/psql postgres://postgres@localhost:5432/${cfg.databaseName} -c "ALTER DATABASE ${cfg.databaseName} SET \"app.website_max_storage_size\" TO ${toString cfg.settings.maxWebsiteStorageSize}"
|
||||||
|
${pkgs.postgresql_16}/bin/psql postgres://postgres@localhost:5432/${cfg.databaseName} -c "ALTER DATABASE ${cfg.databaseName} SET \"app.website_max_number_user\" TO ${toString cfg.settings.maxUserWebsites}"
|
||||||
|
|
||||||
${pkgs.dbmate}/bin/dbmate --url postgres://postgres@localhost:5432/archtika?sslmode=disable --migrations-dir ${cfg.package}/rest-api/db/migrations up
|
${pkgs.dbmate}/bin/dbmate --url postgres://postgres@localhost:5432/archtika?sslmode=disable --migrations-dir ${cfg.package}/rest-api/db/migrations up
|
||||||
|
|
||||||
@@ -132,7 +150,7 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
script = ''
|
script = ''
|
||||||
REGISTRATION_IS_DISABLED=${toString cfg.disableRegistration} BODY_SIZE_LIMIT=10M ORIGIN=https://${cfg.domain} PORT=${toString cfg.webAppPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app
|
REGISTRATION_IS_DISABLED=${toString cfg.settings.disableRegistration} BODY_SIZE_LIMIT=10M ORIGIN=https://${cfg.domain} PORT=${toString cfg.webAppPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -189,7 +207,7 @@ in
|
|||||||
default_type application/json;
|
default_type application/json;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"/api/rpc/register" = mkIf cfg.disableRegistration {
|
"/api/rpc/register" = mkIf cfg.settings.disableRegistration {
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
deny all;
|
deny all;
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
|
|||||||
|
|
||||||
CREATE TABLE internal.user (
|
CREATE TABLE internal.user (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid (),
|
||||||
username VARCHAR(16) UNIQUE NOT NULL CHECK (LENGTH(username) >= 3),
|
username VARCHAR(16) UNIQUE NOT NULL CHECK (LENGTH(username) >= 3 AND username ~ '^[a-zA-Z0-9_-]+$'),
|
||||||
password_hash CHAR(60) NOT NULL,
|
password_hash CHAR(60) NOT NULL,
|
||||||
user_role NAME NOT NULL DEFAULT 'authenticated_user',
|
user_role NAME NOT NULL DEFAULT 'authenticated_user',
|
||||||
max_number_websites INT NOT NULL DEFAULT CURRENT_SETTING('app.website_max_number_user') ::INT,
|
max_number_websites INT NOT NULL DEFAULT CURRENT_SETTING('app.website_max_number_user') ::INT,
|
||||||
|
|||||||
@@ -26,19 +26,7 @@ CREATE VIEW api.website WITH ( security_invoker = ON
|
|||||||
SELECT
|
SELECT
|
||||||
*
|
*
|
||||||
FROM
|
FROM
|
||||||
internal.website AS w
|
internal.website;
|
||||||
WHERE
|
|
||||||
w.user_id = (
|
|
||||||
CURRENT_SETTING(
|
|
||||||
'request.jwt.claims', TRUE
|
|
||||||
)::JSON ->> 'user_id')::UUID
|
|
||||||
OR w.id IN (
|
|
||||||
SELECT
|
|
||||||
c.website_id
|
|
||||||
FROM
|
|
||||||
internal.collab AS c
|
|
||||||
WHERE
|
|
||||||
c.user_id = (CURRENT_SETTING('request.jwt.claims', TRUE)::JSON ->> 'user_id')::UUID);
|
|
||||||
|
|
||||||
CREATE VIEW api.settings WITH ( security_invoker = ON
|
CREATE VIEW api.settings WITH ( security_invoker = ON
|
||||||
) AS
|
) AS
|
||||||
|
|||||||
@@ -146,22 +146,6 @@ CREATE TRIGGER _prevent_storage_excess_settings
|
|||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION internal.prevent_website_storage_size_excess ();
|
EXECUTE FUNCTION internal.prevent_website_storage_size_excess ();
|
||||||
|
|
||||||
CREATE VIEW api.all_user_websites AS
|
|
||||||
SELECT
|
|
||||||
u.id AS user_id,
|
|
||||||
u.username,
|
|
||||||
u.created_at AS user_created_at,
|
|
||||||
u.max_number_websites,
|
|
||||||
COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT('id', w.id, 'title', w.title, 'max_storage_size', w.max_storage_size)
|
|
||||||
ORDER BY w.created_at DESC) FILTER (WHERE w.id IS NOT NULL), '[]'::JSONB) AS websites
|
|
||||||
FROM
|
|
||||||
internal.user AS u
|
|
||||||
LEFT JOIN internal.website AS w ON u.id = w.user_id
|
|
||||||
GROUP BY
|
|
||||||
u.id;
|
|
||||||
|
|
||||||
GRANT SELECT ON api.all_user_websites TO administrator;
|
|
||||||
|
|
||||||
GRANT UPDATE (max_storage_size) ON internal.website TO administrator;
|
GRANT UPDATE (max_storage_size) ON internal.website TO administrator;
|
||||||
|
|
||||||
GRANT UPDATE, DELETE ON internal.user TO administrator;
|
GRANT UPDATE, DELETE ON internal.user TO administrator;
|
||||||
@@ -193,8 +177,6 @@ DROP TRIGGER _prevent_storage_excess_settings ON internal.settings;
|
|||||||
|
|
||||||
DROP FUNCTION internal.prevent_website_storage_size_excess;
|
DROP FUNCTION internal.prevent_website_storage_size_excess;
|
||||||
|
|
||||||
DROP VIEW api.all_user_websites;
|
|
||||||
|
|
||||||
REVOKE UPDATE (max_storage_size) ON internal.website FROM administrator;
|
REVOKE UPDATE (max_storage_size) ON internal.website FROM administrator;
|
||||||
|
|
||||||
REVOKE UPDATE, DELETE ON internal.user FROM administrator;
|
REVOKE UPDATE, DELETE ON internal.user FROM administrator;
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-- migrate:up
|
||||||
|
CREATE FUNCTION internal.cleanup_filesystem ()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
_website_id UUID;
|
||||||
|
_domain_prefix VARCHAR(16);
|
||||||
|
BEGIN
|
||||||
|
IF TG_TABLE_NAME = 'website' THEN
|
||||||
|
_website_id := OLD.id;
|
||||||
|
SELECT
|
||||||
|
d.prefix INTO _domain_prefix
|
||||||
|
FROM
|
||||||
|
internal.domain_prefix AS d
|
||||||
|
WHERE
|
||||||
|
d.website_id = _website_id;
|
||||||
|
EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/previews/%s''', _website_id);
|
||||||
|
EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/%s''', COALESCE(_domain_prefix, _website_id::VARCHAR));
|
||||||
|
ELSE
|
||||||
|
_website_id := OLD.website_id;
|
||||||
|
SELECT
|
||||||
|
d.prefix INTO _domain_prefix
|
||||||
|
FROM
|
||||||
|
internal.domain_prefix AS d
|
||||||
|
WHERE
|
||||||
|
d.website_id = _website_id;
|
||||||
|
EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/previews/%s/legal-information.html''', _website_id);
|
||||||
|
EXECUTE FORMAT('COPY (SELECT '''') TO PROGRAM ''rm -rf /var/www/archtika-websites/%s/legal-information.html''', COALESCE(_domain_prefix, _website_id::VARCHAR));
|
||||||
|
END IF;
|
||||||
|
RETURN OLD;
|
||||||
|
END;
|
||||||
|
$$
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER;
|
||||||
|
|
||||||
|
CREATE TRIGGER _cleanup_filesystem_website
|
||||||
|
BEFORE DELETE ON internal.website
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION internal.cleanup_filesystem ();
|
||||||
|
|
||||||
|
CREATE TRIGGER _cleanup_filesystem_legal_information
|
||||||
|
BEFORE DELETE ON internal.legal_information
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION internal.cleanup_filesystem ();
|
||||||
|
|
||||||
|
-- migrate:down
|
||||||
|
DROP TRIGGER _cleanup_filesystem_website ON internal.website;
|
||||||
|
|
||||||
|
DROP TRIGGER _cleanup_filesystem_legal_information ON internal.legal_information;
|
||||||
|
|
||||||
|
DROP FUNCTION internal.cleanup_filesystem;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
{websiteUrl}
|
{websiteUrl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Nav {websiteOverview} isDocsTemplate={false} isIndexPage={true} {apiUrl} />
|
<Nav {websiteOverview} isDocsTemplate={false} isIndexPage={true} {isLegalPage} {apiUrl} />
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
websiteOverview,
|
websiteOverview,
|
||||||
isDocsTemplate,
|
isDocsTemplate,
|
||||||
isIndexPage,
|
isIndexPage,
|
||||||
apiUrl
|
apiUrl,
|
||||||
|
isLegalPage
|
||||||
}: {
|
}: {
|
||||||
websiteOverview: WebsiteOverview;
|
websiteOverview: WebsiteOverview;
|
||||||
isDocsTemplate: boolean;
|
isDocsTemplate: boolean;
|
||||||
isIndexPage: boolean;
|
isIndexPage: boolean;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
|
isLegalPage?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const categorizedArticles = Object.fromEntries(
|
const categorizedArticles = Object.fromEntries(
|
||||||
@@ -70,7 +72,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
<svelte:element this={isIndexPage ? "span" : "a"} href="..">
|
<svelte:element
|
||||||
|
this={isIndexPage && !isLegalPage ? "span" : "a"}
|
||||||
|
href={`${isLegalPage ? "./" : "../"}`}
|
||||||
|
>
|
||||||
{#if websiteOverview.header.logo_type === "text"}
|
{#if websiteOverview.header.logo_type === "text"}
|
||||||
<strong>{websiteOverview.header.logo_text}</strong>
|
<strong>{websiteOverview.header.logo_text}</strong>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
{websiteUrl}
|
{websiteUrl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Nav {websiteOverview} isDocsTemplate={true} isIndexPage={true} {apiUrl} />
|
<Nav {websiteOverview} isDocsTemplate={true} isIndexPage={true} {isLegalPage} {apiUrl} />
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -38,7 +38,14 @@
|
|||||||
<form method="POST" use:enhance={enhanceForm()}>
|
<form method="POST" use:enhance={enhanceForm()}>
|
||||||
<label>
|
<label>
|
||||||
Username:
|
Username:
|
||||||
<input type="text" name="username" minlength="3" maxlength="16" required />
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
minlength="3"
|
||||||
|
maxlength="16"
|
||||||
|
pattern="^[a-zA-Z0-9_\-]+$"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Password:
|
Password:
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { Actions, PageServerLoad } from "./$types";
|
import type { Actions, PageServerLoad } from "./$types";
|
||||||
import { apiRequest } from "$lib/server/utils";
|
import { apiRequest } from "$lib/server/utils";
|
||||||
import { API_BASE_PREFIX } from "$lib/server/utils";
|
import { API_BASE_PREFIX } from "$lib/server/utils";
|
||||||
import { rm } from "node:fs/promises";
|
|
||||||
import { join } from "node:path";
|
|
||||||
import type { Website } from "$lib/db-schema";
|
import type { Website } from "$lib/db-schema";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
||||||
@@ -11,7 +9,7 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
|||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
const baseFetchUrl = `${API_BASE_PREFIX}/website?order=last_modified_at.desc,created_at.desc`;
|
const baseFetchUrl = `${API_BASE_PREFIX}/website?select=*,collab(user_id)&collab.user_id=eq.${locals.user.id}&or=(user_id.eq.${locals.user.id},collab.not.is.null)&order=last_modified_at.desc,created_at.desc`;
|
||||||
|
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
params.append("title", `wfts.${searchQuery}`);
|
params.append("title", `wfts.${searchQuery}`);
|
||||||
@@ -77,15 +75,6 @@ export const actions: Actions = {
|
|||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
const id = data.get("id");
|
const id = data.get("id");
|
||||||
|
|
||||||
const oldDomainPrefix = (
|
|
||||||
await apiRequest(fetch, `${API_BASE_PREFIX}/domain_prefix?website_id=eq.${id}`, "GET", {
|
|
||||||
headers: {
|
|
||||||
Accept: "application/vnd.pgrst.object+json"
|
|
||||||
},
|
|
||||||
returnData: true
|
|
||||||
})
|
|
||||||
).data;
|
|
||||||
|
|
||||||
const deleteWebsite = await apiRequest(
|
const deleteWebsite = await apiRequest(
|
||||||
fetch,
|
fetch,
|
||||||
`${API_BASE_PREFIX}/website?id=eq.${id}`,
|
`${API_BASE_PREFIX}/website?id=eq.${id}`,
|
||||||
@@ -99,16 +88,6 @@ export const actions: Actions = {
|
|||||||
return deleteWebsite;
|
return deleteWebsite;
|
||||||
}
|
}
|
||||||
|
|
||||||
await rm(join("/", "var", "www", "archtika-websites", "previews", id as string), {
|
|
||||||
recursive: true,
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
|
|
||||||
await rm(join("/", "var", "www", "archtika-websites", oldDomainPrefix?.prefix ?? id), {
|
|
||||||
recursive: true,
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return deleteWebsite;
|
return deleteWebsite;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const actions: Actions = {
|
|||||||
logout: async ({ cookies }) => {
|
logout: async ({ cookies }) => {
|
||||||
cookies.delete("session_token", { path: "/" });
|
cookies.delete("session_token", { path: "/" });
|
||||||
|
|
||||||
return { success: true, message: "Successfully logged out" };
|
return { success: true, message: "Successfully logged out, you can refresh the page" };
|
||||||
},
|
},
|
||||||
deleteAccount: async ({ request, fetch, cookies }) => {
|
deleteAccount: async ({ request, fetch, cookies }) => {
|
||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if data.storageSizes.data.length > 0}
|
{#if (data.storageSizes.data ?? []).length > 0}
|
||||||
<section id="storage">
|
<section id="storage">
|
||||||
<h2>
|
<h2>
|
||||||
<a href="#storage">Storage</a>
|
<a href="#storage">Storage</a>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import type { Actions, PageServerLoad } from "./$types";
|
import type { Actions, PageServerLoad } from "./$types";
|
||||||
import { API_BASE_PREFIX } from "$lib/server/utils";
|
import { API_BASE_PREFIX } from "$lib/server/utils";
|
||||||
import { apiRequest } from "$lib/server/utils";
|
import { apiRequest } from "$lib/server/utils";
|
||||||
|
import type { Website, User } from "$lib/db-schema";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ fetch }) => {
|
export const load: PageServerLoad = async ({ fetch }) => {
|
||||||
const allUsers = (
|
const usersWithWebsites: (User & { website: Website[] })[] = (
|
||||||
await apiRequest(
|
await apiRequest(
|
||||||
fetch,
|
fetch,
|
||||||
`${API_BASE_PREFIX}/all_user_websites?order=user_created_at.desc`,
|
`${API_BASE_PREFIX}/user?select=*,website!user_id(*)&order=created_at`,
|
||||||
"GET",
|
"GET",
|
||||||
{
|
{
|
||||||
returnData: true
|
returnData: true
|
||||||
@@ -15,7 +16,7 @@ export const load: PageServerLoad = async ({ fetch }) => {
|
|||||||
).data;
|
).data;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allUsers,
|
usersWithWebsites,
|
||||||
API_BASE_PREFIX
|
API_BASE_PREFIX
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -39,8 +40,6 @@ export const actions: Actions = {
|
|||||||
updateStorageLimit: async ({ request, fetch }) => {
|
updateStorageLimit: async ({ request, fetch }) => {
|
||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
|
|
||||||
console.log(`${API_BASE_PREFIX}/website?id=eq.${data.get("website-id")}`);
|
|
||||||
|
|
||||||
return await apiRequest(
|
return await apiRequest(
|
||||||
fetch,
|
fetch,
|
||||||
`${API_BASE_PREFIX}/website?id=eq.${data.get("website-id")}`,
|
`${API_BASE_PREFIX}/website?id=eq.${data.get("website-id")}`,
|
||||||
|
|||||||
@@ -32,15 +32,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each data.allUsers as { user_id, user_created_at, username, max_number_websites, websites }}
|
{#each data.usersWithWebsites as { id, created_at, username, max_number_websites, website }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<DateTime date={user_created_at} />
|
<DateTime date={created_at} />
|
||||||
</td>
|
</td>
|
||||||
<td>{user_id}</td>
|
<td>{id}</td>
|
||||||
<td>{username}</td>
|
<td>{username}</td>
|
||||||
<td>
|
<td>
|
||||||
<Modal id="manage-user-{user_id}" text="Manage">
|
<Modal id="manage-user-{id}" text="Manage">
|
||||||
<hgroup>
|
<hgroup>
|
||||||
<h3>Manage user</h3>
|
<h3>Manage user</h3>
|
||||||
<p>User "{username}"</p>
|
<p>User "{username}"</p>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
action="?/updateMaxWebsiteAmount"
|
action="?/updateMaxWebsiteAmount"
|
||||||
use:enhance={enhanceForm({ reset: false })}
|
use:enhance={enhanceForm({ reset: false })}
|
||||||
>
|
>
|
||||||
<input type="hidden" name="user-id" value={user_id} />
|
<input type="hidden" name="user-id" value={id} />
|
||||||
<label>
|
<label>
|
||||||
Number of websites allowed:
|
Number of websites allowed:
|
||||||
<input
|
<input
|
||||||
@@ -64,9 +64,9 @@
|
|||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#if websites.length > 0}
|
{#if website.length > 0}
|
||||||
<h4>Websites</h4>
|
<h4>Websites</h4>
|
||||||
{#each websites as { id, title, max_storage_size }}
|
{#each website as { id, title, max_storage_size }}
|
||||||
<details>
|
<details>
|
||||||
<summary>{title}</summary>
|
<summary>{title}</summary>
|
||||||
<div>
|
<div>
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
action="?/deleteUser"
|
action="?/deleteUser"
|
||||||
use:enhance={enhanceForm({ closeModal: true })}
|
use:enhance={enhanceForm({ closeModal: true })}
|
||||||
>
|
>
|
||||||
<input type="hidden" name="user-id" value={user_id} />
|
<input type="hidden" name="user-id" value={id} />
|
||||||
<button type="submit">Delete user</button>
|
<button type="submit">Delete user</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,37 +4,24 @@ import { apiRequest } from "$lib/server/utils";
|
|||||||
import type { Settings, Header, Footer } from "$lib/db-schema";
|
import type { Settings, Header, Footer } from "$lib/db-schema";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, fetch }) => {
|
export const load: PageServerLoad = async ({ params, fetch }) => {
|
||||||
const globalSettings: Settings = (
|
const [globalSettingsResponse, headerResponse, footerResponse] = await Promise.all([
|
||||||
await apiRequest(
|
apiRequest(fetch, `${API_BASE_PREFIX}/settings?website_id=eq.${params.websiteId}`, "GET", {
|
||||||
fetch,
|
headers: { Accept: "application/vnd.pgrst.object+json" },
|
||||||
`${API_BASE_PREFIX}/settings?website_id=eq.${params.websiteId}`,
|
|
||||||
"GET",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: "application/vnd.pgrst.object+json"
|
|
||||||
},
|
|
||||||
returnData: true
|
returnData: true
|
||||||
}
|
}),
|
||||||
)
|
apiRequest(fetch, `${API_BASE_PREFIX}/header?website_id=eq.${params.websiteId}`, "GET", {
|
||||||
).data;
|
headers: { Accept: "application/vnd.pgrst.object+json" },
|
||||||
|
returnData: true
|
||||||
const header: Header = (
|
}),
|
||||||
await apiRequest(fetch, `${API_BASE_PREFIX}/header?website_id=eq.${params.websiteId}`, "GET", {
|
apiRequest(fetch, `${API_BASE_PREFIX}/footer?website_id=eq.${params.websiteId}`, "GET", {
|
||||||
headers: {
|
headers: { Accept: "application/vnd.pgrst.object+json" },
|
||||||
Accept: "application/vnd.pgrst.object+json"
|
|
||||||
},
|
|
||||||
returnData: true
|
returnData: true
|
||||||
})
|
})
|
||||||
).data;
|
]);
|
||||||
|
|
||||||
const footer: Footer = (
|
const globalSettings: Settings = globalSettingsResponse.data;
|
||||||
await apiRequest(fetch, `${API_BASE_PREFIX}/footer?website_id=eq.${params.websiteId}`, "GET", {
|
const header: Header = headerResponse.data;
|
||||||
headers: {
|
const footer: Footer = footerResponse.data;
|
||||||
Accept: "application/vnd.pgrst.object+json"
|
|
||||||
},
|
|
||||||
returnData: true
|
|
||||||
})
|
|
||||||
).data;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
globalSettings,
|
globalSettings,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<input type="number" name="article-weight" value={data.article.article_weight} min="0" />
|
<input type="number" name="article-weight" value={data.article.article_weight} min="0" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
{#if data.categories.length > 0}
|
||||||
<label>
|
<label>
|
||||||
Category:
|
Category:
|
||||||
<select name="category">
|
<select name="category">
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Title:
|
Title:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import type { Actions, PageServerLoad } from "./$types";
|
import type { Actions, PageServerLoad } from "./$types";
|
||||||
import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils";
|
import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils";
|
||||||
import { rm } from "node:fs/promises";
|
|
||||||
import { join } from "node:path";
|
|
||||||
import type { LegalInformation } from "$lib/db-schema";
|
import type { LegalInformation } from "$lib/db-schema";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ parent, fetch, params }) => {
|
export const load: PageServerLoad = async ({ parent, fetch, params }) => {
|
||||||
@@ -58,11 +56,6 @@ export const actions: Actions = {
|
|||||||
return deleteLegalInformation;
|
return deleteLegalInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
await rm(
|
|
||||||
join("/", "var", "www", "archtika-websites", params.websiteId, "legal-information.html"),
|
|
||||||
{ force: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
return deleteLegalInformation;
|
return deleteLegalInformation;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import BlogIndex from "$lib/templates/blog/BlogIndex.svelte";
|
|||||||
import DocsArticle from "$lib/templates/docs/DocsArticle.svelte";
|
import DocsArticle from "$lib/templates/docs/DocsArticle.svelte";
|
||||||
import DocsIndex from "$lib/templates/docs/DocsIndex.svelte";
|
import DocsIndex from "$lib/templates/docs/DocsIndex.svelte";
|
||||||
import { type WebsiteOverview, hexToHSL, slugify } from "$lib/utils";
|
import { type WebsiteOverview, hexToHSL, slugify } from "$lib/utils";
|
||||||
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
import { mkdir, readFile, rename, writeFile, chmod, readdir } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { render } from "svelte/server";
|
import { render } from "svelte/server";
|
||||||
import type { Actions, PageServerLoad } from "./$types";
|
import type { Actions, PageServerLoad } from "./$types";
|
||||||
@@ -290,5 +290,20 @@ const generateStaticFiles = async (websiteData: WebsiteOverview, isPreview = tru
|
|||||||
await writeFile(join(uploadDir, "common.css"), commonStyles);
|
await writeFile(join(uploadDir, "common.css"), commonStyles);
|
||||||
await writeFile(join(uploadDir, "scoped.css"), specificStyles);
|
await writeFile(join(uploadDir, "scoped.css"), specificStyles);
|
||||||
|
|
||||||
|
await setPermissions(isPreview ? join(uploadDir, "../") : uploadDir);
|
||||||
|
|
||||||
return { websitePreviewUrl, websiteProdUrl };
|
return { websitePreviewUrl, websiteProdUrl };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setPermissions = async (dir: string) => {
|
||||||
|
await chmod(dir, 0o777);
|
||||||
|
const entries = await readdir(dir, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
await setPermissions(fullPath);
|
||||||
|
} else {
|
||||||
|
await chmod(fullPath, 0o777);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user