diff --git a/asset-api/index.js b/asset-api/index.js index 352174a..06d2cec 100644 --- a/asset-api/index.js +++ b/asset-api/index.js @@ -7,6 +7,7 @@ import { join } from "node:path"; import mime from "mime"; import { promisify } from "node:util"; import { pipeline } from "node:stream"; +import cors from "@fastify/cors"; const M2M_ALGORITHM = "RSA-SHA512"; const { private: M2M_PRIVATE_KEY, public: M2M_PUBLIC_KEY } = loadM2MKeys(); @@ -25,6 +26,9 @@ const fastify = new Fastify({ }); fastify.register(multipart); +fastify.register(cors, { + origin: true, +}) fastify.get("/", async () => { return signString(ASSET_API_LANDING_MESSAGE); diff --git a/asset-api/package.json b/asset-api/package.json index a9b30a0..cfb18ed 100644 --- a/asset-api/package.json +++ b/asset-api/package.json @@ -4,6 +4,7 @@ "type": "module", "packageManager": "yarn@4.3.0", "dependencies": { + "@fastify/cors": "^9.0.1", "@fastify/multipart": "^8.3.0", "fastify": "^4.28.0", "mime": "^4.0.3", diff --git a/asset-api/yarn.lock b/asset-api/yarn.lock index a59ee5c..f2924c9 100644 --- a/asset-api/yarn.lock +++ b/asset-api/yarn.lock @@ -83,6 +83,16 @@ __metadata: languageName: node linkType: hard +"@fastify/cors@npm:^9.0.1": + version: 9.0.1 + resolution: "@fastify/cors@npm:9.0.1" + dependencies: + fastify-plugin: "npm:^4.0.0" + mnemonist: "npm:0.39.6" + checksum: 10c0/4db9d3d02edbca741c8ed053819bf3b235ecd70e07c640ed91ba0fc1ee2dc8abedbbffeb79ae1a38ccbf59832e414cad90a554ee44227d0811d5a2d062940611 + languageName: node + linkType: hard + "@fastify/deepmerge@npm:^1.0.0": version: 1.3.0 resolution: "@fastify/deepmerge@npm:1.3.0" @@ -284,6 +294,7 @@ __metadata: resolution: "asset-api@workspace:." dependencies: "@eslint/js": "npm:^9.5.0" + "@fastify/cors": "npm:^9.0.1" "@fastify/multipart": "npm:^8.3.0" eslint: "npm:9.x" fastify: "npm:^4.28.0" @@ -931,6 +942,15 @@ __metadata: languageName: node linkType: hard +"mnemonist@npm:0.39.6": + version: 0.39.6 + resolution: "mnemonist@npm:0.39.6" + dependencies: + obliterator: "npm:^2.0.1" + checksum: 10c0/a538945ea547976136ee6e16f224c0a50983143619941f6c4d2c82159e36eb6f8ee93d69d3a1267038fc5b16f88e2d43390023de10dfb145fa15c5e2befa1cdf + languageName: node + linkType: hard + "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" @@ -945,6 +965,13 @@ __metadata: languageName: node linkType: hard +"obliterator@npm:^2.0.1": + version: 2.0.4 + resolution: "obliterator@npm:2.0.4" + checksum: 10c0/ff2c10d4de7d62cd1d588b4d18dfc42f246c9e3a259f60d5716f7f88e5b3a3f79856b3207db96ec9a836a01d0958a21c15afa62a3f4e73a1e0b75f2c2f6bab40 + languageName: node + linkType: hard + "on-exit-leak-free@npm:^2.1.0": version: 2.1.2 resolution: "on-exit-leak-free@npm:2.1.2" diff --git a/identity-api/package.json b/identity-api/package.json index d681160..ef5d576 100644 --- a/identity-api/package.json +++ b/identity-api/package.json @@ -7,6 +7,7 @@ "type": "module", "packageManager": "yarn@4.3.0", "dependencies": { + "@fastify/cors": "^9.0.1", "dotenv": "^16.4.5", "fastify": "^4.27.0", "jose": "^5.4.0" diff --git a/identity-api/src/index.js b/identity-api/src/index.js index 593e607..780afec 100644 --- a/identity-api/src/index.js +++ b/identity-api/src/index.js @@ -3,9 +3,14 @@ import { ASSET_API_ENDPOINT, IDENTITY_API_LANDING_MESSAGE, LISTEN_PORT } from ". import { contentFromSigned, verifySignature } from "./m2m.js"; import { startAuth } from "./auth.js"; import { randomUUID } from "node:crypto"; +import cors from "@fastify/cors"; let auth = await startAuth(); +app.register(cors, { + origin: true, +}) + app.get("/", async () => { return IDENTITY_API_LANDING_MESSAGE; }); diff --git a/identity-api/yarn.lock b/identity-api/yarn.lock index 0a3790b..9bf0a0a 100644 --- a/identity-api/yarn.lock +++ b/identity-api/yarn.lock @@ -76,6 +76,16 @@ __metadata: languageName: node linkType: hard +"@fastify/cors@npm:^9.0.1": + version: 9.0.1 + resolution: "@fastify/cors@npm:9.0.1" + dependencies: + fastify-plugin: "npm:^4.0.0" + mnemonist: "npm:0.39.6" + checksum: 10c0/4db9d3d02edbca741c8ed053819bf3b235ecd70e07c640ed91ba0fc1ee2dc8abedbbffeb79ae1a38ccbf59832e414cad90a554ee44227d0811d5a2d062940611 + languageName: node + linkType: hard + "@fastify/error@npm:^3.3.0, @fastify/error@npm:^3.4.0": version: 3.4.1 resolution: "@fastify/error@npm:3.4.1" @@ -591,6 +601,13 @@ __metadata: languageName: node linkType: hard +"fastify-plugin@npm:^4.0.0": + version: 4.5.1 + resolution: "fastify-plugin@npm:4.5.1" + checksum: 10c0/f58f79cd9d3c88fd7f79a3270276c6339fc57bbe72ef14d20b73779193c404e317ac18e8eae2c5071b3909ebee45d7eb6871da4e65464ac64ed0d9746b4e9b9f + languageName: node + linkType: hard + "fastify@npm:^4.27.0": version: 4.27.0 resolution: "fastify@npm:4.27.0" @@ -713,6 +730,7 @@ __metadata: resolution: "identity-api@workspace:." dependencies: "@eslint/js": "npm:^9.5.0" + "@fastify/cors": "npm:^9.0.1" dotenv: "npm:^16.4.5" eslint: "npm:9.x" fastify: "npm:^4.27.0" @@ -900,6 +918,15 @@ __metadata: languageName: node linkType: hard +"mnemonist@npm:0.39.6": + version: 0.39.6 + resolution: "mnemonist@npm:0.39.6" + dependencies: + obliterator: "npm:^2.0.1" + checksum: 10c0/a538945ea547976136ee6e16f224c0a50983143619941f6c4d2c82159e36eb6f8ee93d69d3a1267038fc5b16f88e2d43390023de10dfb145fa15c5e2befa1cdf + languageName: node + linkType: hard + "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" @@ -914,6 +941,13 @@ __metadata: languageName: node linkType: hard +"obliterator@npm:^2.0.1": + version: 2.0.4 + resolution: "obliterator@npm:2.0.4" + checksum: 10c0/ff2c10d4de7d62cd1d588b4d18dfc42f246c9e3a259f60d5716f7f88e5b3a3f79856b3207db96ec9a836a01d0958a21c15afa62a3f4e73a1e0b75f2c2f6bab40 + languageName: node + linkType: hard + "on-exit-leak-free@npm:^2.1.0": version: 2.1.2 resolution: "on-exit-leak-free@npm:2.1.2" diff --git a/identity-web/package.json b/identity-web/package.json index 17ae64c..8ea9ace 100644 --- a/identity-web/package.json +++ b/identity-web/package.json @@ -41,6 +41,7 @@ "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/svelte-fontawesome": "^0.2.2", "felte": "^1.2.14", + "fuse.js": "^7.0.0", "mime": "^4.0.3" } } diff --git a/identity-web/src/lib/components/FeelingsChooser.svelte b/identity-web/src/lib/components/FeelingsChooser.svelte new file mode 100644 index 0000000..4a513fb --- /dev/null +++ b/identity-web/src/lib/components/FeelingsChooser.svelte @@ -0,0 +1,74 @@ + + +
+ {#if displayText} + Feelings + {/if} + +
+ +
+ {#if chosenFeelings.length > 0} +
+ Chosen: + {#each chosenFeelings as feeling (feeling)} +
+ + +
+ {/each} +
+ {:else} + No feelings chosen. + {#if required} + You need to choose at least one feeling. + {/if} + {/if} +
+
+
+ {#each feelingsToChoose as feeling (feeling)} + + {/each} +
+
\ No newline at end of file diff --git a/identity-web/src/lib/entry.ts b/identity-web/src/lib/entry.ts index d64b282..cc32dd0 100644 --- a/identity-web/src/lib/entry.ts +++ b/identity-web/src/lib/entry.ts @@ -2,6 +2,7 @@ export const TITLED_ENTRIES = ["event", "environment", "memory"]; export const FEELINGS = ["relaxed", "afraid", "angry", "bad", "bored", "confused", "excited", "fine", "happy", "hurt", "in love", "mad", "nervous", "okay", "sad", "scared", "shy", "sleepy", "active", "surprised", "tired", "upset", "worried"]; export type KnownFeeling = "relaxed" | "afraid" | "angry" | "bad" | "bored" | "confused" | "excited" | "fine" | "happy" | "hurt" | "in love" | "mad" | "nervous" | "okay" | "sad" | "scared" | "shy" | "sleepy" | "active" | "surprised" | "tired" | "upset" | "worried"; +export type EntryKind = "song" | "album" | "event" | "memory" | "feeling" | "environment" | "date"; export type IdlessEntry = { base: SongEntry | AlbumEntry | EventEntry | MemoryEntry | FeelingEntry | EnvironmentEntry | DateEntry, diff --git a/identity-web/src/routes/dashboard/+page.svelte b/identity-web/src/routes/dashboard/+page.svelte index c0b81fd..cb3a519 100644 --- a/identity-web/src/routes/dashboard/+page.svelte +++ b/identity-web/src/routes/dashboard/+page.svelte @@ -3,27 +3,73 @@ import { account, credentials } from "$lib/stores"; import { onMount } from "svelte"; import Entries from "./Entries.svelte"; - import Overview from "./Overview.svelte" + import Overview from "./Overview.svelte"; + import { FontAwesomeIcon } from "@fortawesome/svelte-fontawesome"; + import { faFilter } from "@fortawesome/free-solid-svg-icons"; + import FilterSelector from "./utils/FilterSelector.svelte"; credentials.subscribe((v) => v == null && (setTimeout(() => window.location.pathname = '/auth/login', 200))) - let throtle = false; - let currentOffset = 10; - let step = 5; - let entries = entryPage($credentials!, 0, currentOffset); + + function createPageHandler({ onLoadingStatusChanged, onEndReached }: { onLoadingStatusChanged: (status: boolean) => any, onEndReached: () => any }) { + let loadingPage = false; + let rechedEnd = false; + let currentOffset = 10; + let step = 5; + + return { + initialEntries: entryPage($credentials!, 0, currentOffset), + nextPage: async () => { + if (loadingPage || reachedEnd) { + return undefined; + } + + loadingPage = true; + onLoadingStatusChanged(loadingPage); + + let page = await entryPage($credentials!, currentOffset, step); + currentOffset += step; + + loadingPage = false; + onLoadingStatusChanged(loadingPage); + if (page.length === 0) { + reachedEnd = true; + onEndReached(); + } + + return page; + } + } + } + let overview = Promise.allSettled([entryPage($credentials!, 0, 3), entryPage($credentials!, 20, 3)]) + let loadingPage = false; + let reachedEnd = false; + let filterStatus = false; + let { initialEntries: entries, nextPage } = createPageHandler({ + onLoadingStatusChanged: (status) => loadingPage = status, + onEndReached: () => reachedEnd = true, + }) + + let showFilterSelector = false + let chosenFilterFeelings = [] + let filters = { + fromDate: null, + toDate: null, + kind: null, + feelings: null, + searchQuery: null, + } + onMount(() => { function handleScroll() { - if (!throtle && window.innerHeight + window.scrollY >= document.body.offsetHeight) { - throtle = true + if (!filterStatus && window.innerHeight + window.scrollY >= document.body.offsetHeight) { entries.then(async (page) => { - console.log("new page requested") - let secondPage = await entryPage($credentials!, currentOffset, step); - page = [...page, ...secondPage]; - - currentOffset += step - throtle = false - entries = new Promise((resolve) => resolve(page)) + let secondPage = await nextPage() + if (secondPage != null) { + page = [...page, ...secondPage]; + entries = new Promise((resolve) => resolve(page)) + } }) } } @@ -66,13 +112,41 @@ {/await} -
-

Memories

- + Add an entry +

Entries

+
+ + Add an entry +
+ + {#if showFilterSelector} + chosenFilterFeelings = e.detail} on:updatedFilter={(e) => filters = e.detail} chosenFeelings={chosenFilterFeelings} filters={filters}/> + {/if} +
- refreshEntries()} entries={entries}/> + filterStatus = e.detail} on:deleted={() => refreshEntries()} entries={entries} filters={filters}/>
+ {#if loadingPage && !reachedEnd} +
+
+ Loading entries... + +
+
+ {/if} + + {#if reachedEnd} +
+
+ You've reached the end +
+
+ {/if} {/if}
diff --git a/identity-web/src/routes/dashboard/Entries.svelte b/identity-web/src/routes/dashboard/Entries.svelte index d58a45a..2afc806 100644 --- a/identity-web/src/routes/dashboard/Entries.svelte +++ b/identity-web/src/routes/dashboard/Entries.svelte @@ -1,23 +1,97 @@ -{#each entries as entry (entry.id)} +{#each filteredEntries as entry (entry.id)} extended = [entry.id, ...extended]} - on:contracted={() => extended = extended.filter(v => v !== entry.id)} - on:deleted={(event) => { dispatch('deleted', event.detail) }} + on:extended={(e) => extended = [e.detail.id, ...extended]} + on:contracted={(e) => extended = extended.filter(v => v !== e.detail.id)} + on:deleted={(e) => { dispatch('deleted', e.detail) }} id={entry.id} kind={entry.base.kind} diff --git a/identity-web/src/routes/dashboard/utils/Entry.svelte b/identity-web/src/routes/dashboard/utils/Entry.svelte index 3649b97..837c904 100644 --- a/identity-web/src/routes/dashboard/utils/Entry.svelte +++ b/identity-web/src/routes/dashboard/utils/Entry.svelte @@ -16,7 +16,7 @@ let prevExtended = isExtended $: if (prevExtended !== isExtended) { - dispatch(isExtended ? 'extended' : 'contracted') + dispatch(isExtended ? 'extended' : 'contracted', { id }) } async function processDeletion(id: string) { @@ -44,7 +44,7 @@
- -
- {#if chosenFeelings.length > 0} -
- Chosen: - {#each chosenFeelings as feeling (feeling)} -
- - -
- {/each} -
- {:else} - No feelings chosen. - {#if kind === "feeling"} - You need to choose at least one feeling. - {/if} - {/if} -
-
-
- {#each feelingsToChoose as feeling (feeling)} - - {/each} -
+
+ {#if $errors.feelings != null}

{$errors.feelings[0]}

{/if} diff --git a/identity-web/yarn.lock b/identity-web/yarn.lock index ea2e121..3de6bc6 100644 --- a/identity-web/yarn.lock +++ b/identity-web/yarn.lock @@ -1759,6 +1759,13 @@ __metadata: languageName: node linkType: hard +"fuse.js@npm:^7.0.0": + version: 7.0.0 + resolution: "fuse.js@npm:7.0.0" + checksum: 10c0/3574b7fc2e0ccb047e05dbe5f8f04e8f0754f62fa209669ef426ea1354a32ae7355620788af8f1d29f94e1fdecd513f1f3787f012848a31ec90bb4e0e6092504 + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -1932,6 +1939,7 @@ __metadata: eslint-config-prettier: "npm:^9.1.0" eslint-plugin-svelte: "npm:^2.36.0" felte: "npm:^1.2.14" + fuse.js: "npm:^7.0.0" globals: "npm:^15.0.0" mime: "npm:^4.0.3" postcss: "npm:^8.4.38"