Add administrator role plus manage dashboard and cleanup database migrations

This commit is contained in:
thiloho
2024-10-08 21:20:44 +02:00
parent c4f1bff2a9
commit 1b74e1e6fb
23 changed files with 625 additions and 87 deletions

View File

@@ -1,9 +1,19 @@
import type { Actions, PageServerLoad } from "./$types";
import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils";
export const load: PageServerLoad = async ({ locals }) => {
export const load: PageServerLoad = async ({ fetch, locals }) => {
const storageSizes = await apiRequest(
fetch,
`${API_BASE_PREFIX}/rpc/user_websites_storage_size`,
"GET",
{
returnData: true
}
);
return {
user: locals.user
user: locals.user,
storageSizes
};
};

View File

@@ -33,6 +33,30 @@
</ul>
</section>
{#if data.storageSizes.data.length > 0}
<section id="storage">
<h2>
<a href="#storage">Storage</a>
</h2>
<ul class="unpadded storage-grid">
{#each data.storageSizes.data as { website_title, storage_size_bytes, max_storage_bytes, max_storage_pretty, diff_storage_pretty }}
<li>
<strong>{website_title}</strong>
<label>
{max_storage_pretty} total &mdash; {diff_storage_pretty} free<br />
<meter
value={storage_size_bytes}
min="0"
max={max_storage_bytes}
high={max_storage_bytes * 0.75}
></meter>
</label>
</li>
{/each}
</ul>
</section>
{/if}
<section id="logout">
<h2>
<a href="#logout">Logout</a>
@@ -71,4 +95,22 @@
form[action="?/logout"] > button {
max-inline-size: fit-content;
}
.storage-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 35ch), 1fr));
row-gap: var(--space-s);
column-gap: var(--space-m);
}
.storage-grid > li {
display: flex;
flex-direction: column;
gap: var(--space-3xs);
}
meter {
inline-size: min(512px, 100%);
block-size: 2rem;
}
</style>

View File

@@ -0,0 +1,68 @@
import type { Actions, PageServerLoad } from "./$types";
import { API_BASE_PREFIX } from "$lib/server/utils";
import { apiRequest } from "$lib/server/utils";
export const load: PageServerLoad = async ({ fetch }) => {
const allUsers = (
await apiRequest(
fetch,
`${API_BASE_PREFIX}/all_user_websites?order=user_created_at.desc`,
"GET",
{
returnData: true
}
)
).data;
return {
allUsers,
API_BASE_PREFIX
};
};
export const actions: Actions = {
updateMaxWebsiteAmount: async ({ request, fetch }) => {
const data = await request.formData();
return await apiRequest(
fetch,
`${API_BASE_PREFIX}/user?id=eq.${data.get("user-id")}`,
"PATCH",
{
body: {
max_number_websites: data.get("number-of-websites")
},
successMessage: "Successfully updated user website limit"
}
);
},
updateStorageLimit: async ({ request, fetch }) => {
const data = await request.formData();
console.log(`${API_BASE_PREFIX}/website?id=eq.${data.get("website-id")}`);
return await apiRequest(
fetch,
`${API_BASE_PREFIX}/website?id=eq.${data.get("website-id")}`,
"PATCH",
{
body: {
max_storage_size: data.get("storage-size")
},
successMessage: "Successfully updated user website storage size"
}
);
},
deleteUser: async ({ request, fetch }) => {
const data = await request.formData();
return await apiRequest(
fetch,
`${API_BASE_PREFIX}/user?id=eq.${data.get("user-id")}`,
"DELETE",
{
successMessage: "Successfully deleted user"
}
);
}
};

View File

@@ -0,0 +1,126 @@
<script lang="ts">
import { enhance } from "$app/forms";
import Modal from "$lib/components/Modal.svelte";
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
import type { ActionData, PageServerData } from "./$types";
import { enhanceForm } from "$lib/utils";
import { sending } from "$lib/runes.svelte";
import DateTime from "$lib/components/DateTime.svelte";
const { data, form }: { data: PageServerData; form: ActionData } = $props();
</script>
<SuccessOrError success={form?.success} message={form?.message} />
{#if sending.value}
<LoadingSpinner />
{/if}
<section id="all-users">
<h2>
<a href="#all-users">All users</a>
</h2>
<div class="scroll-container">
<table>
<thead>
<tr>
<th>Account creation</th>
<th>UUID</th>
<th>Username</th>
<th>Manage</th>
</tr>
</thead>
<tbody>
{#each data.allUsers as { user_id, user_created_at, username, max_number_websites, websites }}
<tr>
<td>
<DateTime date={user_created_at} />
</td>
<td>{user_id}</td>
<td>{username}</td>
<td>
<Modal id="manage-user-{user_id}" text="Manage">
<hgroup>
<h3>Manage user</h3>
<p>User "{username}"</p>
</hgroup>
<form
method="POST"
action="?/updateMaxWebsiteAmount"
use:enhance={enhanceForm({ reset: false })}
>
<input type="hidden" name="user-id" value={user_id} />
<label>
Number of websites allowed:
<input
type="number"
name="number-of-websites"
min="0"
value={max_number_websites}
/>
</label>
<button type="submit">Submit</button>
</form>
{#if websites.length > 0}
<h4>Websites</h4>
{#each websites as { id, title, max_storage_size }}
<details>
<summary>{title}</summary>
<div>
<form
method="POST"
action="?/updateStorageLimit"
use:enhance={enhanceForm({ reset: false })}
>
<input type="hidden" name="website-id" value={id} />
<label>
Storage limit in MB:
<input
type="number"
name="storage-size"
min="0"
value={max_storage_size}
/>
</label>
<button type="submit">Submit</button>
</form>
</div>
</details>
{/each}
{/if}
<h4>Delete user</h4>
<details>
<summary>Delete</summary>
<div>
<p>
<strong>Caution!</strong>
Deleting the user will irretrievably erase all their data.
</p>
<form
method="POST"
action="?/deleteUser"
use:enhance={enhanceForm({ closeModal: true })}
>
<input type="hidden" name="user-id" value={user_id} />
<button type="submit">Delete user</button>
</form>
</div>
</details>
</Modal>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</section>
<style>
form[action="?/deleteUser"] {
margin-block-start: var(--space-2xs);
}
</style>

View File

@@ -46,8 +46,7 @@
<a href="#publication-status">Publication status</a>
</h2>
<p>
Your website is published at:
<br />
Your website is published at:<br />
<a href={data.websiteProdUrl}>{data.websiteProdUrl}</a>
</p>
</section>

View File

@@ -53,6 +53,11 @@
{/if}
<ul class="link-wrapper unpadded">
{#if data.user}
{#if data.user.user_role === "administrator"}
<li>
<a href="/manage">Manage</a>
</li>
{/if}
<li>
<a href="/account">Account</a>
</li>