more progressgit add .

This commit is contained in:
Sofía Aritz 2024-06-26 13:39:06 +02:00
parent 77816595f5
commit ebab36f676
Signed by: sofia
GPG key ID: 90B5116E3542B28F
12 changed files with 170 additions and 44 deletions

View file

@ -63,7 +63,11 @@ fastify.put("/asset", {
}),
});
promisify(pipeline)(file.file, createWriteStream(`.assets/${full_id}`));
await promisify(pipeline)(file.file, createWriteStream(`.assets/${full_id}`));
return {
asset_id: full_id,
}
},
schema: {
query: {

View file

@ -50,6 +50,34 @@ app.get("/asset/endpoint", {
}
})
app.delete("/entry", {
async handler(request, reply) {
let jwt = request.headers["authorization"].replace("Bearer", "").trim();
let { payload } = await auth.verifyJwt(jwt);
let user = await auth.user(payload.uid);
user.entries = user.entries.filter(v => v.id !== request.query.entry_id);
await auth.updateUser(payload.uid, user);
},
schema: {
headers: {
type: "object",
properties: {
authorization: { type: "string" },
},
required: ["authorization"],
},
query: {
type: "object",
properties: {
entry_id: { type: "string" },
},
required: ["entry_id"],
},
},
})
app.put("/entry", {
async handler(request, reply) {
let jwt = request.headers["authorization"].replace("Bearer", "").trim();

View file

@ -16,6 +16,7 @@
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@tailwindcss/forms": "^0.5.7",
"@types/eslint": "^8.56.7",
"autoprefixer": "^10.4.19",
"eslint": "^9.0.0",

View file

@ -1,6 +1,7 @@
import type { Entry, IdlessEntry } from "./entry"
const ENDPOINT = 'http://localhost:3000/'
const ASSET_API_ENDPOINT = 'http://localhost:3001/'
export type Credentials = {
token: string,
@ -81,4 +82,24 @@ export async function addEntry(credentials: Credentials, entry: IdlessEntry): Pr
},
body: JSON.stringify({entry}),
})
}
export async function deleteEntry(credentials: Credentials, entry_id: string): Promise<void> {
await sendRequest('/entry', credentials, { method: 'DELETE' }, `?entry_id=${entry_id}`)
}
export async function uploadAsset(session_key: string, file: File): Promise<string> {
let url = new URL('/asset', ASSET_API_ENDPOINT);
url.search = `?session_key=${session_key}`
let form = new FormData()
form.append("file", file);
let res = await fetch(url, {
method: "PUT",
body: form,
})
let { asset_id } = await res.json();
return asset_id;
}

View file

@ -17,13 +17,13 @@
<div class="text-xl">
{#if $credentials == null}
| <div class="px-3 inline-block"><a href="/">Home</a></div>
| <div class="px-3 inline-block"><a href="https://support.identity.net/">Support</a></div>
| <div class="px-3 inline-block"><a href="mailto:sofi@sofiaritz.com">Support</a></div>
| <div class="px-3 inline-block"><a href="/auth/register">Join</a></div>
|
{:else}
| <div class="px-3 inline-block"><a href="/dashboard">Dashboard</a></div>
| <div class="px-3 inline-block"><a href="/auth/account">Account</a></div>
| <div class="px-3 inline-block"><a href="https://support.identity.net/">Support</a></div>
| <div class="px-3 inline-block"><a href="mailto:sofi@sofiaritz.com">Support</a></div>
|
{/if}
</div>

View file

@ -1 +1,14 @@
<h1>Account management</h1>
<script lang="ts">
import { account, credentials } from "$lib/stores";
credentials.subscribe((v) => v == null && (setTimeout(() => window.location.pathname = '/auth/login', 200)))
</script>
<div class="mt-3.5 justify-center flex">
<div class="w-[60%] flex flex-col">
<h1 class="text-2xl pb-3.5">Welcome back, <span class="font-bold">{$account?.name}</span>.</h1>
<p>
The account panel is still work in progress, feel free to contact support with any inquires.
</p>
</div>
</div>

View file

@ -7,6 +7,10 @@
credentials.subscribe((v) => v == null && (setTimeout(() => window.location.pathname = '/auth/login', 200)))
let entries = entryPage($credentials!, 0, 20);
let overview = Promise.allSettled([entryPage($credentials!, 0, 3), entryPage($credentials!, 20, 3)])
function refreshEntries() {
entries = entryPage($credentials!, 0, 20);
}
</script>
{#if $account != null}
@ -43,7 +47,7 @@
<a class="rounded-lg bg-violet-700 text-white px-3 py-2 text-center hover:bg-violet-800 focus:ring-4 focus:ring-violet-300" href="/entry/new">+ Add an entry</a>
</div>
<div class="mt-3.5 flex flex-col gap-1">
<Entries entries={entries}/>
<Entries on:deleted={() => refreshEntries()} entries={entries}/>
</div>
{/if}
</div>

View file

@ -5,12 +5,15 @@
import Entry from "./utils/Entry.svelte";
import EntryDescription from "./utils/EntryDescription.svelte";
import AssetPreview from "./utils/AssetPreview.svelte";
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher()
export let entries: EntryType[]
</script>
{#each entries as entry (entry.id)}
<Entry id={entry.id} kind={entry.base.kind} creationDate={new Date(entry.creationDate)} title={entry.base.kind === "date" ? new Date(entry.base.referencedDate).toLocaleDateString() : entry.title}>
<Entry on:deleted={(event) => { dispatch('deleted',event.detail) }} id={entry.id} kind={entry.base.kind} creationDate={new Date(entry.creationDate)} title={entry.base.kind === "date" ? new Date(entry.base.referencedDate).toLocaleDateString() : entry.title}>
<div slot="contracted">
{#if entry.base.kind === "song" || entry.base.kind === "album"}
<ExternalLink href={entry.base.link[0]}>{entry.base.artist} &dash; {entry.base.title}</ExternalLink>

View file

@ -1,6 +1,11 @@
<script lang="ts">
import { deleteEntry } from "$lib/api";
import { credentials } from "$lib/stores";
import { TITLED_ENTRIES } from "$lib/entry";
import EntryKind from "./EntryKind.svelte";
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher()
export let id: string;
export let creationDate: Date;
@ -9,6 +14,13 @@
export let isExtended = false;
async function processDeletion(id: string) {
await deleteEntry($credentials!, id);
dispatch('deleted', {
id,
})
}
$: cardClass = () => {
let cardClass = "border border-gray-200 rounded-lg shadow w-full flex p-3.5"
@ -28,14 +40,19 @@
<div class={cardClass()} id={`entry__${id}`}>
<button on:click={() => isExtended = !isExtended}>
<div class="flex items-center gap-2.5">
<EntryKind kind={kind}/>
{#if title != null && isExtended}
<span>Created at: <time datetime={creationDate.toISOString()}>{creationDate.toLocaleDateString()}</time></span>
{:else if title != null}
<h2 class="text-xl text-left font-semibold">{title}</h2>
{:else if isExtended}
<span>Created at: <time datetime={creationDate.toISOString()}>{creationDate.toLocaleDateString()}</time></span>
<div class="flex justify-between items-center">
<div class="flex items-center gap-2.5">
<EntryKind kind={kind}/>
{#if title != null && isExtended}
<span>Created at: <time datetime={creationDate.toISOString()}>{creationDate.toLocaleDateString()}</time></span>
{:else if title != null}
<h2 class="text-xl text-left font-semibold">{title}</h2>
{:else if isExtended}
<span>Created at: <time datetime={creationDate.toISOString()}>{creationDate.toLocaleDateString()}</time></span>
{/if}
</div>
{#if isExtended}
<button on:click={() => processDeletion(id)} class="rounded-lg bg-red-600 text-white px-2.5 py-1.5 text-center hover:bg-red-700 focus:ring-4 focus:ring-violet-300">Delete entry</button>
{/if}
</div>

View file

@ -6,8 +6,8 @@
import { faSpotify, faYoutube } from "@fortawesome/free-brands-svg-icons";
import { faChevronDown, faLink, faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import FeelingPill from "../../dashboard/utils/FeelingPill.svelte";
import { addEntry } from "$lib/api";
import { credentials } from "$lib/stores";
import { addEntry, uploadAsset } from "$lib/api";
import { credentials, session_key } from "$lib/stores";
credentials.subscribe((v) => v == null && (setTimeout(() => window.location.pathname = '/auth/login', 200)))
@ -16,7 +16,7 @@
$: feelingsToChoose = FEELINGS.filter(v => !chosenFeelings.includes(v))
let kind: EntryKind | null
let kind: EntryKind | null = "song"
const { form, errors } = createForm({
onSubmit: async (values) => {
let feelings = Object.keys(values)
@ -48,11 +48,16 @@
kind: values.kind,
}
}
let asset_id
if (values.asset != null && typeof values.asset === "object") {
asset_id = await uploadAsset($session_key!, values.asset)
}
let entry: IdlessEntry = {
base,
creationDate: new Date().toISOString(),
assets: [],
assets: [asset_id].filter(v => v != null) as string[],
feelings,
title: TITLED_ENTRIES.includes(values.kind) ? values.title : undefined,
description: values.description,
@ -199,35 +204,42 @@
<textarea name="description" id="add-entry__description" rows="7" class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-violet-500 focus:border-violet-500" placeholder="Write your thoughts here..."></textarea>
</div>
<div class="mb-5 flex flex-col gap-3.5">
<div class="flex gap-2 items-center">
<button type="button" on:click={() => feelingsDropdownShown = !feelingsDropdownShown} class="text-white bg-violet-700 hover:bg-violet-800 focus:ring-4 focust:outline-none focus:ring-violet-300 font-medium rounded-lg px-3 py-1.5 text-center">
<div class="mb-5">
<label for="add-entry__assets" class="block mb-2 text-sm font-medium text-gray-900">Linked assets (max 5MB)</label>
<input name="asset" id="add-entry__assets" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-violet-500 focus:border-violet-500 block w-full hover:cursor-pointer file:bg-gray-200 file:border-gray-300 file:border-0 file:me-4 file:py-2.5 file:px-4 hover:file:bg-gray-300" type="file">
</div>
<div class="mb-5 flex flex-col">
<span class="block mb-2 text-sm font-medium text-gray-900">Feelings</span>
<div class="flex ">
<button type="button" on:click={() => feelingsDropdownShown = !feelingsDropdownShown} class={`inline-flex gap-1.5 items-center px-2.5 text-sm text-gray-900 bg-gray-200 border rounded-e-0 border-gray-300 border-e-0 ${feelingsDropdownShown ? "rounded-tl-lg" : "rounded-s-lg"} hover:cursor-pointer hover:bg-gray-300`}>
Feelings
<FontAwesomeIcon icon={faChevronDown}/>
</button>
{#if chosenFeelings.length > 0}
<div>
<span class="mr-1">Chosen:</span>
{#each chosenFeelings as feeling (feeling)}
<div class="inline">
<button type="button" on:click={() => chosenFeelings = chosenFeelings.filter(v => v !== feeling)}>
<FeelingPill feeling={feeling}>
<span class="pr-1" slot="pre"><FontAwesomeIcon icon={faXmark}/></span>
</FeelingPill>
</button>
<input type="checkbox" class="hidden" name={`feeling__${feeling}`} checked>
</div>
{/each}
</div>
{:else}
<span>No feelings chosen.</span>
{#if kind === "feeling"}
<span>You need to choose at least one feeling.</span>
<div id="add-entry__feelings" class={`rounded-none ${feelingsDropdownShown ? "rounded-tr-lg" : "rounded-e-lg"} bg-gray-50 border text-gray-900 focus:ring-violet-500 focus:border-violet-500 block flex-1 min-w-0 w-full text-sm border-gray-300 p-2.5`} placeholder="https://www.music.tld/play/...">
{#if chosenFeelings.length > 0}
<div>
<span class="mr-1">Chosen:</span>
{#each chosenFeelings as feeling (feeling)}
<div class="inline">
<button type="button" on:click={() => chosenFeelings = chosenFeelings.filter(v => v !== feeling)}>
<FeelingPill feeling={feeling}>
<span class="pr-1" slot="pre"><FontAwesomeIcon icon={faXmark}/></span>
</FeelingPill>
</button>
<input type="checkbox" class="hidden" name={`feeling__${feeling}`} checked>
</div>
{/each}
</div>
{:else}
<span>No feelings chosen.</span>
{#if kind === "feeling"}
<span>You need to choose at least one feeling.</span>
{/if}
{/if}
{/if}
</div>
</div>
<div class:hidden={!feelingsDropdownShown}>
<div class:hidden={!feelingsDropdownShown} class="bg-gray-50 border border-t-0 border-gray-300 py-3 px-1.5 rounded-b-lg">
{#each feelingsToChoose as feeling (feeling)}
<label class="capitalize p-1">
<button type="button" on:click={() => chosenFeelings = [feeling, ...chosenFeelings]}>
@ -239,7 +251,7 @@
{/each}
</div>
{#if $errors.feelings != null}
<p class="text-sm text-red-600"><span class="font-medium">{$errors.feelings[0]}</span></p>
<p class="text-sm text-red-600 mt-1.5"><span class="font-medium">{$errors.feelings[0]}</span></p>
{/if}
</div>

View file

@ -4,6 +4,8 @@ export default {
theme: {
extend: {},
},
plugins: [],
plugins: [
require("@tailwindcss/forms")
],
}

View file

@ -616,6 +616,17 @@ __metadata:
languageName: node
linkType: hard
"@tailwindcss/forms@npm:^0.5.7":
version: 0.5.7
resolution: "@tailwindcss/forms@npm:0.5.7"
dependencies:
mini-svg-data-uri: "npm:^1.2.3"
peerDependencies:
tailwindcss: ">=3.0.0 || >= 3.0.0-alpha.1"
checksum: 10c0/cd29e0c978402ae87a923ae802dcff43f7b050595666cb067321cac2e37a52f61b9d73385cb0a10455548581ddd0d3886815bd6c64a1da06247c0057fa9f4601
languageName: node
linkType: hard
"@types/cookie@npm:^0.6.0":
version: 0.6.0
resolution: "@types/cookie@npm:0.6.0"
@ -1914,6 +1925,7 @@ __metadata:
"@sveltejs/adapter-auto": "npm:^3.0.0"
"@sveltejs/kit": "npm:^2.0.0"
"@sveltejs/vite-plugin-svelte": "npm:^3.0.0"
"@tailwindcss/forms": "npm:^0.5.7"
"@types/eslint": "npm:^8.56.7"
autoprefixer: "npm:^10.4.19"
eslint: "npm:^9.0.0"
@ -2299,6 +2311,15 @@ __metadata:
languageName: node
linkType: hard
"mini-svg-data-uri@npm:^1.2.3":
version: 1.4.4
resolution: "mini-svg-data-uri@npm:1.4.4"
bin:
mini-svg-data-uri: cli.js
checksum: 10c0/24545fa30b5a45449241bf19c25b8bc37594b63ec06401b3d563bd1c2e8a6abb7c18741f8b354e0064baa63c291be214154bf3a66f201ae71dfab3cc1a5e3191
languageName: node
linkType: hard
"minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"