Skip to content

Commit

Permalink
Fix folksonomy login, minor improvements to Folksonomy card
Browse files Browse the repository at this point in the history
  • Loading branch information
VaiTon committed Dec 13, 2023
1 parent dc45420 commit 89fb8d4
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 116 deletions.
24 changes: 12 additions & 12 deletions src/lib/api/folksonomy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { get } from 'svelte/store';

import type { paths, components } from './folksonomy.d';
import createClient from 'openapi-fetch';
import { formBody as formBodySerializer } from './utils';

export type FolksonomyTag = components['schemas']['ProductTag'];
export type FolksonomyKey = {
Expand All @@ -26,7 +27,10 @@ export class FolksonomyApi {
this.client = createClient({
baseUrl: BASE_URL,
fetch,
headers: { 'Content-Type': 'application/json' }
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + get(preferences).folksonomy.authToken
}
});
}

Expand All @@ -42,8 +46,7 @@ export class FolksonomyApi {

async putTag(tag: FolksonomyTag): Promise<boolean> {
const res = await this.client.PUT('/product', {
body: tag,
headers: { Authorization: 'Bearer ' + get(preferences).folksonomy.authToken }
body: tag
});

return res.response.status === 200;
Expand All @@ -59,8 +62,7 @@ export class FolksonomyApi {

async addTag(tag: FolksonomyTag): Promise<boolean> {
const res = await this.client.POST('/product', {
body: tag,
headers: { Authorization: 'Bearer ' + get(preferences).folksonomy.authToken }
body: tag
});

return res.response.status === 200;
Expand All @@ -71,27 +73,25 @@ export class FolksonomyApi {
params: {
path: { product: tag.product, k: tag.k },
query: { version: tag.version }
},
headers: { Authorization: 'Bearer ' + get(preferences).folksonomy.authToken }
}
});

return res;
}

async login(username: string, password: string) {
const res = await this.client.POST('/auth', {
body: { username, password }
body: { username, password },
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
bodySerializer: formBodySerializer
});

if (res.response.status !== 200) throw new Error('Could not authenticate to Folksonomy API');

const token = (await res.response.json()) as { access_token: string; token_type: string };
preferences.update((p) => ({
...p,
folksonomy: {
...p.folksonomy,
authToken: token.access_token
}
folksonomy: { ...p.folksonomy, authToken: token.access_token }
}));
}
}
7 changes: 7 additions & 0 deletions src/lib/api/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function formBody(params: Record<string, string>) {
const formBody = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
formBody.append(key, value);
}
return formBody;
}
11 changes: 9 additions & 2 deletions src/routes/folksonomy/[key]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
import type { PageData } from './$types';
export let data: PageData;
function productNameOrBarcode(
product: { product_name: string } | null,
tag: { product: string }
) {
return product?.product_name ?? tag.product;
}
</script>

<h2 class="text-2xl font-bold">
Expand All @@ -25,10 +32,10 @@
<tr>
<td>
{#await data.streamed.products}
Loading product names...
Loading product name...
{:then products}
<a class="link" href="/products/{tag.product}">
{products[i].product_name}
{productNameOrBarcode(products[i], tag)}
</a>
{/await}
</td>
Expand Down
2 changes: 1 addition & 1 deletion src/routes/folksonomy/[key]/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export const ssr = false;
export const load: PageLoad = async ({ fetch, params }) => {
const { key } = params;
const tags = await new FolksonomyApi(fetch).getProducts(key);
const productsApi = new ProductsApi(fetch);

const productsApi = new ProductsApi(fetch);
const products = Promise.all(tags.map((tag) => productsApi.getProductName(tag.product)));

return {
Expand Down
216 changes: 122 additions & 94 deletions src/routes/products/[barcode]/Folksonomy.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@
export let barcode: string;
export let keys: readonly string[];
function getFilteredKeys(newKey: string, keys: readonly string[]) {
if (newKey === '') {
return keys;
async function refreshTags() {
const res = await new FolksonomyApi(fetch).getProduct(barcode);
if (res.error) {
console.error(res.error);
} else {
return keys.filter((key) => key.includes(newKey) && key !== newKey);
tags = res.data;
}
}
$: editable = $preferences.folksonomy.authToken != null;
$: filteredKeys = getFilteredKeys(newKey, keys);
function getFilteredKeys(keys: readonly string[], newKey: string) {
if (newKey === '') {
return keys;
}
return keys.filter((key) => key.includes(newKey) && key !== newKey);
}
$: filteredNewKeys = getFilteredKeys(keys, newKey);
let newButton: HTMLButtonElement;
$: loggedIn = $preferences.folksonomy.authToken != null;
async function updateTag(
event: Event & {
currentTarget: EventTarget & HTMLInputElement;
},
idx: number
) {
async function updateTag(newValue: string, idx: number) {
const oldTag = tags[idx];
const newValue = event.currentTarget.value;
const newTag: FolksonomyTag = {
...oldTag,
Expand All @@ -35,39 +35,54 @@
v: newValue
};
const res = await new FolksonomyApi(fetch).putTag(newTag);
if (res) {
console.debug('Updated tag', oldTag, 'to', newTag);
tags[idx] = newTag;
tags = [...tags];
const ok = await new FolksonomyApi(fetch).putTag(newTag);
if (!ok) {
console.error('Failed to update tag', oldTag, 'to', newTag);
return;
}
console.debug('Updated tag', oldTag, 'to', newTag);
await refreshTags();
}
async function removeTag(tag: FolksonomyTag) {
tags = tags.filter((t) => t.k !== tag.k);
const version = tag.version;
if (version == null) {
throw new Error('Tag value is null');
}
// otherwise ts complains about version possibly being null
new FolksonomyApi(fetch).removeTag({ ...tag, version });
}
let newKey = '';
let newValue = '';
let creatingNewTag: boolean;
async function createNewTag() {
newButton.classList.add('loading');
newButton.disabled = true;
if (newKey !== '' && newValue != '') {
const newTag: FolksonomyTag = {
k: newKey,
v: newValue,
product: barcode,
version: 1
};
const ok = await new FolksonomyApi(fetch).addTag(newTag);
if (ok) {
tags = [...tags, newTag];
newKey = '';
newValue = '';
}
creatingNewTag = true;
if (newKey == '' || newValue == '') {
creatingNewTag = false;
throw new Error('New key or value is empty');
}
const newTag: FolksonomyTag = {
k: newKey,
v: newValue,
product: barcode,
version: 1
};
const created = await new FolksonomyApi(fetch).addTag(newTag);
if (created) {
tags = [...tags, newTag];
newKey = '';
newValue = '';
}
newButton.classList.remove('loading');
newButton.disabled = false;
creatingNewTag = false;
}
</script>

Expand All @@ -76,83 +91,96 @@
<tr>
<th>Key</th>
<th>Value</th>
{#if loggedIn}
<th></th>
{/if}
</tr>
</thead>
<tbody>
{#each tags as tag, i}
<tr>
<td aria-label="Key">
<div class="flex w-full">
<input class="input grow max-sm:w-20" type="text" readonly value={tag.k} />
<a href="/folksonomy/{tag.k}" class="link grow pl-2 font-mono max-sm:w-20" type="text">
{tag.k}
</a>
</div>
</td>
<td class="flex gap-2" aria-label="Value">
<input
type="text"
class="input grow max-sm:w-20"
value={tag.v}
readonly={!editable}
on:change={(e) => updateTag(e, i)}
readonly={!loggedIn}
on:change={(e) => updateTag(e.currentTarget.value, i)}
/>
<button
class="btn btn-error"
on:click={() => {
tags = tags.filter((t) => t.k !== tag.k);

if (tag.version == null) {
throw new Error('Tag value is null');
} else {
new FolksonomyApi(fetch).removeTag(tag);
}
}}
>
Delete
</button>
</td>
{#if loggedIn}
<td>
<button
class="btn btn-error"
disabled={!loggedIn}
on:click={() => {
removeTag(tag);
}}
>
Delete
</button>
</td>
{/if}
</tr>
{/each}

<tr>
<td>
<div class="dropdown dropdown-top flex w-full">
{#if loggedIn}
<tr>
<td>
<div class="dropdown dropdown-top flex w-full">
<input
type="text"
class="input grow max-sm:w-20"
placeholder="New key"
readonly={!loggedIn}
bind:value={newKey}
/>

<div class="dropdown-content max-h-52 overflow-y-auto">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul tabindex="0" class=" menu rounded-box bg-base-100 p-2 shadow">
{#each filteredNewKeys as key}
<li>
<button
on:click={() => {
newKey = key;
}}
>
{key}
</button>
</li>
{/each}
</ul>
</div>
</div>
</td>
<td class="flex gap-2">
<input
type="text"
class="input grow max-sm:w-20"
placeholder="New key"
readonly={!editable}
bind:value={newKey}
placeholder="New value"
readonly={!loggedIn}
bind:value={newValue}
/>

<div class="dropdown-content max-h-52 overflow-y-auto">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul tabindex="0" class=" menu rounded-box bg-base-100 p-2 shadow">
{#each filteredKeys as key}
<li>
<button
on:click={() => {
newKey = key;
}}
>
{key}
</button>
</li>
{/each}
</ul>
</div>
</div>
</td>
<td class="flex gap-2">
<input
type="text"
class="input grow max-sm:w-20"
placeholder="New value"
readonly={!editable}
bind:value={newValue}
/>
<button class="btn btn-primary" bind:this={newButton} on:click={createNewTag}>
Create
</button>
</td>
</tr>
</td>
<td>
<button
class="btn btn-primary"
on:click={createNewTag}
disabled={creatingNewTag}
class:loading={creatingNewTag}
>
Create
</button>
</td>
</tr>
{/if}
</tbody>
</table>
Loading

0 comments on commit 89fb8d4

Please sign in to comment.