mirror of
https://github.com/hay-kot/homebox.git
synced 2024-12-19 21:36:30 +00:00
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
backend
app/api
internal
mocks
repo
id_set.gomain_test.gomap_helpers.gorepo_document_tokens.gorepo_document_tokens_test.gorepo_documents.gorepo_documents_test.gorepo_item_attachments.gorepo_items.gorepo_items_test.gorepo_labels.gorepo_labels_test.gorepo_locations.gorepo_locations_test.gorepo_tokens.gorepo_tokens_test.gorepo_users.gorepo_users_test.gorepos_all.go
services
all.gocontexts.gocontexts_test.gomain_test.go
mappers
service_admin.goservice_items.goservice_items_attachments.goservice_items_attachments_test.goservice_items_csv.goservice_items_test.goservice_labels.goservice_locations.goservice_user.goservice_user_defaults.gotypes
pkgs/set
frontend
components/Location
composables
lib/api
pages
stores
scripts
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
|
||||
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:
|
||||
properties:
|
||||
details: {}
|
||||
|
@ -21,10 +318,21 @@ definitions:
|
|||
reason:
|
||||
type: string
|
||||
type: object
|
||||
types.ApiSummary:
|
||||
services.UserRegistration:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
groupName:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
type: object
|
||||
v1.ApiSummary:
|
||||
properties:
|
||||
build:
|
||||
$ref: '#/definitions/types.Build'
|
||||
$ref: '#/definitions/v1.Build'
|
||||
health:
|
||||
type: boolean
|
||||
message:
|
||||
|
@ -36,7 +344,7 @@ definitions:
|
|||
type: string
|
||||
type: array
|
||||
type: object
|
||||
types.Build:
|
||||
v1.Build:
|
||||
properties:
|
||||
buildTime:
|
||||
type: string
|
||||
|
@ -45,367 +353,18 @@ definitions:
|
|||
version:
|
||||
type: string
|
||||
type: object
|
||||
types.DocumentOut:
|
||||
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:
|
||||
v1.ItemAttachmentToken:
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
types.ItemAttachmentUpdate:
|
||||
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:
|
||||
v1.TokenResponse:
|
||||
properties:
|
||||
expiresAt:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
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:
|
||||
contact:
|
||||
name: Don't
|
||||
|
@ -430,7 +389,7 @@ paths:
|
|||
- properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/definitions/types.ItemSummary'
|
||||
$ref: '#/definitions/repo.ItemSummary'
|
||||
type: array
|
||||
type: object
|
||||
security:
|
||||
|
@ -445,14 +404,14 @@ paths:
|
|||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemCreate'
|
||||
$ref: '#/definitions/repo.ItemCreate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemSummary'
|
||||
$ref: '#/definitions/repo.ItemSummary'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create a new item
|
||||
|
@ -489,7 +448,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemOut'
|
||||
$ref: '#/definitions/repo.ItemOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Gets a item and fields
|
||||
|
@ -507,14 +466,14 @@ paths:
|
|||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemUpdate'
|
||||
$ref: '#/definitions/repo.ItemUpdate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemOut'
|
||||
$ref: '#/definitions/repo.ItemOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: updates a item
|
||||
|
@ -549,7 +508,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemOut'
|
||||
$ref: '#/definitions/repo.ItemOut'
|
||||
"422":
|
||||
description: Unprocessable Entity
|
||||
schema:
|
||||
|
@ -600,7 +559,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemAttachmentToken'
|
||||
$ref: '#/definitions/v1.ItemAttachmentToken'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: retrieves an attachment for an item
|
||||
|
@ -623,12 +582,12 @@ paths:
|
|||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemAttachmentUpdate'
|
||||
$ref: '#/definitions/repo.ItemAttachmentUpdate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.ItemOut'
|
||||
$ref: '#/definitions/repo.ItemOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: retrieves an attachment for an item
|
||||
|
@ -688,7 +647,7 @@ paths:
|
|||
- properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/definitions/types.LabelOut'
|
||||
$ref: '#/definitions/repo.LabelOut'
|
||||
type: array
|
||||
type: object
|
||||
security:
|
||||
|
@ -703,14 +662,14 @@ paths:
|
|||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/types.LabelCreate'
|
||||
$ref: '#/definitions/repo.LabelCreate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.LabelSummary'
|
||||
$ref: '#/definitions/repo.LabelSummary'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create a new label
|
||||
|
@ -747,7 +706,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.LabelOut'
|
||||
$ref: '#/definitions/repo.LabelOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Gets a label and fields
|
||||
|
@ -766,7 +725,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.LabelOut'
|
||||
$ref: '#/definitions/repo.LabelOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: updates a label
|
||||
|
@ -785,7 +744,7 @@ paths:
|
|||
- properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/definitions/types.LocationCount'
|
||||
$ref: '#/definitions/repo.LocationOutCount'
|
||||
type: array
|
||||
type: object
|
||||
security:
|
||||
|
@ -800,14 +759,14 @@ paths:
|
|||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/types.LocationCreate'
|
||||
$ref: '#/definitions/repo.LocationCreate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.LocationSummary'
|
||||
$ref: '#/definitions/repo.LocationSummary'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Create a new location
|
||||
|
@ -844,7 +803,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.LocationOut'
|
||||
$ref: '#/definitions/repo.LocationOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Gets a location and fields
|
||||
|
@ -863,7 +822,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.LocationOut'
|
||||
$ref: '#/definitions/repo.LocationOut'
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: updates a location
|
||||
|
@ -877,7 +836,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.ApiSummary'
|
||||
$ref: '#/definitions/v1.ApiSummary'
|
||||
summary: Retrieves the basic information about the API
|
||||
tags:
|
||||
- Base
|
||||
|
@ -903,7 +862,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/types.TokenResponse'
|
||||
$ref: '#/definitions/v1.TokenResponse'
|
||||
summary: User Login
|
||||
tags:
|
||||
- Authentication
|
||||
|
@ -938,7 +897,7 @@ paths:
|
|||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/types.UserRegistration'
|
||||
$ref: '#/definitions/services.UserRegistration'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
@ -970,7 +929,7 @@ paths:
|
|||
- $ref: '#/definitions/server.Result'
|
||||
- properties:
|
||||
item:
|
||||
$ref: '#/definitions/types.UserOut'
|
||||
$ref: '#/definitions/repo.UserOut'
|
||||
type: object
|
||||
security:
|
||||
- Bearer: []
|
||||
|
@ -984,7 +943,7 @@ paths:
|
|||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/types.UserUpdate'
|
||||
$ref: '#/definitions/repo.UserUpdate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
@ -995,7 +954,7 @@ paths:
|
|||
- $ref: '#/definitions/server.Result'
|
||||
- properties:
|
||||
item:
|
||||
$ref: '#/definitions/types.UserUpdate'
|
||||
$ref: '#/definitions/repo.UserUpdate'
|
||||
type: object
|
||||
security:
|
||||
- Bearer: []
|
||||
|
|
|
@ -81,8 +81,8 @@ func run(cfg *config.Config) error {
|
|||
}
|
||||
|
||||
app.db = c
|
||||
app.repos = repo.EntAllRepos(c)
|
||||
app.services = services.NewServices(app.repos, cfg.Storage.Data)
|
||||
app.repos = repo.EntAllRepos(c, cfg.Storage.Data)
|
||||
app.services = services.NewServices(app.repos)
|
||||
|
||||
// =========================================================================
|
||||
// Start Server
|
||||
|
|
|
@ -57,7 +57,7 @@ func (a *app) mwAuthToken(next http.Handler) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
r = r.WithContext(services.SetUserCtx(r.Context(), usr, requestToken))
|
||||
r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken))
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
_ "github.com/hay-kot/homebox/backend/app/api/docs"
|
||||
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/types"
|
||||
"github.com/rs/zerolog/log"
|
||||
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)
|
||||
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,
|
||||
Commit: Commit,
|
||||
BuildTime: BuildTime,
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/services"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
)
|
||||
|
||||
|
@ -19,6 +18,22 @@ type V1Controller struct {
|
|||
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 {
|
||||
v1Base := prefix + "/v1"
|
||||
prefixFunc := func(s string) string {
|
||||
|
@ -42,11 +57,11 @@ type ReadyFunc func() bool
|
|||
// @Summary Retrieves the basic information about the API
|
||||
// @Tags Base
|
||||
// @Produce json
|
||||
// @Success 200 {object} types.ApiSummary
|
||||
// @Success 200 {object} ApiSummary
|
||||
// @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) {
|
||||
server.Respond(w, http.StatusOK, types.ApiSummary{
|
||||
server.Respond(w, http.StatusOK, ApiSummary{
|
||||
Healthy: ready(),
|
||||
Title: "Go API Template",
|
||||
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/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/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"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
|
||||
// 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.
|
||||
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"))
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to parse id")
|
||||
server.RespondError(w, http.StatusBadRequest, err)
|
||||
return uuid.Nil, nil, err
|
||||
return uuid.Nil, &repo.UserOut{}, err
|
||||
}
|
||||
|
||||
user := services.UseUserCtx(r.Context())
|
||||
|
|
|
@ -3,13 +3,25 @@ package v1
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"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/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
|
||||
// @Summary User Login
|
||||
// @Tags Authentication
|
||||
|
@ -18,11 +30,11 @@ import (
|
|||
// @Param username formData string false "string" example(admin@admin.com)
|
||||
// @Param password formData string false "string" example(admin)
|
||||
// @Produce json
|
||||
// @Success 200 {object} types.TokenResponse
|
||||
// @Success 200 {object} TokenResponse
|
||||
// @Router /v1/users/login [POST]
|
||||
func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
loginForm := &types.LoginForm{}
|
||||
loginForm := &LoginForm{}
|
||||
|
||||
if r.Header.Get("Content-Type") == server.ContentFormUrlEncoded {
|
||||
err := r.ParseForm()
|
||||
|
@ -59,9 +71,9 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
server.Respond(w, http.StatusOK, types.TokenResponse{
|
||||
BearerToken: "Bearer " + newToken.Raw,
|
||||
ExpiresAt: newToken.ExpiresAt,
|
||||
server.Respond(w, http.StatusOK, TokenResponse{
|
||||
Token: "Bearer " + newToken.Raw,
|
||||
ExpiresAt: newToken.ExpiresAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"encoding/csv"
|
||||
"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/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ import (
|
|||
// @Summary Get All Items
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Success 200 {object} server.Results{items=[]types.ItemSummary}
|
||||
// @Success 200 {object} server.Results{items=[]repo.ItemSummary}
|
||||
// @Router /v1/items [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
||||
|
@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
|
|||
// @Summary Create a new item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param payload body types.ItemCreate true "Item Data"
|
||||
// @Success 200 {object} types.ItemSummary
|
||||
// @Param payload body repo.ItemCreate true "Item Data"
|
||||
// @Success 200 {object} repo.ItemSummary
|
||||
// @Router /v1/items [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
createData := types.ItemCreate{}
|
||||
createData := repo.ItemCreate{}
|
||||
if err := server.Decode(r, &createData); err != nil {
|
||||
log.Err(err).Msg("failed to decode request body")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
|
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
|
|||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Success 200 {object} types.ItemOut
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
||||
|
@ -114,14 +114,14 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
|||
// @Summary updates a item
|
||||
// @Tags Items
|
||||
// @Produce json
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param payload body types.ItemUpdate true "Item Data"
|
||||
// @Success 200 {object} types.ItemOut
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param payload body repo.ItemUpdate true "Item Data"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body := types.ItemUpdate{}
|
||||
body := repo.ItemUpdate{}
|
||||
if err := server.Decode(r, &body); err != nil {
|
||||
log.Err(err).Msg("failed to decode request body")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
|
|
|
@ -4,17 +4,22 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"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/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type (
|
||||
ItemAttachmentToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
)
|
||||
|
||||
// HandleItemsImport godocs
|
||||
// @Summary imports items into the database
|
||||
// @Tags Items
|
||||
|
@ -23,7 +28,7 @@ import (
|
|||
// @Param file formData file true "File attachment"
|
||||
// @Param type formData string true "Type of file"
|
||||
// @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
|
||||
// @Router /v1/items/{id}/attachments [POST]
|
||||
// @Security Bearer
|
||||
|
@ -105,7 +110,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
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 {
|
||||
log.Err(err).Msg("failed to get attachment")
|
||||
|
@ -113,9 +118,9 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
|||
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")
|
||||
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
|
||||
// @Param id path string true "Item 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]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
|
||||
|
@ -147,10 +152,10 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
|
|||
// HandleItemAttachmentUpdate godocs
|
||||
// @Summary retrieves an attachment for an item
|
||||
// @Tags Items
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Param payload body types.ItemAttachmentUpdate true "Attachment Update"
|
||||
// @Success 200 {object} types.ItemOut
|
||||
// @Param id path string true "Item ID"
|
||||
// @Param attachment_id path string true "Attachment ID"
|
||||
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
||||
// @Success 200 {object} repo.ItemOut
|
||||
// @Router /v1/items/{id}/attachments/{attachment_id} [PUT]
|
||||
// @Security Bearer
|
||||
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
|
||||
case http.MethodDelete:
|
||||
|
@ -216,7 +221,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r
|
|||
|
||||
// Update Attachment Handler
|
||||
case http.MethodPut:
|
||||
var attachment types.ItemAttachmentUpdate
|
||||
var attachment repo.ItemAttachmentUpdate
|
||||
err = server.Decode(r, &attachment)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to decode attachment")
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ import (
|
|||
// @Summary Get All Labels
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Success 200 {object} server.Results{items=[]types.LabelOut}
|
||||
// @Success 200 {object} server.Results{items=[]repo.LabelOut}
|
||||
// @Router /v1/labels [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
||||
|
@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
|||
// @Summary Create a new label
|
||||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param payload body types.LabelCreate true "Label Data"
|
||||
// @Success 200 {object} types.LabelSummary
|
||||
// @Param payload body repo.LabelCreate true "Label Data"
|
||||
// @Success 200 {object} repo.LabelSummary
|
||||
// @Router /v1/labels [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
createData := types.LabelCreate{}
|
||||
createData := repo.LabelCreate{}
|
||||
if err := server.Decode(r, &createData); err != nil {
|
||||
log.Err(err).Msg("error decoding label create data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
|
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
|||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 200 {object} types.LabelOut
|
||||
// @Success 200 {object} repo.LabelOut
|
||||
// @Router /v1/labels/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
||||
|
@ -122,12 +122,12 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
|||
// @Tags Labels
|
||||
// @Produce json
|
||||
// @Param id path string true "Label ID"
|
||||
// @Success 200 {object} types.LabelOut
|
||||
// @Success 200 {object} repo.LabelOut
|
||||
// @Router /v1/labels/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body := types.LabelUpdate{}
|
||||
body := repo.LabelUpdate{}
|
||||
if err := server.Decode(r, &body); err != nil {
|
||||
log.Err(err).Msg("error decoding label update data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -14,7 +14,7 @@ import (
|
|||
// @Summary Get All Locations
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Success 200 {object} server.Results{items=[]types.LocationCount}
|
||||
// @Success 200 {object} server.Results{items=[]repo.LocationOutCount}
|
||||
// @Router /v1/locations [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
||||
|
@ -35,13 +35,13 @@ func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
|
|||
// @Summary Create a new location
|
||||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param payload body types.LocationCreate true "Location Data"
|
||||
// @Success 200 {object} types.LocationSummary
|
||||
// @Param payload body repo.LocationCreate true "Location Data"
|
||||
// @Success 200 {object} repo.LocationSummary
|
||||
// @Router /v1/locations [POST]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
createData := types.LocationCreate{}
|
||||
createData := repo.LocationCreate{}
|
||||
if err := server.Decode(r, &createData); err != nil {
|
||||
log.Err(err).Msg("failed to decode location create data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
|
@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
|
|||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 200 {object} types.LocationOut
|
||||
// @Success 200 {object} repo.LocationOut
|
||||
// @Router /v1/locations/{id} [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
||||
|
@ -105,12 +105,16 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
|||
if ent.IsNotFound(err) {
|
||||
log.Err(err).
|
||||
Str("id", uid.String()).
|
||||
Str("gid", user.GroupID.String()).
|
||||
Msg("location not found")
|
||||
server.RespondError(w, http.StatusNotFound, err)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
@ -123,12 +127,12 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
|
|||
// @Tags Locations
|
||||
// @Produce json
|
||||
// @Param id path string true "Location ID"
|
||||
// @Success 200 {object} types.LocationOut
|
||||
// @Success 200 {object} repo.LocationOut
|
||||
// @Router /v1/locations/{id} [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body := types.LocationUpdate{}
|
||||
body := repo.LocationUpdate{}
|
||||
if err := server.Decode(r, &body); err != nil {
|
||||
log.Err(err).Msg("failed to decode location update data")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -14,12 +14,12 @@ import (
|
|||
// @Summary Get the current user
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body types.UserRegistration true "User Data"
|
||||
// @Param payload body services.UserRegistration true "User Data"
|
||||
// @Success 204
|
||||
// @Router /v1/users/register [Post]
|
||||
func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
regData := types.UserRegistration{}
|
||||
regData := services.UserRegistration{}
|
||||
|
||||
if err := server.Decode(r, ®Data); err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("failed to register user")
|
||||
server.RespondError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
@ -41,7 +42,7 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
|
|||
// @Summary Get the current user
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Success 200 {object} server.Result{item=types.UserOut}
|
||||
// @Success 200 {object} server.Result{item=repo.UserOut}
|
||||
// @Router /v1/users/self [GET]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
||||
|
@ -62,13 +63,13 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
|
|||
// @Summary Update the current user
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Param payload body types.UserUpdate true "User Data"
|
||||
// @Success 200 {object} server.Result{item=types.UserUpdate}
|
||||
// @Param payload body repo.UserUpdate true "User Data"
|
||||
// @Success 200 {object} server.Result{item=repo.UserUpdate}
|
||||
// @Router /v1/users/self [PUT]
|
||||
// @Security Bearer
|
||||
func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
updateData := types.UserUpdate{}
|
||||
updateData := repo.UserUpdate{}
|
||||
if err := server.Decode(r, &updateData); err != nil {
|
||||
log.Err(err).Msg("failed to decode user update data")
|
||||
server.RespondError(w, http.StatusBadRequest, err)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package factories
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func UserFactory() types.UserCreate {
|
||||
func UserFactory() repo.UserCreate {
|
||||
f := faker.NewFaker()
|
||||
return types.UserCreate{
|
||||
return repo.UserCreate{
|
||||
Name: f.Str(10),
|
||||
Email: f.Email(),
|
||||
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
|
||||
|
||||
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.
|
||||
// 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
|
||||
}
|
||||
|
||||
// IDSet is a utility set-like type for working with sets of uuid.UUIDs within a repository
|
||||
// instance. Most useful for comparing lists of UUIDs for processing relationship
|
||||
// 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))
|
||||
func newIDSet[T HasID](entities []T) set.Set[uuid.UUID] {
|
||||
uuids := make([]uuid.UUID, 0, len(entities))
|
||||
for _, e := range entities {
|
||||
s.Add(e.GetID())
|
||||
uuids = append(uuids, e.GetID())
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *IDSet) Slice() []uuid.UUID {
|
||||
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)
|
||||
return set.New(uuids...)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ var (
|
|||
|
||||
tClient *ent.Client
|
||||
tRepos *AllRepos
|
||||
tUser *ent.User
|
||||
tUser UserOut
|
||||
tGroup *ent.Group
|
||||
)
|
||||
|
||||
|
@ -53,7 +53,7 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
tClient = client
|
||||
tRepos = EntAllRepos(tClient)
|
||||
tRepos = EntAllRepos(tClient, os.TempDir())
|
||||
defer client.Close()
|
||||
|
||||
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/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
)
|
||||
|
||||
// DocumentTokensRepository is a repository for Document entity
|
||||
|
@ -15,7 +14,35 @@ type DocumentTokensRepository struct {
|
|||
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().
|
||||
SetDocumentID(data.DocumentID).
|
||||
SetToken(data.TokenHash).
|
||||
|
@ -23,13 +50,13 @@ func (r *DocumentTokensRepository) Create(ctx context.Context, data types.Docume
|
|||
Save(ctx)
|
||||
|
||||
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)).
|
||||
WithDocument().
|
||||
Only(ctx)
|
||||
Only(ctx))
|
||||
}
|
||||
|
||||
func (r *DocumentTokensRepository) PurgeExpiredTokens(ctx context.Context) (int, error) {
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/ent/documenttoken"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -19,7 +18,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
|||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
data types.DocumentTokenCreate
|
||||
data DocumentTokenCreate
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -31,7 +30,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
|||
name: "create document token",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: types.DocumentTokenCreate{
|
||||
data: DocumentTokenCreate{
|
||||
DocumentID: doc.ID,
|
||||
TokenHash: []byte("token"),
|
||||
ExpiresAt: expires,
|
||||
|
@ -39,7 +38,9 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
|||
},
|
||||
want: &ent.DocumentToken{
|
||||
Edges: ent.DocumentTokenEdges{
|
||||
Document: doc,
|
||||
Document: &ent.Document{
|
||||
ID: doc.ID,
|
||||
},
|
||||
},
|
||||
Token: []byte("token"),
|
||||
ExpiresAt: expires,
|
||||
|
@ -50,7 +51,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
|||
name: "create document token with empty token",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: types.DocumentTokenCreate{
|
||||
data: DocumentTokenCreate{
|
||||
DocumentID: doc.ID,
|
||||
TokenHash: []byte(""),
|
||||
ExpiresAt: expires,
|
||||
|
@ -63,7 +64,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
|||
name: "create document token with empty document id",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
data: types.DocumentTokenCreate{
|
||||
data: DocumentTokenCreate{
|
||||
DocumentID: uuid.Nil,
|
||||
TokenHash: []byte("token"),
|
||||
ExpiresAt: expires,
|
||||
|
@ -94,18 +95,18 @@ func TestDocumentTokensRepository_Create(t *testing.T) {
|
|||
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.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]
|
||||
|
||||
results := make([]*ent.DocumentToken, 0, num)
|
||||
results := make([]DocumentToken, 0, num)
|
||||
|
||||
ids := make([]uuid.UUID, 0, num)
|
||||
t.Cleanup(func() {
|
||||
|
@ -115,7 +116,7 @@ func useDocTokens(t *testing.T, num int) []*ent.DocumentToken {
|
|||
})
|
||||
|
||||
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,
|
||||
TokenHash: []byte(fk.Str(10)),
|
||||
ExpiresAt: fk.Time(),
|
|
@ -2,46 +2,117 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/ent/document"
|
||||
"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 {
|
||||
db *ent.Client
|
||||
db *ent.Client
|
||||
dir string
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) Create(ctx context.Context, gid uuid.UUID, doc types.DocumentCreate) (*ent.Document, error) {
|
||||
return r.db.Document.Create().
|
||||
type (
|
||||
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).
|
||||
SetTitle(doc.Title).
|
||||
SetPath(doc.Path).
|
||||
Save(ctx)
|
||||
SetPath(path).
|
||||
Save(ctx),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *DocumentRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]*ent.Document, error) {
|
||||
return r.db.Document.Query().
|
||||
Where(document.HasGroupWith(group.ID(gid))).
|
||||
All(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) Rename(ctx context.Context, id uuid.UUID, title string) (DocumentOut, error) {
|
||||
return mapDocumentOutErr(r.db.Document.UpdateOneID(id).
|
||||
SetTitle(title).
|
||||
Save(ctx))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,110 +1,28 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDocumentRepository_Create(t *testing.T) {
|
||||
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 {
|
||||
func useDocs(t *testing.T, num int) []DocumentOut {
|
||||
t.Helper()
|
||||
|
||||
results := make([]*ent.Document, 0, num)
|
||||
results := make([]DocumentOut, 0, num)
|
||||
ids := make([]uuid.UUID, 0, num)
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
doc, err := tRepos.Docs.Create(context.Background(), tGroup.ID, types.DocumentCreate{
|
||||
Title: fk.Str(10),
|
||||
Path: fk.Path(),
|
||||
doc, err := tRepos.Docs.Create(context.Background(), tGroup.ID, DocumentCreate{
|
||||
Title: fk.Str(10) + ".md",
|
||||
Content: bytes.NewReader([]byte(fk.Str(10))),
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
@ -126,77 +44,68 @@ func useDocs(t *testing.T, num int) []*ent.Document {
|
|||
return results
|
||||
}
|
||||
|
||||
func TestDocumentRepository_GetAll(t *testing.T) {
|
||||
entities := useDocs(t, 10)
|
||||
|
||||
for _, entity := range entities {
|
||||
assert.NotNil(t, entity)
|
||||
func TestDocumentRepository_CreateUpdateDelete(t *testing.T) {
|
||||
temp := t.TempDir()
|
||||
r := DocumentRepository{
|
||||
db: tClient,
|
||||
dir: temp,
|
||||
}
|
||||
|
||||
all, err := tRepos.Docs.GetAll(context.Background(), tGroup.ID)
|
||||
assert.NoError(t, err)
|
||||
type args struct {
|
||||
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)
|
||||
for _, entity := range all {
|
||||
assert.NotNil(t, entity)
|
||||
|
||||
for _, e := range entities {
|
||||
if e.ID == entity.ID {
|
||||
assert.Equal(t, e.Title, entity.Title)
|
||||
assert.Equal(t, e.Path, entity.Path)
|
||||
ensureRead := func() {
|
||||
// Read Document
|
||||
bts, err := os.ReadFile(got.Path)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.content, string(bts))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentRepository_Get(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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
ensureRead()
|
||||
|
||||
// Update Document
|
||||
got, err = r.Rename(tt.args.ctx, got.ID, "__"+tt.title+"__")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "__"+tt.title+"__", got.Title)
|
||||
|
||||
ensureRead()
|
||||
|
||||
// Delete Document
|
||||
err = r.Delete(tt.args.ctx, got.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = os.Stat(got.Path)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
|
@ -16,6 +17,36 @@ type AttachmentRepo struct {
|
|||
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) {
|
||||
return r.db.Attachment.Create().
|
||||
SetType(typ).
|
||||
|
|
|
@ -2,21 +2,187 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/ent/group"
|
||||
"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 {
|
||||
db *ent.Client
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (*ent.Item, error) {
|
||||
return e.db.Item.Query().
|
||||
Where(item.ID(id)).
|
||||
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"`
|
||||
}
|
||||
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().
|
||||
WithLabel().
|
||||
WithLocation().
|
||||
|
@ -24,19 +190,32 @@ func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (*ent.Item,
|
|||
WithAttachments(func(aq *ent.AttachmentQuery) {
|
||||
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.
|
||||
func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]*ent.Item, error) {
|
||||
return e.db.Item.Query().
|
||||
func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemSummary, error) {
|
||||
return mapItemsSummaryErr(e.db.Item.Query().
|
||||
Where(item.HasGroupWith(group.ID(gid))).
|
||||
WithLabel().
|
||||
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().
|
||||
SetName(data.Name).
|
||||
SetDescription(data.Description).
|
||||
|
@ -49,7 +228,7 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data types.
|
|||
|
||||
result, err := q.Save(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ItemOut{}, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*ent.Item, error) {
|
||||
q := e.db.Item.UpdateOneID(data.ID).
|
||||
func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error {
|
||||
_, 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).
|
||||
SetDescription(data.Description).
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ItemOut{}, err
|
||||
}
|
||||
|
||||
set := EntitiesToIDSet(currentLabels)
|
||||
set := newIDSet(currentLabels)
|
||||
|
||||
for _, l := range data.LabelIDs {
|
||||
if set.Has(l) {
|
||||
if set.Contains(l) {
|
||||
set.Remove(l)
|
||||
continue
|
||||
}
|
||||
|
@ -102,7 +291,7 @@ func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*e
|
|||
|
||||
err = q.Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ItemOut{}, err
|
||||
}
|
||||
|
||||
return e.GetOne(ctx, data.ID)
|
||||
|
|
|
@ -6,25 +6,23 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func itemFactory() types.ItemCreate {
|
||||
return types.ItemCreate{
|
||||
func itemFactory() ItemCreate {
|
||||
return ItemCreate{
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(100),
|
||||
}
|
||||
}
|
||||
|
||||
func useItems(t *testing.T, len int) []*ent.Item {
|
||||
func useItems(t *testing.T, len int) []ItemOut {
|
||||
t.Helper()
|
||||
|
||||
location, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory())
|
||||
assert.NoError(t, err)
|
||||
|
||||
items := make([]*ent.Item, len)
|
||||
items := make([]ItemOut, len)
|
||||
for i := 0; i < len; i++ {
|
||||
itm := itemFactory()
|
||||
itm.LocationID = location.ID
|
||||
|
@ -107,7 +105,7 @@ func TestItemsRepository_Create_Location(t *testing.T) {
|
|||
foundItem, err := tRepos.Items.GetOne(context.Background(), result.ID)
|
||||
assert.NoError(t, err)
|
||||
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
|
||||
err = tRepos.Locations.Delete(context.Background(), location.ID)
|
||||
|
@ -168,18 +166,18 @@ func TestItemsRepository_Update_Labels(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Apply all labels to entity
|
||||
updateData := types.ItemUpdate{
|
||||
updateData := ItemUpdate{
|
||||
ID: entity.ID,
|
||||
Name: entity.Name,
|
||||
LocationID: entity.Edges.Location.ID,
|
||||
LocationID: entity.Location.ID,
|
||||
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.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)
|
||||
}
|
||||
})
|
||||
|
@ -192,10 +190,10 @@ func TestItemsRepository_Update(t *testing.T) {
|
|||
|
||||
entity := entities[0]
|
||||
|
||||
updateData := types.ItemUpdate{
|
||||
updateData := ItemUpdate{
|
||||
ID: entity.ID,
|
||||
Name: entity.Name,
|
||||
LocationID: entity.Edges.Location.ID,
|
||||
LocationID: entity.Location.ID,
|
||||
SerialNumber: fk.Str(10),
|
||||
LabelIDs: nil,
|
||||
ModelNumber: fk.Str(10),
|
||||
|
@ -213,7 +211,7 @@ func TestItemsRepository_Update(t *testing.T) {
|
|||
LifetimeWarranty: true,
|
||||
}
|
||||
|
||||
updatedEntity, err := tRepos.Items.Update(context.Background(), updateData)
|
||||
updatedEntity, err := tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, updateData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
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.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.ModelNumber, got.ModelNumber)
|
||||
assert.Equal(t, updateData.Manufacturer, got.Manufacturer)
|
||||
|
|
|
@ -2,34 +2,94 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/ent/group"
|
||||
"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 {
|
||||
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) {
|
||||
return r.db.Label.Query().
|
||||
Where(label.ID(ID)).
|
||||
LabelUpdate struct {
|
||||
ID uuid.UUID `json:"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().
|
||||
WithItems().
|
||||
Only(ctx)
|
||||
Only(ctx),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *LabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]*ent.Label, error) {
|
||||
return r.db.Label.Query().
|
||||
func (r *LabelRepository) GetOne(ctx context.Context, ID uuid.UUID) (LabelOut, error) {
|
||||
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))).
|
||||
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().
|
||||
SetName(data.Name).
|
||||
SetDescription(data.Description).
|
||||
|
@ -37,11 +97,15 @@ func (r *LabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data t
|
|||
SetGroupID(groupdId).
|
||||
Save(ctx)
|
||||
|
||||
if err != nil {
|
||||
return LabelOut{}, err
|
||||
}
|
||||
|
||||
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).
|
||||
SetName(data.Name).
|
||||
SetDescription(data.Description).
|
||||
|
@ -49,10 +113,10 @@ func (r *LabelRepository) Update(ctx context.Context, data types.LabelUpdate) (*
|
|||
Save(ctx)
|
||||
|
||||
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 {
|
||||
|
|
|
@ -4,22 +4,20 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func labelFactory() types.LabelCreate {
|
||||
return types.LabelCreate{
|
||||
func labelFactory() LabelCreate {
|
||||
return LabelCreate{
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(100),
|
||||
}
|
||||
}
|
||||
|
||||
func useLabels(t *testing.T, len int) []*ent.Label {
|
||||
func useLabels(t *testing.T, len int) []LabelOut {
|
||||
t.Helper()
|
||||
|
||||
labels := make([]*ent.Label, len)
|
||||
labels := make([]LabelOut, len)
|
||||
for i := 0; i < len; i++ {
|
||||
itm := labelFactory()
|
||||
|
||||
|
@ -42,7 +40,7 @@ func TestLabelRepository_Get(t *testing.T) {
|
|||
label := labels[0]
|
||||
|
||||
// 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.Equal(t, label.ID, foundLoc.ID)
|
||||
}
|
||||
|
@ -60,7 +58,7 @@ func TestLabelRepository_Create(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// 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.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())
|
||||
assert.NoError(t, err)
|
||||
|
||||
updateData := types.LabelUpdate{
|
||||
updateData := LabelUpdate{
|
||||
ID: loc.ID,
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(100),
|
||||
|
@ -81,7 +79,7 @@ func TestLabelRepository_Update(t *testing.T) {
|
|||
update, err := tRepos.Labels.Update(context.Background(), updateData)
|
||||
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.Equal(t, update.ID, foundLoc.ID)
|
||||
|
@ -99,6 +97,6 @@ func TestLabelRepository_Delete(t *testing.T) {
|
|||
err = tRepos.Labels.Delete(context.Background(), loc.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = tRepos.Labels.Get(context.Background(), loc.ID)
|
||||
_, err = tRepos.Labels.GetOne(context.Background(), loc.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
|
@ -2,24 +2,79 @@ package repo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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/internal/types"
|
||||
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||
)
|
||||
|
||||
type LocationRepository struct {
|
||||
db *ent.Client
|
||||
}
|
||||
|
||||
type LocationWithCount struct {
|
||||
*ent.Location
|
||||
ItemCount int `json:"itemCount"`
|
||||
type (
|
||||
LocationCreate struct {
|
||||
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
|
||||
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
|
||||
SELECT
|
||||
id,
|
||||
|
@ -46,54 +101,62 @@ func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]L
|
|||
return nil, err
|
||||
}
|
||||
|
||||
list := []LocationWithCount{}
|
||||
list := []LocationOutCount{}
|
||||
for rows.Next() {
|
||||
var loc ent.Location
|
||||
var ct LocationWithCount
|
||||
err := rows.Scan(&loc.ID, &loc.Name, &loc.Description, &loc.CreatedAt, &loc.UpdatedAt, &ct.ItemCount)
|
||||
var ct LocationOutCount
|
||||
|
||||
err := rows.Scan(&ct.ID, &ct.Name, &ct.Description, &ct.CreatedAt, &ct.UpdatedAt, &ct.ItemCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.Location = &loc
|
||||
|
||||
list = append(list, ct)
|
||||
}
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (r *LocationRepository) Get(ctx context.Context, ID uuid.UUID) (*ent.Location, error) {
|
||||
return r.db.Location.Query().
|
||||
Where(location.ID(ID)).
|
||||
func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Location) (LocationOut, error) {
|
||||
return mapLocationOutErr(r.db.Location.Query().
|
||||
Where(where...).
|
||||
WithGroup().
|
||||
WithItems(func(iq *ent.ItemQuery) {
|
||||
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().
|
||||
SetName(data.Name).
|
||||
SetDescription(data.Description).
|
||||
SetGroupID(groupdId).
|
||||
SetGroupID(gid).
|
||||
Save(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return LocationOut{}, err
|
||||
}
|
||||
|
||||
location.Edges.Group = &ent.Group{ID: groupdId} // bootstrap group ID
|
||||
return location, err
|
||||
location.Edges.Group = &ent.Group{ID: gid} // bootstrap group ID
|
||||
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).
|
||||
SetName(data.Name).
|
||||
SetDescription(data.Description).
|
||||
Save(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return LocationOut{}, err
|
||||
}
|
||||
|
||||
return r.Get(ctx, data.ID)
|
||||
|
|
|
@ -4,12 +4,11 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func locationFactory() types.LocationCreate {
|
||||
return types.LocationCreate{
|
||||
func locationFactory() LocationCreate {
|
||||
return LocationCreate{
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(100),
|
||||
}
|
||||
|
@ -30,13 +29,13 @@ func TestLocationRepository_Get(t *testing.T) {
|
|||
|
||||
func TestLocationRepositoryGetAllWithCount(t *testing.T) {
|
||||
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),
|
||||
Description: fk.Str(100),
|
||||
})
|
||||
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),
|
||||
Description: fk.Str(100),
|
||||
LocationID: result.ID,
|
||||
|
@ -72,7 +71,7 @@ func TestLocationRepository_Update(t *testing.T) {
|
|||
loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory())
|
||||
assert.NoError(t, err)
|
||||
|
||||
updateData := types.LocationUpdate{
|
||||
updateData := LocationUpdate{
|
||||
ID: loc.ID,
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(100),
|
||||
|
|
|
@ -4,17 +4,34 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/ent/authtokens"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
)
|
||||
|
||||
type TokenRepository struct {
|
||||
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
|
||||
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().
|
||||
Where(authtokens.Token(token)).
|
||||
Where(authtokens.ExpiresAtGTE(time.Now())).
|
||||
|
@ -24,15 +41,14 @@ func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (*
|
|||
Only(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return UserOut{}, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
return mapUserOut(user), nil
|
||||
}
|
||||
|
||||
// Creates a token for a user
|
||||
func (r *TokenRepository) CreateToken(ctx context.Context, createToken types.UserAuthTokenCreate) (types.UserAuthToken, error) {
|
||||
tokenOut := types.UserAuthToken{}
|
||||
func (r *TokenRepository) CreateToken(ctx context.Context, createToken UserAuthTokenCreate) (UserAuthToken, error) {
|
||||
|
||||
dbToken, err := r.db.AuthTokens.Create().
|
||||
SetToken(createToken.TokenHash).
|
||||
|
@ -41,15 +57,17 @@ func (r *TokenRepository) CreateToken(ctx context.Context, createToken types.Use
|
|||
Save(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tokenOut, err
|
||||
return UserAuthToken{}, err
|
||||
}
|
||||
|
||||
tokenOut.TokenHash = dbToken.Token
|
||||
tokenOut.UserID = createToken.UserID
|
||||
tokenOut.CreatedAt = dbToken.CreatedAt
|
||||
tokenOut.ExpiresAt = dbToken.ExpiresAt
|
||||
|
||||
return tokenOut, nil
|
||||
return UserAuthToken{
|
||||
UserAuthTokenCreate: UserAuthTokenCreate{
|
||||
TokenHash: dbToken.Token,
|
||||
UserID: createToken.UserID,
|
||||
ExpiresAt: dbToken.ExpiresAt,
|
||||
},
|
||||
CreatedAt: dbToken.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteToken remove a single token from the database - equivalent to revoke or logout
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -22,7 +21,7 @@ func TestAuthTokenRepo_CreateToken(t *testing.T) {
|
|||
|
||||
generatedToken := hasher.GenerateToken()
|
||||
|
||||
token, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
token, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||
TokenHash: generatedToken.Hash,
|
||||
ExpiresAt: expiresAt,
|
||||
UserID: userOut.ID,
|
||||
|
@ -50,7 +49,7 @@ func TestAuthTokenRepo_DeleteToken(t *testing.T) {
|
|||
|
||||
generatedToken := hasher.GenerateToken()
|
||||
|
||||
_, err = tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
_, err = tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||
TokenHash: generatedToken.Hash,
|
||||
ExpiresAt: expiresAt,
|
||||
UserID: userOut.ID,
|
||||
|
@ -72,7 +71,7 @@ func TestAuthTokenRepo_GetUserByToken(t *testing.T) {
|
|||
expiresAt := time.Now().Add(time.Hour)
|
||||
generatedToken := hasher.GenerateToken()
|
||||
|
||||
token, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
token, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||
TokenHash: generatedToken.Hash,
|
||||
ExpiresAt: expiresAt,
|
||||
UserID: userOut.ID,
|
||||
|
@ -101,13 +100,13 @@ func TestAuthTokenRepo_PurgeExpiredTokens(t *testing.T) {
|
|||
user := userFactory()
|
||||
userOut, _ := tRepos.Users.Create(ctx, user)
|
||||
|
||||
createdTokens := []types.UserAuthToken{}
|
||||
createdTokens := []UserAuthToken{}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
expiresAt := time.Now()
|
||||
generatedToken := hasher.GenerateToken()
|
||||
|
||||
createdToken, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
createdToken, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{
|
||||
TokenHash: generatedToken.Hash,
|
||||
ExpiresAt: expiresAt,
|
||||
UserID: userOut.ID,
|
||||
|
|
|
@ -6,37 +6,77 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/ent/user"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
db *ent.Client
|
||||
}
|
||||
|
||||
func (e *UserRepository) GetOneId(ctx context.Context, id uuid.UUID) (*ent.User, error) {
|
||||
return e.db.User.Query().
|
||||
Where(user.ID(id)).
|
||||
WithGroup().
|
||||
Only(ctx)
|
||||
}
|
||||
|
||||
func (e *UserRepository) GetOneEmail(ctx context.Context, email string) (*ent.User, error) {
|
||||
return e.db.User.Query().
|
||||
Where(user.Email(email)).
|
||||
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
|
||||
type (
|
||||
// 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.
|
||||
UserCreate struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
IsSuperuser bool `json:"isSuperuser"`
|
||||
GroupID uuid.UUID `json:"groupID"`
|
||||
}
|
||||
|
||||
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.
|
||||
Create().
|
||||
SetName(usr.Name).
|
||||
|
@ -46,13 +86,13 @@ func (e *UserRepository) Create(ctx context.Context, usr types.UserCreate) (*ent
|
|||
SetGroupID(usr.GroupID).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
return entUser, err
|
||||
return UserOut{}, err
|
||||
}
|
||||
|
||||
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().
|
||||
Where(user.ID(ID)).
|
||||
SetName(data.Name).
|
||||
|
|
|
@ -5,14 +5,11 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func userFactory() types.UserCreate {
|
||||
|
||||
return types.UserCreate{
|
||||
func userFactory() UserCreate {
|
||||
return UserCreate{
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: fk.Str(10),
|
||||
|
@ -61,7 +58,7 @@ func TestUserRepo_GetOneId(t *testing.T) {
|
|||
|
||||
func TestUserRepo_GetAll(t *testing.T) {
|
||||
// Setup
|
||||
toCreate := []types.UserCreate{
|
||||
toCreate := []UserCreate{
|
||||
userFactory(),
|
||||
userFactory(),
|
||||
userFactory(),
|
||||
|
@ -70,7 +67,7 @@ func TestUserRepo_GetAll(t *testing.T) {
|
|||
|
||||
ctx := context.Background()
|
||||
|
||||
created := []*ent.User{}
|
||||
created := []UserOut{}
|
||||
|
||||
for _, usr := range toCreate {
|
||||
usrOut, _ := tRepos.Users.Create(ctx, usr)
|
||||
|
@ -90,7 +87,7 @@ func TestUserRepo_GetAll(t *testing.T) {
|
|||
assert.Equal(t, usr.Email, usr2.Email)
|
||||
|
||||
// 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())
|
||||
assert.NoError(t, err)
|
||||
|
||||
updateData := types.UserUpdate{
|
||||
updateData := UserUpdate{
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ type AllRepos struct {
|
|||
Attachments *AttachmentRepo
|
||||
}
|
||||
|
||||
func EntAllRepos(db *ent.Client) *AllRepos {
|
||||
func EntAllRepos(db *ent.Client, root string) *AllRepos {
|
||||
return &AllRepos{
|
||||
Users: &UserRepository{db},
|
||||
AuthTokens: &TokenRepository{db},
|
||||
|
@ -23,7 +23,7 @@ func EntAllRepos(db *ent.Client) *AllRepos {
|
|||
Locations: &LocationRepository{db},
|
||||
Labels: &LabelRepository{db},
|
||||
Items: &ItemsRepository{db},
|
||||
Docs: &DocumentRepository{db},
|
||||
Docs: &DocumentRepository{db, root},
|
||||
DocTokens: &DocumentTokensRepository{db},
|
||||
Attachments: &AttachmentRepo{db},
|
||||
}
|
||||
|
|
|
@ -4,29 +4,23 @@ import "github.com/hay-kot/homebox/backend/internal/repo"
|
|||
|
||||
type AllServices struct {
|
||||
User *UserService
|
||||
Admin *AdminService
|
||||
Location *LocationService
|
||||
Labels *LabelService
|
||||
Items *ItemService
|
||||
}
|
||||
|
||||
func NewServices(repos *repo.AllRepos, root string) *AllServices {
|
||||
func NewServices(repos *repo.AllRepos) *AllServices {
|
||||
if repos == nil {
|
||||
panic("repos cannot be nil")
|
||||
}
|
||||
if root == "" {
|
||||
panic("root cannot be empty")
|
||||
}
|
||||
|
||||
return &AllServices{
|
||||
User: &UserService{repos},
|
||||
Admin: &AdminService{repos},
|
||||
Location: &LocationService{repos},
|
||||
Labels: &LabelService{repos},
|
||||
Items: &ItemService{
|
||||
repo: repos,
|
||||
filepath: root,
|
||||
at: attachmentTokens{},
|
||||
repo: repos,
|
||||
at: attachmentTokens{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
)
|
||||
|
||||
type contextKeys struct {
|
||||
|
@ -26,7 +26,7 @@ type Context struct {
|
|||
GID uuid.UUID
|
||||
|
||||
// User is the acting user.
|
||||
User *types.UserOut
|
||||
User *repo.UserOut
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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, ContextUserToken, token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return val.(*types.UserOut)
|
||||
return val.(*repo.UserOut)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@ import (
|
|||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func Test_SetAuthContext(t *testing.T) {
|
||||
user := &types.UserOut{
|
||||
user := &repo.UserOut{
|
||||
ID: uuid.New(),
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
|
||||
"github.com/hay-kot/homebox/backend/ent"
|
||||
"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/mattn/go-sqlite3"
|
||||
)
|
||||
|
@ -21,7 +20,7 @@ var (
|
|||
tCtx = Context{}
|
||||
tClient *ent.Client
|
||||
tRepos *repo.AllRepos
|
||||
tUser *ent.User
|
||||
tUser repo.UserOut
|
||||
tGroup *ent.Group
|
||||
tSvc *AllServices
|
||||
)
|
||||
|
@ -37,7 +36,7 @@ func bootstrap() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
tUser, err = tRepos.Users.Create(ctx, types.UserCreate{
|
||||
tUser, err = tRepos.Users.Create(ctx, repo.UserCreate{
|
||||
Name: fk.Str(10),
|
||||
Email: fk.Email(),
|
||||
Password: fk.Str(10),
|
||||
|
@ -63,11 +62,10 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
tClient = client
|
||||
tRepos = repo.EntAllRepos(tClient)
|
||||
tSvc = NewServices(tRepos, "/tmp/homebox")
|
||||
tRepos = repo.EntAllRepos(tClient, os.TempDir()+"/homebox")
|
||||
tSvc = NewServices(tRepos)
|
||||
defer client.Close()
|
||||
|
||||
|
||||
bootstrap()
|
||||
tCtx = Context{
|
||||
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/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"
|
||||
)
|
||||
|
||||
|
@ -26,76 +24,24 @@ type ItemService struct {
|
|||
at attachmentTokens
|
||||
}
|
||||
|
||||
func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (*types.ItemOut, error) {
|
||||
result, err := svc.repo.Items.GetOne(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Edges.Group.ID != gid {
|
||||
return nil, ErrNotOwner
|
||||
}
|
||||
|
||||
return mappers.ToItemOut(result), nil
|
||||
func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (repo.ItemOut, error) {
|
||||
return svc.repo.Items.GetOneByGroup(ctx, gid, id)
|
||||
}
|
||||
|
||||
func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]*types.ItemSummary, error) {
|
||||
items, err := 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) GetAll(ctx context.Context, gid uuid.UUID) ([]repo.ItemSummary, error) {
|
||||
return svc.repo.Items.GetAll(ctx, gid)
|
||||
}
|
||||
|
||||
func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data types.ItemCreate) (*types.ItemOut, error) {
|
||||
item, err := svc.repo.Items.Create(ctx, gid, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappers.ToItemOut(item), nil
|
||||
func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data repo.ItemCreate) (repo.ItemOut, error) {
|
||||
return svc.repo.Items.Create(ctx, gid, data)
|
||||
}
|
||||
|
||||
func (svc *ItemService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error {
|
||||
item, err := svc.repo.Items.GetOne(ctx, 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
|
||||
return svc.repo.Items.DeleteByGroup(ctx, gid, id)
|
||||
}
|
||||
|
||||
func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data types.ItemUpdate) (*types.ItemOut, error) {
|
||||
item, err := svc.repo.Items.GetOne(ctx, data.ID)
|
||||
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) Update(ctx context.Context, gid uuid.UUID, data repo.ItemUpdate) (repo.ItemOut, error) {
|
||||
return svc.repo.Items.UpdateByGroup(ctx, gid, data)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
result, err := svc.repo.Locations.Create(ctx, gid, types.LocationCreate{
|
||||
result, err := svc.repo.Locations.Create(ctx, gid, repo.LocationCreate{
|
||||
Name: row.Location,
|
||||
Description: "",
|
||||
})
|
||||
|
@ -159,7 +105,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
|||
if _, ok := labels[label]; ok {
|
||||
continue
|
||||
}
|
||||
result, err := svc.repo.Labels.Create(ctx, gid, types.LabelCreate{
|
||||
result, err := svc.repo.Labels.Create(ctx, gid, repo.LabelCreate{
|
||||
Name: label,
|
||||
Description: "",
|
||||
})
|
||||
|
@ -185,7 +131,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
|||
Str("locationId", locationID.String()).
|
||||
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,
|
||||
Name: row.Item.Name,
|
||||
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
|
||||
_, err = svc.repo.Items.Update(ctx, types.ItemUpdate{
|
||||
_, err = svc.repo.Items.UpdateByGroup(ctx, gid, repo.ItemUpdate{
|
||||
// Edges
|
||||
LocationID: locationID,
|
||||
LabelIDs: labelIDs,
|
||||
|
@ -209,6 +155,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
|
|||
Description: result.Description,
|
||||
Insured: row.Item.Insured,
|
||||
Notes: row.Item.Notes,
|
||||
Quantity: row.Item.Quantity,
|
||||
|
||||
// Identifies the item as imported
|
||||
SerialNumber: row.Item.SerialNumber,
|
||||
|
|
|
@ -4,14 +4,13 @@ import (
|
|||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"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/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/pathlib"
|
||||
"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) {
|
||||
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
||||
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if item.Edges.Group.ID != ctx.GID {
|
||||
return "", ErrNotOwner
|
||||
}
|
||||
|
||||
token := hasher.GenerateToken()
|
||||
|
||||
|
@ -67,52 +63,32 @@ func (svc *ItemService) AttachmentToken(ctx Context, itemId, attachmentId uuid.U
|
|||
return token.Raw, nil
|
||||
}
|
||||
|
||||
func (svc *ItemService) attachmentPath(gid, itemId uuid.UUID, filename string) string {
|
||||
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) {
|
||||
func (svc *ItemService) AttachmentPath(ctx context.Context, token string) (*ent.Document, error) {
|
||||
attachmentId, ok := svc.at.Get(token)
|
||||
if !ok {
|
||||
return "", ErrNotFound
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
|
||||
if data.Title != attachment.Edges.Document.Title {
|
||||
newPath := pathlib.Safe(svc.attachmentPath(ctx.GID, itemId, data.Title))
|
||||
|
||||
// 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
|
||||
}
|
||||
_, err = svc.repo.Docs.Rename(ctx, attDoc.ID, data.Title)
|
||||
if err != nil {
|
||||
return repo.ItemOut{}, err
|
||||
}
|
||||
|
||||
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
|
||||
// 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.
|
||||
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
|
||||
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
||||
_, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId)
|
||||
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
|
||||
doc, err := svc.repo.Docs.Create(ctx, ctx.GID, types.DocumentCreate{
|
||||
Title: filename,
|
||||
Path: fp,
|
||||
})
|
||||
doc, err := svc.repo.Docs.Create(ctx, ctx.GID, repo.DocumentCreate{Title: filename, Content: file})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Err(err).Msg("failed to create document")
|
||||
return repo.ItemOut{}, err
|
||||
}
|
||||
|
||||
// Create the attachment
|
||||
_, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachmentType)
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
log.Err(err).Msg("failed to create attachment")
|
||||
return repo.ItemOut{}, err
|
||||
}
|
||||
|
||||
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 {
|
||||
// Get the Item
|
||||
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
||||
_, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if item.Edges.Group.ID != gid {
|
||||
return ErrNotOwner
|
||||
}
|
||||
|
||||
attachment, err := svc.repo.Attachments.Get(ctx, attachmentId)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -19,14 +19,14 @@ func TestItemService_AddAttachment(t *testing.T) {
|
|||
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",
|
||||
Name: "test",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, loc)
|
||||
|
||||
itmC := types.ItemCreate{
|
||||
itmC := repo.ItemCreate{
|
||||
Name: fk.Str(10),
|
||||
Description: fk.Str(10),
|
||||
LocationID: loc.ID,
|
||||
|
@ -52,7 +52,7 @@ func TestItemService_AddAttachment(t *testing.T) {
|
|||
storedPath := afterAttachment.Attachments[0].Document.Path
|
||||
|
||||
// {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
|
||||
bts, err := os.ReadFile(storedPath)
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hay-kot/homebox/backend/internal/types"
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
)
|
||||
|
||||
var ErrInvalidCsv = errors.New("invalid csv")
|
||||
|
@ -45,7 +45,7 @@ func parseInt(s string) int {
|
|||
}
|
||||
|
||||
type csvRow struct {
|
||||
Item types.ItemSummary
|
||||
Item repo.ItemOut
|
||||
Location string
|
||||
LabelStr string
|
||||
}
|
||||
|
@ -54,12 +54,14 @@ func newCsvRow(row []string) csvRow {
|
|||
return csvRow{
|
||||
Location: row[1],
|
||||
LabelStr: row[2],
|
||||
Item: types.ItemSummary{
|
||||
ImportRef: row[0],
|
||||
Quantity: parseInt(row[3]),
|
||||
Name: row[4],
|
||||
Description: row[5],
|
||||
Insured: parseBool(row[6]),
|
||||
Item: repo.ItemOut{
|
||||
ItemSummary: repo.ItemSummary{
|
||||
ImportRef: row[0],
|
||||
Quantity: parseInt(row[3]),
|
||||
Name: row[4],
|
||||
Description: row[5],
|
||||
Insured: parseBool(row[6]),
|
||||
},
|
||||
SerialNumber: row[7],
|
||||
ModelNumber: row[8],
|
||||
Manufacturer: row[9],
|
||||
|
|
|
@ -71,19 +71,8 @@ func TestItemService_CsvImport(t *testing.T) {
|
|||
for _, csvRow := range dataCsv {
|
||||
if csvRow.Item.Name == item.Name {
|
||||
assert.Equal(t, csvRow.Item.Description, item.Description)
|
||||
assert.Equal(t, csvRow.Item.SerialNumber, item.SerialNumber)
|
||||
assert.Equal(t, csvRow.Item.Manufacturer, item.Manufacturer)
|
||||
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)
|
||||
assert.Equal(t, csvRow.Item.Quantity, item.Quantity)
|
||||
assert.Equal(t, csvRow.Item.Insured, item.Insured)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,59 +5,33 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"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 {
|
||||
repos *repo.AllRepos
|
||||
}
|
||||
|
||||
func (svc *LabelService) Create(ctx context.Context, groupId uuid.UUID, data types.LabelCreate) (*types.LabelSummary, error) {
|
||||
label, err := svc.repos.Labels.Create(ctx, groupId, data)
|
||||
return mappers.ToLabelSummaryErr(label, err)
|
||||
func (svc *LabelService) Create(ctx context.Context, groupId uuid.UUID, data repo.LabelCreate) (repo.LabelOut, error) {
|
||||
return svc.repos.Labels.Create(ctx, groupId, data)
|
||||
}
|
||||
|
||||
func (svc *LabelService) Update(ctx context.Context, groupId uuid.UUID, data types.LabelUpdate) (*types.LabelSummary, error) {
|
||||
label, err := svc.repos.Labels.Update(ctx, data)
|
||||
return mappers.ToLabelSummaryErr(label, err)
|
||||
func (svc *LabelService) Update(ctx context.Context, groupId uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) {
|
||||
return svc.repos.Labels.Update(ctx, data)
|
||||
}
|
||||
|
||||
func (svc *LabelService) Delete(ctx context.Context, groupId uuid.UUID, id uuid.UUID) error {
|
||||
label, err := svc.repos.Labels.Get(ctx, id)
|
||||
func (svc *LabelService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error {
|
||||
_, err := svc.repos.Labels.GetOneByGroup(ctx, gid, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if label.Edges.Group.ID != groupId {
|
||||
return ErrNotOwner
|
||||
}
|
||||
return svc.repos.Labels.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *LabelService) Get(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (*types.LabelOut, error) {
|
||||
label, err := svc.repos.Labels.Get(ctx, id)
|
||||
func (svc *LabelService) Get(ctx context.Context, gid uuid.UUID, id uuid.UUID) (repo.LabelOut, error) {
|
||||
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) {
|
||||
labels, err := 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
|
||||
func (svc *LabelService) GetAll(ctx context.Context, groupId uuid.UUID) ([]repo.LabelSummary, error) {
|
||||
return svc.repos.Labels.GetAll(ctx, groupId)
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"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 (
|
||||
|
@ -18,59 +16,32 @@ type LocationService struct {
|
|||
repos *repo.AllRepos
|
||||
}
|
||||
|
||||
func (svc *LocationService) GetOne(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (*types.LocationOut, error) {
|
||||
location, err := svc.repos.Locations.Get(ctx, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if location.Edges.Group.ID != groupId {
|
||||
return nil, ErrNotOwner
|
||||
}
|
||||
|
||||
return mappers.ToLocationOut(location), nil
|
||||
func (svc *LocationService) GetOne(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (repo.LocationOut, error) {
|
||||
return svc.repos.Locations.GetOneByGroup(ctx, groupId, id)
|
||||
}
|
||||
|
||||
func (svc *LocationService) GetAll(ctx context.Context, groupId uuid.UUID) ([]*types.LocationCount, error) {
|
||||
locations, err := 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) GetAll(ctx context.Context, groupId uuid.UUID) ([]repo.LocationOutCount, error) {
|
||||
return svc.repos.Locations.GetAll(ctx, groupId)
|
||||
}
|
||||
|
||||
func (svc *LocationService) Create(ctx context.Context, groupId uuid.UUID, data types.LocationCreate) (*types.LocationOut, error) {
|
||||
location, err := svc.repos.Locations.Create(ctx, groupId, data)
|
||||
return mappers.ToLocationOutErr(location, err)
|
||||
func (svc *LocationService) Create(ctx context.Context, groupId uuid.UUID, data repo.LocationCreate) (repo.LocationOut, error) {
|
||||
return svc.repos.Locations.Create(ctx, groupId, data)
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if location.Edges.Group.ID != groupId {
|
||||
return ErrNotOwner
|
||||
}
|
||||
|
||||
return svc.repos.Locations.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *LocationService) Update(ctx context.Context, groupId uuid.UUID, data types.LocationUpdate) (*types.LocationOut, error) {
|
||||
location, err := svc.repos.Locations.Get(ctx, data.ID)
|
||||
func (svc *LocationService) Update(ctx context.Context, groupId uuid.UUID, data repo.LocationUpdate) (repo.LocationOut, error) {
|
||||
location, err := svc.repos.Locations.GetOneByGroup(ctx, groupId, data.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if location.Edges.Group.ID != groupId {
|
||||
return nil, ErrNotOwner
|
||||
return repo.LocationOut{}, err
|
||||
}
|
||||
|
||||
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/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/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -23,18 +22,46 @@ type UserService struct {
|
|||
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
|
||||
// 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)
|
||||
if err != nil {
|
||||
return &types.UserOut{}, err
|
||||
return repo.UserOut{}, err
|
||||
}
|
||||
|
||||
hashed, _ := hasher.HashPassword(data.User.Password)
|
||||
usrCreate := types.UserCreate{
|
||||
Name: data.User.Name,
|
||||
Email: data.User.Email,
|
||||
hashed, _ := hasher.HashPassword(data.Password)
|
||||
usrCreate := repo.UserCreate{
|
||||
Name: data.Name,
|
||||
Email: data.Email,
|
||||
Password: hashed,
|
||||
IsSuperuser: false,
|
||||
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)
|
||||
if err != nil {
|
||||
return &types.UserOut{}, err
|
||||
return repo.UserOut{}, err
|
||||
}
|
||||
|
||||
for _, label := range defaultLabels() {
|
||||
_, err := svc.repos.Labels.Create(ctx, group.ID, label)
|
||||
if err != nil {
|
||||
return &types.UserOut{}, err
|
||||
return repo.UserOut{}, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, location := range defaultLocations() {
|
||||
_, err := svc.repos.Locations.Create(ctx, group.ID, location)
|
||||
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
|
||||
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)
|
||||
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)
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
created, err := svc.repos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{
|
||||
created, err := svc.repos.AuthTokens.CreateToken(ctx, repo.UserAuthTokenCreate{
|
||||
UserID: userId,
|
||||
TokenHash: newToken.Hash,
|
||||
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)
|
||||
|
||||
if err != nil || !hasher.CheckPasswordHash(password, usr.Password) {
|
||||
return types.UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||
if err != nil || !hasher.CheckPasswordHash(password, usr.PasswordHash) {
|
||||
return UserAuthTokenDetail{}, ErrorInvalidLogin
|
||||
}
|
||||
|
||||
return svc.createToken(ctx, usr.ID)
|
||||
|
@ -108,13 +135,13 @@ func (svc *UserService) Logout(ctx context.Context, token string) error {
|
|||
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)
|
||||
|
||||
dbToken, err := svc.repos.AuthTokens.GetUserFromToken(ctx, hash)
|
||||
|
||||
if err != nil {
|
||||
return types.UserAuthTokenDetail{}, ErrorInvalidToken
|
||||
return UserAuthTokenDetail{}, ErrorInvalidToken
|
||||
}
|
||||
|
||||
newToken, _ := svc.createToken(ctx, dbToken.ID)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package services
|
||||
|
||||
import "github.com/hay-kot/homebox/backend/internal/types"
|
||||
import (
|
||||
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||
)
|
||||
|
||||
func defaultLocations() []types.LocationCreate {
|
||||
return []types.LocationCreate{
|
||||
func defaultLocations() []repo.LocationCreate {
|
||||
return []repo.LocationCreate{
|
||||
{
|
||||
Name: "Living Room",
|
||||
},
|
||||
|
@ -31,8 +33,8 @@ func defaultLocations() []types.LocationCreate {
|
|||
}
|
||||
}
|
||||
|
||||
func defaultLabels() []types.LabelCreate {
|
||||
return []types.LabelCreate{
|
||||
func defaultLabels() []repo.LabelCreate {
|
||||
return []repo.LabelCreate{
|
||||
{
|
||||
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>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LocationCount } from "~~/lib/api/types/data-contracts";
|
||||
import { LocationOutCount } from "~~/lib/api/types/data-contracts";
|
||||
|
||||
defineProps({
|
||||
location: {
|
||||
type: Object as () => LocationCount,
|
||||
type: Object as () => LocationOutCount,
|
||||
required: true,
|
||||
},
|
||||
dense: {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Ref } from "vue";
|
|||
type Store = UseConfirmDialogReturn<any, boolean, boolean> & {
|
||||
text: Ref<string>;
|
||||
setup: boolean;
|
||||
open: (text: string) => Promise<UseConfirmDialogRevealResult<boolean, boolean>>;
|
||||
open: (text: string) => Promise<UseConfirmDialogRevealResult<boolean, boolean>>;
|
||||
};
|
||||
|
||||
const store: Partial<Store> = {
|
||||
|
@ -31,7 +31,7 @@ export function useConfirm(): Store {
|
|||
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;
|
||||
return await store.reveal();
|
||||
}
|
||||
|
|
|
@ -14,11 +14,9 @@ describe("first time user workflow (register, login)", () => {
|
|||
const api = client();
|
||||
const userData = {
|
||||
groupName: "test-group",
|
||||
user: {
|
||||
email: "test-user@email.com",
|
||||
name: "test-user",
|
||||
password: "test-password",
|
||||
},
|
||||
email: "test-user@email.com",
|
||||
name: "test-user",
|
||||
password: "test-password",
|
||||
};
|
||||
|
||||
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 () => {
|
||||
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(data.token).toBeTruthy();
|
||||
|
||||
|
|
|
@ -31,15 +31,13 @@ export async function sharedUserClient(): Promise<UserApi> {
|
|||
}
|
||||
const testUser = {
|
||||
groupName: "test-group",
|
||||
user: {
|
||||
email: "__test__@__test__.com",
|
||||
name: "__test__",
|
||||
password: "__test__",
|
||||
},
|
||||
email: "__test__@__test__.com",
|
||||
name: "__test__",
|
||||
password: "__test__",
|
||||
};
|
||||
|
||||
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) {
|
||||
cache.token = data.token;
|
||||
|
@ -49,7 +47,7 @@ export async function sharedUserClient(): Promise<UserApi> {
|
|||
const { response: registerResp } = await api.register(testUser);
|
||||
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);
|
||||
|
||||
cache.token = loginData.token;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
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";
|
||||
|
||||
export type LocationUpdate = LocationCreate;
|
||||
|
||||
export class LocationsApi extends BaseAPI {
|
||||
getAll() {
|
||||
return this.http.get<Results<LocationCount>>({ url: route("/locations") });
|
||||
return this.http.get<Results<LocationOutCount>>({ url: route("/locations") });
|
||||
}
|
||||
|
||||
create(body: LocationCreate) {
|
||||
|
|
|
@ -1,24 +1,11 @@
|
|||
import { BaseAPI, route } from "./base";
|
||||
|
||||
export type LoginResult = {
|
||||
token: string;
|
||||
expiresAt: string;
|
||||
};
|
||||
import { ApiSummary, TokenResponse, UserRegistration } from "./types/data-contracts";
|
||||
|
||||
export type LoginPayload = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type RegisterPayload = {
|
||||
user: {
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
};
|
||||
groupName: string;
|
||||
};
|
||||
|
||||
export type StatusResult = {
|
||||
health: boolean;
|
||||
versions: string[];
|
||||
|
@ -28,11 +15,11 @@ export type StatusResult = {
|
|||
|
||||
export class PublicApi extends BaseAPI {
|
||||
public status() {
|
||||
return this.http.get<StatusResult>({ url: route("/status") });
|
||||
return this.http.get<ApiSummary>({ url: route("/status") });
|
||||
}
|
||||
|
||||
public login(username: string, password: string) {
|
||||
return this.http.post<LoginPayload, LoginResult>({
|
||||
return this.http.post<LoginPayload, TokenResponse>({
|
||||
url: route("/users/login"),
|
||||
body: {
|
||||
username,
|
||||
|
@ -41,7 +28,7 @@ export class PublicApi extends BaseAPI {
|
|||
});
|
||||
}
|
||||
|
||||
public register(body: RegisterPayload) {
|
||||
return this.http.post<RegisterPayload, LoginResult>({ url: route("/users/register"), body });
|
||||
public register(body: UserRegistration) {
|
||||
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 {
|
||||
id: string;
|
||||
path: string;
|
||||
|
@ -54,10 +24,6 @@ export interface ItemAttachment {
|
|||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface ItemAttachmentToken {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface ItemAttachmentUpdate {
|
||||
title: string;
|
||||
type: string;
|
||||
|
@ -99,8 +65,6 @@ export interface ItemOut {
|
|||
/** Purchase */
|
||||
purchaseTime: Date;
|
||||
quantity: number;
|
||||
|
||||
/** Identifications */
|
||||
serialNumber: string;
|
||||
soldNotes: string;
|
||||
|
||||
|
@ -122,39 +86,11 @@ export interface ItemSummary {
|
|||
insured: boolean;
|
||||
labels: LabelSummary[];
|
||||
|
||||
/** Warranty */
|
||||
lifetimeWarranty: boolean;
|
||||
|
||||
/** Edges */
|
||||
location: LocationSummary;
|
||||
manufacturer: string;
|
||||
modelNumber: string;
|
||||
name: string;
|
||||
|
||||
/** Extras */
|
||||
notes: string;
|
||||
purchaseFrom: string;
|
||||
|
||||
/** @example 0 */
|
||||
purchasePrice: string;
|
||||
|
||||
/** Purchase */
|
||||
purchaseTime: Date;
|
||||
quantity: number;
|
||||
|
||||
/** Identifications */
|
||||
serialNumber: string;
|
||||
soldNotes: string;
|
||||
|
||||
/** @example 0 */
|
||||
soldPrice: string;
|
||||
|
||||
/** Sold */
|
||||
soldTime: Date;
|
||||
soldTo: string;
|
||||
updatedAt: Date;
|
||||
warrantyDetails: string;
|
||||
warrantyExpires: Date;
|
||||
}
|
||||
|
||||
export interface ItemUpdate {
|
||||
|
@ -220,15 +156,6 @@ export interface LabelSummary {
|
|||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface LocationCount {
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
itemCount: number;
|
||||
name: string;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface LocationCreate {
|
||||
description: string;
|
||||
name: string;
|
||||
|
@ -243,6 +170,15 @@ export interface LocationOut {
|
|||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface LocationOutCount {
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
id: string;
|
||||
itemCount: number;
|
||||
name: string;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface LocationSummary {
|
||||
createdAt: Date;
|
||||
description: string;
|
||||
|
@ -251,17 +187,6 @@ export interface LocationSummary {
|
|||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
expiresAt: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface UserIn {
|
||||
email: string;
|
||||
name: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface UserOut {
|
||||
email: string;
|
||||
groupId: string;
|
||||
|
@ -271,12 +196,53 @@ export interface UserOut {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface UserRegistration {
|
||||
groupName: string;
|
||||
user: UserIn;
|
||||
}
|
||||
|
||||
export interface UserUpdate {
|
||||
email: 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() {
|
||||
loading.value = true;
|
||||
const { error } = await api.register({
|
||||
user: {
|
||||
name: username.value,
|
||||
email: email.value,
|
||||
password: password.value,
|
||||
},
|
||||
name: username.value,
|
||||
email: email.value,
|
||||
password: password.value,
|
||||
groupName: groupName.value,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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", {
|
||||
state: () => ({
|
||||
allLocations: null as LocationCount[] | null,
|
||||
allLocations: null as LocationOutCount[] | null,
|
||||
client: useUserApi(),
|
||||
}),
|
||||
getters: {
|
||||
|
@ -12,7 +12,7 @@ export const useLocationStore = defineStore("locations", {
|
|||
* synched with the server by intercepting the API calls and updating on the
|
||||
* response
|
||||
*/
|
||||
locations(state): LocationCount[] {
|
||||
locations(state): LocationOutCount[] {
|
||||
if (state.allLocations === null) {
|
||||
Promise.resolve(this.refresh());
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export const useLocationStore = defineStore("locations", {
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
async refresh(): Promise<LocationCount[]> {
|
||||
async refresh(): Promise<LocationOutCount[]> {
|
||||
const result = await this.client.locations.getAll();
|
||||
if (result.error) {
|
||||
return result;
|
||||
|
|
|
@ -22,7 +22,9 @@ def date_types(*names: list[str]) -> 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"\?:"): ":",
|
||||
**date_types(
|
||||
"createdAt",
|
||||
|
|
Loading…
Reference in a new issue