replace attachment specific tokens with gen token

This commit is contained in:
Hayden 2022-12-03 10:39:34 -09:00
parent 093e698e0e
commit 27ed919a1c
No known key found for this signature in database
GPG key ID: 17CF79474E257545
9 changed files with 98 additions and 53 deletions

View file

@ -10,7 +10,12 @@
<span class="ml-2 w-0 flex-1 truncate"> {{ attachment.document.title }}</span> <span class="ml-2 w-0 flex-1 truncate"> {{ attachment.document.title }}</span>
</div> </div>
<div class="ml-4 flex-shrink-0"> <div class="ml-4 flex-shrink-0">
<button class="font-medium" @click="getAttachmentUrl(attachment)">Download</button> <a class="tooltip mr-2" data-tip="Download" :href="attachmentURL(attachment.id)" target="_blank">
<Icon class="h-5 w-5" name="mdi-download"/>
</a>
<a class="tooltip" data-tip="Open" :href="attachmentURL(attachment.id)" target="_blank">
<Icon class="h-5 w-5" name="mdi-open-in-new"/>
</a>
</div> </div>
</li> </li>
</ul> </ul>
@ -31,26 +36,12 @@
}); });
const api = useUserApi(); const api = useUserApi();
const toast = useNotifier();
async function getAttachmentUrl(attachment: ItemAttachment) {
const url = await api.items.getAttachmentUrl(props.itemId, attachment.id);
if (!url) { function attachmentURL(attachmentId : string) {
toast.error("Failed to get attachment url"); return api.authURL(`/items/${props.itemId}/attachments/${attachmentId}`);
return;
}
if (!document) {
window.open(url, "_blank");
return;
}
const link = document.createElement("a");
link.href = url;
link.target = "_blank";
link.setAttribute("download", attachment.document.title);
link.click();
} }
</script> </script>
<style scoped></style> <style scoped></style>

View file

@ -43,5 +43,5 @@ export function useUserApi(): UserClient {
requests.addResponseInterceptor(observer.handler); requests.addResponseInterceptor(observer.handler);
} }
return new UserClient(requests); return new UserClient(requests, authStore.attachmentToken);
} }

View file

