Compare commits

..

10 Commits

Author SHA1 Message Date
renovate[bot]
88f9ff90d4 fix(deps): update module entgo.io/ent to v0.11.8 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:43:44 -09:00
renovate[bot]
354f1adbee chore(deps): update dependency nuxt to v3.2.0 (#259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:43:22 -09:00
renovate[bot]
2a62a43493 fix(deps): update dependency dompurify to v3 (#277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:42:48 -09:00
Kyle Brown
673db41f37 build(docker): add image source label (#288)
This adds the `org.opencontainers.image.source` label pointing to this repository for cross-referencing information. For example: https://github.com/renovatebot/renovate/blob/main/lib/modules/datasource/docker/readme.md
2023-02-16 10:36:40 -09:00
renovate[bot]
830ce2b0a9 fix(deps): update module github.com/ardanlabs/conf/v3 to v3.1.4 (#291)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-16 10:35:54 -09:00
Hayden
e8e6a425dd fix: button display on light mode (#293) 2023-02-16 10:28:52 -09:00
Hayden
da00db0608 fix: code generation and type processing (#292)
regular expressions are order specific and when applied in a random order you can get a variety of outputs. Using a list preserves order and ensures that the data-contracts.ts file is deterministic.
2023-02-16 10:13:09 -09:00
Hayden
efd7069fe4 feat: hide registration button when disabled (#287)
* add allow registration to API Summary

* code gen

* use env for troubleshooting

* disable registration toggle based on backend
2023-02-15 08:58:38 -09:00
Hayden
dd349aa98e fix #285 (#286) 2023-02-15 08:52:13 -09:00
Hayden
607b06d2f2 fix: date and datetime regression (#282)
* use custom types.Date implementation

* fix user registration bug

* remove sanity check

* fix datetime bug
2023-02-15 08:40:35 -09:00
21 changed files with 744 additions and 424 deletions

View File

@@ -41,6 +41,7 @@ COPY --from=builder /go/bin/api /app
RUN chmod +x /app/api RUN chmod +x /app/api
LABEL Name=homebox Version=0.0.1 LABEL Name=homebox Version=0.0.1
LABEL org.opencontainers.image.source="https://github.com/hay-kot/homebox"
EXPOSE 7745 EXPOSE 7745
WORKDIR /app WORKDIR /app
VOLUME [ "/data" ] VOLUME [ "/data" ]

View File

@@ -2,6 +2,7 @@ version: "3"
env: env:
HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1 HBOX_STORAGE_SQLITE_URL: .data/homebox.db?_fk=1
HBOX_OPTIONS_ALLOW_REGISTRATION: true
UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure" UNSAFE_DISABLE_PASSWORD_PROJECTION: "yes_i_am_sure"
tasks: tasks:
setup: setup:

View File

@@ -50,6 +50,7 @@ type (
Message string `json:"message"` Message string `json:"message"`
Build Build `json:"build"` Build Build `json:"build"`
Demo bool `json:"demo"` Demo bool `json:"demo"`
AllowRegistration bool `json:"allowRegistration"`
} }
) )
@@ -87,6 +88,7 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) server.Handle
Message: "Welcome to the Go API Template Application!", Message: "Welcome to the Go API Template Application!",
Build: build, Build: build,
Demo: ctrl.isDemo, Demo: ctrl.isDemo,
AllowRegistration: ctrl.allowRegistration,
}) })
} }
} }

View File

@@ -1,6 +1,7 @@
package v1 package v1
import ( import (
"fmt"
"net/http" "net/http"
"github.com/google/uuid" "github.com/google/uuid"
@@ -28,7 +29,7 @@ func (ctrl *V1Controller) HandleUserRegistration() server.HandlerFunc {
} }
if !ctrl.allowRegistration && regData.GroupToken == "" { if !ctrl.allowRegistration && regData.GroupToken == "" {
return validate.NewRequestError(nil, http.StatusForbidden) return validate.NewRequestError(fmt.Errorf("user registration disabled"), http.StatusForbidden)
} }
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData) _, err := ctrl.svc.User.RegisterUser(r.Context(), regData)

