forked from mirrors/homebox
refactor: repositories (#28)
* cleanup unnecessary mocks * refactor document storage location * remove unused function * move ownership to document types to repo package * move types and mappers to repo package * refactor sets to own package
This commit is contained in:
parent
2e82398e5c
commit
343290a55a
79 changed files with 3169 additions and 3160 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,302 @@
|
||||||
basePath: /api
|
basePath: /api
|
||||||
definitions:
|
definitions:
|
||||||
|
repo.DocumentOut:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.ItemAttachment:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
document:
|
||||||
|
$ref: '#/definitions/repo.DocumentOut'
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.ItemAttachmentUpdate:
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.ItemCreate:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
labelIds:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
locationId:
|
||||||
|
description: Edges
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.ItemOut:
|
||||||
|
properties:
|
||||||
|
attachments:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.ItemAttachment'
|
||||||
|
type: array
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
insured:
|
||||||
|
type: boolean
|
||||||
|
labels:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.LabelSummary'
|
||||||
|
type: array
|
||||||
|
lifetimeWarranty:
|
||||||
|
description: Warranty
|
||||||
|
type: boolean
|
||||||
|
location:
|
||||||
|
$ref: '#/definitions/repo.LocationSummary'
|
||||||
|
description: Edges
|
||||||
|
manufacturer:
|
||||||
|
type: string
|
||||||
|
modelNumber:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
notes:
|
||||||
|
description: Extras
|
||||||
|
type: string
|
||||||
|
purchaseFrom:
|
||||||
|
type: string
|
||||||
|
purchasePrice:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
purchaseTime:
|
||||||
|
description: Purchase
|
||||||
|
type: string
|
||||||
|
quantity:
|
||||||
|
type: integer
|
||||||
|
serialNumber:
|
||||||
|
type: string
|
||||||
|
soldNotes:
|
||||||
|
type: string
|
||||||
|
soldPrice:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
soldTime:
|
||||||
|
description: Sold
|
||||||
|
type: string
|
||||||
|
soldTo:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
warrantyDetails:
|
||||||
|
type: string
|
||||||
|
warrantyExpires:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.ItemSummary:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
insured:
|
||||||
|
type: boolean
|
||||||
|
labels:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.LabelSummary'
|
||||||
|
type: array
|
||||||
|
location:
|
||||||
|
$ref: '#/definitions/repo.LocationSummary'
|
||||||
|
description: Edges
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
quantity:
|
||||||
|
type: integer
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.ItemUpdate:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
insured:
|
||||||
|
type: boolean
|
||||||
|
labelIds:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
lifetimeWarranty:
|
||||||
|
description: Warranty
|
||||||
|
type: boolean
|
||||||
|
locationId:
|
||||||
|
description: Edges
|
||||||
|
type: string
|
||||||
|
manufacturer:
|
||||||
|
type: string
|
||||||
|
modelNumber:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
notes:
|
||||||
|
description: Extras
|
||||||
|
type: string
|
||||||
|
purchaseFrom:
|
||||||
|
type: string
|
||||||
|
purchasePrice:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
purchaseTime:
|
||||||
|
description: Purchase
|
||||||
|
type: string
|
||||||
|
quantity:
|
||||||
|
type: integer
|
||||||
|
serialNumber:
|
||||||
|
description: Identifications
|
||||||
|
type: string
|
||||||
|
soldNotes:
|
||||||
|
type: string
|
||||||
|
soldPrice:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
soldTime:
|
||||||
|
description: Sold
|
||||||
|
type: string
|
||||||
|
soldTo:
|
||||||
|
type: string
|
||||||
|
warrantyDetails:
|
||||||
|
type: string
|
||||||
|
warrantyExpires:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.LabelCreate:
|
||||||
|
properties:
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.LabelOut:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
items:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.ItemSummary'
|
||||||
|
type: array
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.LabelSummary:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.LocationCreate:
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.LocationOut:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
items:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.ItemSummary'
|
||||||
|
type: array
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.LocationOutCount:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
itemCount:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.LocationSummary:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.UserOut:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
groupId:
|
||||||
|
type: string
|
||||||
|
groupName:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
isSuperuser:
|
||||||
|
type: boolean
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.UserUpdate:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
server.Result:
|
server.Result:
|
||||||
properties:
|
properties:
|
||||||
details: {}
|
details: {}
|
||||||
|
@ -21,10 +318,21 @@ definitions:
|
||||||
reason:
|
reason:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.ApiSummary:
|
services.UserRegistration:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
groupName:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
v1.ApiSummary:
|
||||||
properties:
|
properties:
|
||||||
build:
|
build:
|
||||||
$ref: '#/definitions/types.Build'
|
$ref: '#/definitions/v1.Build'
|
||||||
health:
|
health:
|
||||||
type: boolean
|
type: boolean
|
||||||
message:
|
message:
|
||||||
|
@ -36,7 +344,7 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
types.Build:
|
v1.Build:
|
||||||
properties:
|
properties:
|
||||||
buildTime:
|
buildTime:
|
||||||
type: string
|
type: string
|
||||||
|
@ -45,367 +353,18 @@ definitions:
|
||||||
version:
|
version:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.DocumentOut:
|
v1.ItemAttachmentToken:
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
path:
|
|
||||||
type: string
|
|
||||||
title:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.ItemAttachment:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
document:
|
|
||||||
$ref: '#/definitions/types.DocumentOut'
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.ItemAttachmentToken:
|
|
||||||
properties:
|
properties:
|
||||||
token:
|
token:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.ItemAttachmentUpdate:
|
v1.TokenResponse:
|
||||||
properties:
|
|
||||||
title:
|
|
||||||
type: string
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.ItemCreate:
|
|
||||||
properties:
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
labelIds:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
locationId:
|
|
||||||
description: Edges
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.ItemOut:
|
|
||||||
properties:
|
|
||||||
attachments:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/types.ItemAttachment'
|
|
||||||
type: array
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
insured:
|
|
||||||
type: boolean
|
|
||||||
labels:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/types.LabelSummary'
|
|
||||||
type: array
|
|
||||||
lifetimeWarranty:
|
|
||||||
description: Warranty
|
|
||||||
type: boolean
|
|
||||||
location:
|
|
||||||
$ref: '#/definitions/types.LocationSummary'
|
|
||||||
description: Edges
|
|
||||||
manufacturer:
|
|
||||||
type: string
|
|
||||||
modelNumber:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
notes:
|
|
||||||
description: Extras
|
|
||||||
type: string
|
|
||||||
purchaseFrom:
|
|
||||||
type: string
|
|
||||||
purchasePrice:
|
|
||||||
example: "0"
|
|
||||||
type: string
|
|
||||||
purchaseTime:
|
|
||||||
description: Purchase
|
|
||||||
type: string
|
|
||||||
quantity:
|
|
||||||
type: integer
|
|
||||||
serialNumber:
|
|
||||||
description: Identifications
|
|
||||||
type: string
|
|
||||||
soldNotes:
|
|
||||||
type: string
|
|
||||||
soldPrice:
|
|
||||||
example: "0"
|
|
||||||
type: string
|
|
||||||
soldTime:
|
|
||||||
description: Sold
|
|
||||||
type: string
|
|
||||||
soldTo:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
warrantyDetails:
|
|
||||||
type: string
|
|
||||||
warrantyExpires:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.ItemSummary:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
insured:
|
|
||||||
type: boolean
|
|
||||||
labels:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/types.LabelSummary'
|
|
||||||
type: array
|
|
||||||
lifetimeWarranty:
|
|
||||||
description: Warranty
|
|
||||||
type: boolean
|
|
||||||
location:
|
|
||||||
$ref: '#/definitions/types.LocationSummary'
|
|
||||||
description: Edges
|
|
||||||
manufacturer:
|
|
||||||
type: string
|
|
||||||
modelNumber:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
notes:
|
|
||||||
description: Extras
|
|
||||||
type: string
|
|
||||||
purchaseFrom:
|
|
||||||
type: string
|
|
||||||
purchasePrice:
|
|
||||||
example: "0"
|
|
||||||
type: string
|
|
||||||
purchaseTime:
|
|
||||||
description: Purchase
|
|
||||||
type: string
|
|
||||||
quantity:
|
|
||||||
type: integer
|
|
||||||
serialNumber:
|
|
||||||
description: Identifications
|
|
||||||
type: string
|
|
||||||
soldNotes:
|
|
||||||
type: string
|
|
||||||
soldPrice:
|
|
||||||
example: "0"
|
|
||||||
type: string
|
|
||||||
soldTime:
|
|
||||||
description: Sold
|
|
||||||
type: string
|
|
||||||
soldTo:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
warrantyDetails:
|
|
||||||
type: string
|
|
||||||
warrantyExpires:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.ItemUpdate:
|
|
||||||
properties:
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
insured:
|
|
||||||
type: boolean
|
|
||||||
labelIds:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
lifetimeWarranty:
|
|
||||||
description: Warranty
|
|
||||||
type: boolean
|
|
||||||
locationId:
|
|
||||||
description: Edges
|
|
||||||
type: string
|
|
||||||
manufacturer:
|
|
||||||
type: string
|
|
||||||
modelNumber:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
notes:
|
|
||||||
description: Extras
|
|
||||||
type: string
|
|
||||||
purchaseFrom:
|
|
||||||
type: string
|
|
||||||
purchasePrice:
|
|
||||||
example: "0"
|
|
||||||
type: string
|
|
||||||
purchaseTime:
|
|
||||||
description: Purchase
|
|
||||||
type: string
|
|
||||||
quantity:
|
|
||||||
type: integer
|
|
||||||
serialNumber:
|
|
||||||
description: Identifications
|
|
||||||
type: string
|
|
||||||
soldNotes:
|
|
||||||
type: string
|
|
||||||
soldPrice:
|
|
||||||
example: "0"
|
|
||||||
type: string
|
|
||||||
soldTime:
|
|
||||||
description: Sold
|
|
||||||
type: string
|
|
||||||
soldTo:
|
|
||||||
type: string
|
|
||||||
warrantyDetails:
|
|
||||||
type: string
|
|
||||||
warrantyExpires:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.LabelCreate:
|
|
||||||
properties:
|
|
||||||
color:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.LabelOut:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
items:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/types.ItemSummary'
|
|
||||||
type: array
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.LabelSummary:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.LocationCount:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
itemCount:
|
|
||||||
type: integer
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.LocationCreate:
|
|
||||||
properties:
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.LocationOut:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
items:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/types.ItemSummary'
|
|
||||||
type: array
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.LocationSummary:
|
|
||||||
properties:
|
|
||||||
createdAt:
|
|
||||||
type: string
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
updatedAt:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.TokenResponse:
|
|
||||||
properties:
|
properties:
|
||||||
expiresAt:
|
expiresAt:
|
||||||
type: string
|
type: string
|
||||||
token:
|
token:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.UserIn:
|
|
||||||
properties:
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.UserOut:
|
|
||||||
properties:
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
groupId:
|
|
||||||
type: string
|
|
||||||
groupName:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
isSuperuser:
|
|
||||||
type: boolean
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
types.UserRegistration:
|
|
||||||
properties:
|
|
||||||
groupName:
|
|
||||||
type: string
|
|
||||||
user:
|
|
||||||
$ref: '#/definitions/types.UserIn'
|
|
||||||
type: object
|
|
||||||
types.UserUpdate:
|
|
||||||
properties:
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
name: Don't
|
name: Don't
|
||||||
|
@ -430,7 +389,7 @@ paths:
|
||||||
- properties:
|
- properties:
|
||||||
items:
|
items:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/types.ItemSummary'
|
$ref: '#/definitions/repo.ItemSummary'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
security:
|
security:
|
||||||
|
@ -445,14 +404,14 @@ paths:
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemCreate'
|
$ref: '#/definitions/repo.ItemCreate'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemSummary'
|
$ref: '#/definitions/repo.ItemSummary'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Create a new item
|
summary: Create a new item
|
||||||
|
@ -489,7 +448,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemOut'
|
$ref: '#/definitions/repo.ItemOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Gets a item and fields
|
summary: Gets a item and fields
|
||||||
|
@ -507,14 +466,14 @@ paths:
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemUpdate'
|
$ref: '#/definitions/repo.ItemUpdate'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemOut'
|
$ref: '#/definitions/repo.ItemOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: updates a item
|
summary: updates a item
|
||||||
|
@ -549,7 +508,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemOut'
|
$ref: '#/definitions/repo.ItemOut'
|
||||||
"422":
|
"422":
|
||||||
description: Unprocessable Entity
|
description: Unprocessable Entity
|
||||||
schema:
|
schema:
|
||||||
|
@ -600,7 +559,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemAttachmentToken'
|
$ref: '#/definitions/v1.ItemAttachmentToken'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
|
@ -623,12 +582,12 @@ paths:
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemAttachmentUpdate'
|
$ref: '#/definitions/repo.ItemAttachmentUpdate'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ItemOut'
|
$ref: '#/definitions/repo.ItemOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
|
@ -688,7 +647,7 @@ paths:
|
||||||
- properties:
|
- properties:
|
||||||
items:
|
items:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/types.LabelOut'
|
$ref: '#/definitions/repo.LabelOut'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
security:
|
security:
|
||||||
|
@ -703,14 +662,14 @@ paths:
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LabelCreate'
|
$ref: '#/definitions/repo.LabelCreate'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LabelSummary'
|
$ref: '#/definitions/repo.LabelSummary'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Create a new label
|
summary: Create a new label
|
||||||
|
@ -747,7 +706,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LabelOut'
|
$ref: '#/definitions/repo.LabelOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Gets a label and fields
|
summary: Gets a label and fields
|
||||||
|
@ -766,7 +725,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LabelOut'
|
$ref: '#/definitions/repo.LabelOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: updates a label
|
summary: updates a label
|
||||||
|
@ -785,7 +744,7 @@ paths:
|
||||||
- properties:
|
- properties:
|
||||||
items:
|
items:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/types.LocationCount'
|
$ref: '#/definitions/repo.LocationOutCount'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
security:
|
security:
|
||||||
|
@ -800,14 +759,14 @@ paths:
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LocationCreate'
|
$ref: '#/definitions/repo.LocationCreate'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LocationSummary'
|
$ref: '#/definitions/repo.LocationSummary'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Create a new location
|
summary: Create a new location
|
||||||
|
@ -844,7 +803,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LocationOut'
|
$ref: '#/definitions/repo.LocationOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Gets a location and fields
|
summary: Gets a location and fields
|
||||||
|
@ -863,7 +822,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.LocationOut'
|
$ref: '#/definitions/repo.LocationOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: updates a location
|
summary: updates a location
|
||||||
|
@ -877,7 +836,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.ApiSummary'
|
$ref: '#/definitions/v1.ApiSummary'
|
||||||
summary: Retrieves the basic information about the API
|
summary: Retrieves the basic information about the API
|
||||||
tags:
|
tags:
|
||||||
- Base
|
- Base
|
||||||
|
@ -903,7 +862,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.TokenResponse'
|
$ref: '#/definitions/v1.TokenResponse'
|
||||||
summary: User Login
|
summary: User Login
|
||||||
tags:
|
tags:
|
||||||
- Authentication
|
- Authentication
|
||||||
|
@ -938,7 +897,7 @@ paths:
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.UserRegistration'
|
$ref: '#/definitions/services.UserRegistration'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
@ -970,7 +929,7 @@ paths:
|
||||||
- $ref: '#/definitions/server.Result'
|
- $ref: '#/definitions/server.Result'
|
||||||
- properties:
|
- properties:
|
||||||
item:
|
item:
|
||||||
$ref: '#/definitions/types.UserOut'
|
$ref: '#/definitions/repo.UserOut'
|
||||||
type: object
|
type: object
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
|
@ -984,7 +943,7 @@ paths:
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/types.UserUpdate'
|
$ref: '#/definitions/repo.UserUpdate'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
@ -995,7 +954,7 @@ paths:
|
||||||
- $ref: '#/definitions/server.Result'
|
- $ref: '#/definitions/server.Result'
|
||||||
- properties:
|
- properties:
|
||||||
item:
|
item:
|
||||||
$ref: '#/definitions/types.UserUpdate'
|
$ref: '#/definitions/repo.UserUpdate'
|
||||||
type: object
|
type: object
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
|
|
|
@ -81,8 +81,8 @@ func run(cfg *config.Config) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.db = c
|
app.db = c
|
||||||
app.repos = repo.EntAllRepos(c)
|
app.repos = repo.EntAllRepos(c, cfg.Storage.Data)
|
||||||
app.services = services.NewServices(app.repos, cfg.Storage.Data)
|
app.services = services.NewServices(app.repos)
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Start Server
|
// Start Server
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (a *app) mwAuthToken(next http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r = r.WithContext(services.SetUserCtx(r.Context(), usr, requestToken))
|
r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken))
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,7 +14,6 @@ import (
|
||||||
_ "github.com/hay-kot/homebox/backend/app/api/docs"
|
_ "github.com/hay-kot/homebox/backend/app/api/docs"
|
||||||
v1 "github.com/hay-kot/homebox/backend/app/api/v1"
|
v1 "github.com/hay-kot/homebox/backend/app/api/v1"
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
|
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
|
||||||
)
|
)
|
||||||
|
@ -45,7 +44,7 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
|
||||||
v1Base := v1.BaseUrlFunc(prefix)
|
v1Base := v1.BaseUrlFunc(prefix)
|
||||||
v1Ctrl := v1.NewControllerV1(a.services, v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize))
|
v1Ctrl := v1.NewControllerV1(a.services, v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize))
|
||||||
{
|
{
|
||||||
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, types.Build{
|
r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
|
||||||
Version: Version,
|
Version: Version,
|
||||||
Commit: Commit,
|
Commit: Commit,
|
||||||
BuildTime: BuildTime,
|
BuildTime: BuildTime,
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,6 +18,22 @@ type V1Controller struct {
|
||||||
maxUploadSize int64
|
maxUploadSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Build struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
BuildTime string `json:"buildTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiSummary struct {
|
||||||
|
Healthy bool `json:"health"`
|
||||||
|
Versions []string `json:"versions"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Build Build
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func BaseUrlFunc(prefix string) func(s string) string {
|
func BaseUrlFunc(prefix string) func(s string) string {
|
||||||
v1Base := prefix + "/v1"
|
v1Base := prefix + "/v1"
|
||||||
prefixFunc := func(s string) string {
|
prefixFunc := func(s string) string {
|
||||||
|
@ -42,11 +57,11 @@ type ReadyFunc func() bool
|
||||||
// @Summary Retrieves the basic information about the API
|
// @Summary Retrieves the basic information about the API
|
||||||
// @Tags Base
|
// @Tags Base
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} types.ApiSummary
|
// @Success 200 {object} ApiSummary
|
||||||
// @Router /v1/status [GET]
|
// @Router /v1/status [GET]
|
||||||
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build types.Build) http.HandlerFunc {
|
func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
server.Respond(w, http.StatusOK, types.ApiSummary{
|
server.Respond(w, http.StatusOK, ApiSummary{
|
||||||
Healthy: ready(),
|
Healthy: ready(),
|
||||||
Title: "Go API Template",
|
Title: "Go API Template",
|
||||||
Message: "Welcome to the Go API Template Application!",
|
Message: "Welcome to the Go API Template Application!",
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_NewHandlerV1(t *testing.T) {
|
|
||||||
|
|
||||||
v1Base := BaseUrlFunc("/testing/v1")
|
|
||||||
ctrl := NewControllerV1(mockHandler.svc)
|
|
||||||
|
|
||||||
assert.NotNil(t, ctrl)
|
|
||||||
|
|
||||||
assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123"))
|
|
||||||
assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandlersv1_HandleBase(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
hdlrFunc := mockHandler.HandleBase(func() bool { return true }, types.Build{
|
|
||||||
Version: "0.1.0",
|
|
||||||
Commit: "HEAD",
|
|
||||||
BuildTime: "now",
|
|
||||||
})
|
|
||||||
|
|
||||||
// Call Handler Func
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
hdlrFunc(rr, nil)
|
|
||||||
|
|
||||||
// Validate Status Code
|
|
||||||
if rr.Code != http.StatusOK {
|
|
||||||
t.Errorf("Expected status code to be %d, got %d", http.StatusOK, rr.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Json Payload
|
|
||||||
expected := `{"health":true,"versions":null,"title":"Go API Template","message":"Welcome to the Go API Template Application!","Build":{"version":"0.1.0","commit":"HEAD","buildTime":"now"}}`
|
|
||||||
|
|
||||||
if rr.Body.String() != expected {
|
|
||||||
t.Errorf("Expected json to be %s, got %s", expected, rr.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/mocks"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/mocks/factories"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var mockHandler = &V1Controller{}
|
|
||||||
var users = []*ent.User{}
|
|
||||||
|
|
||||||
func userPool() func() {
|
|
||||||
create := []types.UserCreate{
|
|
||||||
factories.UserFactory(),
|
|
||||||
factories.UserFactory(),
|
|
||||||
factories.UserFactory(),
|
|
||||||
factories.UserFactory(),
|
|
||||||
}
|
|
||||||
|
|
||||||
userOut := []*ent.User{}
|
|
||||||
|
|
||||||
for _, user := range create {
|
|
||||||
usrOut, _ := mockHandler.svc.Admin.Create(context.Background(), user)
|
|
||||||
userOut = append(userOut, usrOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
users = userOut
|
|
||||||
|
|
||||||
purge := func() {
|
|
||||||
_ = mockHandler.svc.Admin.DeleteAll(context.Background())
|
|
||||||
}
|
|
||||||
|
|
||||||
return purge
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
// Set Handler Vars
|
|
||||||
repos, closeDb := mocks.GetEntRepos()
|
|
||||||
mockHandler.svc = mocks.GetMockServices(repos)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = closeDb()
|
|
||||||
}()
|
|
||||||
|
|
||||||
purge := userPool()
|
|
||||||
defer purge()
|
|
||||||
|
|
||||||
m.Run()
|
|
||||||
}
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -21,12 +21,12 @@ and makes it a little more consistent when error handling and logging.
|
||||||
// from the context. If either of these fail, it will return an error. When an error
|
// from the context. If either of these fail, it will return an error. When an error
|
||||||
// occurs it will also write the error to the response. As such, if an error is returned
|
// occurs it will also write the error to the response. As such, if an error is returned
|
||||||
// from this function you can return immediately without writing to the response.
|
// from this function you can return immediately without writing to the response.
|
||||||
func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *types.UserOut, error) {
|
func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *repo.UserOut, error) {
|
||||||
uid, err := uuid.Parse(chi.URLParam(r, "id"))
|
uid, err := uuid.Parse(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to parse id")
|
log.Err(err).Msg("failed to parse id")
|
||||||
server.RespondError(w, http.StatusBadRequest, err)
|
server.RespondError(w, http.StatusBadRequest, err)
|
||||||
return uuid.Nil, nil, err
|
return uuid.Nil, &repo.UserOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
user := services.UseUserCtx(r.Context())
|
user := services.UseUserCtx(r.Context())
|
||||||
|
|
|
@ -3,13 +3,25 @@ package v1
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
TokenResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginForm struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// HandleAuthLogin godoc
|
// HandleAuthLogin godoc
|
||||||
// @Summary User Login
|
// @Summary User Login
|
||||||
// @Tags Authentication
|
// @Tags Authentication
|
||||||
|
@ -18,11 +30,11 @@ import (
|
||||||
// @Param username formData string false "string" example(admin@admin.com)
|
// @Param username formData string false "string" example(admin@admin.com)
|
||||||
// @Param password formData string false "string" example(admin)
|
// @Param password formData string false "string" example(admin)
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} types.TokenResponse
|
// @Success 200 {object} TokenResponse
|
||||||
// @Router /v1/users/login [POST]
|
// @Router /v1/users/login [POST]
|
||||||
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
loginForm := &types.LoginForm{}
|
loginForm := &LoginForm{}
|
||||||
|
|
||||||
if r.Header.Get("Content-Type") == server.ContentFormUrlEncoded {
|
if r.Header.Get("Content-Type") == server.ContentFormUrlEncoded {
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
|
@ -59,9 +71,9 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Respond(w, http.StatusOK, types.TokenResponse{
|
server.Respond(w, http.StatusOK, TokenResponse{
|
||||||
BearerToken: "Bearer " + newToken.Raw,
|
Token: "Bearer " + newToken.Raw,
|
||||||
ExpiresAt: newToken.ExpiresAt,
|
ExpiresAt: newToken.ExpiresAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ import (
|
||||||
// @Summary Get All Items
|
// @Summary Get All Items
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Results{items=[]types.ItemSummary}
|
// @Success 200 {object} server.Results{items=[]repo.ItemSummary}
|
||||||
// @Router /v1/items [GET]
|
// @Router /v1/items [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
||||||
|
@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
||||||
// @Summary Create a new item
|
// @Summary Create a new item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body types.ItemCreate true "Item Data"
|
// @Param payload body repo.ItemCreate true "Item Data"
|
||||||
// @Success 200 {object} types.ItemSummary
|
// @Success 200 {object} repo.ItemSummary
|
||||||
// @Router /v1/items [POST]
|
// @Router /v1/items [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
createData := types.ItemCreate{}
|
createData := repo.ItemCreate{}
|
||||||
if err := server.Decode(r, &createData); err != nil {
|
if err := server.Decode(r, &createData); err != nil {
|
||||||
log.Err(err).Msg("failed to decode request body")
|
log.Err(err).Msg("failed to decode request body")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Success 200 {object} types.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Router /v1/items/{id} [GET]
|
// @Router /v1/items/{id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
||||||
|
@ -114,14 +114,14 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
||||||
// @Summary updates a item
|
// @Summary updates a item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param payload body types.ItemUpdate true "Item Data"
|
// @Param payload body repo.ItemUpdate true "Item Data"
|
||||||
// @Success 200 {object} types.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Router /v1/items/{id} [PUT]
|
// @Router /v1/items/{id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
body := types.ItemUpdate{}
|
body := repo.ItemUpdate{}
|
||||||
if err := server.Decode(r, &body); err != nil {
|
if err := server.Decode(r, &body); err != nil {
|
||||||
log.Err(err).Msg("failed to decode request body")
|
log.Err(err).Msg("failed to decode request body")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
|
|
@ -4,17 +4,22 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent/attachment"
|
"github.com/hay-kot/homebox/backend/ent/attachment"
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ItemAttachmentToken struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// HandleItemsImport godocs
|
// HandleItemsImport godocs
|
||||||
// @Summary imports items into the database
|
// @Summary imports items into the database
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
|
@ -23,7 +28,7 @@ import (
|
||||||
// @Param file formData file true "File attachment"
|
// @Param file formData file true "File attachment"
|
||||||
// @Param type formData string true "Type of file"
|
// @Param type formData string true "Type of file"
|
||||||
// @Param name formData string true "name of the file including extension"
|
// @Param name formData string true "name of the file including extension"
|
||||||
// @Success 200 {object} types.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Failure 422 {object} []server.ValidationError
|
// @Failure 422 {object} []server.ValidationError
|
||||||
// @Router /v1/items/{id}/attachments [POST]
|
// @Router /v1/items/{id}/attachments [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
@ -105,7 +110,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
token := server.GetParam(r, "token", "")
|
token := server.GetParam(r, "token", "")
|
||||||
|
|
||||||
path, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
|
doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to get attachment")
|
log.Err(err).Msg("failed to get attachment")
|
||||||
|
@ -113,9 +118,9 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(path)))
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title))
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
http.ServeFile(w, r, path)
|
http.ServeFile(w, r, doc.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +130,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
||||||
// @Produce application/octet-stream
|
// @Produce application/octet-stream
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
// @Success 200 {object} types.ItemAttachmentToken
|
// @Success 200 {object} ItemAttachmentToken
|
||||||
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
|
// @Router /v1/items/{id}/attachments/{attachment_id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
|
||||||
|
@ -147,10 +152,10 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
|
||||||
// HandleItemAttachmentUpdate godocs
|
// HandleItemAttachmentUpdate godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
// @Param payload body types.ItemAttachmentUpdate true "Attachment Update"
|
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
||||||
// @Success 200 {object} types.ItemOut
|
// @Success 200 {object} repo.ItemOut
|
||||||
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
|
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc {
|
||||||
|
@ -201,7 +206,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Respond(w, http.StatusOK, types.ItemAttachmentToken{Token: token})
|
server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token})
|
||||||
|
|
||||||
// Delete Attachment Handler
|
// Delete Attachment Handler
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
|
@ -216,7 +221,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
|
||||||
|
|
||||||
// Update Attachment Handler
|
// Update Attachment Handler
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
var attachment types.ItemAttachmentUpdate
|
var attachment repo.ItemAttachmentUpdate
|
||||||
err = server.Decode(r, &attachment)
|
err = server.Decode(r, &attachment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to decode attachment")
|
log.Err(err).Msg("failed to decode attachment")
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ import (
|
||||||
// @Summary Get All Labels
|
// @Summary Get All Labels
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Results{items=[]types.LabelOut}
|
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
|
||||||
// @Router /v1/labels [GET]
|
// @Router /v1/labels [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
||||||
|
@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
||||||
// @Summary Create a new label
|
// @Summary Create a new label
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body types.LabelCreate true "Label Data"
|
// @Param payload body repo.LabelCreate true "Label Data"
|
||||||
// @Success 200 {object} types.LabelSummary
|
// @Success 200 {object} repo.LabelSummary
|
||||||
// @Router /v1/labels [POST]
|
// @Router /v1/labels [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
createData := types.LabelCreate{}
|
createData := repo.LabelCreate{}
|
||||||
if err := server.Decode(r, &createData); err != nil {
|
if err := server.Decode(r, &createData); err != nil {
|
||||||
log.Err(err).Msg("error decoding label create data")
|
log.Err(err).Msg("error decoding label create data")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Label ID"
|
// @Param id path string true "Label ID"
|
||||||
// @Success 200 {object} types.LabelOut
|
// @Success 200 {object} repo.LabelOut
|
||||||
// @Router /v1/labels/{id} [GET]
|
// @Router /v1/labels/{id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
||||||
|
@ -122,12 +122,12 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
||||||
// @Tags Labels
|
// @Tags Labels
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Label ID"
|
// @Param id path string true "Label ID"
|
||||||
// @Success 200 {object} types.LabelOut
|
// @Success 200 {object} repo.LabelOut
|
||||||
// @Router /v1/labels/{id} [PUT]
|
// @Router /v1/labels/{id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
body := types.LabelUpdate{}
|
body := repo.LabelUpdate{}
|
||||||
if err := server.Decode(r, &body); err != nil {
|
if err := server.Decode(r, &body); err != nil {
|
||||||
log.Err(err).Msg("error decoding label update data")
|
log.Err(err).Msg("error decoding label update data")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,7 @@ import (
|
||||||
// @Summary Get All Locations
|
// @Summary Get All Locations
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Results{items=[]types.LocationCount}
|
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
|
||||||
// @Router /v1/locations [GET]
|
// @Router /v1/locations [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
||||||
|
@ -35,13 +35,13 @@ func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
||||||
// @Summary Create a new location
|
// @Summary Create a new location
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body types.LocationCreate true "Location Data"
|
// @Param payload body repo.LocationCreate true "Location Data"
|
||||||
// @Success 200 {object} types.LocationSummary
|
// @Success 200 {object} repo.LocationSummary
|
||||||
// @Router /v1/locations [POST]
|
// @Router /v1/locations [POST]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
createData := types.LocationCreate{}
|
createData := repo.LocationCreate{}
|
||||||
if err := server.Decode(r, &createData); err != nil {
|
if err := server.Decode(r, &createData); err != nil {
|
||||||
log.Err(err).Msg("failed to decode location create data")
|
log.Err(err).Msg("failed to decode location create data")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Location ID"
|
// @Param id path string true "Location ID"
|
||||||
// @Success 200 {object} types.LocationOut
|
// @Success 200 {object} repo.LocationOut
|
||||||
// @Router /v1/locations/{id} [GET]
|
// @Router /v1/locations/{id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
||||||
|
@ -105,12 +105,16 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
||||||
if ent.IsNotFound(err) {
|
if ent.IsNotFound(err) {
|
||||||
log.Err(err).
|
log.Err(err).
|
||||||
Str("id", uid.String()).
|
Str("id", uid.String()).
|
||||||
|
Str("gid", user.GroupID.String()).
|
||||||
Msg("location not found")
|
Msg("location not found")
|
||||||
server.RespondError(w, http.StatusNotFound, err)
|
server.RespondError(w, http.StatusNotFound, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Err(err).Msg("failed to get location")
|
log.Err(err).
|
||||||
|
Str("id", uid.String()).
|
||||||
|
Str("gid", user.GroupID.String()).
|
||||||
|
Msg("failed to get location")
|
||||||
server.RespondServerError(w)
|
server.RespondServerError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -123,12 +127,12 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
||||||
// @Tags Locations
|
// @Tags Locations
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Location ID"
|
// @Param id path string true "Location ID"
|
||||||
// @Success 200 {object} types.LocationOut
|
// @Success 200 {object} repo.LocationOut
|
||||||
// @Router /v1/locations/{id} [PUT]
|
// @Router /v1/locations/{id} [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
body := types.LocationUpdate{}
|
body := repo.LocationUpdate{}
|
||||||
if err := server.Decode(r, &body); err != nil {
|
if err := server.Decode(r, &body); err != nil {
|
||||||
log.Err(err).Msg("failed to decode location update data")
|
log.Err(err).Msg("failed to decode location update data")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -14,12 +14,12 @@ import (
|
||||||
// @Summary Get the current user
|
// @Summary Get the current user
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body types.UserRegistration true "User Data"
|
// @Param payload body services.UserRegistration true "User Data"
|
||||||
// @Success 204
|
// @Success 204
|
||||||
// @Router /v1/users/register [Post]
|
// @Router /v1/users/register [Post]
|
||||||
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
regData := types.UserRegistration{}
|
regData := services.UserRegistration{}
|
||||||
|
|
||||||
if err := server.Decode(r, ®Data); err != nil {
|
if err := server.Decode(r, ®Data); err != nil {
|
||||||
log.Err(err).Msg("failed to decode user registration data")
|
log.Err(err).Msg("failed to decode user registration data")
|
||||||
|
@ -29,6 +29,7 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
||||||
|
|
||||||
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
|
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Err(err).Msg("failed to register user")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -41,7 +42,7 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
||||||
// @Summary Get the current user
|
// @Summary Get the current user
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} server.Result{item=types.UserOut}
|
// @Success 200 {object} server.Result{item=repo.UserOut}
|
||||||
// @Router /v1/users/self [GET]
|
// @Router /v1/users/self [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
||||||
|
@ -62,13 +63,13 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
||||||
// @Summary Update the current user
|
// @Summary Update the current user
|
||||||
// @Tags User
|
// @Tags User
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body types.UserUpdate true "User Data"
|
// @Param payload body repo.UserUpdate true "User Data"
|
||||||
// @Success 200 {object} server.Result{item=types.UserUpdate}
|
// @Success 200 {object} server.Result{item=repo.UserUpdate}
|
||||||
// @Router /v1/users/self [PUT]
|
// @Router /v1/users/self [PUT]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
updateData := types.UserUpdate{}
|
updateData := repo.UserUpdate{}
|
||||||
if err := server.Decode(r, &updateData); err != nil {
|
if err := server.Decode(r, &updateData); err != nil {
|
||||||
log.Err(err).Msg("failed to decode user update data")
|
log.Err(err).Msg("failed to decode user update data")
|
||||||
server.RespondError(w, http.StatusBadRequest, err)
|
server.RespondError(w, http.StatusBadRequest, err)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package factories
|
package factories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/faker"
|
"github.com/hay-kot/homebox/backend/pkgs/faker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UserFactory() types.UserCreate {
|
func UserFactory() repo.UserCreate {
|
||||||
f := faker.NewFaker()
|
f := faker.NewFaker()
|
||||||
return types.UserCreate{
|
return repo.UserCreate{
|
||||||
Name: f.Str(10),
|
Name: f.Str(10),
|
||||||
Email: f.Email(),
|
Email: f.Email(),
|
||||||
Password: f.Str(10),
|
Password: f.Str(10),
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
package mocks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetMockServices(repos *repo.AllRepos) *services.AllServices {
|
|
||||||
return services.NewServices(repos, "/tmp/homebox")
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package mocks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetEntRepos() (*repo.AllRepos, func() error) {
|
|
||||||
c, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Schema.Create(context.Background()); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo.EntAllRepos(c), c.Close
|
|
||||||
}
|
|
|
@ -1,6 +1,9 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/hay-kot/homebox/backend/pkgs/set"
|
||||||
|
)
|
||||||
|
|
||||||
// HasID is an interface to entities that have an ID uuid.UUID field and a GetID() method.
|
// HasID is an interface to entities that have an ID uuid.UUID field and a GetID() method.
|
||||||
// This interface is fulfilled by all entities generated by entgo.io/ent via a custom template
|
// This interface is fulfilled by all entities generated by entgo.io/ent via a custom template
|
||||||
|
@ -8,55 +11,11 @@ type HasID interface {
|
||||||
GetID() uuid.UUID
|
GetID() uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDSet is a utility set-like type for working with sets of uuid.UUIDs within a repository
|
func newIDSet[T HasID](entities []T) set.Set[uuid.UUID] {
|
||||||
// instance. Most useful for comparing lists of UUIDs for processing relationship
|
uuids := make([]uuid.UUID, 0, len(entities))
|
||||||
// IDs and remove/adding relationships as required.
|
|
||||||
//
|
|
||||||
// # See how ItemRepo uses it to manage the Labels-To-Items relationship
|
|
||||||
//
|
|
||||||
// NOTE: may be worth moving this to a more generic package/set implementation
|
|
||||||
// or use a 3rd party set library, but this is good enough for now
|
|
||||||
type IDSet struct {
|
|
||||||
mp map[uuid.UUID]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIDSet(l int) *IDSet {
|
|
||||||
return &IDSet{
|
|
||||||
mp: make(map[uuid.UUID]struct{}, l),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func EntitiesToIDSet[T HasID](entities []T) *IDSet {
|
|
||||||
s := NewIDSet(len(entities))
|
|
||||||
for _, e := range entities {
|
for _, e := range entities {
|
||||||
s.Add(e.GetID())
|
uuids = append(uuids, e.GetID())
|
||||||
}
|
}
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *IDSet) Slice() []uuid.UUID {
|
return set.New(uuids...)
|
||||||
s := make([]uuid.UUID, 0, len(t.mp))
|
|
||||||
for k := range t.mp {
|
|
||||||
s = append(s, k)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *IDSet) Add(ids ...uuid.UUID) {
|
|
||||||
for _, id := range ids {
|
|
||||||
t.mp[id] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *IDSet) Has(id uuid.UUID) bool {
|
|
||||||
_, ok := t.mp[id]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *IDSet) Len() int {
|
|
||||||
return len(t.mp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *IDSet) Remove(id uuid.UUID) {
|
|
||||||
delete(t.mp, id)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ var (
|
||||||
|
|
||||||
tClient *ent.Client
|
tClient *ent.Client
|
||||||
tRepos *AllRepos
|
tRepos *AllRepos
|
||||||
tUser *ent.User
|
tUser UserOut
|
||||||
tGroup *ent.Group
|
tGroup *ent.Group
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tClient = client
|
tClient = client
|
||||||
tRepos = EntAllRepos(tClient)
|
tRepos = EntAllRepos(tClient, os.TempDir())
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
bootstrap()
|
bootstrap()
|
||||||
|
|
52
backend/internal/repo/map_helpers.go
Normal file
52
backend/internal/repo/map_helpers.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package repo
|
||||||
|
|
||||||
|
// mapTErrFunc is a factory function that returns a mapper function that
|
||||||
|
// wraps the given mapper function but first will check for an error and
|
||||||
|
// return the error if present.
|
||||||
|
//
|
||||||
|
// Helpful for wrapping database calls that return both a value and an error
|
||||||
|
func mapTErrFunc[T any, Y any](fn func(T) Y) func(T, error) (Y, error) {
|
||||||
|
return func(t T, err error) (Y, error) {
|
||||||
|
if err != nil {
|
||||||
|
var zero Y
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(t), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Future Usage
|
||||||
|
// func mapEachFunc[T any, Y any](fn func(T) Y) func([]T) []Y {
|
||||||
|
// return func(items []T) []Y {
|
||||||
|
// result := make([]Y, len(items))
|
||||||
|
// for i, item := range items {
|
||||||
|
// result[i] = fn(item)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return result
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func mapTEachErrFunc[T any, Y any](fn func(T) Y) func([]T, error) ([]Y, error) {
|
||||||
|
return func(items []T, err error) ([]Y, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]Y, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
result[i] = fn(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapEach[T any, U any](items []T, fn func(T) U) []U {
|
||||||
|
result := make([]U, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
result[i] = fn(item)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DocumentTokensRepository is a repository for Document entity
|
// DocumentTokensRepository is a repository for Document entity
|
||||||
|
@ -15,7 +14,35 @@ type DocumentTokensRepository struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DocumentTokensRepository) Create(ctx context.Context, data types.DocumentTokenCreate) (*ent.DocumentToken, error) {
|
type (
|
||||||
|
DocumentToken struct {
|
||||||
|
ID uuid.UUID `json:"-"`
|
||||||
|
TokenHash []byte `json:"tokenHash"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
DocumentID uuid.UUID `json:"documentId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentTokenCreate struct {
|
||||||
|
TokenHash []byte `json:"tokenHash"`
|
||||||
|
DocumentID uuid.UUID `json:"documentId"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapDocumentTokenErr = mapTErrFunc(mapDocumentToken)
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapDocumentToken(e *ent.DocumentToken) DocumentToken {
|
||||||
|
return DocumentToken{
|
||||||
|
ID: e.ID,
|
||||||
|
TokenHash: e.Token,
|
||||||
|
ExpiresAt: e.ExpiresAt,
|
||||||
|
DocumentID: e.Edges.Document.ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DocumentTokensRepository) Create(ctx context.Context, data DocumentTokenCreate) (DocumentToken, error) {
|
||||||
result, err := r.db.DocumentToken.Create().
|
result, err := r.db.DocumentToken.Create().
|
||||||
SetDocumentID(data.DocumentID).
|
SetDocumentID(data.DocumentID).
|
||||||
SetToken(data.TokenHash).
|
SetToken(data.TokenHash).
|
||||||
|
@ -23,13 +50,13 @@ func (r *DocumentTokensRepository) Create(ctx context.Context, data types.Docume
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return DocumentToken{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.db.DocumentToken.Query().
|
return mapDocumentTokenErr(r.db.DocumentToken.Query().
|
||||||
Where(documenttoken.ID(result.ID)).
|
Where(documenttoken.ID(result.ID)).
|
||||||
WithDocument().
|
WithDocument().
|
||||||
Only(ctx)
|
Only(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DocumentTokensRepository) PurgeExpiredTokens(ctx context.Context) (int, error) {
|
func (r *DocumentTokensRepository) PurgeExpiredTokens(ctx context.Context) (int, error) {
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
data types.DocumentTokenCreate
|
data DocumentTokenCreate
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -31,7 +30,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
||||||
name: "create document token",
|
name: "create document token",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
data: types.DocumentTokenCreate{
|
data: DocumentTokenCreate{
|
||||||
DocumentID: doc.ID,
|
DocumentID: doc.ID,
|
||||||
TokenHash: []byte("token"),
|
TokenHash: []byte("token"),
|
||||||
ExpiresAt: expires,
|
ExpiresAt: expires,
|
||||||
|
@ -39,7 +38,9 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: &ent.DocumentToken{
|
want: &ent.DocumentToken{
|
||||||
Edges: ent.DocumentTokenEdges{
|
Edges: ent.DocumentTokenEdges{
|
||||||
Document: doc,
|
Document: &ent.Document{
|
||||||
|
ID: doc.ID,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Token: []byte("token"),
|
Token: []byte("token"),
|
||||||
ExpiresAt: expires,
|
ExpiresAt: expires,
|
||||||
|
@ -50,7 +51,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
||||||
name: "create document token with empty token",
|
name: "create document token with empty token",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
data: types.DocumentTokenCreate{
|
data: DocumentTokenCreate{
|
||||||
DocumentID: doc.ID,
|
DocumentID: doc.ID,
|
||||||
TokenHash: []byte(""),
|
TokenHash: []byte(""),
|
||||||
ExpiresAt: expires,
|
ExpiresAt: expires,
|
||||||
|
@ -63,7 +64,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
||||||
name: "create document token with empty document id",
|
name: "create document token with empty document id",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
data: types.DocumentTokenCreate{
|
data: DocumentTokenCreate{
|
||||||
DocumentID: uuid.Nil,
|
DocumentID: uuid.Nil,
|
||||||
TokenHash: []byte("token"),
|
TokenHash: []byte("token"),
|
||||||
ExpiresAt: expires,
|
ExpiresAt: expires,
|
||||||
|
@ -94,18 +95,18 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, tt.want.Token, got.Token)
|
assert.Equal(t, tt.want.Token, got.TokenHash)
|
||||||
assert.WithinDuration(t, tt.want.ExpiresAt, got.ExpiresAt, time.Duration(1)*time.Second)
|
assert.WithinDuration(t, tt.want.ExpiresAt, got.ExpiresAt, time.Duration(1)*time.Second)
|
||||||
assert.Equal(t, tt.want.Edges.Document.ID, got.Edges.Document.ID)
|
assert.Equal(t, tt.want.Edges.Document.ID, got.DocumentID)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func useDocTokens(t *testing.T, num int) []*ent.DocumentToken {
|
func useDocTokens(t *testing.T, num int) []DocumentToken {
|
||||||
entity := useDocs(t, 1)[0]
|
entity := useDocs(t, 1)[0]
|
||||||
|
|
||||||
results := make([]*ent.DocumentToken, 0, num)
|
results := make([]DocumentToken, 0, num)
|
||||||
|
|
||||||
ids := make([]uuid.UUID, 0, num)
|
ids := make([]uuid.UUID, 0, num)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
@ -115,7 +116,7 @@ func useDocTokens(t *testing.T, num int) []*ent.DocumentToken {
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
e, err := tRepos.DocTokens.Create(context.Background(), types.DocumentTokenCreate{
|
e, err := tRepos.DocTokens.Create(context.Background(), DocumentTokenCreate{
|
||||||
DocumentID: entity.ID,
|
DocumentID: entity.ID,
|
||||||
TokenHash: []byte(fk.Str(10)),
|
TokenHash: []byte(fk.Str(10)),
|
||||||
ExpiresAt: fk.Time(),
|
ExpiresAt: fk.Time(),
|
|
@ -2,46 +2,117 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/document"
|
"github.com/hay-kot/homebox/backend/ent/document"
|
||||||
"github.com/hay-kot/homebox/backend/ent/group"
|
"github.com/hay-kot/homebox/backend/ent/group"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/pkgs/pathlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidDocExtension = errors.New("invalid document extension")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DocumentRepository is a repository for Document entity
|
|
||||||
type DocumentRepository struct {
|
type DocumentRepository struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
|
dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DocumentRepository) Create(ctx context.Context, gid uuid.UUID, doc types.DocumentCreate) (*ent.Document, error) {
|
type (
|
||||||
return r.db.Document.Create().
|
DocumentCreate struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content io.Reader `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentOut struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapDocumentOut(doc *ent.Document) DocumentOut {
|
||||||
|
return DocumentOut{
|
||||||
|
ID: doc.ID,
|
||||||
|
Title: doc.Title,
|
||||||
|
Path: doc.Path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapDocumentOutErr = mapTErrFunc(mapDocumentOut)
|
||||||
|
mapDocumentOutEachErr = mapTEachErrFunc(mapDocumentOut)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *DocumentRepository) path(gid uuid.UUID, ext string) string {
|
||||||
|
return pathlib.Safe(filepath.Join(r.dir, gid.String(), "documents", uuid.NewString()+ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DocumentRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]DocumentOut, error) {
|
||||||
|
return mapDocumentOutEachErr(r.db.Document.
|
||||||
|
Query().
|
||||||
|
Where(document.HasGroupWith(group.ID(gid))).
|
||||||
|
All(ctx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DocumentRepository) Get(ctx context.Context, id uuid.UUID) (DocumentOut, error) {
|
||||||
|
return mapDocumentOutErr(r.db.Document.Get(ctx, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DocumentRepository) Create(ctx context.Context, gid uuid.UUID, doc DocumentCreate) (DocumentOut, error) {
|
||||||
|
ext := filepath.Ext(doc.Title)
|
||||||
|
if ext == "" {
|
||||||
|
return DocumentOut{}, ErrInvalidDocExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
path := r.path(gid, ext)
|
||||||
|
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
err := os.MkdirAll(parent, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return DocumentOut{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return DocumentOut{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(f, doc.Content)
|
||||||
|
if err != nil {
|
||||||
|
return DocumentOut{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapDocumentOutErr(r.db.Document.Create().
|
||||||
SetGroupID(gid).
|
SetGroupID(gid).
|
||||||
SetTitle(doc.Title).
|
SetTitle(doc.Title).
|
||||||
SetPath(doc.Path).
|
SetPath(path).
|
||||||
Save(ctx)
|
Save(ctx),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DocumentRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]*ent.Document, error) {
|
func (r *DocumentRepository) Rename(ctx context.Context, id uuid.UUID, title string) (DocumentOut, error) {
|
||||||
return r.db.Document.Query().
|
return mapDocumentOutErr(r.db.Document.UpdateOneID(id).
|
||||||
Where(document.HasGroupWith(group.ID(gid))).
|
SetTitle(title).
|
||||||
All(ctx)
|
Save(ctx))
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DocumentRepository) Get(ctx context.Context, id uuid.UUID) (*ent.Document, error) {
|
|
||||||
return r.db.Document.Query().
|
|
||||||
Where(document.ID(id)).
|
|
||||||
Only(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DocumentRepository) Update(ctx context.Context, id uuid.UUID, doc types.DocumentUpdate) (*ent.Document, error) {
|
|
||||||
return r.db.Document.UpdateOneID(id).
|
|
||||||
SetTitle(doc.Title).
|
|
||||||
SetPath(doc.Path).
|
|
||||||
Save(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DocumentRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
func (r *DocumentRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
doc, err := r.db.Document.Get(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(doc.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return r.db.Document.DeleteOneID(id).Exec(ctx)
|
return r.db.Document.DeleteOneID(id).Exec(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,110 +1,28 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDocumentRepository_Create(t *testing.T) {
|
func useDocs(t *testing.T, num int) []DocumentOut {
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
gid uuid.UUID
|
|
||||||
doc types.DocumentCreate
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *ent.Document
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "create document",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
gid: tGroup.ID,
|
|
||||||
doc: types.DocumentCreate{
|
|
||||||
Title: "test document",
|
|
||||||
Path: "/test/document",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: &ent.Document{
|
|
||||||
Title: "test document",
|
|
||||||
Path: "/test/document",
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create document with empty title",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
gid: tGroup.ID,
|
|
||||||
doc: types.DocumentCreate{
|
|
||||||
Title: "",
|
|
||||||
Path: "/test/document",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create document with empty path",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
gid: tGroup.ID,
|
|
||||||
doc: types.DocumentCreate{
|
|
||||||
Title: "test document",
|
|
||||||
Path: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ids := make([]uuid.UUID, 0, len(tests))
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
for _, id := range ids {
|
|
||||||
err := tRepos.Docs.Delete(context.Background(), id)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := tRepos.Docs.Create(tt.args.ctx, tt.args.gid, tt.args.doc)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("DocumentRepository.Create() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, got)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, tt.want.Title, got.Title)
|
|
||||||
assert.Equal(t, tt.want.Path, got.Path)
|
|
||||||
ids = append(ids, got.ID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func useDocs(t *testing.T, num int) []*ent.Document {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
results := make([]*ent.Document, 0, num)
|
results := make([]DocumentOut, 0, num)
|
||||||
ids := make([]uuid.UUID, 0, num)
|
ids := make([]uuid.UUID, 0, num)
|
||||||
|
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
doc, err := tRepos.Docs.Create(context.Background(), tGroup.ID, types.DocumentCreate{
|
doc, err := tRepos.Docs.Create(context.Background(), tGroup.ID, DocumentCreate{
|
||||||
Title: fk.Str(10),
|
Title: fk.Str(10) + ".md",
|
||||||
Path: fk.Path(),
|
Content: bytes.NewReader([]byte(fk.Str(10))),
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -126,77 +44,68 @@ func useDocs(t *testing.T, num int) []*ent.Document {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDocumentRepository_GetAll(t *testing.T) {
|
func TestDocumentRepository_CreateUpdateDelete(t *testing.T) {
|
||||||
entities := useDocs(t, 10)
|
temp := t.TempDir()
|
||||||
|
r := DocumentRepository{
|
||||||
for _, entity := range entities {
|
db: tClient,
|
||||||
assert.NotNil(t, entity)
|
dir: temp,
|
||||||
}
|
}
|
||||||
|
|
||||||
all, err := tRepos.Docs.GetAll(context.Background(), tGroup.ID)
|
type args struct {
|
||||||
assert.NoError(t, err)
|
ctx context.Context
|
||||||
|
gid uuid.UUID
|
||||||
|
doc DocumentCreate
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
args args
|
||||||
|
title string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic create",
|
||||||
|
title: "test.md",
|
||||||
|
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||||
|
args: args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
gid: tGroup.ID,
|
||||||
|
doc: DocumentCreate{
|
||||||
|
Title: "test.md",
|
||||||
|
Content: bytes.NewReader([]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Create Document
|
||||||
|
got, err := r.Create(tt.args.ctx, tt.args.gid, tt.args.doc)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.title, got.Title)
|
||||||
|
assert.Equal(t, fmt.Sprintf("%s/%s/documents", temp, tt.args.gid), filepath.Dir(got.Path))
|
||||||
|
|
||||||
assert.Len(t, all, 10)
|
ensureRead := func() {
|
||||||
for _, entity := range all {
|
// Read Document
|
||||||
assert.NotNil(t, entity)
|
bts, err := os.ReadFile(got.Path)
|
||||||
|
assert.NoError(t, err)
|
||||||
for _, e := range entities {
|
assert.Equal(t, tt.content, string(bts))
|
||||||
if e.ID == entity.ID {
|
|
||||||
assert.Equal(t, e.Title, entity.Title)
|
|
||||||
assert.Equal(t, e.Path, entity.Path)
|
|
||||||
}
|
}
|
||||||
}
|
ensureRead()
|
||||||
}
|
|
||||||
}
|
// Update Document
|
||||||
|
got, err = r.Rename(tt.args.ctx, got.ID, "__"+tt.title+"__")
|
||||||
func TestDocumentRepository_Get(t *testing.T) {
|
assert.NoError(t, err)
|
||||||
entities := useDocs(t, 10)
|
assert.Equal(t, "__"+tt.title+"__", got.Title)
|
||||||
|
|
||||||
for _, entity := range entities {
|
ensureRead()
|
||||||
got, err := tRepos.Docs.Get(context.Background(), entity.ID)
|
|
||||||
|
// Delete Document
|
||||||
assert.NoError(t, err)
|
err = r.Delete(tt.args.ctx, got.ID)
|
||||||
assert.Equal(t, entity.ID, got.ID)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, entity.Title, got.Title)
|
|
||||||
assert.Equal(t, entity.Path, got.Path)
|
_, err = os.Stat(got.Path)
|
||||||
}
|
assert.Error(t, err)
|
||||||
}
|
})
|
||||||
|
|
||||||
func TestDocumentRepository_Update(t *testing.T) {
|
|
||||||
entities := useDocs(t, 10)
|
|
||||||
|
|
||||||
for _, entity := range entities {
|
|
||||||
got, err := tRepos.Docs.Get(context.Background(), entity.ID)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, entity.ID, got.ID)
|
|
||||||
assert.Equal(t, entity.Title, got.Title)
|
|
||||||
assert.Equal(t, entity.Path, got.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entity := range entities {
|
|
||||||
updateData := types.DocumentUpdate{
|
|
||||||
Title: fk.Str(10),
|
|
||||||
Path: fk.Path(),
|
|
||||||
}
|
|
||||||
|
|
||||||
updated, err := tRepos.Docs.Update(context.Background(), entity.ID, updateData)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, entity.ID, updated.ID)
|
|
||||||
assert.Equal(t, updateData.Title, updated.Title)
|
|
||||||
assert.Equal(t, updateData.Path, updated.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDocumentRepository_Delete(t *testing.T) {
|
|
||||||
entities := useDocs(t, 10)
|
|
||||||
|
|
||||||
for _, entity := range entities {
|
|
||||||
err := tRepos.Docs.Delete(context.Background(), entity.ID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = tRepos.Docs.Get(context.Background(), entity.ID)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
|
@ -16,6 +17,36 @@ type AttachmentRepo struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
ItemAttachment struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Document DocumentOut `json:"document"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemAttachmentUpdate struct {
|
||||||
|
ID uuid.UUID `json:"-"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToItemAttachment(attachment *ent.Attachment) ItemAttachment {
|
||||||
|
return ItemAttachment{
|
||||||
|
ID: attachment.ID,
|
||||||
|
CreatedAt: attachment.CreatedAt,
|
||||||
|
UpdatedAt: attachment.UpdatedAt,
|
||||||
|
Type: attachment.Type.String(),
|
||||||
|
Document: DocumentOut{
|
||||||
|
ID: attachment.Edges.Document.ID,
|
||||||
|
Title: attachment.Edges.Document.Title,
|
||||||
|
Path: attachment.Edges.Document.Path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *AttachmentRepo) Create(ctx context.Context, itemId, docId uuid.UUID, typ attachment.Type) (*ent.Attachment, error) {
|
func (r *AttachmentRepo) Create(ctx context.Context, itemId, docId uuid.UUID, typ attachment.Type) (*ent.Attachment, error) {
|
||||||
return r.db.Attachment.Create().
|
return r.db.Attachment.Create().
|
||||||
SetType(typ).
|
SetType(typ).
|
||||||
|
|
|
@ -2,21 +2,187 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/group"
|
"github.com/hay-kot/homebox/backend/ent/group"
|
||||||
"github.com/hay-kot/homebox/backend/ent/item"
|
"github.com/hay-kot/homebox/backend/ent/item"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ItemsRepository struct {
|
type ItemsRepository struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (*ent.Item, error) {
|
type (
|
||||||
return e.db.Item.Query().
|
ItemCreate struct {
|
||||||
Where(item.ID(id)).
|
ImportRef string `json:"-"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Edges
|
||||||
|
LocationID uuid.UUID `json:"locationId"`
|
||||||
|
LabelIDs []uuid.UUID `json:"labelIds"`
|
||||||
|
}
|
||||||
|
ItemUpdate struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
Insured bool `json:"insured"`
|
||||||
|
|
||||||
|
// Edges
|
||||||
|
LocationID uuid.UUID `json:"locationId"`
|
||||||
|
LabelIDs []uuid.UUID `json:"labelIds"`
|
||||||
|
|
||||||
|
// Identifications
|
||||||
|
SerialNumber string `json:"serialNumber"`
|
||||||
|
ModelNumber string `json:"modelNumber"`
|
||||||
|
Manufacturer string `json:"manufacturer"`
|
||||||
|
|
||||||
|
// Warranty
|
||||||
|
LifetimeWarranty bool `json:"lifetimeWarranty"`
|
||||||
|
WarrantyExpires time.Time `json:"warrantyExpires"`
|
||||||
|
WarrantyDetails string `json:"warrantyDetails"`
|
||||||
|
|
||||||
|
// Purchase
|
||||||
|
PurchaseTime time.Time `json:"purchaseTime"`
|
||||||
|
PurchaseFrom string `json:"purchaseFrom"`
|
||||||
|
PurchasePrice float64 `json:"purchasePrice,string"`
|
||||||
|
|
||||||
|
// Sold
|
||||||
|
SoldTime time.Time `json:"soldTime"`
|
||||||
|
SoldTo string `json:"soldTo"`
|
||||||
|
SoldPrice float64 `json:"soldPrice,string"`
|
||||||
|
SoldNotes string `json:"soldNotes"`
|
||||||
|
|
||||||
|
// Extras
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
// Fields []*FieldSummary `json:"fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemSummary struct {
|
||||||
|
ImportRef string `json:"-"`
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
Insured bool `json:"insured"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
|
||||||
|
// Edges
|
||||||
|
Location LocationSummary `json:"location"`
|
||||||
|
Labels []LabelSummary `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemOut struct {
|
||||||
|
ItemSummary
|
||||||
|
|
||||||
|
SerialNumber string `json:"serialNumber"`
|
||||||
|
ModelNumber string `json:"modelNumber"`
|
||||||
|
Manufacturer string `json:"manufacturer"`
|
||||||
|
|
||||||
|
// Warranty
|
||||||
|
LifetimeWarranty bool `json:"lifetimeWarranty"`
|
||||||
|
WarrantyExpires time.Time `json:"warrantyExpires"`
|
||||||
|
WarrantyDetails string `json:"warrantyDetails"`
|
||||||
|
|
||||||
|
// Purchase
|
||||||
|
PurchaseTime time.Time `json:"purchaseTime"`
|
||||||
|
PurchaseFrom string `json:"purchaseFrom"`
|
||||||
|
PurchasePrice float64 `json:"purchasePrice,string"`
|
||||||
|
|
||||||
|
// Sold
|
||||||
|
SoldTime time.Time `json:"soldTime"`
|
||||||
|
SoldTo string `json:"soldTo"`
|
||||||
|
SoldPrice float64 `json:"soldPrice,string"`
|
||||||
|
SoldNotes string `json:"soldNotes"`
|
||||||
|
|
||||||
|
// Extras
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
|
||||||
|
Attachments []ItemAttachment `json:"attachments"`
|
||||||
|
// Future
|
||||||
|
// Fields []*FieldSummary `json:"fields"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapItemsSummaryErr = mapTEachErrFunc(mapItemSummary)
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapItemSummary(item *ent.Item) ItemSummary {
|
||||||
|
var location LocationSummary
|
||||||
|
if item.Edges.Location != nil {
|
||||||
|
location = mapLocationSummary(item.Edges.Location)
|
||||||
|
}
|
||||||
|
|
||||||
|
var labels []LabelSummary
|
||||||
|
if item.Edges.Label != nil {
|
||||||
|
labels = mapEach(item.Edges.Label, mapLabelSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ItemSummary{
|
||||||
|
ID: item.ID,
|
||||||
|
Name: item.Name,
|
||||||
|
Description: item.Description,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
CreatedAt: item.CreatedAt,
|
||||||
|
UpdatedAt: item.UpdatedAt,
|
||||||
|
|
||||||
|
// Edges
|
||||||
|
Location: location,
|
||||||
|
Labels: labels,
|
||||||
|
|
||||||
|
// Warranty
|
||||||
|
Insured: item.Insured,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapItemOutErr = mapTErrFunc(mapItemOut)
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapItemOut(item *ent.Item) ItemOut {
|
||||||
|
var attachments []ItemAttachment
|
||||||
|
if item.Edges.Attachments != nil {
|
||||||
|
attachments = mapEach(item.Edges.Attachments, ToItemAttachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ItemOut{
|
||||||
|
ItemSummary: mapItemSummary(item),
|
||||||
|
LifetimeWarranty: item.LifetimeWarranty,
|
||||||
|
WarrantyExpires: item.WarrantyExpires,
|
||||||
|
WarrantyDetails: item.WarrantyDetails,
|
||||||
|
|
||||||
|
// Identification
|
||||||
|
SerialNumber: item.SerialNumber,
|
||||||
|
ModelNumber: item.ModelNumber,
|
||||||
|
Manufacturer: item.Manufacturer,
|
||||||
|
|
||||||
|
// Purchase
|
||||||
|
PurchaseTime: item.PurchaseTime,
|
||||||
|
PurchaseFrom: item.PurchaseFrom,
|
||||||
|
PurchasePrice: item.PurchasePrice,
|
||||||
|
|
||||||
|
// Sold
|
||||||
|
SoldTime: item.SoldTime,
|
||||||
|
SoldTo: item.SoldTo,
|
||||||
|
SoldPrice: item.SoldPrice,
|
||||||
|
SoldNotes: item.SoldNotes,
|
||||||
|
|
||||||
|
// Extras
|
||||||
|
Notes: item.Notes,
|
||||||
|
Attachments: attachments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ItemsRepository) getOne(ctx context.Context, where ...predicate.Item) (ItemOut, error) {
|
||||||
|
q := e.db.Item.Query().Where(where...)
|
||||||
|
|
||||||
|
return mapItemOutErr(q.
|
||||||
WithFields().
|
WithFields().
|
||||||
WithLabel().
|
WithLabel().
|
||||||
WithLocation().
|
WithLocation().
|
||||||
|
@ -24,19 +190,32 @@ func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (*ent.Item,
|
||||||
WithAttachments(func(aq *ent.AttachmentQuery) {
|
WithAttachments(func(aq *ent.AttachmentQuery) {
|
||||||
aq.WithDocument()
|
aq.WithDocument()
|
||||||
}).
|
}).
|
||||||
Only(ctx)
|
Only(ctx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOne returns a single item by ID. If the item does not exist, an error is returned.
|
||||||
|
// See also: GetOneByGroup to ensure that the item belongs to a specific group.
|
||||||
|
func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (ItemOut, error) {
|
||||||
|
return e.getOne(ctx, item.ID(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOneByGroup returns a single item by ID. If the item does not exist, an error is returned.
|
||||||
|
// GetOneByGroup ensures that the item belongs to a specific group.
|
||||||
|
func (e *ItemsRepository) GetOneByGroup(ctx context.Context, gid, id uuid.UUID) (ItemOut, error) {
|
||||||
|
return e.getOne(ctx, item.ID(id), item.HasGroupWith(group.ID(gid)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll returns all the items in the database with the Labels and Locations eager loaded.
|
// GetAll returns all the items in the database with the Labels and Locations eager loaded.
|
||||||
func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]*ent.Item, error) {
|
func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemSummary, error) {
|
||||||
return e.db.Item.Query().
|
return mapItemsSummaryErr(e.db.Item.Query().
|
||||||
Where(item.HasGroupWith(group.ID(gid))).
|
Where(item.HasGroupWith(group.ID(gid))).
|
||||||
WithLabel().
|
WithLabel().
|
||||||
WithLocation().
|
WithLocation().
|
||||||
All(ctx)
|
All(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data types.ItemCreate) (*ent.Item, error) {
|
func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCreate) (ItemOut, error) {
|
||||||
q := e.db.Item.Create().
|
q := e.db.Item.Create().
|
||||||
SetName(data.Name).
|
SetName(data.Name).
|
||||||
SetDescription(data.Description).
|
SetDescription(data.Description).
|
||||||
|
@ -49,7 +228,7 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data types.
|
||||||
|
|
||||||
result, err := q.Save(ctx)
|
result, err := q.Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ItemOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.GetOne(ctx, result.ID)
|
return e.GetOne(ctx, result.ID)
|
||||||
|
@ -59,8 +238,18 @@ func (e *ItemsRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
return e.db.Item.DeleteOneID(id).Exec(ctx)
|
return e.db.Item.DeleteOneID(id).Exec(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*ent.Item, error) {
|
func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error {
|
||||||
q := e.db.Item.UpdateOneID(data.ID).
|
_, err := e.db.Item.
|
||||||
|
Delete().
|
||||||
|
Where(
|
||||||
|
item.ID(id),
|
||||||
|
item.HasGroupWith(group.ID(gid)),
|
||||||
|
).Exec(ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data ItemUpdate) (ItemOut, error) {
|
||||||
|
q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(gid))).
|
||||||
SetName(data.Name).
|
SetName(data.Name).
|
||||||
SetDescription(data.Description).
|
SetDescription(data.Description).
|
||||||
SetLocationID(data.LocationID).
|
SetLocationID(data.LocationID).
|
||||||
|
@ -83,13 +272,13 @@ func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*e
|
||||||
|
|
||||||
currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx)
|
currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ItemOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
set := EntitiesToIDSet(currentLabels)
|
set := newIDSet(currentLabels)
|
||||||
|
|
||||||
for _, l := range data.LabelIDs {
|
for _, l := range data.LabelIDs {
|
||||||
if set.Has(l) {
|
if set.Contains(l) {
|
||||||
set.Remove(l)
|
set.Remove(l)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -102,7 +291,7 @@ func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*e
|
||||||
|
|
||||||
err = q.Exec(ctx)
|
err = q.Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ItemOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.GetOne(ctx, data.ID)
|
return e.GetOne(ctx, data.ID)
|
||||||
|
|
|
@ -6,25 +6,23 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func itemFactory() types.ItemCreate {
|
func itemFactory() ItemCreate {
|
||||||
return types.ItemCreate{
|
return ItemCreate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(100),
|
Description: fk.Str(100),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func useItems(t *testing.T, len int) []*ent.Item {
|
func useItems(t *testing.T, len int) []ItemOut {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
location, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory())
|
location, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
items := make([]*ent.Item, len)
|
items := make([]ItemOut, len)
|
||||||
for i := 0; i < len; i++ {
|
for i := 0; i < len; i++ {
|
||||||
itm := itemFactory()
|
itm := itemFactory()
|
||||||
itm.LocationID = location.ID
|
itm.LocationID = location.ID
|
||||||
|
@ -107,7 +105,7 @@ func TestItemsRepository_Create_Location(t *testing.T) {
|
||||||
foundItem, err := tRepos.Items.GetOne(context.Background(), result.ID)
|
foundItem, err := tRepos.Items.GetOne(context.Background(), result.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, result.ID, foundItem.ID)
|
assert.Equal(t, result.ID, foundItem.ID)
|
||||||
assert.Equal(t, location.ID, foundItem.Edges.Location.ID)
|
assert.Equal(t, location.ID, foundItem.Location.ID)
|
||||||
|
|
||||||
// Cleanup - Also deletes item
|
// Cleanup - Also deletes item
|
||||||
err = tRepos.Locations.Delete(context.Background(), location.ID)
|
err = tRepos.Locations.Delete(context.Background(), location.ID)
|
||||||
|
@ -168,18 +166,18 @@ func TestItemsRepository_Update_Labels(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Apply all labels to entity
|
// Apply all labels to entity
|
||||||
updateData := types.ItemUpdate{
|
updateData := ItemUpdate{
|
||||||
ID: entity.ID,
|
ID: entity.ID,
|
||||||
Name: entity.Name,
|
Name: entity.Name,
|
||||||
LocationID: entity.Edges.Location.ID,
|
LocationID: entity.Location.ID,
|
||||||
LabelIDs: tt.args.labelIds,
|
LabelIDs: tt.args.labelIds,
|
||||||
}
|
}
|
||||||
|
|
||||||
updated, err := tRepos.Items.Update(context.Background(), updateData)
|
updated, err := tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, updateData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, tt.want, len(updated.Edges.Label))
|
assert.Len(t, tt.want, len(updated.Labels))
|
||||||
|
|
||||||
for _, label := range updated.Edges.Label {
|
for _, label := range updated.Labels {
|
||||||
assert.Contains(t, tt.want, label.ID)
|
assert.Contains(t, tt.want, label.ID)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -192,10 +190,10 @@ func TestItemsRepository_Update(t *testing.T) {
|
||||||
|
|
||||||
entity := entities[0]
|
entity := entities[0]
|
||||||
|
|
||||||
updateData := types.ItemUpdate{
|
updateData := ItemUpdate{
|
||||||
ID: entity.ID,
|
ID: entity.ID,
|
||||||
Name: entity.Name,
|
Name: entity.Name,
|
||||||
LocationID: entity.Edges.Location.ID,
|
LocationID: entity.Location.ID,
|
||||||
SerialNumber: fk.Str(10),
|
SerialNumber: fk.Str(10),
|
||||||
LabelIDs: nil,
|
LabelIDs: nil,
|
||||||
ModelNumber: fk.Str(10),
|
ModelNumber: fk.Str(10),
|
||||||
|
@ -213,7 +211,7 @@ func TestItemsRepository_Update(t *testing.T) {
|
||||||
LifetimeWarranty: true,
|
LifetimeWarranty: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedEntity, err := tRepos.Items.Update(context.Background(), updateData)
|
updatedEntity, err := tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, updateData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
got, err := tRepos.Items.GetOne(context.Background(), updatedEntity.ID)
|
got, err := tRepos.Items.GetOne(context.Background(), updatedEntity.ID)
|
||||||
|
@ -221,7 +219,7 @@ func TestItemsRepository_Update(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, updateData.ID, got.ID)
|
assert.Equal(t, updateData.ID, got.ID)
|
||||||
assert.Equal(t, updateData.Name, got.Name)
|
assert.Equal(t, updateData.Name, got.Name)
|
||||||
assert.Equal(t, updateData.LocationID, got.Edges.Location.ID)
|
assert.Equal(t, updateData.LocationID, got.Location.ID)
|
||||||
assert.Equal(t, updateData.SerialNumber, got.SerialNumber)
|
assert.Equal(t, updateData.SerialNumber, got.SerialNumber)
|
||||||
assert.Equal(t, updateData.ModelNumber, got.ModelNumber)
|
assert.Equal(t, updateData.ModelNumber, got.ModelNumber)
|
||||||
assert.Equal(t, updateData.Manufacturer, got.Manufacturer)
|
assert.Equal(t, updateData.Manufacturer, got.Manufacturer)
|
||||||
|
|
|
@ -2,34 +2,94 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/group"
|
"github.com/hay-kot/homebox/backend/ent/group"
|
||||||
"github.com/hay-kot/homebox/backend/ent/label"
|
"github.com/hay-kot/homebox/backend/ent/label"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LabelRepository struct {
|
type LabelRepository struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
}
|
}
|
||||||
|
type (
|
||||||
|
LabelCreate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LabelRepository) Get(ctx context.Context, ID uuid.UUID) (*ent.Label, error) {
|
LabelUpdate struct {
|
||||||
return r.db.Label.Query().
|
ID uuid.UUID `json:"id"`
|
||||||
Where(label.ID(ID)).
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelSummary struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelOut struct {
|
||||||
|
LabelSummary
|
||||||
|
Items []ItemSummary `json:"items"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapLabelSummary(label *ent.Label) LabelSummary {
|
||||||
|
return LabelSummary{
|
||||||
|
ID: label.ID,
|
||||||
|
Name: label.Name,
|
||||||
|
Description: label.Description,
|
||||||
|
CreatedAt: label.CreatedAt,
|
||||||
|
UpdatedAt: label.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapLabelOutErr = mapTErrFunc(mapLabelOut)
|
||||||
|
mapLabelsOut = mapTEachErrFunc(mapLabelSummary)
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapLabelOut(label *ent.Label) LabelOut {
|
||||||
|
return LabelOut{
|
||||||
|
LabelSummary: mapLabelSummary(label),
|
||||||
|
Items: mapEach(label.Edges.Items, mapItemSummary),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LabelRepository) getOne(ctx context.Context, where ...predicate.Label) (LabelOut, error) {
|
||||||
|
return mapLabelOutErr(r.db.Label.Query().
|
||||||
|
Where(where...).
|
||||||
WithGroup().
|
WithGroup().
|
||||||
WithItems().
|
WithItems().
|
||||||
Only(ctx)
|
Only(ctx),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]*ent.Label, error) {
|
func (r *LabelRepository) GetOne(ctx context.Context, ID uuid.UUID) (LabelOut, error) {
|
||||||
return r.db.Label.Query().
|
return r.getOne(ctx, label.ID(ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LabelRepository) GetOneByGroup(ctx context.Context, gid, ld uuid.UUID) (LabelOut, error) {
|
||||||
|
return r.getOne(ctx, label.ID(ld), label.HasGroupWith(group.ID(gid)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LabelSummary, error) {
|
||||||
|
return mapLabelsOut(r.db.Label.Query().
|
||||||
Where(label.HasGroupWith(group.ID(groupId))).
|
Where(label.HasGroupWith(group.ID(groupId))).
|
||||||
WithGroup().
|
WithGroup().
|
||||||
All(ctx)
|
All(ctx),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data types.LabelCreate) (*ent.Label, error) {
|
func (r *LabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data LabelCreate) (LabelOut, error) {
|
||||||
label, err := r.db.Label.Create().
|
label, err := r.db.Label.Create().
|
||||||
SetName(data.Name).
|
SetName(data.Name).
|
||||||
SetDescription(data.Description).
|
SetDescription(data.Description).
|
||||||
|
@ -37,11 +97,15 @@ func (r *LabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data t
|
||||||
SetGroupID(groupdId).
|
SetGroupID(groupdId).
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return LabelOut{}, err
|
||||||
|
}
|
||||||
|
|
||||||
label.Edges.Group = &ent.Group{ID: groupdId} // bootstrap group ID
|
label.Edges.Group = &ent.Group{ID: groupdId} // bootstrap group ID
|
||||||
return label, err
|
return mapLabelOut(label), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LabelRepository) Update(ctx context.Context, data types.LabelUpdate) (*ent.Label, error) {
|
func (r *LabelRepository) Update(ctx context.Context, data LabelUpdate) (LabelOut, error) {
|
||||||
_, err := r.db.Label.UpdateOneID(data.ID).
|
_, err := r.db.Label.UpdateOneID(data.ID).
|
||||||
SetName(data.Name).
|
SetName(data.Name).
|
||||||
SetDescription(data.Description).
|
SetDescription(data.Description).
|
||||||
|
@ -49,10 +113,10 @@ func (r *LabelRepository) Update(ctx context.Context, data types.LabelUpdate) (*
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return LabelOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Get(ctx, data.ID)
|
return r.GetOne(ctx, data.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LabelRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
func (r *LabelRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
|
|
@ -4,22 +4,20 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func labelFactory() types.LabelCreate {
|
func labelFactory() LabelCreate {
|
||||||
return types.LabelCreate{
|
return LabelCreate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(100),
|
Description: fk.Str(100),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func useLabels(t *testing.T, len int) []*ent.Label {
|
func useLabels(t *testing.T, len int) []LabelOut {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
labels := make([]*ent.Label, len)
|
labels := make([]LabelOut, len)
|
||||||
for i := 0; i < len; i++ {
|
for i := 0; i < len; i++ {
|
||||||
itm := labelFactory()
|
itm := labelFactory()
|
||||||
|
|
||||||
|
@ -42,7 +40,7 @@ func TestLabelRepository_Get(t *testing.T) {
|
||||||
label := labels[0]
|
label := labels[0]
|
||||||
|
|
||||||
// Get by ID
|
// Get by ID
|
||||||
foundLoc, err := tRepos.Labels.Get(context.Background(), label.ID)
|
foundLoc, err := tRepos.Labels.GetOne(context.Background(), label.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, label.ID, foundLoc.ID)
|
assert.Equal(t, label.ID, foundLoc.ID)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +58,7 @@ func TestLabelRepository_Create(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Get by ID
|
// Get by ID
|
||||||
foundLoc, err := tRepos.Labels.Get(context.Background(), loc.ID)
|
foundLoc, err := tRepos.Labels.GetOne(context.Background(), loc.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, loc.ID, foundLoc.ID)
|
assert.Equal(t, loc.ID, foundLoc.ID)
|
||||||
|
|
||||||
|
@ -72,7 +70,7 @@ func TestLabelRepository_Update(t *testing.T) {
|
||||||
loc, err := tRepos.Labels.Create(context.Background(), tGroup.ID, labelFactory())
|
loc, err := tRepos.Labels.Create(context.Background(), tGroup.ID, labelFactory())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updateData := types.LabelUpdate{
|
updateData := LabelUpdate{
|
||||||
ID: loc.ID,
|
ID: loc.ID,
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(100),
|
Description: fk.Str(100),
|
||||||
|
@ -81,7 +79,7 @@ func TestLabelRepository_Update(t *testing.T) {
|
||||||
update, err := tRepos.Labels.Update(context.Background(), updateData)
|
update, err := tRepos.Labels.Update(context.Background(), updateData)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
foundLoc, err := tRepos.Labels.Get(context.Background(), loc.ID)
|
foundLoc, err := tRepos.Labels.GetOne(context.Background(), loc.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, update.ID, foundLoc.ID)
|
assert.Equal(t, update.ID, foundLoc.ID)
|
||||||
|
@ -99,6 +97,6 @@ func TestLabelRepository_Delete(t *testing.T) {
|
||||||
err = tRepos.Labels.Delete(context.Background(), loc.ID)
|
err = tRepos.Labels.Delete(context.Background(), loc.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = tRepos.Labels.Get(context.Background(), loc.ID)
|
_, err = tRepos.Labels.GetOne(context.Background(), loc.ID)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,79 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
|
"github.com/hay-kot/homebox/backend/ent/group"
|
||||||
"github.com/hay-kot/homebox/backend/ent/location"
|
"github.com/hay-kot/homebox/backend/ent/location"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocationRepository struct {
|
type LocationRepository struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocationWithCount struct {
|
type (
|
||||||
*ent.Location
|
LocationCreate struct {
|
||||||
ItemCount int `json:"itemCount"`
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationUpdate struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationSummary struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationOutCount struct {
|
||||||
|
LocationSummary
|
||||||
|
ItemCount int `json:"itemCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationOut struct {
|
||||||
|
LocationSummary
|
||||||
|
Items []ItemSummary `json:"items"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapLocationSummary(location *ent.Location) LocationSummary {
|
||||||
|
return LocationSummary{
|
||||||
|
ID: location.ID,
|
||||||
|
Name: location.Name,
|
||||||
|
Description: location.Description,
|
||||||
|
CreatedAt: location.CreatedAt,
|
||||||
|
UpdatedAt: location.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapLocationOutErr = mapTErrFunc(mapLocationOut)
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapLocationOut(location *ent.Location) LocationOut {
|
||||||
|
return LocationOut{
|
||||||
|
LocationSummary: LocationSummary{
|
||||||
|
ID: location.ID,
|
||||||
|
Name: location.Name,
|
||||||
|
Description: location.Description,
|
||||||
|
CreatedAt: location.CreatedAt,
|
||||||
|
UpdatedAt: location.UpdatedAt,
|
||||||
|
},
|
||||||
|
Items: mapEach(location.Edges.Items, mapItemSummary),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetALlWithCount returns all locations with item count field populated
|
// GetALlWithCount returns all locations with item count field populated
|
||||||
func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LocationWithCount, error) {
|
func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LocationOutCount, error) {
|
||||||
query := `--sql
|
query := `--sql
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
|
@ -46,54 +101,62 @@ func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]L
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
list := []LocationWithCount{}
|
list := []LocationOutCount{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var loc ent.Location
|
var ct LocationOutCount
|
||||||
var ct LocationWithCount
|
|
||||||
err := rows.Scan(&loc.ID, &loc.Name, &loc.Description, &loc.CreatedAt, &loc.UpdatedAt, &ct.ItemCount)
|
err := rows.Scan(&ct.ID, &ct.Name, &ct.Description, &ct.CreatedAt, &ct.UpdatedAt, &ct.ItemCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ct.Location = &loc
|
|
||||||
list = append(list, ct)
|
list = append(list, ct)
|
||||||
}
|
}
|
||||||
|
|
||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LocationRepository) Get(ctx context.Context, ID uuid.UUID) (*ent.Location, error) {
|
func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Location) (LocationOut, error) {
|
||||||
return r.db.Location.Query().
|
return mapLocationOutErr(r.db.Location.Query().
|
||||||
Where(location.ID(ID)).
|
Where(where...).
|
||||||
WithGroup().
|
WithGroup().
|
||||||
WithItems(func(iq *ent.ItemQuery) {
|
WithItems(func(iq *ent.ItemQuery) {
|
||||||
iq.WithLabel()
|
iq.WithLabel()
|
||||||
}).
|
}).
|
||||||
Only(ctx)
|
Only(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LocationRepository) Create(ctx context.Context, groupdId uuid.UUID, data types.LocationCreate) (*ent.Location, error) {
|
func (r *LocationRepository) Get(ctx context.Context, ID uuid.UUID) (LocationOut, error) {
|
||||||
|
return r.getOne(ctx, location.ID(ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LocationRepository) GetOneByGroup(ctx context.Context, GID, ID uuid.UUID) (LocationOut, error) {
|
||||||
|
return r.getOne(ctx, location.ID(ID), location.HasGroupWith(group.ID(GID)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LocationRepository) Create(ctx context.Context, gid uuid.UUID, data LocationCreate) (LocationOut, error) {
|
||||||
location, err := r.db.Location.Create().
|
location, err := r.db.Location.Create().
|
||||||
SetName(data.Name).
|
SetName(data.Name).
|
||||||
SetDescription(data.Description).
|
SetDescription(data.Description).
|
||||||
SetGroupID(groupdId).
|
SetGroupID(gid).
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return LocationOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
location.Edges.Group = &ent.Group{ID: groupdId} // bootstrap group ID
|
location.Edges.Group = &ent.Group{ID: gid} // bootstrap group ID
|
||||||
return location, err
|
return mapLocationOut(location), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LocationRepository) Update(ctx context.Context, data types.LocationUpdate) (*ent.Location, error) {
|
func (r *LocationRepository) Update(ctx context.Context, data LocationUpdate) (LocationOut, error) {
|
||||||
_, err := r.db.Location.UpdateOneID(data.ID).
|
_, err := r.db.Location.UpdateOneID(data.ID).
|
||||||
SetName(data.Name).
|
SetName(data.Name).
|
||||||
SetDescription(data.Description).
|
SetDescription(data.Description).
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return LocationOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Get(ctx, data.ID)
|
return r.Get(ctx, data.ID)
|
||||||
|
|
|
@ -4,12 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func locationFactory() types.LocationCreate {
|
func locationFactory() LocationCreate {
|
||||||
return types.LocationCreate{
|
return LocationCreate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(100),
|
Description: fk.Str(100),
|
||||||
}
|
}
|
||||||
|
@ -30,13 +29,13 @@ func TestLocationRepository_Get(t *testing.T) {
|
||||||
|
|
||||||
func TestLocationRepositoryGetAllWithCount(t *testing.T) {
|
func TestLocationRepositoryGetAllWithCount(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
result, err := tRepos.Locations.Create(ctx, tGroup.ID, types.LocationCreate{
|
result, err := tRepos.Locations.Create(ctx, tGroup.ID, LocationCreate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(100),
|
Description: fk.Str(100),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = tRepos.Items.Create(ctx, tGroup.ID, types.ItemCreate{
|
_, err = tRepos.Items.Create(ctx, tGroup.ID, ItemCreate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(100),
|
Description: fk.Str(100),
|
||||||
LocationID: result.ID,
|
LocationID: result.ID,
|
||||||
|
@ -72,7 +71,7 @@ func TestLocationRepository_Update(t *testing.T) {
|
||||||
loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory())
|
loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updateData := types.LocationUpdate{
|
updateData := LocationUpdate{
|
||||||
ID: loc.ID,
|
ID: loc.ID,
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(100),
|
Description: fk.Str(100),
|
||||||
|
|
|
@ -4,17 +4,34 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/authtokens"
|
"github.com/hay-kot/homebox/backend/ent/authtokens"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenRepository struct {
|
type TokenRepository struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
UserAuthTokenCreate struct {
|
||||||
|
TokenHash []byte `json:"token"`
|
||||||
|
UserID uuid.UUID `json:"userId"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAuthToken struct {
|
||||||
|
UserAuthTokenCreate
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u UserAuthToken) IsExpired() bool {
|
||||||
|
return u.ExpiresAt.Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
// GetUserFromToken get's a user from a token
|
// GetUserFromToken get's a user from a token
|
||||||
func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (*ent.User, error) {
|
func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (UserOut, error) {
|
||||||
user, err := r.db.AuthTokens.Query().
|
user, err := r.db.AuthTokens.Query().
|
||||||
Where(authtokens.Token(token)).
|
Where(authtokens.Token(token)).
|
||||||
Where(authtokens.ExpiresAtGTE(time.Now())).
|
Where(authtokens.ExpiresAtGTE(time.Now())).
|
||||||
|
@ -24,15 +41,14 @@ func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (*
|
||||||
Only(ctx)
|
Only(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return UserOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return mapUserOut(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a token for a user
|
// Creates a token for a user
|
||||||
func (r *TokenRepository) CreateToken(ctx context.Context, createToken types.UserAuthTokenCreate) (types.UserAuthToken, error) {
|
func (r *TokenRepository) CreateToken(ctx context.Context, createToken UserAuthTokenCreate) (UserAuthToken, error) {
|
||||||
tokenOut := types.UserAuthToken{}
|
|
||||||
|
|
||||||
dbToken, err := r.db.AuthTokens.Create().
|
dbToken, err := r.db.AuthTokens.Create().
|
||||||
SetToken(createToken.TokenHash).
|
SetToken(createToken.TokenHash).
|
||||||
|
@ -41,15 +57,17 @@ func (r *TokenRepository) CreateToken(ctx context.Context, createToken types.Use
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tokenOut, err
|
return UserAuthToken{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenOut.TokenHash = dbToken.Token
|
return UserAuthToken{
|
||||||
tokenOut.UserID = createToken.UserID
|
UserAuthTokenCreate: UserAuthTokenCreate{
|
||||||
tokenOut.CreatedAt = dbToken.CreatedAt
|
TokenHash: dbToken.Token,
|
||||||
tokenOut.ExpiresAt = dbToken.ExpiresAt
|
UserID: createToken.UserID,
|
||||||
|
ExpiresAt: dbToken.ExpiresAt,
|
||||||
return tokenOut, nil
|
},
|
||||||
|
CreatedAt: dbToken.CreatedAt,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteToken remove a single token from the database - equivalent to revoke or logout
|
// DeleteToken remove a single token from the database - equivalent to revoke or logout
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -22,7 +21,7 @@ func TestAuthTokenRepo_CreateToken(t *testing.T) {
|
||||||
|
|
||||||
generatedToken := hasher.GenerateToken()
|
generatedToken := hasher.GenerateToken()
|
||||||
|
|
||||||
token, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
token, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||||
TokenHash: generatedToken.Hash,
|
TokenHash: generatedToken.Hash,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
UserID: userOut.ID,
|
UserID: userOut.ID,
|
||||||
|
@ -50,7 +49,7 @@ func TestAuthTokenRepo_DeleteToken(t *testing.T) {
|
||||||
|
|
||||||
generatedToken := hasher.GenerateToken()
|
generatedToken := hasher.GenerateToken()
|
||||||
|
|
||||||
_, err = tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
_, err = tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||||
TokenHash: generatedToken.Hash,
|
TokenHash: generatedToken.Hash,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
UserID: userOut.ID,
|
UserID: userOut.ID,
|
||||||
|
@ -72,7 +71,7 @@ func TestAuthTokenRepo_GetUserByToken(t *testing.T) {
|
||||||
expiresAt := time.Now().Add(time.Hour)
|
expiresAt := time.Now().Add(time.Hour)
|
||||||
generatedToken := hasher.GenerateToken()
|
generatedToken := hasher.GenerateToken()
|
||||||
|
|
||||||
token, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
token, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||||
TokenHash: generatedToken.Hash,
|
TokenHash: generatedToken.Hash,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
UserID: userOut.ID,
|
UserID: userOut.ID,
|
||||||
|
@ -101,13 +100,13 @@ func TestAuthTokenRepo_PurgeExpiredTokens(t *testing.T) {
|
||||||
user := userFactory()
|
user := userFactory()
|
||||||
userOut, _ := tRepos.Users.Create(ctx, user)
|
userOut, _ := tRepos.Users.Create(ctx, user)
|
||||||
|
|
||||||
createdTokens := []types.UserAuthToken{}
|
createdTokens := []UserAuthToken{}
|
||||||
|
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
expiresAt := time.Now()
|
expiresAt := time.Now()
|
||||||
generatedToken := hasher.GenerateToken()
|
generatedToken := hasher.GenerateToken()
|
||||||
|
|
||||||
createdToken, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
createdToken, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||||
TokenHash: generatedToken.Hash,
|
TokenHash: generatedToken.Hash,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
UserID: userOut.ID,
|
UserID: userOut.ID,
|
||||||
|
|
|
@ -6,37 +6,77 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/user"
|
"github.com/hay-kot/homebox/backend/ent/user"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRepository struct {
|
type UserRepository struct {
|
||||||
db *ent.Client
|
db *ent.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UserRepository) GetOneId(ctx context.Context, id uuid.UUID) (*ent.User, error) {
|
type (
|
||||||
return e.db.User.Query().
|
// UserCreate is the Data object contain the requirements of creating a user
|
||||||
Where(user.ID(id)).
|
// in the database. It should to create users from an API unless the user has
|
||||||
WithGroup().
|
// rights to create SuperUsers. For regular user in data use the UserIn struct.
|
||||||
Only(ctx)
|
UserCreate struct {
|
||||||
}
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
func (e *UserRepository) GetOneEmail(ctx context.Context, email string) (*ent.User, error) {
|
Password string `json:"password"`
|
||||||
return e.db.User.Query().
|
IsSuperuser bool `json:"isSuperuser"`
|
||||||
Where(user.Email(email)).
|
GroupID uuid.UUID `json:"groupID"`
|
||||||
WithGroup().
|
|
||||||
Only(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UserRepository) GetAll(ctx context.Context) ([]*ent.User, error) {
|
|
||||||
return e.db.User.Query().WithGroup().All(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UserRepository) Create(ctx context.Context, usr types.UserCreate) (*ent.User, error) {
|
|
||||||
err := usr.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return &ent.User{}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserUpdate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
UserOut struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
IsSuperuser bool `json:"isSuperuser"`
|
||||||
|
GroupID uuid.UUID `json:"groupId"`
|
||||||
|
GroupName string `json:"groupName"`
|
||||||
|
PasswordHash string `json:"-"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mapUserOutErr = mapTErrFunc(mapUserOut)
|
||||||
|
mapUsersOutErr = mapTEachErrFunc(mapUserOut)
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapUserOut(user *ent.User) UserOut {
|
||||||
|
return UserOut{
|
||||||
|
ID: user.ID,
|
||||||
|
Name: user.Name,
|
||||||
|
Email: user.Email,
|
||||||
|
IsSuperuser: user.IsSuperuser,
|
||||||
|
GroupID: user.Edges.Group.ID,
|
||||||
|
GroupName: user.Edges.Group.Name,
|
||||||
|
PasswordHash: user.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UserRepository) GetOneId(ctx context.Context, id uuid.UUID) (UserOut, error) {
|
||||||
|
return mapUserOutErr(e.db.User.Query().
|
||||||
|
Where(user.ID(id)).
|
||||||
|
WithGroup().
|
||||||
|
Only(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UserRepository) GetOneEmail(ctx context.Context, email string) (UserOut, error) {
|
||||||
|
return mapUserOutErr(e.db.User.Query().
|
||||||
|
Where(user.Email(email)).
|
||||||
|
WithGroup().
|
||||||
|
Only(ctx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UserRepository) GetAll(ctx context.Context) ([]UserOut, error) {
|
||||||
|
return mapUsersOutErr(e.db.User.Query().WithGroup().All(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UserRepository) Create(ctx context.Context, usr UserCreate) (UserOut, error) {
|
||||||
entUser, err := e.db.User.
|
entUser, err := e.db.User.
|
||||||
Create().
|
Create().
|
||||||
SetName(usr.Name).
|
SetName(usr.Name).
|
||||||
|
@ -46,13 +86,13 @@ func (e *UserRepository) Create(ctx context.Context, usr types.UserCreate) (*ent
|
||||||
SetGroupID(usr.GroupID).
|
SetGroupID(usr.GroupID).
|
||||||
Save(ctx)
|
Save(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entUser, err
|
return UserOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.GetOneId(ctx, entUser.ID)
|
return e.GetOneId(ctx, entUser.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UserRepository) Update(ctx context.Context, ID uuid.UUID, data types.UserUpdate) error {
|
func (e *UserRepository) Update(ctx context.Context, ID uuid.UUID, data UserUpdate) error {
|
||||||
q := e.db.User.Update().
|
q := e.db.User.Update().
|
||||||
Where(user.ID(ID)).
|
Where(user.ID(ID)).
|
||||||
SetName(data.Name).
|
SetName(data.Name).
|
||||||
|
|
|
@ -5,14 +5,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func userFactory() types.UserCreate {
|
func userFactory() UserCreate {
|
||||||
|
return UserCreate{
|
||||||
return types.UserCreate{
|
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Email: fk.Email(),
|
Email: fk.Email(),
|
||||||
Password: fk.Str(10),
|
Password: fk.Str(10),
|
||||||
|
@ -61,7 +58,7 @@ func TestUserRepo_GetOneId(t *testing.T) {
|
||||||
|
|
||||||
func TestUserRepo_GetAll(t *testing.T) {
|
func TestUserRepo_GetAll(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
toCreate := []types.UserCreate{
|
toCreate := []UserCreate{
|
||||||
userFactory(),
|
userFactory(),
|
||||||
userFactory(),
|
userFactory(),
|
||||||
userFactory(),
|
userFactory(),
|
||||||
|
@ -70,7 +67,7 @@ func TestUserRepo_GetAll(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
created := []*ent.User{}
|
created := []UserOut{}
|
||||||
|
|
||||||
for _, usr := range toCreate {
|
for _, usr := range toCreate {
|
||||||
usrOut, _ := tRepos.Users.Create(ctx, usr)
|
usrOut, _ := tRepos.Users.Create(ctx, usr)
|
||||||
|
@ -90,7 +87,7 @@ func TestUserRepo_GetAll(t *testing.T) {
|
||||||
assert.Equal(t, usr.Email, usr2.Email)
|
assert.Equal(t, usr.Email, usr2.Email)
|
||||||
|
|
||||||
// Check groups are loaded
|
// Check groups are loaded
|
||||||
assert.NotNil(t, usr2.Edges.Group)
|
assert.NotNil(t, usr2.GroupID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +105,7 @@ func TestUserRepo_Update(t *testing.T) {
|
||||||
user, err := tRepos.Users.Create(context.Background(), userFactory())
|
user, err := tRepos.Users.Create(context.Background(), userFactory())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updateData := types.UserUpdate{
|
updateData := UserUpdate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Email: fk.Email(),
|
Email: fk.Email(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ type AllRepos struct {
|
||||||
Attachments *AttachmentRepo
|
Attachments *AttachmentRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func EntAllRepos(db *ent.Client) *AllRepos {
|
func EntAllRepos(db *ent.Client, root string) *AllRepos {
|
||||||
return &AllRepos{
|
return &AllRepos{
|
||||||
Users: &UserRepository{db},
|
Users: &UserRepository{db},
|
||||||
AuthTokens: &TokenRepository{db},
|
AuthTokens: &TokenRepository{db},
|
||||||
|
@ -23,7 +23,7 @@ func EntAllRepos(db *ent.Client) *AllRepos {
|
||||||
Locations: &LocationRepository{db},
|
Locations: &LocationRepository{db},
|
||||||
Labels: &LabelRepository{db},
|
Labels: &LabelRepository{db},
|
||||||
Items: &ItemsRepository{db},
|
Items: &ItemsRepository{db},
|
||||||
Docs: &DocumentRepository{db},
|
Docs: &DocumentRepository{db, root},
|
||||||
DocTokens: &DocumentTokensRepository{db},
|
DocTokens: &DocumentTokensRepository{db},
|
||||||
Attachments: &AttachmentRepo{db},
|
Attachments: &AttachmentRepo{db},
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,23 @@ import "github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
|
|
||||||
type AllServices struct {
|
type AllServices struct {
|
||||||
User *UserService
|
User *UserService
|
||||||
Admin *AdminService
|
|
||||||
Location *LocationService
|
Location *LocationService
|
||||||
Labels *LabelService
|
Labels *LabelService
|
||||||
Items *ItemService
|
Items *ItemService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServices(repos *repo.AllRepos, root string) *AllServices {
|
func NewServices(repos *repo.AllRepos) *AllServices {
|
||||||
if repos == nil {
|
if repos == nil {
|
||||||
panic("repos cannot be nil")
|
panic("repos cannot be nil")
|
||||||
}
|
}
|
||||||
if root == "" {
|
|
||||||
panic("root cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AllServices{
|
return &AllServices{
|
||||||
User: &UserService{repos},
|
User: &UserService{repos},
|
||||||
Admin: &AdminService{repos},
|
|
||||||
Location: &LocationService{repos},
|
Location: &LocationService{repos},
|
||||||
Labels: &LabelService{repos},
|
Labels: &LabelService{repos},
|
||||||
Items: &ItemService{
|
Items: &ItemService{
|
||||||
repo: repos,
|
repo: repos,
|
||||||
filepath: root,
|
at: attachmentTokens{},
|
||||||
at: attachmentTokens{},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKeys struct {
|
type contextKeys struct {
|
||||||
|
@ -26,7 +26,7 @@ type Context struct {
|
||||||
GID uuid.UUID
|
GID uuid.UUID
|
||||||
|
|
||||||
// User is the acting user.
|
// User is the acting user.
|
||||||
User *types.UserOut
|
User *repo.UserOut
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContext is a helper function that returns the service context from the context.
|
// NewContext is a helper function that returns the service context from the context.
|
||||||
|
@ -43,16 +43,16 @@ func NewContext(ctx context.Context) Context {
|
||||||
|
|
||||||
// SetUserCtx is a helper function that sets the ContextUser and ContextUserToken
|
// SetUserCtx is a helper function that sets the ContextUser and ContextUserToken
|
||||||
// values within the context of a web request (or any context).
|
// values within the context of a web request (or any context).
|
||||||
func SetUserCtx(ctx context.Context, user *types.UserOut, token string) context.Context {
|
func SetUserCtx(ctx context.Context, user *repo.UserOut, token string) context.Context {
|
||||||
ctx = context.WithValue(ctx, ContextUser, user)
|
ctx = context.WithValue(ctx, ContextUser, user)
|
||||||
ctx = context.WithValue(ctx, ContextUserToken, token)
|
ctx = context.WithValue(ctx, ContextUserToken, token)
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseUserCtx is a helper function that returns the user from the context.
|
// UseUserCtx is a helper function that returns the user from the context.
|
||||||
func UseUserCtx(ctx context.Context) *types.UserOut {
|
func UseUserCtx(ctx context.Context) *repo.UserOut {
|
||||||
if val := ctx.Value(ContextUser); val != nil {
|
if val := ctx.Value(ContextUser); val != nil {
|
||||||
return val.(*types.UserOut)
|
return val.(*repo.UserOut)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_SetAuthContext(t *testing.T) {
|
func Test_SetAuthContext(t *testing.T) {
|
||||||
user := &types.UserOut{
|
user := &repo.UserOut{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/faker"
|
"github.com/hay-kot/homebox/backend/pkgs/faker"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +20,7 @@ var (
|
||||||
tCtx = Context{}
|
tCtx = Context{}
|
||||||
tClient *ent.Client
|
tClient *ent.Client
|
||||||
tRepos *repo.AllRepos
|
tRepos *repo.AllRepos
|
||||||
tUser *ent.User
|
tUser repo.UserOut
|
||||||
tGroup *ent.Group
|
tGroup *ent.Group
|
||||||
tSvc *AllServices
|
tSvc *AllServices
|
||||||
)
|
)
|
||||||
|
@ -37,7 +36,7 @@ func bootstrap() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tUser, err = tRepos.Users.Create(ctx, types.UserCreate{
|
tUser, err = tRepos.Users.Create(ctx, repo.UserCreate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Email: fk.Email(),
|
Email: fk.Email(),
|
||||||
Password: fk.Str(10),
|
Password: fk.Str(10),
|
||||||
|
@ -63,11 +62,10 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tClient = client
|
tClient = client
|
||||||
tRepos = repo.EntAllRepos(tClient)
|
tRepos = repo.EntAllRepos(tClient, os.TempDir()+"/homebox")
|
||||||
tSvc = NewServices(tRepos, "/tmp/homebox")
|
tSvc = NewServices(tRepos)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
|
|
||||||
bootstrap()
|
bootstrap()
|
||||||
tCtx = Context{
|
tCtx = Context{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package mappers
|
|
||||||
|
|
||||||
func MapEach[T any, U any](items []T, fn func(T) U) []U {
|
|
||||||
result := make([]U, len(items))
|
|
||||||
for i, item := range items {
|
|
||||||
result[i] = fn(item)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package mappers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ToItemAttachment(attachment *ent.Attachment) *types.ItemAttachment {
|
|
||||||
return &types.ItemAttachment{
|
|
||||||
ID: attachment.ID,
|
|
||||||
CreatedAt: attachment.CreatedAt,
|
|
||||||
UpdatedAt: attachment.UpdatedAt,
|
|
||||||
Type: attachment.Type.String(),
|
|
||||||
Document: types.DocumentOut{
|
|
||||||
ID: attachment.Edges.Document.ID,
|
|
||||||
Title: attachment.Edges.Document.Title,
|
|
||||||
Path: attachment.Edges.Document.Path,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToItemSummary(item *ent.Item) *types.ItemSummary {
|
|
||||||
var location *types.LocationSummary
|
|
||||||
if item.Edges.Location != nil {
|
|
||||||
location = ToLocationSummary(item.Edges.Location)
|
|
||||||
}
|
|
||||||
|
|
||||||
var labels []*types.LabelSummary
|
|
||||||
if item.Edges.Label != nil {
|
|
||||||
labels = MapEach(item.Edges.Label, ToLabelSummary)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.ItemSummary{
|
|
||||||
ID: item.ID,
|
|
||||||
Name: item.Name,
|
|
||||||
Description: item.Description,
|
|
||||||
CreatedAt: item.CreatedAt,
|
|
||||||
UpdatedAt: item.UpdatedAt,
|
|
||||||
|
|
||||||
Quantity: item.Quantity,
|
|
||||||
Insured: item.Insured,
|
|
||||||
|
|
||||||
// Warranty
|
|
||||||
LifetimeWarranty: item.LifetimeWarranty,
|
|
||||||
WarrantyExpires: item.WarrantyExpires,
|
|
||||||
WarrantyDetails: item.WarrantyDetails,
|
|
||||||
|
|
||||||
// Edges
|
|
||||||
Location: location,
|
|
||||||
Labels: labels,
|
|
||||||
|
|
||||||
// Identification
|
|
||||||
SerialNumber: item.SerialNumber,
|
|
||||||
ModelNumber: item.ModelNumber,
|
|
||||||
Manufacturer: item.Manufacturer,
|
|
||||||
|
|
||||||
// Purchase
|
|
||||||
PurchaseTime: item.PurchaseTime,
|
|
||||||
PurchaseFrom: item.PurchaseFrom,
|
|
||||||
PurchasePrice: item.PurchasePrice,
|
|
||||||
|
|
||||||
// Sold
|
|
||||||
SoldTime: item.SoldTime,
|
|
||||||
SoldTo: item.SoldTo,
|
|
||||||
SoldPrice: item.SoldPrice,
|
|
||||||
SoldNotes: item.SoldNotes,
|
|
||||||
|
|
||||||
// Extras
|
|
||||||
Notes: item.Notes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToItemSummaryErr(item *ent.Item, err error) (*types.ItemSummary, error) {
|
|
||||||
return ToItemSummary(item), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToItemOut(item *ent.Item) *types.ItemOut {
|
|
||||||
var attachments []*types.ItemAttachment
|
|
||||||
if item.Edges.Attachments != nil {
|
|
||||||
attachments = MapEach(item.Edges.Attachments, ToItemAttachment)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.ItemOut{
|
|
||||||
ItemSummary: *ToItemSummary(item),
|
|
||||||
Attachments: attachments,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToItemOutErr(item *ent.Item, err error) (*types.ItemOut, error) {
|
|
||||||
return ToItemOut(item), err
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package mappers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ToLabelSummary(label *ent.Label) *types.LabelSummary {
|
|
||||||
return &types.LabelSummary{
|
|
||||||
ID: label.ID,
|
|
||||||
Name: label.Name,
|
|
||||||
Description: label.Description,
|
|
||||||
CreatedAt: label.CreatedAt,
|
|
||||||
UpdatedAt: label.UpdatedAt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLabelSummaryErr(label *ent.Label, err error) (*types.LabelSummary, error) {
|
|
||||||
return ToLabelSummary(label), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLabelOut(label *ent.Label) *types.LabelOut {
|
|
||||||
return &types.LabelOut{
|
|
||||||
LabelSummary: *ToLabelSummary(label),
|
|
||||||
Items: MapEach(label.Edges.Items, ToItemSummary),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLabelOutErr(label *ent.Label, err error) (*types.LabelOut, error) {
|
|
||||||
return ToLabelOut(label), err
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
package mappers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ToLocationCount(location *repo.LocationWithCount) *types.LocationCount {
|
|
||||||
return &types.LocationCount{
|
|
||||||
LocationSummary: types.LocationSummary{
|
|
||||||
ID: location.ID,
|
|
||||||
Name: location.Name,
|
|
||||||
Description: location.Description,
|
|
||||||
CreatedAt: location.CreatedAt,
|
|
||||||
UpdatedAt: location.UpdatedAt,
|
|
||||||
},
|
|
||||||
ItemCount: location.ItemCount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLocationCountErr(location *repo.LocationWithCount, err error) (*types.LocationCount, error) {
|
|
||||||
return ToLocationCount(location), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLocationSummary(location *ent.Location) *types.LocationSummary {
|
|
||||||
return &types.LocationSummary{
|
|
||||||
ID: location.ID,
|
|
||||||
Name: location.Name,
|
|
||||||
Description: location.Description,
|
|
||||||
CreatedAt: location.CreatedAt,
|
|
||||||
UpdatedAt: location.UpdatedAt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLocationSummaryErr(location *ent.Location, err error) (*types.LocationSummary, error) {
|
|
||||||
return ToLocationSummary(location), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLocationOut(location *ent.Location) *types.LocationOut {
|
|
||||||
return &types.LocationOut{
|
|
||||||
LocationSummary: types.LocationSummary{
|
|
||||||
ID: location.ID,
|
|
||||||
Name: location.Name,
|
|
||||||
Description: location.Description,
|
|
||||||
CreatedAt: location.CreatedAt,
|
|
||||||
UpdatedAt: location.UpdatedAt,
|
|
||||||
},
|
|
||||||
Items: MapEach(location.Edges.Items, ToItemSummary),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToLocationOutErr(location *ent.Location, err error) (*types.LocationOut, error) {
|
|
||||||
return ToLocationOut(location), err
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package mappers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ToOutUser(user *ent.User, err error) (*types.UserOut, error) {
|
|
||||||
if err != nil {
|
|
||||||
return &types.UserOut{}, err
|
|
||||||
}
|
|
||||||
return &types.UserOut{
|
|
||||||
ID: user.ID,
|
|
||||||
Name: user.Name,
|
|
||||||
Email: user.Email,
|
|
||||||
IsSuperuser: user.IsSuperuser,
|
|
||||||
GroupName: user.Edges.Group.Name,
|
|
||||||
GroupID: user.Edges.Group.ID,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AdminService struct {
|
|
||||||
repos *repo.AllRepos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *AdminService) Create(ctx context.Context, usr types.UserCreate) (*ent.User, error) {
|
|
||||||
return svc.repos.Users.Create(ctx, usr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *AdminService) GetAll(ctx context.Context) ([]*ent.User, error) {
|
|
||||||
return svc.repos.Users.GetAll(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *AdminService) GetByID(ctx context.Context, id uuid.UUID) (*ent.User, error) {
|
|
||||||
return svc.repos.Users.GetOneId(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *AdminService) GetByEmail(ctx context.Context, email string) (*ent.User, error) {
|
|
||||||
return svc.repos.Users.GetOneEmail(ctx, email)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *AdminService) UpdateProperties(ctx context.Context, ID uuid.UUID, data types.UserUpdate) (*ent.User, error) {
|
|
||||||
err := svc.repos.Users.Update(ctx, ID, data)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return &ent.User{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc.repos.Users.GetOneId(ctx, ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *AdminService) Delete(ctx context.Context, id uuid.UUID) error {
|
|
||||||
return svc.repos.Users.Delete(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *AdminService) DeleteAll(ctx context.Context) error {
|
|
||||||
return svc.repos.Users.DeleteAll(ctx)
|
|
||||||
}
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services/mappers"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,76 +24,24 @@ type ItemService struct {
|
||||||
at attachmentTokens
|
at attachmentTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (*types.ItemOut, error) {
|
func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (repo.ItemOut, error) {
|
||||||
result, err := svc.repo.Items.GetOne(ctx, id)
|
return svc.repo.Items.GetOneByGroup(ctx, gid, id)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Edges.Group.ID != gid {
|
|
||||||
return nil, ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappers.ToItemOut(result), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]*types.ItemSummary, error) {
|
func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]repo.ItemSummary, error) {
|
||||||
items, err := svc.repo.Items.GetAll(ctx, gid)
|
return svc.repo.Items.GetAll(ctx, gid)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
itemsOut := make([]*types.ItemSummary, len(items))
|
|
||||||
for i, item := range items {
|
|
||||||
itemsOut[i] = mappers.ToItemSummary(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemsOut, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data types.ItemCreate) (*types.ItemOut, error) {
|
func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data repo.ItemCreate) (repo.ItemOut, error) {
|
||||||
item, err := svc.repo.Items.Create(ctx, gid, data)
|
return svc.repo.Items.Create(ctx, gid, data)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappers.ToItemOut(item), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error {
|
func (svc *ItemService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error {
|
||||||
item, err := svc.repo.Items.GetOne(ctx, id)
|
return svc.repo.Items.DeleteByGroup(ctx, gid, id)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.Edges.Group.ID != gid {
|
|
||||||
return ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
err = svc.repo.Items.Delete(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data types.ItemUpdate) (*types.ItemOut, error) {
|
func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data repo.ItemUpdate) (repo.ItemOut, error) {
|
||||||
item, err := svc.repo.Items.GetOne(ctx, data.ID)
|
return svc.repo.Items.UpdateByGroup(ctx, gid, data)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.Edges.Group.ID != gid {
|
|
||||||
return nil, ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
item, err = svc.repo.Items.Update(ctx, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappers.ToItemOut(item), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]string) error {
|
func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]string) error {
|
||||||
|
@ -144,7 +90,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
||||||
|
|
||||||
fmt.Println("Creating Location: ", row.Location)
|
fmt.Println("Creating Location: ", row.Location)
|
||||||
|
|
||||||
result, err := svc.repo.Locations.Create(ctx, gid, types.LocationCreate{
|
result, err := svc.repo.Locations.Create(ctx, gid, repo.LocationCreate{
|
||||||
Name: row.Location,
|
Name: row.Location,
|
||||||
Description: "",
|
Description: "",
|
||||||
})
|
})
|
||||||
|
@ -159,7 +105,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
||||||
if _, ok := labels[label]; ok {
|
if _, ok := labels[label]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result, err := svc.repo.Labels.Create(ctx, gid, types.LabelCreate{
|
result, err := svc.repo.Labels.Create(ctx, gid, repo.LabelCreate{
|
||||||
Name: label,
|
Name: label,
|
||||||
Description: "",
|
Description: "",
|
||||||
})
|
})
|
||||||
|
@ -185,7 +131,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
||||||
Str("locationId", locationID.String()).
|
Str("locationId", locationID.String()).
|
||||||
Msgf("Creating Item: %s", row.Item.Name)
|
Msgf("Creating Item: %s", row.Item.Name)
|
||||||
|
|
||||||
result, err := svc.repo.Items.Create(ctx, gid, types.ItemCreate{
|
result, err := svc.repo.Items.Create(ctx, gid, repo.ItemCreate{
|
||||||
ImportRef: row.Item.ImportRef,
|
ImportRef: row.Item.ImportRef,
|
||||||
Name: row.Item.Name,
|
Name: row.Item.Name,
|
||||||
Description: row.Item.Description,
|
Description: row.Item.Description,
|
||||||
|
@ -198,7 +144,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the item with the rest of the data
|
// Update the item with the rest of the data
|
||||||
_, err = svc.repo.Items.Update(ctx, types.ItemUpdate{
|
_, err = svc.repo.Items.UpdateByGroup(ctx, gid, repo.ItemUpdate{
|
||||||
// Edges
|
// Edges
|
||||||
LocationID: locationID,
|
LocationID: locationID,
|
||||||
LabelIDs: labelIDs,
|
LabelIDs: labelIDs,
|
||||||
|
@ -209,6 +155,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
||||||
Description: result.Description,
|
Description: result.Description,
|
||||||
Insured: row.Item.Insured,
|
Insured: row.Item.Insured,
|
||||||
Notes: row.Item.Notes,
|
Notes: row.Item.Notes,
|
||||||
|
Quantity: row.Item.Quantity,
|
||||||
|
|
||||||
// Identifies the item as imported
|
// Identifies the item as imported
|
||||||
SerialNumber: row.Item.SerialNumber,
|
SerialNumber: row.Item.SerialNumber,
|
||||||
|
|
|
@ -4,14 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/attachment"
|
"github.com/hay-kot/homebox/backend/ent/attachment"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/pathlib"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,13 +40,10 @@ func (at attachmentTokens) Delete(token string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) AttachmentToken(ctx Context, itemId, attachmentId uuid.UUID) (string, error) {
|
func (svc *ItemService) AttachmentToken(ctx Context, itemId, attachmentId uuid.UUID) (string, error) {
|
||||||
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if item.Edges.Group.ID != ctx.GID {
|
|
||||||
return "", ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
token := hasher.GenerateToken()
|
token := hasher.GenerateToken()
|
||||||
|
|
||||||
|
@ -67,52 +63,32 @@ func (svc *ItemService) AttachmentToken(ctx Context, itemId, attachmentId uuid.U
|
||||||
return token.Raw, nil
|
return token.Raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ItemService) attachmentPath(gid, itemId uuid.UUID, filename string) string {
|
func (svc *ItemService) AttachmentPath(ctx context.Context, token string) (*ent.Document, error) {
|
||||||
path := filepath.Join(svc.filepath, gid.String(), itemId.String(), filename)
|
|
||||||
path = pathlib.Safe(path)
|
|
||||||
log.Debug().Str("path", path).Msg("attachment path")
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *ItemService) AttachmentPath(ctx context.Context, token string) (string, error) {
|
|
||||||
attachmentId, ok := svc.at.Get(token)
|
attachmentId, ok := svc.at.Get(token)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
|
attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return attachment.Edges.Document.Path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *types.ItemAttachmentUpdate) (*types.ItemOut, error) {
|
|
||||||
// Update Properties
|
|
||||||
attachment, err := svc.repo.Attachments.Update(ctx, data.ID, attachment.Type(data.Type))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return attachment.Edges.Document, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) {
|
||||||
|
// Update Attachment
|
||||||
|
attachment, err := svc.repo.Attachments.Update(ctx, data.ID, attachment.Type(data.Type))
|
||||||
|
if err != nil {
|
||||||
|
return repo.ItemOut{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Document
|
||||||
attDoc := attachment.Edges.Document
|
attDoc := attachment.Edges.Document
|
||||||
|
_, err = svc.repo.Docs.Rename(ctx, attDoc.ID, data.Title)
|
||||||
if data.Title != attachment.Edges.Document.Title {
|
if err != nil {
|
||||||
newPath := pathlib.Safe(svc.attachmentPath(ctx.GID, itemId, data.Title))
|
return repo.ItemOut{}, err
|
||||||
|
|
||||||
// Move File
|
|
||||||
err = os.Rename(attachment.Edges.Document.Path, newPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = svc.repo.Docs.Update(ctx, attDoc.ID, types.DocumentUpdate{
|
|
||||||
Title: data.Title,
|
|
||||||
Path: newPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.GetOne(ctx, ctx.GID, itemId)
|
return svc.GetOne(ctx, ctx.GID, itemId)
|
||||||
|
@ -121,50 +97,25 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *ty
|
||||||
// AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
|
// AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
|
||||||
// Table and Items table. The file provided via the reader is stored on the file system based on the provided
|
// Table and Items table. The file provided via the reader is stored on the file system based on the provided
|
||||||
// relative path during construction of the service.
|
// relative path during construction of the service.
|
||||||
func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (*types.ItemOut, error) {
|
func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) {
|
||||||
// Get the Item
|
// Get the Item
|
||||||
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return repo.ItemOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.Edges.Group.ID != ctx.GID {
|
|
||||||
return nil, ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
fp := svc.attachmentPath(ctx.GID, itemId, filename)
|
|
||||||
filename = filepath.Base(fp)
|
|
||||||
|
|
||||||
// Create the document
|
// Create the document
|
||||||
doc, err := svc.repo.Docs.Create(ctx, ctx.GID, types.DocumentCreate{
|
doc, err := svc.repo.Docs.Create(ctx, ctx.GID, repo.DocumentCreate{Title: filename, Content: file})
|
||||||
Title: filename,
|
|
||||||
Path: fp,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Err(err).Msg("failed to create document")
|
||||||
|
return repo.ItemOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the attachment
|
// Create the attachment
|
||||||
_, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachmentType)
|
_, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachmentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.Err(err).Msg("failed to create attachment")
|
||||||
}
|
return repo.ItemOut{}, err
|
||||||
|
|
||||||
// Read the contents and write them to a file on the file system
|
|
||||||
err = os.MkdirAll(filepath.Dir(doc.Path), os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(doc.Path)
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("failed to create file")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(f, file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.GetOne(ctx, ctx.GID, itemId)
|
return svc.GetOne(ctx, ctx.GID, itemId)
|
||||||
|
@ -172,15 +123,11 @@ func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename st
|
||||||
|
|
||||||
func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemId, attachmentId uuid.UUID) error {
|
func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemId, attachmentId uuid.UUID) error {
|
||||||
// Get the Item
|
// Get the Item
|
||||||
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
_, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.Edges.Group.ID != gid {
|
|
||||||
return ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
|
attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ func TestItemService_AddAttachment(t *testing.T) {
|
||||||
filepath: temp,
|
filepath: temp,
|
||||||
}
|
}
|
||||||
|
|
||||||
loc, err := tSvc.Location.Create(context.Background(), tGroup.ID, types.LocationCreate{
|
loc, err := tSvc.Location.Create(context.Background(), tGroup.ID, repo.LocationCreate{
|
||||||
Description: "test",
|
Description: "test",
|
||||||
Name: "test",
|
Name: "test",
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, loc)
|
assert.NotNil(t, loc)
|
||||||
|
|
||||||
itmC := types.ItemCreate{
|
itmC := repo.ItemCreate{
|
||||||
Name: fk.Str(10),
|
Name: fk.Str(10),
|
||||||
Description: fk.Str(10),
|
Description: fk.Str(10),
|
||||||
LocationID: loc.ID,
|
LocationID: loc.ID,
|
||||||
|
@ -52,7 +52,7 @@ func TestItemService_AddAttachment(t *testing.T) {
|
||||||
storedPath := afterAttachment.Attachments[0].Document.Path
|
storedPath := afterAttachment.Attachments[0].Document.Path
|
||||||
|
|
||||||
// {root}/{group}/{item}/{attachment}
|
// {root}/{group}/{item}/{attachment}
|
||||||
assert.Equal(t, path.Join(temp, tGroup.ID.String(), itm.ID.String(), "testfile.txt"), storedPath)
|
assert.Equal(t, path.Join(temp, "homebox", tGroup.ID.String(), "documents"), path.Dir(storedPath))
|
||||||
|
|
||||||
// Check that the file contents are correct
|
// Check that the file contents are correct
|
||||||
bts, err := os.ReadFile(storedPath)
|
bts, err := os.ReadFile(storedPath)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidCsv = errors.New("invalid csv")
|
var ErrInvalidCsv = errors.New("invalid csv")
|
||||||
|
@ -45,7 +45,7 @@ func parseInt(s string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
type csvRow struct {
|
type csvRow struct {
|
||||||
Item types.ItemSummary
|
Item repo.ItemOut
|
||||||
Location string
|
Location string
|
||||||
LabelStr string
|
LabelStr string
|
||||||
}
|
}
|
||||||
|
@ -54,12 +54,14 @@ func newCsvRow(row []string) csvRow {
|
||||||
return csvRow{
|
return csvRow{
|
||||||
Location: row[1],
|
Location: row[1],
|
||||||
LabelStr: row[2],
|
LabelStr: row[2],
|
||||||
Item: types.ItemSummary{
|
Item: repo.ItemOut{
|
||||||
ImportRef: row[0],
|
ItemSummary: repo.ItemSummary{
|
||||||
Quantity: parseInt(row[3]),
|
ImportRef: row[0],
|
||||||
Name: row[4],
|
Quantity: parseInt(row[3]),
|
||||||
Description: row[5],
|
Name: row[4],
|
||||||
Insured: parseBool(row[6]),
|
Description: row[5],
|
||||||
|
Insured: parseBool(row[6]),
|
||||||
|
},
|
||||||
SerialNumber: row[7],
|
SerialNumber: row[7],
|
||||||
ModelNumber: row[8],
|
ModelNumber: row[8],
|
||||||
Manufacturer: row[9],
|
Manufacturer: row[9],
|
||||||
|
|
|
@ -71,19 +71,8 @@ func TestItemService_CsvImport(t *testing.T) {
|
||||||
for _, csvRow := range dataCsv {
|
for _, csvRow := range dataCsv {
|
||||||
if csvRow.Item.Name == item.Name {
|
if csvRow.Item.Name == item.Name {
|
||||||
assert.Equal(t, csvRow.Item.Description, item.Description)
|
assert.Equal(t, csvRow.Item.Description, item.Description)
|
||||||
assert.Equal(t, csvRow.Item.SerialNumber, item.SerialNumber)
|
assert.Equal(t, csvRow.Item.Quantity, item.Quantity)
|
||||||
assert.Equal(t, csvRow.Item.Manufacturer, item.Manufacturer)
|
assert.Equal(t, csvRow.Item.Insured, item.Insured)
|
||||||
assert.Equal(t, csvRow.Item.Notes, item.Notes)
|
|
||||||
|
|
||||||
// Purchase Fields
|
|
||||||
assert.Equal(t, csvRow.Item.PurchaseTime, item.PurchaseTime)
|
|
||||||
assert.Equal(t, csvRow.Item.PurchaseFrom, item.PurchaseFrom)
|
|
||||||
assert.Equal(t, csvRow.Item.PurchasePrice, item.PurchasePrice)
|
|
||||||
|
|
||||||
// Sold Fields
|
|
||||||
assert.Equal(t, csvRow.Item.SoldTime, item.SoldTime)
|
|
||||||
assert.Equal(t, csvRow.Item.SoldTo, item.SoldTo)
|
|
||||||
assert.Equal(t, csvRow.Item.SoldPrice, item.SoldPrice)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,59 +5,33 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services/mappers"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LabelService struct {
|
type LabelService struct {
|
||||||
repos *repo.AllRepos
|
repos *repo.AllRepos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LabelService) Create(ctx context.Context, groupId uuid.UUID, data types.LabelCreate) (*types.LabelSummary, error) {
|
func (svc *LabelService) Create(ctx context.Context, groupId uuid.UUID, data repo.LabelCreate) (repo.LabelOut, error) {
|
||||||
label, err := svc.repos.Labels.Create(ctx, groupId, data)
|
return svc.repos.Labels.Create(ctx, groupId, data)
|
||||||
return mappers.ToLabelSummaryErr(label, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LabelService) Update(ctx context.Context, groupId uuid.UUID, data types.LabelUpdate) (*types.LabelSummary, error) {
|
func (svc *LabelService) Update(ctx context.Context, groupId uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) {
|
||||||
label, err := svc.repos.Labels.Update(ctx, data)
|
return svc.repos.Labels.Update(ctx, data)
|
||||||
return mappers.ToLabelSummaryErr(label, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LabelService) Delete(ctx context.Context, groupId uuid.UUID, id uuid.UUID) error {
|
func (svc *LabelService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error {
|
||||||
label, err := svc.repos.Labels.Get(ctx, id)
|
_, err := svc.repos.Labels.GetOneByGroup(ctx, gid, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if label.Edges.Group.ID != groupId {
|
|
||||||
return ErrNotOwner
|
|
||||||
}
|
|
||||||
return svc.repos.Labels.Delete(ctx, id)
|
return svc.repos.Labels.Delete(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LabelService) Get(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (*types.LabelOut, error) {
|
func (svc *LabelService) Get(ctx context.Context, gid uuid.UUID, id uuid.UUID) (repo.LabelOut, error) {
|
||||||
label, err := svc.repos.Labels.Get(ctx, id)
|
return svc.repos.Labels.GetOneByGroup(ctx, gid, id)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if label.Edges.Group.ID != groupId {
|
|
||||||
return nil, ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappers.ToLabelOut(label), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LabelService) GetAll(ctx context.Context, groupId uuid.UUID) ([]*types.LabelSummary, error) {
|
func (svc *LabelService) GetAll(ctx context.Context, groupId uuid.UUID) ([]repo.LabelSummary, error) {
|
||||||
labels, err := svc.repos.Labels.GetAll(ctx, groupId)
|
return svc.repos.Labels.GetAll(ctx, groupId)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
labelsOut := make([]*types.LabelSummary, len(labels))
|
|
||||||
for i, label := range labels {
|
|
||||||
labelsOut[i] = mappers.ToLabelSummary(label)
|
|
||||||
}
|
|
||||||
|
|
||||||
return labelsOut, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services/mappers"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -18,59 +16,32 @@ type LocationService struct {
|
||||||
repos *repo.AllRepos
|
repos *repo.AllRepos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LocationService) GetOne(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (*types.LocationOut, error) {
|
func (svc *LocationService) GetOne(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (repo.LocationOut, error) {
|
||||||
location, err := svc.repos.Locations.Get(ctx, id)
|
return svc.repos.Locations.GetOneByGroup(ctx, groupId, id)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if location.Edges.Group.ID != groupId {
|
|
||||||
return nil, ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappers.ToLocationOut(location), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LocationService) GetAll(ctx context.Context, groupId uuid.UUID) ([]*types.LocationCount, error) {
|
func (svc *LocationService) GetAll(ctx context.Context, groupId uuid.UUID) ([]repo.LocationOutCount, error) {
|
||||||
locations, err := svc.repos.Locations.GetAll(ctx, groupId)
|
return svc.repos.Locations.GetAll(ctx, groupId)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
locationsOut := make([]*types.LocationCount, len(locations))
|
|
||||||
for i, location := range locations {
|
|
||||||
locationsOut[i] = mappers.ToLocationCount(&location)
|
|
||||||
}
|
|
||||||
|
|
||||||
return locationsOut, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LocationService) Create(ctx context.Context, groupId uuid.UUID, data types.LocationCreate) (*types.LocationOut, error) {
|
func (svc *LocationService) Create(ctx context.Context, groupId uuid.UUID, data repo.LocationCreate) (repo.LocationOut, error) {
|
||||||
location, err := svc.repos.Locations.Create(ctx, groupId, data)
|
return svc.repos.Locations.Create(ctx, groupId, data)
|
||||||
return mappers.ToLocationOutErr(location, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LocationService) Delete(ctx context.Context, groupId uuid.UUID, id uuid.UUID) error {
|
func (svc *LocationService) Delete(ctx context.Context, groupId uuid.UUID, id uuid.UUID) error {
|
||||||
location, err := svc.repos.Locations.Get(ctx, id)
|
_, err := svc.repos.Locations.GetOneByGroup(ctx, groupId, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if location.Edges.Group.ID != groupId {
|
|
||||||
return ErrNotOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc.repos.Locations.Delete(ctx, id)
|
return svc.repos.Locations.Delete(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *LocationService) Update(ctx context.Context, groupId uuid.UUID, data types.LocationUpdate) (*types.LocationOut, error) {
|
func (svc *LocationService) Update(ctx context.Context, groupId uuid.UUID, data repo.LocationUpdate) (repo.LocationOut, error) {
|
||||||
location, err := svc.repos.Locations.Get(ctx, data.ID)
|
location, err := svc.repos.Locations.GetOneByGroup(ctx, groupId, data.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return repo.LocationOut{}, err
|
||||||
}
|
|
||||||
if location.Edges.Group.ID != groupId {
|
|
||||||
return nil, ErrNotOwner
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappers.ToLocationOutErr(svc.repos.Locations.Update(ctx, data))
|
data.ID = location.ID
|
||||||
|
return svc.repos.Locations.Update(ctx, data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services/mappers"
|
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -23,18 +22,46 @@ type UserService struct {
|
||||||
repos *repo.AllRepos
|
repos *repo.AllRepos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
UserRegistration struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
GroupName string `json:"groupName"`
|
||||||
|
}
|
||||||
|
UserAuthTokenDetail struct {
|
||||||
|
Raw string `json:"raw"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
}
|
||||||
|
UserAuthTokenCreate struct {
|
||||||
|
TokenHash []byte `json:"token"`
|
||||||
|
UserID uuid.UUID `json:"userId"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
}
|
||||||
|
LoginForm struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// RegisterUser creates a new user and group in the data with the provided data. It also bootstraps the user's group
|
// RegisterUser creates a new user and group in the data with the provided data. It also bootstraps the user's group
|
||||||
// with default Labels and Locations.
|
// with default Labels and Locations.
|
||||||
func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistration) (*types.UserOut, error) {
|
func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration) (repo.UserOut, error) {
|
||||||
|
log.Debug().
|
||||||
|
Str("name", data.Name).
|
||||||
|
Str("email", data.Email).
|
||||||
|
Str("groupName", data.GroupName).
|
||||||
|
Msg("Registering new user")
|
||||||
|
|
||||||
group, err := svc.repos.Groups.Create(ctx, data.GroupName)
|
group, err := svc.repos.Groups.Create(ctx, data.GroupName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &types.UserOut{}, err
|
return repo.UserOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hashed, _ := hasher.HashPassword(data.User.Password)
|
hashed, _ := hasher.HashPassword(data.Password)
|
||||||
usrCreate := types.UserCreate{
|
usrCreate := repo.UserCreate{
|
||||||
Name: data.User.Name,
|
Name: data.Name,
|
||||||
Email: data.User.Email,
|
Email: data.Email,
|
||||||
Password: hashed,
|
Password: hashed,
|
||||||
IsSuperuser: false,
|
IsSuperuser: false,
|
||||||
GroupID: group.ID,
|
GroupID: group.ID,
|
||||||
|
@ -42,61 +69,61 @@ func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistr
|
||||||
|
|
||||||
usr, err := svc.repos.Users.Create(ctx, usrCreate)
|
usr, err := svc.repos.Users.Create(ctx, usrCreate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &types.UserOut{}, err
|
return repo.UserOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, label := range defaultLabels() {
|
for _, label := range defaultLabels() {
|
||||||
_, err := svc.repos.Labels.Create(ctx, group.ID, label)
|
_, err := svc.repos.Labels.Create(ctx, group.ID, label)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &types.UserOut{}, err
|
return repo.UserOut{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, location := range defaultLocations() {
|
for _, location := range defaultLocations() {
|
||||||
_, err := svc.repos.Locations.Create(ctx, group.ID, location)
|
_, err := svc.repos.Locations.Create(ctx, group.ID, location)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &types.UserOut{}, err
|
return repo.UserOut{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappers.ToOutUser(usr, nil)
|
return usr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSelf returns the user that is currently logged in based of the token provided within
|
// GetSelf returns the user that is currently logged in based of the token provided within
|
||||||
func (svc *UserService) GetSelf(ctx context.Context, requestToken string) (*types.UserOut, error) {
|
func (svc *UserService) GetSelf(ctx context.Context, requestToken string) (repo.UserOut, error) {
|
||||||
hash := hasher.HashToken(requestToken)
|
hash := hasher.HashToken(requestToken)
|
||||||
return mappers.ToOutUser(svc.repos.AuthTokens.GetUserFromToken(ctx, hash))
|
return svc.repos.AuthTokens.GetUserFromToken(ctx, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data types.UserUpdate) (*types.UserOut, error) {
|
func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) {
|
||||||
err := svc.repos.Users.Update(ctx, ID, data)
|
err := svc.repos.Users.Update(ctx, ID, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &types.UserOut{}, err
|
return repo.UserOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return mappers.ToOutUser(svc.repos.Users.GetOneId(ctx, ID))
|
return svc.repos.Users.GetOneId(ctx, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// User Authentication
|
// User Authentication
|
||||||
|
|
||||||
func (svc *UserService) createToken(ctx context.Context, userId uuid.UUID) (types.UserAuthTokenDetail, error) {
|
func (svc *UserService) createToken(ctx context.Context, userId uuid.UUID) (UserAuthTokenDetail, error) {
|
||||||
newToken := hasher.GenerateToken()
|
newToken := hasher.GenerateToken()
|
||||||
|
|
||||||
created, err := svc.repos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
created, err := svc.repos.AuthTokens.CreateToken(ctx, repo.UserAuthTokenCreate{
|
||||||
UserID: userId,
|
UserID: userId,
|
||||||
TokenHash: newToken.Hash,
|
TokenHash: newToken.Hash,
|
||||||
ExpiresAt: time.Now().Add(oneWeek),
|
ExpiresAt: time.Now().Add(oneWeek),
|
||||||
})
|
})
|
||||||
|
|
||||||
return types.UserAuthTokenDetail{Raw: newToken.Raw, ExpiresAt: created.ExpiresAt}, err
|
return UserAuthTokenDetail{Raw: newToken.Raw, ExpiresAt: created.ExpiresAt}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *UserService) Login(ctx context.Context, username, password string) (types.UserAuthTokenDetail, error) {
|
func (svc *UserService) Login(ctx context.Context, username, password string) (UserAuthTokenDetail, error) {
|
||||||
usr, err := svc.repos.Users.GetOneEmail(ctx, username)
|
usr, err := svc.repos.Users.GetOneEmail(ctx, username)
|
||||||
|
|
||||||
if err != nil || !hasher.CheckPasswordHash(password, usr.Password) {
|
if err != nil || !hasher.CheckPasswordHash(password, usr.PasswordHash) {
|
||||||
return types.UserAuthTokenDetail{}, ErrorInvalidLogin
|
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.createToken(ctx, usr.ID)
|
return svc.createToken(ctx, usr.ID)
|
||||||
|
@ -108,13 +135,13 @@ func (svc *UserService) Logout(ctx context.Context, token string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *UserService) RenewToken(ctx context.Context, token string) (types.UserAuthTokenDetail, error) {
|
func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthTokenDetail, error) {
|
||||||
hash := hasher.HashToken(token)
|
hash := hasher.HashToken(token)
|
||||||
|
|
||||||
dbToken, err := svc.repos.AuthTokens.GetUserFromToken(ctx, hash)
|
dbToken, err := svc.repos.AuthTokens.GetUserFromToken(ctx, hash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.UserAuthTokenDetail{}, ErrorInvalidToken
|
return UserAuthTokenDetail{}, ErrorInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
newToken, _ := svc.createToken(ctx, dbToken.ID)
|
newToken, _ := svc.createToken(ctx, dbToken.ID)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import "github.com/hay-kot/homebox/backend/internal/types"
|
import (
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
|
)
|
||||||
|
|
||||||
func defaultLocations() []types.LocationCreate {
|
func defaultLocations() []repo.LocationCreate {
|
||||||
return []types.LocationCreate{
|
return []repo.LocationCreate{
|
||||||
{
|
{
|
||||||
Name: "Living Room",
|
Name: "Living Room",
|
||||||
},
|
},
|
||||||
|
@ -31,8 +33,8 @@ func defaultLocations() []types.LocationCreate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultLabels() []types.LabelCreate {
|
func defaultLabels() []repo.LabelCreate {
|
||||||
return []types.LabelCreate{
|
return []repo.LabelCreate{
|
||||||
{
|
{
|
||||||
Name: "Appliances",
|
Name: "Appliances",
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
// ApiSummary
|
|
||||||
//
|
|
||||||
// @public
|
|
||||||
type ApiSummary struct {
|
|
||||||
Healthy bool `json:"health"`
|
|
||||||
Versions []string `json:"versions"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Build Build
|
|
||||||
}
|
|
||||||
|
|
||||||
type Build struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
Commit string `json:"commit"`
|
|
||||||
BuildTime string `json:"buildTime"`
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DocumentOut struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DocumentCreate struct {
|
|
||||||
Title string `json:"name"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DocumentUpdate = DocumentCreate
|
|
||||||
|
|
||||||
type DocumentToken struct {
|
|
||||||
Raw string `json:"raw"`
|
|
||||||
ExpiresAt time.Time `json:"expiresAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DocumentTokenCreate struct {
|
|
||||||
TokenHash []byte `json:"tokenHash"`
|
|
||||||
DocumentID uuid.UUID `json:"documentId"`
|
|
||||||
ExpiresAt time.Time `json:"expiresAt"`
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ItemCreate struct {
|
|
||||||
ImportRef string `json:"-"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
|
|
||||||
// Edges
|
|
||||||
LocationID uuid.UUID `json:"locationId"`
|
|
||||||
LabelIDs []uuid.UUID `json:"labelIds"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemUpdate struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Quantity int `json:"quantity"`
|
|
||||||
Insured bool `json:"insured"`
|
|
||||||
|
|
||||||
// Edges
|
|
||||||
LocationID uuid.UUID `json:"locationId"`
|
|
||||||
LabelIDs []uuid.UUID `json:"labelIds"`
|
|
||||||
|
|
||||||
// Identifications
|
|
||||||
SerialNumber string `json:"serialNumber"`
|
|
||||||
ModelNumber string `json:"modelNumber"`
|
|
||||||
Manufacturer string `json:"manufacturer"`
|
|
||||||
|
|
||||||
// Warranty
|
|
||||||
LifetimeWarranty bool `json:"lifetimeWarranty"`
|
|
||||||
WarrantyExpires time.Time `json:"warrantyExpires"`
|
|
||||||
WarrantyDetails string `json:"warrantyDetails"`
|
|
||||||
|
|
||||||
// Purchase
|
|
||||||
PurchaseTime time.Time `json:"purchaseTime"`
|
|
||||||
PurchaseFrom string `json:"purchaseFrom"`
|
|
||||||
PurchasePrice float64 `json:"purchasePrice,string"`
|
|
||||||
|
|
||||||
// Sold
|
|
||||||
SoldTime time.Time `json:"soldTime"`
|
|
||||||
SoldTo string `json:"soldTo"`
|
|
||||||
SoldPrice float64 `json:"soldPrice,string"`
|
|
||||||
SoldNotes string `json:"soldNotes"`
|
|
||||||
|
|
||||||
// Extras
|
|
||||||
Notes string `json:"notes"`
|
|
||||||
// Fields []*FieldSummary `json:"fields"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemSummary struct {
|
|
||||||
ImportRef string `json:"-"`
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
|
||||||
Quantity int `json:"quantity"`
|
|
||||||
Insured bool `json:"insured"`
|
|
||||||
|
|
||||||
// Edges
|
|
||||||
Location *LocationSummary `json:"location"`
|
|
||||||
Labels []*LabelSummary `json:"labels"`
|
|
||||||
|
|
||||||
// Identifications
|
|
||||||
SerialNumber string `json:"serialNumber"`
|
|
||||||
ModelNumber string `json:"modelNumber"`
|
|
||||||
Manufacturer string `json:"manufacturer"`
|
|
||||||
|
|
||||||
// Warranty
|
|
||||||
LifetimeWarranty bool `json:"lifetimeWarranty"`
|
|
||||||
WarrantyExpires time.Time `json:"warrantyExpires"`
|
|
||||||
WarrantyDetails string `json:"warrantyDetails"`
|
|
||||||
|
|
||||||
// Purchase
|
|
||||||
PurchaseTime time.Time `json:"purchaseTime"`
|
|
||||||
PurchaseFrom string `json:"purchaseFrom"`
|
|
||||||
PurchasePrice float64 `json:"purchasePrice,string"`
|
|
||||||
|
|
||||||
// Sold
|
|
||||||
SoldTime time.Time `json:"soldTime"`
|
|
||||||
SoldTo string `json:"soldTo"`
|
|
||||||
SoldPrice float64 `json:"soldPrice,string"`
|
|
||||||
SoldNotes string `json:"soldNotes"`
|
|
||||||
|
|
||||||
// Extras
|
|
||||||
Notes string `json:"notes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemOut struct {
|
|
||||||
ItemSummary
|
|
||||||
Attachments []*ItemAttachment `json:"attachments"`
|
|
||||||
// Future
|
|
||||||
// Fields []*FieldSummary `json:"fields"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemAttachment struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Document DocumentOut `json:"document"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemAttachmentToken struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemAttachmentUpdate struct {
|
|
||||||
ID uuid.UUID `json:"-"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LabelCreate struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Color string `json:"color"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LabelUpdate struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Color string `json:"color"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LabelSummary struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LabelOut struct {
|
|
||||||
LabelSummary
|
|
||||||
Items []*ItemSummary `json:"items"`
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LocationCreate struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationUpdate struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationSummary struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationCount struct {
|
|
||||||
LocationSummary
|
|
||||||
ItemCount int `json:"itemCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocationOut struct {
|
|
||||||
LocationSummary
|
|
||||||
Items []*ItemSummary `json:"items"`
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LoginForm struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenResponse struct {
|
|
||||||
BearerToken string `json:"token"`
|
|
||||||
ExpiresAt time.Time `json:"expiresAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserAuthTokenDetail struct {
|
|
||||||
Raw string `json:"raw"`
|
|
||||||
ExpiresAt time.Time `json:"expiresAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserAuthToken struct {
|
|
||||||
TokenHash []byte `json:"token"`
|
|
||||||
UserID uuid.UUID `json:"userId"`
|
|
||||||
ExpiresAt time.Time `json:"expiresAt"`
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UserAuthToken) IsExpired() bool {
|
|
||||||
return u.ExpiresAt.Before(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserAuthTokenCreate struct {
|
|
||||||
TokenHash []byte `json:"token"`
|
|
||||||
UserID uuid.UUID `json:"userId"`
|
|
||||||
ExpiresAt time.Time `json:"expiresAt"`
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNameEmpty = errors.New("name is empty")
|
|
||||||
ErrEmailEmpty = errors.New("email is empty")
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserIn is a basic user input struct containing only the fields that are
|
|
||||||
// required for user creation.
|
|
||||||
type UserIn struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserCreate is the Data object contain the requirements of creating a user
|
|
||||||
// in the database. It should to create users from an API unless the user has
|
|
||||||
// rights to create SuperUsers. For regular user in data use the UserIn struct.
|
|
||||||
type UserCreate struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
IsSuperuser bool `json:"isSuperuser"`
|
|
||||||
GroupID uuid.UUID `json:"groupID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UserCreate) Validate() error {
|
|
||||||
if u.Name == "" {
|
|
||||||
return ErrNameEmpty
|
|
||||||
}
|
|
||||||
if u.Email == "" {
|
|
||||||
return ErrEmailEmpty
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserUpdate struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRegistration struct {
|
|
||||||
User UserIn `json:"user"`
|
|
||||||
GroupName string `json:"groupName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserOut struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
IsSuperuser bool `json:"isSuperuser"`
|
|
||||||
GroupID uuid.UUID `json:"groupId"`
|
|
||||||
GroupName string `json:"groupName"`
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUserCreate_Validate(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Name string
|
|
||||||
Email string
|
|
||||||
Password string
|
|
||||||
IsSuperuser bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no_name",
|
|
||||||
fields: fields{
|
|
||||||
Name: "",
|
|
||||||
Email: "",
|
|
||||||
Password: "",
|
|
||||||
IsSuperuser: false,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no_email",
|
|
||||||
fields: fields{
|
|
||||||
Name: "test",
|
|
||||||
Email: "",
|
|
||||||
Password: "",
|
|
||||||
IsSuperuser: false,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid",
|
|
||||||
fields: fields{
|
|
||||||
Name: "test",
|
|
||||||
Email: "test@email.com",
|
|
||||||
Password: "mypassword",
|
|
||||||
IsSuperuser: false,
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
u := &UserCreate{
|
|
||||||
Name: tt.fields.Name,
|
|
||||||
Email: tt.fields.Email,
|
|
||||||
Password: tt.fields.Password,
|
|
||||||
IsSuperuser: tt.fields.IsSuperuser,
|
|
||||||
}
|
|
||||||
if err := u.Validate(); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("UserCreate.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
101
backend/pkgs/set/funcs.go
Normal file
101
backend/pkgs/set/funcs.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package set
|
||||||
|
|
||||||
|
// Diff returns the difference between two sets
|
||||||
|
func Diff[T key](a, b Set[T]) Set[T] {
|
||||||
|
s := New[T]()
|
||||||
|
for k := range a.mp {
|
||||||
|
if !b.Contains(k) {
|
||||||
|
s.Insert(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intersect returns the intersection between two sets
|
||||||
|
func Intersect[T key](a, b Set[T]) Set[T] {
|
||||||
|
s := New[T]()
|
||||||
|
for k := range a.mp {
|
||||||
|
if b.Contains(k) {
|
||||||
|
s.Insert(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union returns the union between two sets
|
||||||
|
func Union[T key](a, b Set[T]) Set[T] {
|
||||||
|
s := New[T]()
|
||||||
|
for k := range a.mp {
|
||||||
|
s.Insert(k)
|
||||||
|
}
|
||||||
|
for k := range b.mp {
|
||||||
|
s.Insert(k)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xor returns the symmetric difference between two sets
|
||||||
|
func Xor[T key](a, b Set[T]) Set[T] {
|
||||||
|
s := New[T]()
|
||||||
|
for k := range a.mp {
|
||||||
|
if !b.Contains(k) {
|
||||||
|
s.Insert(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k := range b.mp {
|
||||||
|
if !a.Contains(k) {
|
||||||
|
s.Insert(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true if two sets are equal
|
||||||
|
func Equal[T key](a, b Set[T]) bool {
|
||||||
|
if a.Len() != b.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k := range a.mp {
|
||||||
|
if !b.Contains(k) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subset returns true if a is a subset of b
|
||||||
|
func Subset[T key](a, b Set[T]) bool {
|
||||||
|
if a.Len() > b.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k := range a.mp {
|
||||||
|
if !b.Contains(k) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Superset returns true if a is a superset of b
|
||||||
|
func Superset[T key](a, b Set[T]) bool {
|
||||||
|
if a.Len() < b.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for k := range b.mp {
|
||||||
|
if !a.Contains(k) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disjoint returns true if two sets are disjoint
|
||||||
|
func Disjoint[T key](a, b Set[T]) bool {
|
||||||
|
for k := range a.mp {
|
||||||
|
if b.Contains(k) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
287
backend/pkgs/set/funcs_test.go
Normal file
287
backend/pkgs/set/funcs_test.go
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
package set
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
a Set[string]
|
||||||
|
b Set[string]
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
argsBasic = args{
|
||||||
|
a: New("a", "b", "c"),
|
||||||
|
b: New("b", "c", "d"),
|
||||||
|
}
|
||||||
|
|
||||||
|
argsNoOverlap = args{
|
||||||
|
a: New("a", "b", "c"),
|
||||||
|
b: New("d", "e", "f"),
|
||||||
|
}
|
||||||
|
|
||||||
|
argsIdentical = args{
|
||||||
|
a: New("a", "b", "c"),
|
||||||
|
b: New("a", "b", "c"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiff(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want Set[string]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "diff basic",
|
||||||
|
args: argsBasic,
|
||||||
|
want: New("a"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff empty",
|
||||||
|
args: argsIdentical,
|
||||||
|
want: New[string](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff no overlap",
|
||||||
|
args: argsNoOverlap,
|
||||||
|
want: New("a", "b", "c"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Diff(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Diff() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntersect(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want Set[string]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "intersect basic",
|
||||||
|
args: argsBasic,
|
||||||
|
want: New("b", "c"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identical sets",
|
||||||
|
args: argsIdentical,
|
||||||
|
want: New("a", "b", "c"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no overlap",
|
||||||
|
args: argsNoOverlap,
|
||||||
|
want: New[string](),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Intersect(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Intersect() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want Set[string]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "intersect basic",
|
||||||
|
args: argsBasic,
|
||||||
|
want: New("a", "b", "c", "d"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identical sets",
|
||||||
|
args: argsIdentical,
|
||||||
|
want: New("a", "b", "c"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no overlap",
|
||||||
|
args: argsNoOverlap,
|
||||||
|
want: New("a", "b", "c", "d", "e", "f"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Union(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Union() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want Set[string]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "xor basic",
|
||||||
|
args: argsBasic,
|
||||||
|
want: New("a", "d"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identical sets",
|
||||||
|
args: argsIdentical,
|
||||||
|
want: New[string](),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no overlap",
|
||||||
|
args: argsNoOverlap,
|
||||||
|
want: New("a", "b", "c", "d", "e", "f"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Xor(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Xor() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqual(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "equal basic",
|
||||||
|
args: argsBasic,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identical sets",
|
||||||
|
args: argsIdentical,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Equal(tt.args.a, tt.args.b); got != tt.want {
|
||||||
|
t.Errorf("Equal() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubset(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
a Set[string]
|
||||||
|
b Set[string]
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "subset basic",
|
||||||
|
args: args{
|
||||||
|
a: New("a", "b"),
|
||||||
|
b: New("a", "b", "c"),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subset basic false",
|
||||||
|
args: args{
|
||||||
|
a: New("a", "b", "d"),
|
||||||
|
b: New("a", "b", "c"),
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Subset(tt.args.a, tt.args.b); got != tt.want {
|
||||||
|
t.Errorf("Subset() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuperset(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
a Set[string]
|
||||||
|
b Set[string]
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "superset basic",
|
||||||
|
args: args{
|
||||||
|
a: New("a", "b", "c"),
|
||||||
|
b: New("a", "b"),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "superset basic false",
|
||||||
|
args: args{
|
||||||
|
a: New("a", "b", "c"),
|
||||||
|
b: New("a", "b", "d"),
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Superset(tt.args.a, tt.args.b); got != tt.want {
|
||||||
|
t.Errorf("Superset() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisjoint(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
a Set[string]
|
||||||
|
b Set[string]
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "disjoint basic",
|
||||||
|
args: args{
|
||||||
|
a: New("a", "b"),
|
||||||
|
b: New("c", "d"),
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disjoint basic false",
|
||||||
|
args: args{
|
||||||
|
a: New("a", "b"),
|
||||||
|
b: New("b", "c"),
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Disjoint(tt.args.a, tt.args.b); got != tt.want {
|
||||||
|
t.Errorf("Disjoint() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
56
backend/pkgs/set/set.go
Normal file
56
backend/pkgs/set/set.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package set
|
||||||
|
|
||||||
|
type key interface {
|
||||||
|
comparable
|
||||||
|
}
|
||||||
|
|
||||||
|
type Set[T key] struct {
|
||||||
|
mp map[T]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New[T key](v ...T) Set[T] {
|
||||||
|
mp := make(map[T]struct{}, len(v))
|
||||||
|
|
||||||
|
s := Set[T]{mp}
|
||||||
|
|
||||||
|
s.Insert(v...)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) Insert(v ...T) {
|
||||||
|
for _, e := range v {
|
||||||
|
s.mp[e] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) Remove(v ...T) {
|
||||||
|
for _, e := range v {
|
||||||
|
delete(s.mp, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) Contains(v T) bool {
|
||||||
|
_, ok := s.mp[v]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) ContainsAll(v ...T) bool {
|
||||||
|
for _, e := range v {
|
||||||
|
if !s.Contains(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) Slice() []T {
|
||||||
|
slice := make([]T, 0, len(s.mp))
|
||||||
|
for k := range s.mp {
|
||||||
|
slice = append(slice, k)
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Set[T]) Len() int {
|
||||||
|
return len(s.mp)
|
||||||
|
}
|
255
backend/pkgs/set/set_test.go
Normal file
255
backend/pkgs/set/set_test.go
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
package set
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
v []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want Set[string]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "new",
|
||||||
|
args: args{
|
||||||
|
v: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
want: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "new empty",
|
||||||
|
args: args{
|
||||||
|
v: []string{},
|
||||||
|
},
|
||||||
|
want: Set[string]{
|
||||||
|
mp: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := New(tt.args.v...); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("New() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet_Insert(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
v []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s Set[string]
|
||||||
|
args args
|
||||||
|
want Set[string]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "insert",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
v: []string{"d", "e", "f"},
|
||||||
|
},
|
||||||
|
want: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
"d": {},
|
||||||
|
"e": {},
|
||||||
|
"f": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "insert empty",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
v: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
want: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.s.Insert(tt.args.v...)
|
||||||
|
if !reflect.DeepEqual(tt.s, tt.want) {
|
||||||
|
t.Errorf("Set.Insert() = %v, want %v", tt.s, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet_Delete(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
v []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s Set[string]
|
||||||
|
args args
|
||||||
|
want Set[string]
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "insert",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
"d": {},
|
||||||
|
"e": {},
|
||||||
|
"f": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
v: []string{"d", "e", "f"},
|
||||||
|
},
|
||||||
|
want: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete empty",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
v: []string{},
|
||||||
|
},
|
||||||
|
want: Set[string]{
|
||||||
|
mp: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.s.Remove(tt.args.v...)
|
||||||
|
if !reflect.DeepEqual(tt.s, tt.want) {
|
||||||
|
t.Errorf("Set.Delete() = %v, want %v", tt.s, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet_ContainsAll(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
v []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s Set[string]
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "contains",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
v: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains empty",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
v: []string{},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not contains",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
v: []string{"d", "e", "f"},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.s.ContainsAll(tt.args.v...); got != tt.want {
|
||||||
|
t.Errorf("Set.ContainsAll() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet_Slice(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
s Set[string]
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "slice",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
"b": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "slice empty",
|
||||||
|
s: Set[string]{
|
||||||
|
mp: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.s.Slice(); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Set.Slice() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,11 +26,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { LocationCount } from "~~/lib/api/types/data-contracts";
|
import { LocationOutCount } from "~~/lib/api/types/data-contracts";
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
location: {
|
location: {
|
||||||
type: Object as () => LocationCount,
|
type: Object as () => LocationOutCount,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
dense: {
|
dense: {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Ref } from "vue";
|
||||||
type Store = UseConfirmDialogReturn<any, boolean, boolean> & {
|
type Store = UseConfirmDialogReturn<any, boolean, boolean> & {
|
||||||
text: Ref<string>;
|
text: Ref<string>;
|
||||||
setup: boolean;
|
setup: boolean;
|
||||||
open: (text: string) => Promise<UseConfirmDialogRevealResult<boolean, boolean>>;
|
open: (text: string) => Promise<UseConfirmDialogRevealResult<boolean, boolean>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const store: Partial<Store> = {
|
const store: Partial<Store> = {
|
||||||
|
@ -31,7 +31,7 @@ export function useConfirm(): Store {
|
||||||
store.cancel = cancel;
|
store.cancel = cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDialog(msg: string): Promise<UseConfirmDialogRevealResult<boolean, boolean>> {
|
async function openDialog(msg: string): Promise<UseConfirmDialogRevealResult<boolean, boolean>> {
|
||||||
store.text.value = msg;
|
store.text.value = msg;
|
||||||
return await store.reveal();
|
return await store.reveal();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,9 @@ describe("first time user workflow (register, login)", () => {
|
||||||
const api = client();
|
const api = client();
|
||||||
const userData = {
|
const userData = {
|
||||||
groupName: "test-group",
|
groupName: "test-group",
|
||||||
user: {
|
email: "test-user@email.com",
|
||||||
email: "test-user@email.com",
|
name: "test-user",
|
||||||
name: "test-user",
|
password: "test-password",
|
||||||
password: "test-password",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
test("user should be able to register", async () => {
|
test("user should be able to register", async () => {
|
||||||
|
@ -27,7 +25,7 @@ describe("first time user workflow (register, login)", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user should be able to login", async () => {
|
test("user should be able to login", async () => {
|
||||||
const { response, data } = await api.login(userData.user.email, userData.user.password);
|
const { response, data } = await api.login(userData.email, userData.password);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
expect(data.token).toBeTruthy();
|
expect(data.token).toBeTruthy();
|
||||||
|
|
||||||
|
|
|
@ -31,15 +31,13 @@ export async function sharedUserClient(): Promise<UserApi> {
|
||||||
}
|
}
|
||||||
const testUser = {
|
const testUser = {
|
||||||
groupName: "test-group",
|
groupName: "test-group",
|
||||||
user: {
|
email: "__test__@__test__.com",
|
||||||
email: "__test__@__test__.com",
|
name: "__test__",
|
||||||
name: "__test__",
|
password: "__test__",
|
||||||
password: "__test__",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const api = client();
|
const api = client();
|
||||||
const { response: tryLoginResp, data } = await api.login(testUser.user.email, testUser.user.password);
|
const { response: tryLoginResp, data } = await api.login(testUser.email, testUser.password);
|
||||||
|
|
||||||
if (tryLoginResp.status === 200) {
|
if (tryLoginResp.status === 200) {
|
||||||
cache.token = data.token;
|
cache.token = data.token;
|
||||||
|
@ -49,7 +47,7 @@ export async function sharedUserClient(): Promise<UserApi> {
|
||||||
const { response: registerResp } = await api.register(testUser);
|
const { response: registerResp } = await api.register(testUser);
|
||||||
expect(registerResp.status).toBe(204);
|
expect(registerResp.status).toBe(204);
|
||||||
|
|
||||||
const { response: loginResp, data: loginData } = await api.login(testUser.user.email, testUser.user.password);
|
const { response: loginResp, data: loginData } = await api.login(testUser.email, testUser.password);
|
||||||
expect(loginResp.status).toBe(200);
|
expect(loginResp.status).toBe(200);
|
||||||
|
|
||||||
cache.token = loginData.token;
|
cache.token = loginData.token;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { BaseAPI, route } from "../base";
|
import { BaseAPI, route } from "../base";
|
||||||
import { LocationCount, LocationCreate, LocationOut } from "../types/data-contracts";
|
import { LocationOutCount, LocationCreate, LocationOut } from "../types/data-contracts";
|
||||||
import { Results } from "./types";
|
import { Results } from "./types";
|
||||||
|
|
||||||
export type LocationUpdate = LocationCreate;
|
export type LocationUpdate = LocationCreate;
|
||||||
|
|
||||||
export class LocationsApi extends BaseAPI {
|
export class LocationsApi extends BaseAPI {
|
||||||
getAll() {
|
getAll() {
|
||||||
return this.http.get<Results<LocationCount>>({ url: route("/locations") });
|
return this.http.get<Results<LocationOutCount>>({ url: route("/locations") });
|
||||||
}
|
}
|
||||||
|
|
||||||
create(body: LocationCreate) {
|
create(body: LocationCreate) {
|
||||||
|
|
|
@ -1,24 +1,11 @@
|
||||||
import { BaseAPI, route } from "./base";
|
import { BaseAPI, route } from "./base";
|
||||||
|
import { ApiSummary, TokenResponse, UserRegistration } from "./types/data-contracts";
|
||||||
export type LoginResult = {
|
|
||||||
token: string;
|
|
||||||
expiresAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LoginPayload = {
|
export type LoginPayload = {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RegisterPayload = {
|
|
||||||
user: {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
groupName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type StatusResult = {
|
export type StatusResult = {
|
||||||
health: boolean;
|
health: boolean;
|
||||||
versions: string[];
|
versions: string[];
|
||||||
|
@ -28,11 +15,11 @@ export type StatusResult = {
|
||||||
|
|
||||||
export class PublicApi extends BaseAPI {
|
export class PublicApi extends BaseAPI {
|
||||||
public status() {
|
public status() {
|
||||||
return this.http.get<StatusResult>({ url: route("/status") });
|
return this.http.get<ApiSummary>({ url: route("/status") });
|
||||||
}
|
}
|
||||||
|
|
||||||
public login(username: string, password: string) {
|
public login(username: string, password: string) {
|
||||||
return this.http.post<LoginPayload, LoginResult>({
|
return this.http.post<LoginPayload, TokenResponse>({
|
||||||
url: route("/users/login"),
|
url: route("/users/login"),
|
||||||
body: {
|
body: {
|
||||||
username,
|
username,
|
||||||
|
@ -41,7 +28,7 @@ export class PublicApi extends BaseAPI {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public register(body: RegisterPayload) {
|
public register(body: UserRegistration) {
|
||||||
return this.http.post<RegisterPayload, LoginResult>({ url: route("/users/register"), body });
|
return this.http.post<UserRegistration, TokenResponse>({ url: route("/users/register"), body });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,36 +10,6 @@
|
||||||
* ---------------------------------------------------------------
|
* ---------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ServerResult {
|
|
||||||
details: any;
|
|
||||||
error: boolean;
|
|
||||||
item: any;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerResults {
|
|
||||||
items: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerValidationError {
|
|
||||||
field: string;
|
|
||||||
reason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiSummary {
|
|
||||||
build: Build;
|
|
||||||
health: boolean;
|
|
||||||
message: string;
|
|
||||||
title: string;
|
|
||||||
versions: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Build {
|
|
||||||
buildTime: string;
|
|
||||||
commit: string;
|
|
||||||
version: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DocumentOut {
|
export interface DocumentOut {
|
||||||
id: string;
|
id: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -54,10 +24,6 @@ export interface ItemAttachment {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemAttachmentToken {
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemAttachmentUpdate {
|
export interface ItemAttachmentUpdate {
|
||||||
title: string;
|
title: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -99,8 +65,6 @@ export interface ItemOut {
|
||||||
/** Purchase */
|
/** Purchase */
|
||||||
purchaseTime: Date;
|
purchaseTime: Date;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
|
||||||
/** Identifications */
|
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
soldNotes: string;
|
soldNotes: string;
|
||||||
|
|
||||||
|
@ -122,39 +86,11 @@ export interface ItemSummary {
|
||||||
insured: boolean;
|
insured: boolean;
|
||||||
labels: LabelSummary[];
|
labels: LabelSummary[];
|
||||||
|
|
||||||
/** Warranty */
|
|
||||||
lifetimeWarranty: boolean;
|
|
||||||
|
|
||||||
/** Edges */
|
/** Edges */
|
||||||
location: LocationSummary;
|
location: LocationSummary;
|
||||||
manufacturer: string;
|
|
||||||
modelNumber: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/** Extras */
|
|
||||||
notes: string;
|
|
||||||
purchaseFrom: string;
|
|
||||||
|
|
||||||
/** @example 0 */
|
|
||||||
purchasePrice: string;
|
|
||||||
|
|
||||||
/** Purchase */
|
|
||||||
purchaseTime: Date;
|
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
|
||||||
/** Identifications */
|
|
||||||
serialNumber: string;
|
|
||||||
soldNotes: string;
|
|
||||||
|
|
||||||
/** @example 0 */
|
|
||||||
soldPrice: string;
|
|
||||||
|
|
||||||
/** Sold */
|
|
||||||
soldTime: Date;
|
|
||||||
soldTo: string;
|
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
warrantyDetails: string;
|
|
||||||
warrantyExpires: Date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemUpdate {
|
export interface ItemUpdate {
|
||||||
|
@ -220,15 +156,6 @@ export interface LabelSummary {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LocationCount {
|
|
||||||
createdAt: Date;
|
|
||||||
description: string;
|
|
||||||
id: string;
|
|
||||||
itemCount: number;
|
|
||||||
name: string;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LocationCreate {
|
export interface LocationCreate {
|
||||||
description: string;
|
description: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -243,6 +170,15 @@ export interface LocationOut {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocationOutCount {
|
||||||
|
createdAt: Date;
|
||||||
|
description: string;
|
||||||
|
id: string;
|
||||||
|
itemCount: number;
|
||||||
|
name: string;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LocationSummary {
|
export interface LocationSummary {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -251,17 +187,6 @@ export interface LocationSummary {
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenResponse {
|
|
||||||
expiresAt: string;
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserIn {
|
|
||||||
email: string;
|
|
||||||
name: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserOut {
|
export interface UserOut {
|
||||||
email: string;
|
email: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
|
@ -271,12 +196,53 @@ export interface UserOut {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserRegistration {
|
|
||||||
groupName: string;
|
|
||||||
user: UserIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserUpdate {
|
export interface UserUpdate {
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServerResult {
|
||||||
|
details: any;
|
||||||
|
error: boolean;
|
||||||
|
item: any;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerResults {
|
||||||
|
items: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerValidationError {
|
||||||
|
field: string;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserRegistration {
|
||||||
|
email: string;
|
||||||
|
groupName: string;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiSummary {
|
||||||
|
build: Build;
|
||||||
|
health: boolean;
|
||||||
|
message: string;
|
||||||
|
title: string;
|
||||||
|
versions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Build {
|
||||||
|
buildTime: string;
|
||||||
|
commit: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ItemAttachmentToken {
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TokenResponse {
|
||||||
|
expiresAt: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
|
@ -25,11 +25,9 @@
|
||||||
async function registerUser() {
|
async function registerUser() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { error } = await api.register({
|
const { error } = await api.register({
|
||||||
user: {
|
name: username.value,
|
||||||
name: username.value,
|
email: email.value,
|
||||||
email: email.value,
|
password: password.value,
|
||||||
password: password.value,
|
|
||||||
},
|
|
||||||
groupName: groupName.value,
|
groupName: groupName.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { LocationCount } from "~~/lib/api/types/data-contracts";
|
import { LocationOutCount } from "~~/lib/api/types/data-contracts";
|
||||||
|
|
||||||
export const useLocationStore = defineStore("locations", {
|
export const useLocationStore = defineStore("locations", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
allLocations: null as LocationCount[] | null,
|
allLocations: null as LocationOutCount[] | null,
|
||||||
client: useUserApi(),
|
client: useUserApi(),
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -12,7 +12,7 @@ export const useLocationStore = defineStore("locations", {
|
||||||
* synched with the server by intercepting the API calls and updating on the
|
* synched with the server by intercepting the API calls and updating on the
|
||||||
* response
|
* response
|
||||||
*/
|
*/
|
||||||
locations(state): LocationCount[] {
|
locations(state): LocationOutCount[] {
|
||||||
if (state.allLocations === null) {
|
if (state.allLocations === null) {
|
||||||
Promise.resolve(this.refresh());
|
Promise.resolve(this.refresh());
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export const useLocationStore = defineStore("locations", {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async refresh(): Promise<LocationCount[]> {
|
async refresh(): Promise<LocationOutCount[]> {
|
||||||
const result = await this.client.locations.getAll();
|
const result = await this.client.locations.getAll();
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -22,7 +22,9 @@ def date_types(*names: list[str]) -> dict[re.Pattern, str]:
|
||||||
|
|
||||||
|
|
||||||
regex_replace: dict[re.Pattern, str] = {
|
regex_replace: dict[re.Pattern, str] = {
|
||||||
re.compile(r"Types"): "",
|
re.compile(r" Repo"): " ",
|
||||||
|
re.compile(r" Services"): " ",
|
||||||
|
re.compile(r" V1"): " ",
|
||||||
re.compile(r"\?:"): ":",
|
re.compile(r"\?:"): ":",
|
||||||
**date_types(
|
**date_types(
|
||||||
"createdAt",
|
"createdAt",
|
||||||
|
|
Loading…
Reference in a new issue