feat(frontend): add data export page
This commit is contained in:
parent
1344099d14
commit
419a3831ee
5 changed files with 120 additions and 0 deletions
|
@ -17,6 +17,7 @@
|
||||||
"@sveltejs/kit": "^1.5.0",
|
"@sveltejs/kit": "^1.5.0",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
|
"@types/luxon": "^3.2.0",
|
||||||
"@types/marked": "^4.0.8",
|
"@types/marked": "^4.0.8",
|
||||||
"@types/sanitize-html": "^2.8.1",
|
"@types/sanitize-html": "^2.8.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
"base64-arraybuffer": "^1.0.2",
|
"base64-arraybuffer": "^1.0.2",
|
||||||
"bootstrap": "5.3.0-alpha1",
|
"bootstrap": "5.3.0-alpha1",
|
||||||
"bootstrap-icons": "^1.10.3",
|
"bootstrap-icons": "^1.10.3",
|
||||||
|
"luxon": "^3.3.0",
|
||||||
"marked": "^4.2.12",
|
"marked": "^4.2.12",
|
||||||
"sanitize-html": "^2.10.0",
|
"sanitize-html": "^2.10.0",
|
||||||
"sveltestrap": "^5.10.0"
|
"sveltestrap": "^5.10.0"
|
||||||
|
|
|
@ -8,6 +8,7 @@ specifiers:
|
||||||
'@sveltejs/kit': ^1.5.0
|
'@sveltejs/kit': ^1.5.0
|
||||||
'@tailwindcss/forms': ^0.5.3
|
'@tailwindcss/forms': ^0.5.3
|
||||||
'@tailwindcss/typography': ^0.5.9
|
'@tailwindcss/typography': ^0.5.9
|
||||||
|
'@types/luxon': ^3.2.0
|
||||||
'@types/marked': ^4.0.8
|
'@types/marked': ^4.0.8
|
||||||
'@types/sanitize-html': ^2.8.1
|
'@types/sanitize-html': ^2.8.1
|
||||||
'@typescript-eslint/eslint-plugin': ^5.45.0
|
'@typescript-eslint/eslint-plugin': ^5.45.0
|
||||||
|
@ -19,6 +20,7 @@ specifiers:
|
||||||
eslint: ^8.28.0
|
eslint: ^8.28.0
|
||||||
eslint-config-prettier: ^8.5.0
|
eslint-config-prettier: ^8.5.0
|
||||||
eslint-plugin-svelte3: ^4.0.0
|
eslint-plugin-svelte3: ^4.0.0
|
||||||
|
luxon: ^3.3.0
|
||||||
marked: ^4.2.12
|
marked: ^4.2.12
|
||||||
postcss: ^8.4.21
|
postcss: ^8.4.21
|
||||||
prettier: ^2.8.0
|
prettier: ^2.8.0
|
||||||
|
@ -38,6 +40,7 @@ dependencies:
|
||||||
base64-arraybuffer: 1.0.2
|
base64-arraybuffer: 1.0.2
|
||||||
bootstrap: 5.3.0-alpha1_@popperjs+core@2.11.6
|
bootstrap: 5.3.0-alpha1_@popperjs+core@2.11.6
|
||||||
bootstrap-icons: 1.10.3
|
bootstrap-icons: 1.10.3
|
||||||
|
luxon: 3.3.0
|
||||||
marked: 4.2.12
|
marked: 4.2.12
|
||||||
sanitize-html: 2.10.0
|
sanitize-html: 2.10.0
|
||||||
sveltestrap: 5.10.0_svelte@3.55.1
|
sveltestrap: 5.10.0_svelte@3.55.1
|
||||||
|
@ -48,6 +51,7 @@ devDependencies:
|
||||||
'@sveltejs/kit': 1.11.0_svelte@3.55.1+vite@4.1.4
|
'@sveltejs/kit': 1.11.0_svelte@3.55.1+vite@4.1.4
|
||||||
'@tailwindcss/forms': 0.5.3_tailwindcss@3.2.7
|
'@tailwindcss/forms': 0.5.3_tailwindcss@3.2.7
|
||||||
'@tailwindcss/typography': 0.5.9_tailwindcss@3.2.7
|
'@tailwindcss/typography': 0.5.9_tailwindcss@3.2.7
|
||||||
|
'@types/luxon': 3.2.0
|
||||||
'@types/marked': 4.0.8
|
'@types/marked': 4.0.8
|
||||||
'@types/sanitize-html': 2.8.1
|
'@types/sanitize-html': 2.8.1
|
||||||
'@typescript-eslint/eslint-plugin': 5.54.1_mlk7dnz565t663n4razh6a6v6i
|
'@typescript-eslint/eslint-plugin': 5.54.1_mlk7dnz565t663n4razh6a6v6i
|
||||||
|
@ -555,6 +559,10 @@ packages:
|
||||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/luxon/3.2.0:
|
||||||
|
resolution: {integrity: sha512-lGmaGFoaXHuOLXFvuju2bfvZRqxAqkHPx9Y9IQdQABrinJJshJwfNCKV+u7rR3kJbiqfTF/NhOkcxxAFrObyaA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/marked/4.0.8:
|
/@types/marked/4.0.8:
|
||||||
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==}
|
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1605,6 +1613,11 @@ packages:
|
||||||
resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==}
|
resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/luxon/3.3.0:
|
||||||
|
resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/magic-string/0.27.0:
|
/magic-string/0.27.0:
|
||||||
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
83
frontend/src/routes/settings/export/+page.svelte
Normal file
83
frontend/src/routes/settings/export/+page.svelte
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from "./$types";
|
||||||
|
import { DateTime, Duration } from "luxon";
|
||||||
|
import { Alert, Button } from "sveltestrap";
|
||||||
|
import { PUBLIC_BASE_URL } from "$env/static/public";
|
||||||
|
import { apiFetchClient } from "$lib/api/fetch";
|
||||||
|
import type { APIError } from "$lib/api/entities";
|
||||||
|
import { addToast } from "$lib/toast";
|
||||||
|
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
|
const createdAt = data.exportData?.created_at
|
||||||
|
? DateTime.fromISO(data.exportData.created_at).toLocal()
|
||||||
|
: DateTime.now();
|
||||||
|
const now = DateTime.now().toLocal();
|
||||||
|
|
||||||
|
const availableFor = Duration.fromObject({ days: 7 });
|
||||||
|
const minTimeBetween = Duration.fromObject({ days: 1 });
|
||||||
|
const durationSinceCreated = now.diff(createdAt, "days");
|
||||||
|
|
||||||
|
let error: APIError | null = null;
|
||||||
|
|
||||||
|
const requestExport = async () => {
|
||||||
|
try {
|
||||||
|
await apiFetchClient<any>("/auth/export/start");
|
||||||
|
|
||||||
|
addToast({
|
||||||
|
header: "Export in progress",
|
||||||
|
body: "Your data is now being exported! Check back here in a few minutes.",
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
error = e as APIError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
Data export
|
||||||
|
<Button
|
||||||
|
color="success"
|
||||||
|
disabled={durationSinceCreated.days < minTimeBetween.days}
|
||||||
|
on:click={requestExport}>Export your data</Button
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<ErrorAlert {error} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if data.exportData}
|
||||||
|
<div>
|
||||||
|
{#if durationSinceCreated.days < minTimeBetween.days}
|
||||||
|
<Alert color="secondary" fade={false}>
|
||||||
|
You can only export your data once a day. You can next export your data at <b
|
||||||
|
>{createdAt.plus(minTimeBetween).toLocaleString(DateTime.DATETIME_MED)}</b
|
||||||
|
>.
|
||||||
|
</Alert>
|
||||||
|
{/if}
|
||||||
|
<p>
|
||||||
|
You last exported your data at {createdAt.toLocaleString(DateTime.DATETIME_MED)}.
|
||||||
|
<br />
|
||||||
|
This file will be available until {createdAt
|
||||||
|
.plus(availableFor)
|
||||||
|
.toLocal()
|
||||||
|
.toLocaleString(DateTime.DATETIME_MED)}
|
||||||
|
<br />
|
||||||
|
Download your export file below:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Button color="primary" href="{PUBLIC_BASE_URL}/media{data.exportData.path}" target="_blank"
|
||||||
|
>Download data export</Button
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Alert color="secondary" fade={false}>
|
||||||
|
You haven't exported your data in the past week. To create an export file, click the button
|
||||||
|
above.
|
||||||
|
</Alert>
|
||||||
|
{/if}
|
||||||
|
</div>
|
19
frontend/src/routes/settings/export/+page.ts
Normal file
19
frontend/src/routes/settings/export/+page.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { ErrorCode, type APIError } from "$lib/api/entities";
|
||||||
|
import { apiFetchClient } from "$lib/api/fetch";
|
||||||
|
import { error } from "@sveltejs/kit";
|
||||||
|
|
||||||
|
export const load = async () => {
|
||||||
|
try {
|
||||||
|
const data = await apiFetchClient<ExportResponse>("/auth/export");
|
||||||
|
return { exportData: data };
|
||||||
|
} catch (e) {
|
||||||
|
if ((e as APIError).code === ErrorCode.NotFound) return { exportData: null };
|
||||||
|
|
||||||
|
throw error((e as APIError).code, (e as APIError).message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ExportResponse {
|
||||||
|
path: string;
|
||||||
|
created_at: Date;
|
||||||
|
}
|
3
frontend/src/routes/settings/tokens/+page.svelte
Normal file
3
frontend/src/routes/settings/tokens/+page.svelte
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<h1>API tokens</h1>
|
||||||
|
|
||||||
|
<p>This page is a work in progress, sorry!</p>
|
Loading…
Reference in a new issue