@ -27,9 +27,21 @@ export function parseDate<T>(obj: T, keys: Array<keyof T> = []): T {
export class BaseAPI { export class BaseAPI {
http: Requests; http: Requests;
attachmentToken: string
constructor(requests: Requests) { constructor(requests: Requests, attachmentToken: string = "") {
this.http = requests; this.http = requests;
this.attachmentToken = attachmentToken;
}
// if a attachmentToken is present it will be added to URL as a query param
// this is done with a simple appending of the query param to the URL. If your
// URL already has a query param, this will not work.
authURL(url: string): string {
if (this.attachmentToken) {
return `/api/v1${url}?access_token=${this.attachmentToken}`;
}
return url;
} }
/** /**

View file

@ -1,7 +1,6 @@
import { BaseAPI, route } from "../base"; import { BaseAPI, route } from "../base";
import { parseDate } from "../base/base-api"; import { parseDate } from "../base/base-api";
import { import {
ItemAttachmentToken,
ItemAttachmentUpdate, ItemAttachmentUpdate,
ItemCreate, ItemCreate,
ItemOut, ItemOut,
@ -79,18 +78,6 @@ export class ItemsApi extends BaseAPI {
}); });
} }
async getAttachmentUrl(id: string, attachmentId: string): Promise<string> {
const payload = await this.http.get<ItemAttachmentToken>({
url: route(`/items/${id}/attachments/${attachmentId}`),
});
if (!payload.data) {
return "";
}
return route(`/items/${id}/attachments/download`, { token: payload.data.token });
}
async deleteAttachment(id: string, attachmentId: string) { async deleteAttachment(id: string, attachmentId: string) {
return await this.http.delete<void>({ url: route(`/items/${id}/attachments/${attachmentId}`) }); return await this.http.delete<void>({ url: route(`/items/${id}/attachments/${attachmentId}`) });
} }

View file

@ -324,6 +324,7 @@ export interface ItemAttachmentToken {
} }
export interface TokenResponse { export interface TokenResponse {
attachmentToken: string;
expiresAt: Date; expiresAt: Date;
token: string; token: string;
} }

View file

@ -15,8 +15,8 @@ export class UserClient extends BaseAPI {
user: UserApi; user: UserApi;
actions: ActionsAPI; actions: ActionsAPI;
constructor(requests: Requests) { constructor(requests: Requests, attachmentToken: string) {
super(requests); super(requests, attachmentToken);
this.locations = new LocationsApi(requests); this.locations = new LocationsApi(requests);
this.labels = new LabelsApi(requests); this.labels = new LabelsApi(requests);

View file

@ -105,6 +105,7 @@
authStore.$patch({ authStore.$patch({
token: data.token, token: data.token,
expires: data.expiresAt, expires: data.expiresAt,
attachmentToken: data.attachmentToken,
}); });
navigateTo("/home"); navigateTo("/home");

View file

@ -27,17 +27,30 @@
}); });
type FilteredAttachments = { type FilteredAttachments = {
photos: ItemAttachment[];
attachments: ItemAttachment[]; attachments: ItemAttachment[];
warranty: ItemAttachment[]; warranty: ItemAttachment[];
manuals: ItemAttachment[]; manuals: ItemAttachment[];
receipts: ItemAttachment[]; receipts: ItemAttachment[];
}; };
type Photo = {
src: string;
}
const photos = computed<Photo[]>(() => {
return item.value?.attachments.reduce((acc, cur) => {
if (cur.type === "photo") {
acc.push({
src: api.authURL(`/items/${item.value.id}/attachments/${cur.id}`),
})
}
return acc;
}, [] as Photo[]) || [];
});
const attachments = computed<FilteredAttachments>(() => { const attachments = computed<FilteredAttachments>(() => {
if (!item.value) { if (!item.value) {
return { return {
photos: [],
attachments: [], attachments: [],
manuals: [], manuals: [],
warranty: [], warranty: [],
@ -48,8 +61,9 @@
return item.value.attachments.reduce( return item.value.attachments.reduce(
(acc, attachment) => { (acc, attachment) => {
if (attachment.type === "photo") { if (attachment.type === "photo") {
acc.photos.push(attachment); return acc;
} else if (attachment.type === "warranty") { }
if (attachment.type === "warranty") {
acc.warranty.push(attachment); acc.warranty.push(attachment);
} else if (attachment.type === "manual") { } else if (attachment.type === "manual") {
acc.manuals.push(attachment); acc.manuals.push(attachment);
@ -61,7 +75,6 @@
return acc; return acc;
}, },
{ {
photos: [] as ItemAttachment[],
attachments: [] as ItemAttachment[], attachments: [] as ItemAttachment[],
warranty: [] as ItemAttachment[], warranty: [] as ItemAttachment[],
manuals: [] as ItemAttachment[], manuals: [] as ItemAttachment[],
@ -144,7 +157,6 @@
} }
return ( return (
attachments.value.photos.length > 0 ||
attachments.value.attachments.length > 0 || attachments.value.attachments.length > 0 ||
attachments.value.warranty.length > 0 || attachments.value.warranty.length > 0 ||
attachments.value.manuals.length > 0 || attachments.value.manuals.length > 0 ||
@ -163,10 +175,6 @@
}); });
}; };
if (attachments.value.photos.length > 0) {
push("Photos");
}
if (attachments.value.attachments.length > 0) { if (attachments.value.attachments.length > 0) {
push("Attachments"); push("Attachments");
} }
@ -292,10 +300,51 @@
toast.success("Item deleted"); toast.success("Item deleted");
navigateTo("/home"); navigateTo("/home");
} }
const refDialog = ref<HTMLDialogElement>();
const dialoged = reactive({
src: "",
})
function openDialog(img: Photo) {
refDialog.value.showModal();
dialoged.src = img.src;
}
function closeDialog() {
refDialog.value.close();
}
const refDialogBody = ref<HTMLDivElement>();
onClickOutside(refDialogBody, () => {
closeDialog();
});
</script> </script>
<style>
/* Style dialog background */
dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
}
</style>
<template> <template>
<BaseContainer v-if="item" class="pb-8"> <BaseContainer v-if="item" class="pb-8">
<dialog ref="refDialog" class="z-[999] fixed bg-transparent">
<div class="relative" ref="refDialogBody">
<div class="absolute right-0 -mt-3 -mr-3 sm:-mt-4 sm:-mr-4 space-x-1">
<a class="btn btn-sm sm:btn-md btn-primary btn-circle" :href="dialoged.src" download>
<Icon class="h-5 w-5" name="mdi-download"/>
</a>
<button class="btn btn-sm sm:btn-md btn-primary btn-circle" @click="closeDialog()">
<Icon class="h-5 w-5" name="mdi-close" />
</button>
</div>
<img class="max-w-[80vw] max-h-[80vh]" :src="dialoged.src"/>
</div>
</dialog>
<section class="px-3"> <section class="px-3">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="form-control"></div> <div class="form-control"></div>
@ -353,6 +402,15 @@
<DetailsSection :details="itemDetails" /> <DetailsSection :details="itemDetails" />
</BaseCard> </BaseCard>
<BaseCard>
<template #title> Photos </template>
<div class="container p-4 flex flex-wrap gap-2 mx-auto max-h-[500px] overflow-scroll">
<button v-for="img in photos" @click="openDialog(img)">
<img class="rounded max-h-[200px]" :src="img.src"/>
</button>
</div>
</BaseCard>
<BaseCard v-if="showAttachments"> <BaseCard v-if="showAttachments">
<template #title> Attachments </template> <template #title> Attachments </template>
<DetailsSection :details="attachmentDetails"> <DetailsSection :details="attachmentDetails">
@ -377,13 +435,6 @@
:item-id="item.id" :item-id="item.id"
/> />
</template> </template>
<template #photos>
<ItemAttachmentsList
v-if="attachments.photos.length > 0"
:attachments="attachments.photos"
:item-id="item.id"
/>
</template>
<template #receipts> <template #receipts>
<ItemAttachmentsList <ItemAttachmentsList
v-if="attachments.receipts.length > 0" v-if="attachments.receipts.length > 0"

View file

@ -6,6 +6,7 @@ import { UserOut } from "~~/lib/api/types/data-contracts";
export const useAuthStore = defineStore("auth", { export const useAuthStore = defineStore("auth", {
state: () => ({ state: () => ({
token: useLocalStorage("pinia/auth/token", ""), token: useLocalStorage("pinia/auth/token", ""),
attachmentToken: useLocalStorage("pinia/auth/attachmentToken", ""),
expires: useLocalStorage("pinia/auth/expires", ""), expires: useLocalStorage("pinia/auth/expires", ""),
self: null as UserOut | null, self: null as UserOut | null,
}), }),
@ -27,6 +28,7 @@ export const useAuthStore = defineStore("auth", {
const result = await api.user.logout(); const result = await api.user.logout();
this.token = ""; this.token = "";
this.attachmentToken = "";
this.expires = ""; this.expires = "";
this.self = null; this.self = null;