mirror of
https://github.com/hay-kot/homebox.git
synced 2025-08-06 17:40:30 +00:00
replace attachment specific tokens with gen token
This commit is contained in:
parent
093e698e0e
commit
27ed919a1c
9 changed files with 98 additions and 53 deletions
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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}`) });
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,6 +324,7 @@ export interface ItemAttachmentToken {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenResponse {
|
export interface TokenResponse {
|
||||||
|
attachmentToken: string;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue