Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 6 additions & 86 deletions backend/go.sum

Large diffs are not rendered by default.

20 changes: 17 additions & 3 deletions frontend/components/Item/CreateModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,12 @@
import BaseModal from "@/components/App/CreateModal.vue";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import type { EntityCreate, EntityTemplateOut, EntityTemplateSummary, EntityOut } from "~~/lib/api/types/data-contracts";
import type {
EntityCreate,
EntityTemplateOut,
EntityTemplateSummary,
EntityOut,
} from "~~/lib/api/types/data-contracts";
import { useTagStore } from "~/stores/tags";
import { useLocationStore } from "~~/stores/locations";
import MdiBarcode from "~icons/mdi/barcode";
Expand Down Expand Up @@ -390,7 +395,11 @@
if (et?.defaultTemplateId && et.defaultTemplate) {
const { data, error } = await api.templates.get(et.defaultTemplateId);
if (!error && data) {
selectedTemplate.value = { id: data.id, name: data.name, description: data.description } as EntityTemplateSummary;
selectedTemplate.value = {
id: data.id,
name: data.name,
description: data.description,
} as EntityTemplateSummary;
templateData.value = data;
form.quantity = data.defaultQuantity;
if (data.defaultName) form.name = data.defaultName;
Expand Down Expand Up @@ -635,6 +644,11 @@
return;
}

if (form.quantity < 0) {
toast.error(t("components.item.create_modal.toast.quantity_cannot_negative"));
return;
}
Comment on lines +647 to +650
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wrong i18n key — toast will display the raw key string instead of a translated message. 🐰🌍

The guard calls t("components.item.create_modal.toast.quantity_cannot_negative"), but that path doesn't exist in frontend/locales/en.json. The actual key lives under items.toast.quantity_cannot_negative (see en.json line 620), and the sibling frontend/pages/item/[id]/index/edit.vue (lines 97-100) and the PR description both use that path. With vue-i18n's default missing-handler the user will see the literal string components.item.create_modal.toast.quantity_cannot_negative in the toast — in every language. Switch to the existing key.

🥕 Proposed fix
     if (form.quantity < 0) {
-      toast.error(t("components.item.create_modal.toast.quantity_cannot_negative"));
+      toast.error(t("items.toast.quantity_cannot_negative"));
       return;
     }

🔐 Security recommendation (carried over): this remains a client-side check only. Please confirm the backend handlers behind api.items.create and api.templates.createItem reject quantity < 0 (and ideally non-finite values) so a crafted request can't bypass the UI guard.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/Item/CreateModal.vue` around lines 647 - 650, The toast
uses the wrong i18n key so users will see the raw key; in CreateModal.vue update
the t(...) call used in the quantity guard (the code referencing form.quantity
and toast.error) to use the existing key "items.toast.quantity_cannot_negative"
instead of "components.item.create_modal.toast.quantity_cannot_negative", and
confirm server-side validation for api.items.create / api.templates.createItem
rejects quantity < 0 (and non-finite values) so the UI-only guard can't be
bypassed.


loading.value = true;

if (shift?.value) close = false;
Expand All @@ -657,7 +671,7 @@
} else {
// Normal item creation without template
const out: EntityCreate = {
parentId: form.parentId || form.location.id as string,
parentId: form.parentId || (form.location.id as string),
name: form.name,
quantity: form.quantity,
description: form.description,
Expand Down
9 changes: 2 additions & 7 deletions frontend/components/Location/CreateModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@

<template v-if="showAdvanced">
<TagSelector v-model="form.tags" :tags="tags ?? []" />
<FormTextArea
v-model="form.notes"
label="Notes"
:max-length="1000"
/>
<FormTextArea v-model="form.notes" :label="$t('components.location.create_modal.notes')" :max-length="1000" />
</template>

<div class="mt-4 flex flex-row-reverse">
Expand Down Expand Up @@ -142,8 +138,7 @@
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import BaseModal from "@/components/App/CreateModal.vue";
import type { EntityTypeSummary } from "~~/lib/api/types/data-contracts";
import type { EntitySummary } from "~~/lib/api/types/data-contracts";
import type { EntityTypeSummary, EntitySummary } from "~~/lib/api/types/data-contracts";
import { AttachmentTypes } from "~~/lib/api/types/non-generated";
import { useDialog, useDialogHotkey } from "~/components/ui/dialog-provider";
import { useTagStore } from "~/stores/tags";
Expand Down
8 changes: 7 additions & 1 deletion frontend/components/Scanner/ARControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
<div class="pointer-events-none absolute inset-0 z-20 flex flex-col justify-between">
<!-- Top bar -->
<div class="pointer-events-auto flex items-center gap-3 bg-background/60 p-3 backdrop-blur-sm">
<Button variant="ghost" size="icon" :aria-label="t('scanner_ar.back')" :title="t('scanner_ar.back')" @click="$emit('back')">
<Button
variant="ghost"
size="icon"
:aria-label="t('scanner_ar.back')"
:title="t('scanner_ar.back')"
@click="$emit('back')"
>
<MdiArrowLeft class="size-5" />
</Button>
<span class="text-sm font-semibold text-foreground">{{ t("scanner_ar.title") }}</span>
Expand Down
4 changes: 3 additions & 1 deletion frontend/lib/api/classes/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ export class ItemsApi extends BaseAPI {
// =========================================================================

async getLocations(q: LocationsQuery = { filterChildren: false }) {
const resp = await this.http.get<{ items: EntitySummary[] }>({ url: route("/entities", { ...q, isLocation: true }) });
const resp = await this.http.get<{ items: EntitySummary[] }>({
url: route("/entities", { ...q, isLocation: true }),
});
// Unwrap paginated response to flat array for backward compat
return {
...resp,
Expand Down
36 changes: 12 additions & 24 deletions frontend/pages/collection/index/entity-types.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { useDialog } from "@/components/ui/dialog-provider";
import { DialogID } from "~/components/ui/dialog-provider/utils";
import FormTextField from "~/components/Form/TextField.vue";
Expand Down Expand Up @@ -98,7 +92,11 @@
updateForm.icon = et.icon;
updateForm.isLocation = et.isLocation;
updateTemplate.value = et.defaultTemplate
? { id: et.defaultTemplate.id, name: et.defaultTemplate.name, description: et.defaultTemplate.description } as EntityTemplateSummary
? ({
id: et.defaultTemplate.id,
name: et.defaultTemplate.name,
description: et.defaultTemplate.description,
} as EntityTemplateSummary)
: null;
openDialog(DialogID.UpdateEntityType);
}
Expand Down Expand Up @@ -154,13 +152,7 @@
<DialogTitle>Create Entity Type</DialogTitle>
</DialogHeader>
<form class="flex flex-col gap-3" @submit.prevent="create">
<FormTextField
v-model="createForm.name"
:autofocus="true"
label="Name"
:max-length="255"
:min-length="1"
/>
<FormTextField v-model="createForm.name" :autofocus="true" :label="t('common.name')" :max-length="255" :min-length="1" />
<FormCheckbox v-model="createForm.isLocation" label="Is a container / location type" />
<TemplateSelector v-model="createTemplate" />

Expand All @@ -178,13 +170,7 @@
<DialogTitle>Update Entity Type</DialogTitle>
</DialogHeader>
<form class="flex flex-col gap-3" @submit.prevent="update">
<FormTextField
v-model="updateForm.name"
:autofocus="true"
label="Name"
:max-length="255"
:min-length="1"
/>
<FormTextField v-model="updateForm.name" :autofocus="true" label="Name" :max-length="255" :min-length="1" />
<FormCheckbox v-model="updateForm.isLocation" label="Is a container / location type" />
<TemplateSelector v-model="updateTemplate" />

Expand All @@ -196,7 +182,7 @@
</Dialog>

<!-- Page Content -->
<div class="flex items-center justify-between mb-4">
<div class="mb-4 flex items-center justify-between">
<h3 class="text-lg font-medium">Entity Types</h3>
<Button size="sm" @click="openDialog(DialogID.CreateEntityType)">
<MdiPlus class="mr-1 size-4" />
Expand All @@ -207,7 +193,9 @@
<div v-if="entityTypes && entityTypes.length > 0" class="space-y-2">
<Card v-for="et in entityTypes" :key="et.id" class="p-4">
<div class="flex items-center gap-3">
<div class="flex size-10 shrink-0 items-center justify-center rounded-full bg-secondary text-secondary-foreground">
<div
class="flex size-10 shrink-0 items-center justify-center rounded-full bg-secondary text-secondary-foreground"
>
<MdiMapMarkerOutline v-if="et.isLocation" class="size-5" />
<MdiPackageVariantClosed v-else class="size-5" />
</div>
Expand Down
5 changes: 5 additions & 0 deletions frontend/pages/item/[id]/index/edit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@
return;
}

if (form.quantity < 0) {
toast.error(t("items.toast.quantity_cannot_negative"));
return;
}

saving.value = true;

let purchasePrice = 0;
Expand Down
9 changes: 4 additions & 5 deletions frontend/pages/location/[id]/index.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
<script setup lang="ts">
definePageMeta({
middleware: ["auth"],
});
</script>
<script setup lang="ts"></script>

<template>
<BaseContainer>
Expand All @@ -12,4 +8,7 @@

<script lang="ts">
import BaseContainer from "@/components/Base/Container.vue";
definePageMeta({
middleware: ["auth"],
});
</script>
13 changes: 9 additions & 4 deletions frontend/pages/location/[id]/index/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,12 @@
else acc.attachments.push(attachment);
return acc;
},
{ attachments: [] as ItemAttachment[], warranty: [] as ItemAttachment[], manuals: [] as ItemAttachment[], receipts: [] as ItemAttachment[] }
{
attachments: [] as ItemAttachment[],
warranty: [] as ItemAttachment[],
manuals: [] as ItemAttachment[],
receipts: [] as ItemAttachment[],
}
);
});

Expand All @@ -171,12 +176,12 @@
type: "markdown",
text: location.value.notes,
},
...((location.value.fields || []).map(field => {
...(location.value.fields || []).map(field => {
return {
name: field.name,
text: field.textValue,
} as AnyDetail;
})),
}),
];

if (!preferences.value.showEmpty) {
Expand Down Expand Up @@ -224,7 +229,7 @@
<button
v-for="(photo, i) in photos"
:key="i"
class="group relative aspect-square overflow-hidden rounded-lg border bg-muted"
class="aspect-square group relative overflow-hidden rounded-lg border bg-muted"
@click="openImageDialog(photo, location.id)"
>
<img
Expand Down
Loading