mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-23 22:18:22 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88f9ff90d4 | ||
|
|
354f1adbee | ||
|
|
2a62a43493 | ||
|
|
673db41f37 | ||
|
|
830ce2b0a9 | ||
|
|
e8e6a425dd | ||
|
|
da00db0608 | ||
|
|
efd7069fe4 | ||
|
|
dd349aa98e | ||
|
|
607b06d2f2 |
@@ -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" ]
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
88
backend/internal/data/types/date.go
Normal file
88
backend/internal/data/types/date.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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 }">
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
825
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user