forked from mirrors/homebox
fix: add custom action for fixing broken date/times (#268)
This commit is contained in:
parent
bd933af874
commit
ce2fc7712a
11 changed files with 240 additions and 59 deletions
|
@ -9,15 +9,15 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type EnsureAssetIDResult struct {
|
||||
type ActionAmountResult struct {
|
||||
Completed int `json:"completed"`
|
||||
}
|
||||
|
||||
// HandleGroupInvitationsCreate godoc
|
||||
// @Summary Get the current user
|
||||
// @Summary Ensures all items in the database have an asset id
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Success 200 {object} EnsureAssetIDResult
|
||||
// @Success 200 {object} ActionAmountResult
|
||||
// @Router /v1/actions/ensure-asset-ids [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc {
|
||||
|
@ -30,6 +30,27 @@ func (ctrl *V1Controller) HandleEnsureAssetID() server.HandlerFunc {
|
|||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.Respond(w, http.StatusOK, EnsureAssetIDResult{Completed: totalCompleted})
|
||||
return server.Respond(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
|
||||
}
|
||||
}
|
||||
|
||||
// HandleItemDateZeroOut godoc
|
||||
// @Summary Resets all item date fields to the beginning of the day
|
||||
// @Tags Group
|
||||
// @Produce json
|
||||
// @Success 200 {object} ActionAmountResult
|
||||
// @Router /v1/actions/zero-item-time-fields [Post]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemDateZeroOut() server.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx := services.NewContext(r.Context())
|
||||
|
||||
totalCompleted, err := ctrl.repo.Items.ZeroOutTimeFields(ctx, ctx.GID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to ensure asset id")
|
||||
return validate.NewRequestError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return server.Respond(w, http.StatusOK, ActionAmountResult{Completed: totalCompleted})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ func (a *app) mountRoutes(repos *repo.AllRepos) {
|
|||
a.server.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate(), userMW...)
|
||||
|
||||
a.server.Post(v1Base("/actions/ensure-asset-ids"), v1Ctrl.HandleEnsureAssetID(), userMW...)
|
||||
a.server.Post(v1Base("/actions/zero-item-time-fields"), v1Ctrl.HandleItemDateZeroOut(), userMW...)
|
||||
|
||||
a.server.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll(), userMW...)
|
||||
a.server.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate(), userMW...)
|
||||
|
|
|
@ -34,12 +34,36 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get the current user",
|
||||
"summary": "Ensures all items in the database have an asset id",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.EnsureAssetIDResult"
|
||||
"$ref": "#/definitions/v1.ActionAmountResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/actions/zero-item-time-fields": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Resets all item date fields to the beginning of the day",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.ActionAmountResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2365,6 +2389,14 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.ActionAmountResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ApiSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -2416,14 +2448,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.EnsureAssetIDResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -26,12 +26,36 @@
|
|||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Get the current user",
|
||||
"summary": "Ensures all items in the database have an asset id",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.EnsureAssetIDResult"
|
||||
"$ref": "#/definitions/v1.ActionAmountResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/actions/zero-item-time-fields": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Group"
|
||||
],
|
||||
"summary": "Resets all item date fields to the beginning of the day",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.ActionAmountResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2357,6 +2381,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.ActionAmountResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.ApiSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -2408,14 +2440,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.EnsureAssetIDResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completed": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.GroupInvitation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -556,6 +556,11 @@ definitions:
|
|||
token:
|
||||
type: string
|
||||
type: object
|
||||
v1.ActionAmountResult:
|
||||
properties:
|
||||
completed:
|
||||
type: integer
|
||||
type: object
|
||||
v1.ApiSummary:
|
||||
properties:
|
||||
build:
|
||||
|
@ -589,11 +594,6 @@ definitions:
|
|||
new:
|
||||
type: string
|
||||
type: object
|
||||
v1.EnsureAssetIDResult:
|
||||
properties:
|
||||
completed:
|
||||
type: integer
|
||||
type: object
|
||||
v1.GroupInvitation:
|
||||
properties:
|
||||
expiresAt:
|
||||
|
@ -643,10 +643,24 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/v1.EnsureAssetIDResult'
|
||||
$ref: '#/definitions/v1.ActionAmountResult'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get the current user
|
||||
summary: Ensures all items in the database have an asset id
|
||||
tags:
|
||||
- Group
|
||||
/v1/actions/zero-item-time-fields:
|
||||
post:
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/v1.ActionAmountResult'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Resets all item date fields to the beginning of the day
|
||||
tags:
|
||||
- Group
|
||||
/v1/assets/{id}:
|
||||
|
|
|
@ -673,3 +673,56 @@ func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.U
|
|||
|
||||
return fieldNames, nil
|
||||
}
|
||||
|
||||
// ZeroOutTimeFields is a helper function that can be invoked via the UI by a group member which will
|
||||
// set all date fields to the beginning of the day.
|
||||
//
|
||||
// This is designed to resolve a long-time bug that has since been fixed with the time selector on the
|
||||
// frontend. This function is intended to be used as a one-time fix for existing databases and may be
|
||||
// removed in the future.
|
||||
func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID) (int, error) {
|
||||
q := e.db.Item.Query().Where(
|
||||
item.HasGroupWith(group.ID(GID)),
|
||||
item.Or(
|
||||
item.PurchaseTimeNotNil(),
|
||||
item.SoldTimeNotNil(),
|
||||
item.WarrantyExpiresNotNil(),
|
||||
),
|
||||
)
|
||||
|
||||
items, err := q.All(ctx)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("ZeroOutTimeFields() -> failed to get items: %w", err)
|
||||
}
|
||||
|
||||
toDateOnly := func(t time.Time) time.Time {
|
||||
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
updated := 0
|
||||
|
||||
for _, i := range items {
|
||||
updateQ := e.db.Item.Update().Where(item.ID(i.ID))
|
||||
|
||||
if !i.PurchaseTime.IsZero() {
|
||||
updateQ.SetPurchaseTime(toDateOnly(i.PurchaseTime))
|
||||
}
|
||||
|
||||
if !i.SoldTime.IsZero() {
|
||||
updateQ.SetSoldTime(toDateOnly(i.SoldTime))
|
||||
}
|
||||
|
||||
if !i.WarrantyExpires.IsZero() {
|
||||
updateQ.SetWarrantyExpires(toDateOnly(i.WarrantyExpires))
|
||||
}
|
||||
|
||||
_, err = updateQ.Save(ctx)
|
||||
if err != nil {
|
||||
return updated, fmt.Errorf("ZeroOutTimeFields() -> failed to update item: %w", err)
|
||||
}
|
||||
|
||||
updated++
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
.btn {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,11 @@
|
|||
const selected = computed({
|
||||
get() {
|
||||
// return modelValue as string as YYYY-MM-DD or null
|
||||
if (validDate(props.modelValue)) {
|
||||
return props.modelValue ? props.modelValue.toISOString().split("T")[0] : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
set(value: string | null) {
|
||||
// emit update:modelValue with a Date object or null
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { BaseAPI, route } from "../base";
|
||||
import { EnsureAssetIDResult } from "../types/data-contracts";
|
||||
import { ActionAmountResult } from "../types/data-contracts";
|
||||
|
||||
export class ActionsAPI extends BaseAPI {
|
||||
ensureAssetIDs() {
|
||||
return this.http.post<void, EnsureAssetIDResult>({
|
||||
return this.http.post<void, ActionAmountResult>({
|
||||
url: route("/actions/ensure-asset-ids"),
|
||||
});
|
||||
}
|
||||
|
||||
resetItemDateTimes() {
|
||||
return this.http.post<void, ActionAmountResult>({
|
||||
url: route("/actions/zero-item-time-fields"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export interface DocumentOut {
|
|||
}
|
||||
|
||||
export interface Group {
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
currency: string;
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -39,7 +39,7 @@ export interface GroupUpdate {
|
|||
}
|
||||
|
||||
export interface ItemAttachment {
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
document: DocumentOut;
|
||||
id: string;
|
||||
type: string;
|
||||
|
@ -76,7 +76,7 @@ export interface ItemOut {
|
|||
assetId: string;
|
||||
attachments: ItemAttachment[];
|
||||
children: ItemSummary[];
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
fields: ItemField[];
|
||||
id: string;
|
||||
|
@ -96,7 +96,7 @@ export interface ItemOut {
|
|||
/** @example "0" */
|
||||
purchasePrice: string;
|
||||
/** Purchase */
|
||||
purchaseTime: Date;
|
||||
purchaseTime: string;
|
||||
quantity: number;
|
||||
serialNumber: string;
|
||||
soldNotes: string;
|
||||
|
@ -112,7 +112,7 @@ export interface ItemOut {
|
|||
|
||||
export interface ItemSummary {
|
||||
archived: boolean;
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
insured: boolean;
|
||||
|
@ -148,7 +148,7 @@ export interface ItemUpdate {
|
|||
/** @example "0" */
|
||||
purchasePrice: string;
|
||||
/** Purchase */
|
||||
purchaseTime: Date;
|
||||
purchaseTime: string;
|
||||
quantity: number;
|
||||
/** Identifications */
|
||||
serialNumber: string;
|
||||
|
@ -169,7 +169,7 @@ export interface LabelCreate {
|
|||
}
|
||||
|
||||
export interface LabelOut {
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
items: ItemSummary[];
|
||||
|
@ -178,7 +178,7 @@ export interface LabelOut {
|
|||
}
|
||||
|
||||
export interface LabelSummary {
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -193,7 +193,7 @@ export interface LocationCreate {
|
|||
|
||||
export interface LocationOut {
|
||||
children: LocationSummary[];
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
items: ItemSummary[];
|
||||
|
@ -203,7 +203,7 @@ export interface LocationOut {
|
|||
}
|
||||
|
||||
export interface LocationOutCount {
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
itemCount: number;
|
||||
|
@ -212,7 +212,7 @@ export interface LocationOutCount {
|
|||
}
|
||||
|
||||
export interface LocationSummary {
|
||||
createdAt: string;
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -229,7 +229,7 @@ export interface LocationUpdate {
|
|||
export interface MaintenanceEntry {
|
||||
/** @example "0" */
|
||||
cost: string;
|
||||
date: Date;
|
||||
date: string;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -238,7 +238,7 @@ export interface MaintenanceEntry {
|
|||
export interface MaintenanceEntryCreate {
|
||||
/** @example "0" */
|
||||
cost: string;
|
||||
date: Date;
|
||||
date: string;
|
||||
description: string;
|
||||
name: string;
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ export interface MaintenanceEntryCreate {
|
|||
export interface MaintenanceEntryUpdate {
|
||||
/** @example "0" */
|
||||
cost: string;
|
||||
date: Date;
|
||||
date: string;
|
||||
description: string;
|
||||
name: string;
|
||||
}
|
||||
|
@ -258,7 +258,7 @@ export interface MaintenanceLog {
|
|||
itemId: string;
|
||||
}
|
||||
|
||||
export interface PaginationResultRepoItemSummary {
|
||||
export interface PaginationResultItemSummary {
|
||||
items: ItemSummary[];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
|
@ -302,7 +302,7 @@ export interface ValueOverTime {
|
|||
}
|
||||
|
||||
export interface ValueOverTimeEntry {
|
||||
date: Date;
|
||||
date: string;
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
@ -330,6 +330,10 @@ export interface UserRegistration {
|
|||
token: string;
|
||||
}
|
||||
|
||||
export interface ActionAmountResult {
|
||||
completed: number;
|
||||
}
|
||||
|
||||
export interface ApiSummary {
|
||||
build: Build;
|
||||
demo: boolean;
|
||||
|
@ -350,18 +354,14 @@ export interface ChangePassword {
|
|||
new: string;
|
||||
}
|
||||
|
||||
export interface EnsureAssetIDResult {
|
||||
completed: number;
|
||||
}
|
||||
|
||||
export interface GroupInvitation {
|
||||
expiresAt: Date;
|
||||
expiresAt: string;
|
||||
token: string;
|
||||
uses: number;
|
||||
}
|
||||
|
||||
export interface GroupInvitationCreate {
|
||||
expiresAt: Date;
|
||||
expiresAt: string;
|
||||
uses: number;
|
||||
}
|
||||
|
||||
|
@ -371,6 +371,6 @@ export interface ItemAttachmentToken {
|
|||
|
||||
export interface TokenResponse {
|
||||
attachmentToken: string;
|
||||
expiresAt: Date;
|
||||
expiresAt: string;
|
||||
token: string;
|
||||
}
|
||||
|
|
|
@ -180,6 +180,25 @@
|
|||
|
||||
notify.success(`${result.data.completed} assets have been updated.`);
|
||||
}
|
||||
|
||||
async function resetItemDateTimes() {
|
||||
const { isCanceled } = await confirm.open(
|
||||
"Are you sure you want to reset all date and time values? This will take a while and cannot be undone."
|
||||
);
|
||||
|
||||
if (isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await api.actions.resetItemDateTimes();
|
||||
|
||||
if (result.error) {
|
||||
notify.error("Failed to reset date and time values.");
|
||||
return;
|
||||
}
|
||||
|
||||
notify.success(`${result.data.completed} assets have been updated.`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -313,7 +332,7 @@
|
|||
</template>
|
||||
</BaseSectionHeader>
|
||||
|
||||
<div class="py-4 border-t-2 border-gray-300">
|
||||
<div class="py-4 border-t-2 border-gray-300 space-y-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-10">
|
||||
<div class="col-span-3">
|
||||
<h4>Manage Asset IDs</h4>
|
||||
|
@ -325,6 +344,20 @@
|
|||
</div>
|
||||
<BaseButton class="btn-primary mt-auto" @click="ensureAssetIDs"> Ensure Asset IDs </BaseButton>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-10">
|
||||
<div class="col-span-3">
|
||||
<h4>Zero Item Date Times</h4>
|
||||
<p class="text-sm">
|
||||
Resets the time value for all date time fields in your inventory to the beginning of the date. This is
|
||||
to fix a bug that was introduced early on in the development of the site that caused the time value to
|
||||
be stored with the time which caused issues with date fields displaying accurate values.
|
||||
<a class="link" href="https://github.com/hay-kot/homebox/issues/236" target="_blank">
|
||||
See Github Issue #236 for more details
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<BaseButton class="btn-primary mt-auto" @click="resetItemDateTimes"> Zero Item Date Times </BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BaseCard>
|
||||
|
|
Loading…
Reference in a new issue