View File

@@ -1983,6 +1983,7 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"warrantyExpires": { "warrantyExpires": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
@@ -2424,6 +2425,9 @@ const docTemplate = `{
"v1.ApiSummary": { "v1.ApiSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
"allowRegistration": {
"type": "boolean"
},
"build": { "build": {
"$ref": "#/definitions/v1.Build" "$ref": "#/definitions/v1.Build"
}, },

View File

@@ -1975,6 +1975,7 @@
"type": "string" "type": "string"
}, },
"warrantyExpires": { "warrantyExpires": {
"description": "Sold",
"type": "string" "type": "string"
} }
} }
@@ -2416,6 +2417,9 @@
"v1.ApiSummary": { "v1.ApiSummary": {
"type": "object", "type": "object",
"properties": { "properties": {
"allowRegistration": {
"type": "boolean"
},
"build": { "build": {
"$ref": "#/definitions/v1.Build" "$ref": "#/definitions/v1.Build"
}, },

View File

@@ -275,6 +275,7 @@ definitions:
warrantyDetails: warrantyDetails:
type: string type: string
warrantyExpires: warrantyExpires:
description: Sold
type: string type: string
type: object type: object
repo.LabelCreate: repo.LabelCreate:
@@ -563,6 +564,8 @@ definitions:
type: object type: object
v1.ApiSummary: v1.ApiSummary:
properties: properties:
allowRegistration:
type: boolean
build: build:
$ref: '#/definitions/v1.Build' $ref: '#/definitions/v1.Build'
demo: demo:

View File

@@ -4,8 +4,8 @@ go 1.19
require ( require (
ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb
entgo.io/ent v0.11.7 entgo.io/ent v0.11.8
github.com/ardanlabs/conf/v3 v3.1.3 github.com/ardanlabs/conf/v3 v3.1.4
github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/chi/v5 v5.0.8
github.com/go-playground/validator/v10 v10.11.2 github.com/go-playground/validator/v10 v10.11.2
github.com/gocarina/gocsv v0.0.0-20230123225133-763e25b40669 github.com/gocarina/gocsv v0.0.0-20230123225133-763e25b40669

View File

@@ -2,6 +2,8 @@ ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb h1:mbsFtavDqGdYwdDpP50LGOOZ2
ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU= ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU=
entgo.io/ent v0.11.7 h1:V+wKFh0jhAbY/FoU+PPbdMOf2Ma5vh07R/IdF+N/nFg= entgo.io/ent v0.11.7 h1:V+wKFh0jhAbY/FoU+PPbdMOf2Ma5vh07R/IdF+N/nFg=
entgo.io/ent v0.11.7/go.mod h1:ericBi6Q8l3wBH1wEIDfKxw7rcQEuRPyBfbIzjtxJ18= entgo.io/ent v0.11.7/go.mod h1:ericBi6Q8l3wBH1wEIDfKxw7rcQEuRPyBfbIzjtxJ18=
entgo.io/ent v0.11.8 h1:M/M0QL1CYCUSdqGRXUrXhFYSDRJPsOOrr+RLEej/gyQ=
entgo.io/ent v0.11.8/go.mod h1:ericBi6Q8l3wBH1wEIDfKxw7rcQEuRPyBfbIzjtxJ18=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
@@ -11,6 +13,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/ardanlabs/conf/v3 v3.1.3 h1:16+Nzfc4PBd/ERtYERUFL/75eVKNyW15Y+vn3W1XZzQ= github.com/ardanlabs/conf/v3 v3.1.3 h1:16+Nzfc4PBd/ERtYERUFL/75eVKNyW15Y+vn3W1XZzQ=
github.com/ardanlabs/conf/v3 v3.1.3/go.mod h1:bIacyuGeZjkTdtszdbvOcuq49VhHpV3+IPZ2ewOAK4I= github.com/ardanlabs/conf/v3 v3.1.3/go.mod h1:bIacyuGeZjkTdtszdbvOcuq49VhHpV3+IPZ2ewOAK4I=
github.com/ardanlabs/conf/v3 v3.1.4 h1:c0jJYbqHJcrR/uYImbGC1q7quH3DYxH49zGCT7WLJH4=
github.com/ardanlabs/conf/v3 v3.1.4/go.mod h1:bIacyuGeZjkTdtszdbvOcuq49VhHpV3+IPZ2ewOAK4I=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@@ -7,9 +7,9 @@ import (
"io" "io"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/hay-kot/homebox/backend/internal/data/repo" "github.com/hay-kot/homebox/backend/internal/data/repo"
"github.com/hay-kot/homebox/backend/internal/data/types"
) )
func determineSeparator(data []byte) (rune, error) { func determineSeparator(data []byte) (rune, error) {
@@ -62,15 +62,6 @@ func parseFloat(s string) float64 {
return f return f
} }
func parseDate(s string) time.Time {
if s == "" {
return time.Time{}
}
p, _ := time.Parse("01/02/2006", s)
return p
}
func parseBool(s string) bool { func parseBool(s string) bool {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "true", "yes", "1": case "true", "yes", "1":
@@ -92,6 +83,7 @@ type csvRow struct {
} }
func newCsvRow(row []string) csvRow { func newCsvRow(row []string) csvRow {
return csvRow{ return csvRow{
Location: row[1], Location: row[1],
LabelStr: row[2], LabelStr: row[2],
@@ -109,13 +101,13 @@ func newCsvRow(row []string) csvRow {
Manufacturer: row[9], Manufacturer: row[9],
Notes: row[10], Notes: row[10],
PurchaseFrom: row[11], PurchaseFrom: row[11],
PurchaseTime: parseDate(row[13]), PurchaseTime: types.DateFromString(row[13]),
LifetimeWarranty: parseBool(row[14]), LifetimeWarranty: parseBool(row[14]),
WarrantyExpires: parseDate(row[15]), WarrantyExpires: types.DateFromString(row[15]),
WarrantyDetails: row[16], WarrantyDetails: row[16],
SoldTo: row[17], SoldTo: row[17],
SoldPrice: parseFloat(row[18]), SoldPrice: parseFloat(row[18]),
SoldTime: parseDate(row[19]), SoldTime: types.DateFromString(row[19]),
SoldNotes: row[20], SoldNotes: row[20],
}, },
} }

View File

@@ -50,9 +50,9 @@ func Test_CorrectDateParsing(t *testing.T) {
entity := newCsvRow(record) entity := newCsvRow(record)
expected := expected[i-1] expected := expected[i-1]
assert.Equal(t, expected, entity.Item.PurchaseTime, fmt.Sprintf("Failed on row %d", i)) assert.Equal(t, expected, entity.Item.PurchaseTime.Time(), fmt.Sprintf("Failed on row %d", i))
assert.Equal(t, expected, entity.Item.WarrantyExpires, fmt.Sprintf("Failed on row %d", i)) assert.Equal(t, expected, entity.Item.WarrantyExpires.Time(), fmt.Sprintf("Failed on row %d", i))
assert.Equal(t, expected, entity.Item.SoldTime, fmt.Sprintf("Failed on row %d", i)) assert.Equal(t, expected, entity.Item.SoldTime.Time(), fmt.Sprintf("Failed on row %d", i))
} }
} }

View File

@@ -13,6 +13,7 @@ import (
"github.com/hay-kot/homebox/backend/internal/data/ent/label" "github.com/hay-kot/homebox/backend/internal/data/ent/label"
"github.com/hay-kot/homebox/backend/internal/data/ent/location" "github.com/hay-kot/homebox/backend/internal/data/ent/location"
"github.com/hay-kot/homebox/backend/internal/data/ent/predicate" "github.com/hay-kot/homebox/backend/internal/data/ent/predicate"
"github.com/hay-kot/homebox/backend/internal/data/types"
) )
type ItemsRepository struct { type ItemsRepository struct {
@@ -79,16 +80,16 @@ type (
// Warranty // Warranty
LifetimeWarranty bool `json:"lifetimeWarranty"` LifetimeWarranty bool `json:"lifetimeWarranty"`
WarrantyExpires time.Time `json:"warrantyExpires"` WarrantyExpires types.Date `json:"warrantyExpires"`
WarrantyDetails string `json:"warrantyDetails"` WarrantyDetails string `json:"warrantyDetails"`
// Purchase // Purchase
PurchaseTime time.Time `json:"purchaseTime"` PurchaseTime types.Date `json:"purchaseTime"`
PurchaseFrom string `json:"purchaseFrom"` PurchaseFrom string `json:"purchaseFrom"`
PurchasePrice float64 `json:"purchasePrice,string"` PurchasePrice float64 `json:"purchasePrice,string"`
// Sold // Sold
SoldTime time.Time `json:"soldTime"` SoldTime types.Date `json:"soldTime"`
SoldTo string `json:"soldTo"` SoldTo string `json:"soldTo"`
SoldPrice float64 `json:"soldPrice,string"` SoldPrice float64 `json:"soldPrice,string"`
SoldNotes string `json:"soldNotes"` SoldNotes string `json:"soldNotes"`
@@ -127,15 +128,15 @@ type (
// Warranty // Warranty
LifetimeWarranty bool `json:"lifetimeWarranty"` LifetimeWarranty bool `json:"lifetimeWarranty"`
WarrantyExpires time.Time `json:"warrantyExpires"` WarrantyExpires types.Date `json:"warrantyExpires"`
WarrantyDetails string `json:"warrantyDetails"` WarrantyDetails string `json:"warrantyDetails"`
// Purchase // Purchase
PurchaseTime time.Time `json:"purchaseTime"` PurchaseTime types.Date `json:"purchaseTime"`
PurchaseFrom string `json:"purchaseFrom"` PurchaseFrom string `json:"purchaseFrom"`
// Sold // Sold
SoldTime time.Time `json:"soldTime"` SoldTime types.Date `json:"soldTime"`
SoldTo string `json:"soldTo"` SoldTo string `json:"soldTo"`
SoldPrice float64 `json:"soldPrice,string"` SoldPrice float64 `json:"soldPrice,string"`
SoldNotes string `json:"soldNotes"` SoldNotes string `json:"soldNotes"`
@@ -232,7 +233,7 @@ func mapItemOut(item *ent.Item) ItemOut {
AssetID: AssetID(item.AssetID), AssetID: AssetID(item.AssetID),
ItemSummary: mapItemSummary(item), ItemSummary: mapItemSummary(item),
LifetimeWarranty: item.LifetimeWarranty, LifetimeWarranty: item.LifetimeWarranty,
WarrantyExpires: item.WarrantyExpires, WarrantyExpires: types.DateFromTime(item.WarrantyExpires),
WarrantyDetails: item.WarrantyDetails, WarrantyDetails: item.WarrantyDetails,
// Identification // Identification
@@ -241,11 +242,11 @@ func mapItemOut(item *ent.Item) ItemOut {
Manufacturer: item.Manufacturer, Manufacturer: item.Manufacturer,
// Purchase // Purchase
PurchaseTime: item.PurchaseTime, PurchaseTime: types.DateFromTime(item.PurchaseTime),
PurchaseFrom: item.PurchaseFrom, PurchaseFrom: item.PurchaseFrom,
// Sold // Sold
SoldTime: item.SoldTime, SoldTime: types.DateFromTime(item.SoldTime),
SoldTo: item.SoldTo, SoldTo: item.SoldTo,
SoldPrice: item.SoldPrice, SoldPrice: item.SoldPrice,
SoldNotes: item.SoldNotes, SoldNotes: item.SoldNotes,
@@ -526,17 +527,17 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data
SetModelNumber(data.ModelNumber). SetModelNumber(data.ModelNumber).
SetManufacturer(data.Manufacturer). SetManufacturer(data.Manufacturer).
SetArchived(data.Archived). SetArchived(data.Archived).
SetPurchaseTime(data.PurchaseTime). SetPurchaseTime(data.PurchaseTime.Time()).
SetPurchaseFrom(data.PurchaseFrom). SetPurchaseFrom(data.PurchaseFrom).
SetPurchasePrice(data.PurchasePrice). SetPurchasePrice(data.PurchasePrice).
SetSoldTime(data.SoldTime). SetSoldTime(data.SoldTime.Time()).
SetSoldTo(data.SoldTo). SetSoldTo(data.SoldTo).
SetSoldPrice(data.SoldPrice). SetSoldPrice(data.SoldPrice).
SetSoldNotes(data.SoldNotes). SetSoldNotes(data.SoldNotes).
SetNotes(data.Notes). SetNotes(data.Notes).
SetLifetimeWarranty(data.LifetimeWarranty). SetLifetimeWarranty(data.LifetimeWarranty).
SetInsured(data.Insured). SetInsured(data.Insured).
SetWarrantyExpires(data.WarrantyExpires). SetWarrantyExpires(data.WarrantyExpires.Time()).
SetWarrantyDetails(data.WarrantyDetails). SetWarrantyDetails(data.WarrantyDetails).
SetQuantity(data.Quantity). SetQuantity(data.Quantity).
SetAssetID(int(data.AssetID)) SetAssetID(int(data.AssetID))

View File

@@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/homebox/backend/internal/data/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -237,15 +238,15 @@ func TestItemsRepository_Update(t *testing.T) {
LabelIDs: nil, LabelIDs: nil,
ModelNumber: fk.Str(10), ModelNumber: fk.Str(10),
Manufacturer: fk.Str(10), Manufacturer: fk.Str(10),
PurchaseTime: time.Now(), PurchaseTime: types.DateFromTime(time.Now()),
PurchaseFrom: fk.Str(10), PurchaseFrom: fk.Str(10),
PurchasePrice: 300.99, PurchasePrice: 300.99,
SoldTime: time.Now(), SoldTime: types.DateFromTime(time.Now()),
SoldTo: fk.Str(10), SoldTo: fk.Str(10),
SoldPrice: 300.99, SoldPrice: 300.99,
SoldNotes: fk.Str(10), SoldNotes: fk.Str(10),
Notes: fk.Str(10), Notes: fk.Str(10),
WarrantyExpires: time.Now(), WarrantyExpires: types.DateFromTime(time.Now()),
WarrantyDetails: fk.Str(10), WarrantyDetails: fk.Str(10),
LifetimeWarranty: true, LifetimeWarranty: true,
} }

View File

@@ -0,0 +1,88 @@
package types
import "time"
// Date is a custom type that implements the MarshalJSON interface
// that applies date only formatting to the time.Time fields in order
// to avoid common time and timezone pitfalls when working with Times.
//
// Examples:
//
// "2019-01-01" -> time.Time{2019-01-01 00:00:00 +0000 UTC}
// "2019-01-01T21:10:30Z" -> time.Time{2019-01-01 00:00:00 +0000 UTC}
// "2019-01-01T21:10:30+01:00" -> time.Time{2019-01-01 00:00:00 +0000 UTC}
type Date time.Time
func (d Date) Time() time.Time {
return time.Time(d)
}
// DateFromTime returns a Date type from a time.Time type by stripping
// the time and timezone information.
func DateFromTime(t time.Time) Date {
dateOnlyTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
return Date(dateOnlyTime)
}
// DateFromString returns a Date type from a string by parsing the
// string into a time.Time type and then stripping the time and
// timezone information.
//
// Errors are ignored and an empty Date is returned.
func DateFromString(s string) Date {
if s == "" {
return Date{}
}
t, err := time.Parse("2006-01-02", s)
if err != nil {
// TODO: Remove - used by legacy importer
t, err = time.Parse("01/02/2006", s)
if err != nil {
return Date{}
}
}
return DateFromTime(t)
}
func (d Date) String() string {
if time.Time(d).IsZero() {
return ""
}
return time.Time(d).Format("2006-01-02")
}
func (d Date) MarshalJSON() ([]byte, error) {
if time.Time(d).IsZero() {
return []byte(`""`), nil
}
return []byte(`"` + d.String() + `"`), nil
}
func (d *Date) UnmarshalJSON(data []byte) error {
str := string(data)
if str == `""` {
*d = Date{}
return nil
}
// Try YYYY-MM-DD format
var t time.Time
t, err := time.Parse("2006-01-02", str)
if err != nil {
// Try default interface
err = t.UnmarshalJSON(data)
if err != nil {
return err
}
}
// strip the time and timezone information
*d = DateFromTime(t)
return nil
}

View File

@@ -27,7 +27,7 @@
<li <li
:class="[ :class="[
'relative cursor-default select-none py-2 pl-3 pr-9 duration-75 ease-in-out transition-colors', 'relative cursor-default select-none py-2 pl-3 pr-9 duration-75 ease-in-out transition-colors',
active ? 'bg-primary text-white' : 'text-gray-900', active ? 'bg-primary text-primary-content' : 'text-base-content',
]" ]"
> >
<slot name="display" v-bind="{ item: item, selected, active }"> <slot name="display" v-bind="{ item: item, selected, active }">

View File

@@ -16,9 +16,11 @@ export function hasKey(obj: object, key: string): obj is Required<BaseApiType> {
export function parseDate<T>(obj: T, keys: Array<keyof T> = []): T { export function parseDate<T>(obj: T, keys: Array<keyof T> = []): T {
const result = { ...obj }; const result = { ...obj };
[...keys, "createdAt", "updatedAt"].forEach(key => { [...keys, "createdAt", "updatedAt"].forEach(key => {
// @ts-ignore - TS doesn't know that we're checking for the key above // @ts-expect-error - TS doesn't know that we're checking for the key above
if (hasKey(result, key)) { if (hasKey(result, key)) {
if (result[key] === ZERO_DATE) { const value = result[key] as string;
if (value === undefined || value === "" || value.startsWith(ZERO_DATE)) {
const dt = new Date(); const dt = new Date();
dt.setFullYear(1); dt.setFullYear(1);
@@ -26,11 +28,33 @@ export function parseDate<T>(obj: T, keys: Array<keyof T> = []): T {
return; return;
} }
// transform string to ensure dates are parsed as UTC dates instead of // Possible Formats
// localized time stamps // Date Only: YYYY-MM-DD
const asStr = result[key] as string; // Timestamp: 0001-01-01T00:00:00Z
const cleaned = asStr.replaceAll("-", "/").split("T")[0];
result[key] = new Date(cleaned); // Parse timestamps with default date
if (value.includes("T")) {
result[key] = new Date(value);
return;
}
// Parse dates with default time
const split = value.split("-");
if (split.length !== 3) {
console.log(`Invalid date format: ${value}`);
throw new Error(`Invalid date format: ${value}`);
}
const [year, month, day] = split;
const dt = new Date();
dt.setFullYear(parseInt(year, 10));
dt.setMonth(parseInt(month, 10) - 1);
dt.setDate(parseInt(day, 10));
result[key] = dt;
} }
}); });

View File

@@ -159,6 +159,7 @@ export interface ItemUpdate {
soldTime: Date | string; soldTime: Date | string;
soldTo: string; soldTo: string;
warrantyDetails: string; warrantyDetails: string;
/** Sold */
warrantyExpires: Date | string; warrantyExpires: Date | string;
} }
@@ -335,6 +336,7 @@ export interface ActionAmountResult {
} }
export interface ApiSummary { export interface ApiSummary {
allowRegistration: boolean;
build: Build; build: Build;
demo: boolean; demo: boolean;
health: boolean; health: boolean;

View File

@@ -21,7 +21,7 @@
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.4.0", "eslint-plugin-vue": "^9.4.0",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"nuxt": "3.1.1", "nuxt": "3.2.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"typescript": "^4.8.3", "typescript": "^4.8.3",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
@@ -40,7 +40,7 @@
"autoprefixer": "^10.4.8", "autoprefixer": "^10.4.8",
"chart.js": "^4.0.1", "chart.js": "^4.0.1",
"daisyui": "^2.24.0", "daisyui": "^2.24.0",
"dompurify": "^2.4.1", "dompurify": "^3.0.0",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"pinia": "^2.0.21", "pinia": "^2.0.21",
"postcss": "^8.4.16", "postcss": "^8.4.16",

View File

@@ -101,7 +101,6 @@
toast.success("Logged in successfully"); toast.success("Logged in successfully");
// @ts-expect-error - expires is either a date or a string, need to figure out store typing
authStore.$patch({ authStore.$patch({
token: data.token, token: data.token,
expires: data.expiresAt, expires: data.expiresAt,
@@ -213,12 +212,22 @@
</form> </form>
</Transition> </Transition>
<div class="text-center mt-6"> <div class="text-center mt-6">
<button <BaseButton
class="text-base-content text-lg hover:bg-primary hover:text-primary-content px-3 py-1 rounded-xl transition-colors duration-200" v-if="status && status.allowRegistration"
class="btn-primary btn-wide"
@click="() => toggleLogin()" @click="() => toggleLogin()"
> >
{{ registerForm ? "Already a User? Login" : "Not a User? Register" }} <template #icon>
</button> <Icon v-if="!registerForm" name="mdi-account-plus-outline" class="w-5 h-5 swap-off" />
<Icon v-else name="mdi-login" class="w-5 h-5 swap-off" />
<Icon name="mdi-arrow-right" class="w-5 h-5 swap-on" />
</template>
{{ registerForm ? "Login" : "Register" }}
</BaseButton>
<p v-else class="text-base-content italic text-sm inline-flex items-center gap-2">
<Icon name="mdi-lock" class="w-4 h-4 inline-block" />
Registration Disabled
</p>
</div> </div>
</div> </div>
</div> </div>

825
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,23 @@ import (
"regexp" "regexp"
) )
func dateTypes(names []string) map[*regexp.Regexp]string { type ReReplace struct {
result := make(map[*regexp.Regexp]string) Regex *regexp.Regexp
for _, name := range names { Text string
result[regexp.MustCompile(fmt.Sprintf(`%s: string`, name))] = fmt.Sprintf(`%s: Date | string`, name) }
func NewReReplace(regex string, replace string) ReReplace {
return ReReplace{
Regex: regexp.MustCompile(regex),
Text: replace,
}
}
func NewReDate(dateStr string) ReReplace {
return ReReplace{
Regex: regexp.MustCompile(fmt.Sprintf(`%s: string`, dateStr)),
Text: fmt.Sprintf(`%s: Date | string`, dateStr),
} }
return result
} }
func main() { func main() {
@@ -37,29 +48,24 @@ func main() {
} }
text += string(data) text += string(data)
regexReplace := map[*regexp.Regexp]string{ replaces := [...]ReReplace{
regexp.MustCompile(` PaginationResultRepo`): " PaginationResult", NewReReplace(` Repo`, " "),
regexp.MustCompile(` Repo`): " ", NewReReplace(` PaginationResultRepo`, " PaginationResult"),
regexp.MustCompile(` Services`): " ", NewReReplace(` Services`, " "),
regexp.MustCompile(` V1`): " ", NewReReplace(` V1`, " "),
regexp.MustCompile(`\?:`): ":", NewReReplace(`\?:`, ":"),
NewReDate("createdAt"),
NewReDate("updatedAt"),
NewReDate("soldTime"),
NewReDate("purchaseTime"),
NewReDate("warrantyExpires"),
NewReDate("expiresAt"),
NewReDate("date"),
} }
for regex, replace := range dateTypes([]string{ for _, replace := range replaces {
"createdAt", fmt.Printf("Replacing '%v' -> '%s'\n", replace.Regex, replace.Text)
"updatedAt", text = replace.Regex.ReplaceAllString(text, replace.Text)
"soldTime",
"purchaseTime",
"warrantyExpires",
"expiresAt",
"date",
}) {
regexReplace[regex] = replace
}
for regex, replace := range regexReplace {
fmt.Printf("Replacing '%v' -> '%s'\n", regex, replace)
text = regex.ReplaceAllString(text, replace)
} }
err = os.WriteFile(path, []byte(text), 0644) err = os.WriteFile(path, []byte(text), 0644)