forked from mirrors/homebox
labels create and get
This commit is contained in:
parent
f956ec8eb2
commit
8ece3bd7bf
24 changed files with 850 additions and 132 deletions
|
@ -1273,13 +1273,70 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"types.LabelCreate": {
|
"types.LabelCreate": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"types.LabelOut": {
|
"types.LabelOut": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/types.ItemSummary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"types.LabelSummary": {
|
"types.LabelSummary": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"types.LocationCreate": {
|
"types.LocationCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -1265,13 +1265,70 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"types.LabelCreate": {
|
"types.LabelCreate": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"types.LabelOut": {
|
"types.LabelOut": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/types.ItemSummary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"types.LabelSummary": {
|
"types.LabelSummary": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"types.LocationCreate": {
|
"types.LocationCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
@ -354,10 +354,47 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.LabelCreate:
|
types.LabelCreate:
|
||||||
|
properties:
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.LabelOut:
|
types.LabelOut:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
groupId:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
items:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/types.ItemSummary'
|
||||||
|
type: array
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.LabelSummary:
|
types.LabelSummary:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
groupId:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
types.LocationCreate:
|
types.LocationCreate:
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -2,6 +2,10 @@ package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/hay-kot/content/backend/internal/services"
|
||||||
|
"github.com/hay-kot/content/backend/internal/types"
|
||||||
|
"github.com/hay-kot/content/backend/pkgs/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleLabelsGetAll godoc
|
// HandleLabelsGetAll godoc
|
||||||
|
@ -13,6 +17,14 @@ import (
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user := services.UseUserCtx(r.Context())
|
||||||
|
labels, err := ctrl.svc.Labels.GetAll(r.Context(), user.GroupID)
|
||||||
|
if err != nil {
|
||||||
|
ctrl.log.Error(err, nil)
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.Respond(w, http.StatusOK, server.Results{Items: labels})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +38,23 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
createData := types.LabelCreate{}
|
||||||
|
if err := server.Decode(r, &createData); err != nil {
|
||||||
|
ctrl.log.Error(err, nil)
|
||||||
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := services.UseUserCtx(r.Context())
|
||||||
|
label, err := ctrl.svc.Labels.Create(r.Context(), user.GroupID, createData)
|
||||||
|
if err != nil {
|
||||||
|
ctrl.log.Error(err, nil)
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Respond(w, http.StatusCreated, label)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +68,18 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctrl.svc.Labels.Delete(r.Context(), user.GroupID, uid)
|
||||||
|
if err != nil {
|
||||||
|
ctrl.log.Error(err, nil)
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.Respond(w, http.StatusNoContent, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +93,18 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, err := ctrl.svc.Labels.Get(r.Context(), user.GroupID, uid)
|
||||||
|
if err != nil {
|
||||||
|
ctrl.log.Error(err, nil)
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.Respond(w, http.StatusOK, labels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,5 +118,24 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body := types.LabelUpdate{}
|
||||||
|
if err := server.Decode(r, &body); err != nil {
|
||||||
|
ctrl.log.Error(err, nil)
|
||||||
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ID = uid
|
||||||
|
result, err := ctrl.svc.Labels.Update(r.Context(), user.GroupID, body)
|
||||||
|
if err != nil {
|
||||||
|
ctrl.log.Error(err, nil)
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server.Respond(w, http.StatusOK, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
60
backend/internal/repo/repo_labels.go
Normal file
60
backend/internal/repo/repo_labels.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/hay-kot/content/backend/ent"
|
||||||
|
"github.com/hay-kot/content/backend/ent/group"
|
||||||
|
"github.com/hay-kot/content/backend/ent/label"
|
||||||
|
"github.com/hay-kot/content/backend/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntLabelRepository struct {
|
||||||
|
db *ent.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EntLabelRepository) Get(ctx context.Context, ID uuid.UUID) (*ent.Label, error) {
|
||||||
|
return r.db.Label.Query().
|
||||||
|
Where(label.ID(ID)).
|
||||||
|
WithGroup().
|
||||||
|
WithItems().
|
||||||
|
Only(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EntLabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]*ent.Label, error) {
|
||||||
|
return r.db.Label.Query().
|
||||||
|
Where(label.HasGroupWith(group.ID(groupId))).
|
||||||
|
WithGroup().
|
||||||
|
All(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EntLabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data types.LabelCreate) (*ent.Label, error) {
|
||||||
|
label, err := r.db.Label.Create().
|
||||||
|
SetName(data.Name).
|
||||||
|
SetDescription(data.Description).
|
||||||
|
SetColor(data.Color).
|
||||||
|
SetGroupID(groupdId).
|
||||||
|
Save(ctx)
|
||||||
|
|
||||||
|
label.Edges.Group = &ent.Group{ID: groupdId} // bootstrap group ID
|
||||||
|
return label, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EntLabelRepository) Update(ctx context.Context, data types.LabelUpdate) (*ent.Label, error) {
|
||||||
|
_, err := r.db.Label.UpdateOneID(data.ID).
|
||||||
|
SetName(data.Name).
|
||||||
|
SetDescription(data.Description).
|
||||||
|
SetColor(data.Color).
|
||||||
|
Save(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Get(ctx, data.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EntLabelRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.Label.DeleteOneID(id).Exec(ctx)
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ type AllRepos struct {
|
||||||
AuthTokens *EntTokenRepository
|
AuthTokens *EntTokenRepository
|
||||||
Groups *EntGroupRepository
|
Groups *EntGroupRepository
|
||||||
Locations *EntLocationRepository
|
Locations *EntLocationRepository
|
||||||
|
Labels *EntLabelRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func EntAllRepos(db *ent.Client) *AllRepos {
|
func EntAllRepos(db *ent.Client) *AllRepos {
|
||||||
|
@ -16,5 +17,6 @@ func EntAllRepos(db *ent.Client) *AllRepos {
|
||||||
AuthTokens: &EntTokenRepository{db},
|
AuthTokens: &EntTokenRepository{db},
|
||||||
Groups: &EntGroupRepository{db},
|
Groups: &EntGroupRepository{db},
|
||||||
Locations: &EntLocationRepository{db},
|
Locations: &EntLocationRepository{db},
|
||||||
|
Labels: &EntLabelRepository{db},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ type AllServices struct {
|
||||||
User *UserService
|
User *UserService
|
||||||
Admin *AdminService
|
Admin *AdminService
|
||||||
Location *LocationService
|
Location *LocationService
|
||||||
|
Labels *LabelService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServices(repos *repo.AllRepos) *AllServices {
|
func NewServices(repos *repo.AllRepos) *AllServices {
|
||||||
|
@ -13,5 +14,6 @@ func NewServices(repos *repo.AllRepos) *AllServices {
|
||||||
User: &UserService{repos},
|
User: &UserService{repos},
|
||||||
Admin: &AdminService{repos},
|
Admin: &AdminService{repos},
|
||||||
Location: &LocationService{repos},
|
Location: &LocationService{repos},
|
||||||
|
Labels: &LabelService{repos},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
backend/internal/services/mappers/labels.go
Normal file
32
backend/internal/services/mappers/labels.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hay-kot/content/backend/ent"
|
||||||
|
"github.com/hay-kot/content/backend/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToLabelSummary(label *ent.Label) *types.LabelSummary {
|
||||||
|
return &types.LabelSummary{
|
||||||
|
ID: label.ID,
|
||||||
|
GroupID: label.Edges.Group.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
|
||||||
|
}
|
63
backend/internal/services/service_labels.go
Normal file
63
backend/internal/services/service_labels.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/hay-kot/content/backend/internal/repo"
|
||||||
|
"github.com/hay-kot/content/backend/internal/services/mappers"
|
||||||
|
"github.com/hay-kot/content/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) 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) Delete(ctx context.Context, groupId uuid.UUID, id uuid.UUID) error {
|
||||||
|
label, err := svc.repos.Labels.Get(ctx, 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)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -1,7 +1,34 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
type LabelOut struct{}
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
type LabelCreate struct{}
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type LabelSummary struct{}
|
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"`
|
||||||
|
GroupID uuid.UUID `json:"groupId"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
|
@ -25,79 +25,50 @@
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const modals = reactive({
|
||||||
|
location: false,
|
||||||
|
label: false,
|
||||||
|
item: false,
|
||||||
|
});
|
||||||
|
|
||||||
const dropdown = [
|
const dropdown = [
|
||||||
{
|
{
|
||||||
name: 'Location',
|
name: 'Location',
|
||||||
action: () => {
|
action: () => {
|
||||||
modal.value = true;
|
modals.location = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Item / Asset',
|
name: 'Item / Asset',
|
||||||
action: () => {},
|
action: () => {
|
||||||
|
modals.item = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Label',
|
name: 'Label',
|
||||||
action: () => {},
|
action: () => {
|
||||||
|
modals.label = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// ----------------------------
|
|
||||||
// Location Stuff
|
|
||||||
// Should move to own component
|
|
||||||
const locationLoading = ref(false);
|
|
||||||
const locationForm = reactive({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const locationNameRef = ref(null);
|
|
||||||
const triggerFocus = ref(false);
|
|
||||||
const modal = ref(false);
|
|
||||||
|
|
||||||
whenever(
|
|
||||||
() => modal.value,
|
|
||||||
() => {
|
|
||||||
triggerFocus.value = true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
async function createLocation() {
|
|
||||||
locationLoading.value = true;
|
|
||||||
const { data } = await api.locations.create(locationForm);
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
navigateTo(`/location/${data.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
locationLoading.value = false;
|
|
||||||
modal.value = false;
|
|
||||||
locationForm.name = '';
|
|
||||||
locationForm.description = '';
|
|
||||||
triggerFocus.value = false;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!--
|
||||||
|
Confirmation Modal is a singleton used by all components so we render
|
||||||
|
it here to ensure it's always available. Possibly could move this further
|
||||||
|
up the tree
|
||||||
|
-->
|
||||||
<ModalConfirm />
|
<ModalConfirm />
|
||||||
<BaseModal v-model="modal">
|
<LabelCreateModal v-model="modals.label" />
|
||||||
<template #title> Create Location </template>
|
<LocationCreateModal v-model="modals.location" />
|
||||||
<form @submit.prevent="createLocation">
|
|
||||||
<FormTextField
|
|
||||||
:trigger-focus="triggerFocus"
|
|
||||||
ref="locationNameRef"
|
|
||||||
:autofocus="true"
|
|
||||||
label="Location Name"
|
|
||||||
v-model="locationForm.name"
|
|
||||||
/>
|
|
||||||
<FormTextField label="Location Description" v-model="locationForm.description" />
|
|
||||||
<div class="modal-action">
|
|
||||||
<BaseButton type="submit" :loading="locationLoading"> Create </BaseButton>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</BaseModal>
|
|
||||||
<BaseContainer is="header" class="py-6">
|
<BaseContainer is="header" class="py-6">
|
||||||
<h2 class="mt-1 text-4xl font-bold tracking-tight text-base-content sm:text-5xl lg:text-6xl">Homebox</h2>
|
<h2 class="mt-1 text-4xl font-bold tracking-tight text-base-content sm:text-5xl lg:text-6xl flex">
|
||||||
|
HomeB
|
||||||
|
<AppLogo class="w-12 -mb-4" style="padding-left: 3px; padding-right: 2px" />
|
||||||
|
x
|
||||||
|
</h2>
|
||||||
<div class="ml-1 mt-2 text-lg text-base-content/50 space-x-2">
|
<div class="ml-1 mt-2 text-lg text-base-content/50 space-x-2">
|
||||||
<template v-for="link in links">
|
<template v-for="link in links">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
|
|
123
frontend/components/App/Logo.vue
Normal file
123
frontend/components/App/Logo.vue
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<template>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 596.5055138004384 585.369487986598">
|
||||||
|
<g
|
||||||
|
stroke-linecap="round"
|
||||||
|
transform="translate(437.568672588907 210.93877417794465) rotate(332.3235338946895 66.970006481548 27.559467997664797)"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M-0.3 -0.89 L131.27 -1.27 L131.05 52.32 L-2.89 53.3"
|
||||||
|
stroke="none"
|
||||||
|
stroke-width="0"
|
||||||
|
fill="#15aabf"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M1.61 2.92 C34.39 0.43, 67.49 -3.76, 136.43 -0.16 M-1.81 1.81 C54.26 1.13, 105.28 -0.86, 133.28 -0.04 M132.66 3.06 C133.92 12.97, 132.16 31.97, 132.92 51.2 M134.28 1.16 C131.72 11.69, 133.56 23.47, 134.52 54.65 M133.93 53.09 C103.49 59.22, 80.28 58.51, -1.19 52.09 M133.03 54.88 C92.08 50.88, 53.71 52.46, -0.4 54.88 M-3.64 53.12 C-1.65 33.33, 3.49 15.58, -2.23 -1.93 M-1.08 54.38 C0.63 44.74, -0.42 33.82, 0.29 1.34"
|
||||||
|
stroke="#000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(308.4481755172761 281.2115533662909) rotate(0 1.1385609918289674 145.9953857867422)">
|
||||||
|
<path
|
||||||
|
d="M-1.01 -2.17 C-1.07 46.71, 0.3 244.44, -0.46 294.16 M3.63 2.83 C3.44 50.71, -0.72 241.22, -1.36 289.41"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(308.16925883018916 284.66360015581995) rotate(0 135.1525798049602 -68.20042785962323)">
|
||||||
|
<path
|
||||||
|
d="M2.47 0.47 C46.8 -21.36, 220.33 -110.19, 264.96 -133.2 M0.37 -1.74 C45.62 -24.11, 225.01 -114.58, 269.93 -136.88"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(311.39372316987726 570.9674003164946) rotate(0 136.24116036890297 -67.43777376368234)">
|
||||||
|
<path
|
||||||
|
d="M-2.63 2.46 C20.4 -9.48, 94.34 -47.87, 140.63 -71.17 C186.92 -94.47, 252.17 -126.49, 275.11 -137.33 M1.14 1.33 C23.81 -10.35, 94.55 -46.04, 139.83 -68.58 C185.12 -91.11, 249.48 -121.76, 272.86 -133.89"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(580.6092831051336 150.72201134532952) rotate(0 1.5678417062894852 142.75141008423634)">
|
||||||
|
<path
|
||||||
|
d="M2.66 -1.91 C3.6 45.58, 2.41 239.01, 2.99 287.41 M0.67 3.23 C1.39 51.03, -0.51 235.96, 0.31 282.74"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(306.6976102947664 283.14653391715) rotate(0 -140.18354779216435 -59.60806644015338)">
|
||||||
|
<path
|
||||||
|
d="M-0.81 0.62 C-48.48 -18.36, -235.46 -96.23, -283.34 -115.75 M3.94 -1.52 C-44.13 -21.12, -236.56 -99.84, -284.31 -119.83"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(304.3414324224632 572.5226612839633) rotate(0 -144.27019052747903 -64.7761163684645)">
|
||||||
|
<path
|
||||||
|
d="M2.34 1.71 C-46 -19.52, -242.75 -105.04, -290.88 -126.78 M0.17 0.18 C-47.25 -21.98, -237.9 -110.2, -285.78 -131.27"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g
|
||||||
|
transform="translate(15.275892138818847 448.50738095516135) rotate(0 -0.49579063445983707 -143.71703352554232)"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M-2.4 0.97 C-2.94 -47.38, -0.78 -240.15, -0.9 -288.41 M1.49 -0.97 C0.55 -49.09, -0.95 -237.81, -2.03 -285.33"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(10.301143858432795 164.72182108536072) rotate(0 142.35890827057267 -76.26873721417542)">
|
||||||
|
<path
|
||||||
|
d="M2.04 -1.02 C49.94 -26.43, 238.14 -126.5, 285.02 -151.52 M-0.3 -4.04 C47.43 -29.16, 234.44 -124.45, 282.68 -148.52"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(291.46813332015165 14.258444139957646) rotate(0 143.3244001532809 66.53622476241344)">
|
||||||
|
<path
|
||||||
|
d="M-0.18 -1.16 C46.98 21.36, 236.83 111.22, 284.98 134.14 M-3.72 -4.26 C44.15 18.77, 241.76 114.69, 290.37 137.33"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g stroke-linecap="round">
|
||||||
|
<g transform="translate(175.60844139934756 81.23280016017816) rotate(0 131.7777041676277 66.73388742398038)">
|
||||||
|
<path
|
||||||
|
d="M-1.87 -0.8 C42.4 22.26, 220.78 113.99, 265.42 137.17 M2.32 -3.7 C46.4 18.63, 220.35 109.95, 264.21 132.92"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-width="2"
|
||||||
|
fill="none"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
27
frontend/components/Label/Chip.vue
Normal file
27
frontend/components/Label/Chip.vue
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Label } from '~~/lib/api/classes/labels';
|
||||||
|
defineProps({
|
||||||
|
label: {
|
||||||
|
type: Object as () => Label,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const badge = ref(null);
|
||||||
|
const isHover = useElementHover(badge);
|
||||||
|
const { focused } = useFocus(badge);
|
||||||
|
|
||||||
|
const isActive = computed(() => isHover.value || focused.value);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink ref="badge" :to="`/label/${label.id}`">
|
||||||
|
<span class="badge badge-lg p-4">
|
||||||
|
<label class="swap swap-rotate" :class="isActive ? 'swap-active' : ''">
|
||||||
|
<Icon name="heroicons-arrow-right" class="mr-2 swap-on"></Icon>
|
||||||
|
<Icon name="heroicons-tag" class="mr-2 swap-off"></Icon>
|
||||||
|
</label>
|
||||||
|
{{ label.name }}
|
||||||
|
</span>
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
66
frontend/components/Label/CreateModal.vue
Normal file
66
frontend/components/Label/CreateModal.vue
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<BaseModal v-model="modal">
|
||||||
|
<template #title> Create Label </template>
|
||||||
|
<form @submit.prevent="create">
|
||||||
|
<FormTextField
|
||||||
|
:trigger-focus="focused"
|
||||||
|
ref="locationNameRef"
|
||||||
|
:autofocus="true"
|
||||||
|
label="Label Name"
|
||||||
|
v-model="form.name"
|
||||||
|
/>
|
||||||
|
<FormTextField label="Label Description" v-model="form.description" />
|
||||||
|
<div class="modal-action">
|
||||||
|
<BaseButton type="submit" :loading="loading"> Create </BaseButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</BaseModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const modal = useVModel(props, 'modelValue');
|
||||||
|
const loading = ref(false);
|
||||||
|
const focused = ref(false);
|
||||||
|
const form = reactive({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
color: '', // Future!
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
form.name = '';
|
||||||
|
form.description = '';
|
||||||
|
form.color = '';
|
||||||
|
focused.value = false;
|
||||||
|
modal.value = false;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
whenever(
|
||||||
|
() => modal.value,
|
||||||
|
() => {
|
||||||
|
focused.value = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const api = useUserApi();
|
||||||
|
const toast = useNotifier();
|
||||||
|
|
||||||
|
async function create() {
|
||||||
|
const { data, error } = await api.labels.create(form);
|
||||||
|
if (error) {
|
||||||
|
toast.error("Couldn't create label");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Label created');
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
</script>
|
70
frontend/components/Location/CreateModal.vue
Normal file
70
frontend/components/Location/CreateModal.vue
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<BaseModal v-model="modal">
|
||||||
|
<template #title> Create Location </template>
|
||||||
|
<form @submit.prevent="create">
|
||||||
|
<FormTextField
|
||||||
|
:trigger-focus="focused"
|
||||||
|
ref="locationNameRef"
|
||||||
|
:autofocus="true"
|
||||||
|
label="Location Name"
|
||||||
|
v-model="form.name"
|
||||||
|
/>
|
||||||
|
<FormTextField label="Location Description" v-model="form.description" />
|
||||||
|
<div class="modal-action">
|
||||||
|
<BaseButton type="submit" :loading="loading"> Create </BaseButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</BaseModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const modal = useVModel(props, 'modelValue');
|
||||||
|
const loading = ref(false);
|
||||||
|
const focused = ref(false);
|
||||||
|
const form = reactive({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
whenever(
|
||||||
|
() => modal.value,
|
||||||
|
() => {
|
||||||
|
focused.value = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
form.name = '';
|
||||||
|
form.description = '';
|
||||||
|
focused.value = false;
|
||||||
|
modal.value = false;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = useUserApi();
|
||||||
|
const toast = useNotifier();
|
||||||
|
|
||||||
|
async function create() {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
const { data, error } = await api.locations.create(form);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error("Couldn't create location");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
toast.success('Location created');
|
||||||
|
navigateTo(`/location/${data.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,3 +0,0 @@
|
||||||
export type Results<T> = {
|
|
||||||
items: T[];
|
|
||||||
};
|
|
32
frontend/lib/api/classes/labels.ts
Normal file
32
frontend/lib/api/classes/labels.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { BaseAPI, UrlBuilder } from '../base';
|
||||||
|
import { Details, OutType, Results } from './types';
|
||||||
|
|
||||||
|
export type LabelCreate = Details & {
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LabelUpdate = LabelCreate;
|
||||||
|
|
||||||
|
export type Label = LabelCreate & OutType;
|
||||||
|
|
||||||
|
export class LabelsApi extends BaseAPI {
|
||||||
|
async getAll() {
|
||||||
|
return this.http.get<Results<Label>>(UrlBuilder('/labels'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(label: LabelCreate) {
|
||||||
|
return this.http.post<LabelCreate, Label>(UrlBuilder('/labels'), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(id: string) {
|
||||||
|
return this.http.get<Label>(UrlBuilder(`/labels/${id}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string) {
|
||||||
|
return this.http.delete<void>(UrlBuilder(`/labels/${id}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, label: LabelUpdate) {
|
||||||
|
return this.http.put<LabelUpdate, Label>(UrlBuilder(`/labels/${id}`), label);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,12 @@
|
||||||
import { BaseAPI, UrlBuilder } from '../base';
|
import { BaseAPI, UrlBuilder } from '../base';
|
||||||
import { type Results } from '../base/base-types';
|
import { Details, OutType, Results } from './types';
|
||||||
|
|
||||||
export type LocationCreate = {
|
export type LocationCreate = Details;
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Location = LocationCreate & {
|
export type Location = LocationCreate &
|
||||||
id: string;
|
OutType & {
|
||||||
groupId: string;
|
groupId: string;
|
||||||
createdAt: string;
|
};
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LocationUpdate = LocationCreate;
|
export type LocationUpdate = LocationCreate;
|
||||||
|
|
||||||
|
|
19
frontend/lib/api/classes/types/index.ts
Normal file
19
frontend/lib/api/classes/types/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* OutType is the base type that is returned from the API.
|
||||||
|
* In contains the common fields that are included with every
|
||||||
|
* API response that isn't a bulk result
|
||||||
|
*/
|
||||||
|
export type OutType = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Details = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Results<T> = {
|
||||||
|
items: T[];
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import { BaseAPI, UrlBuilder } from "./base";
|
import { BaseAPI, UrlBuilder } from './base';
|
||||||
|
|
||||||
export type LoginResult = {
|
export type LoginResult = {
|
||||||
token: string;
|
token: string;
|
||||||
|
@ -21,19 +21,13 @@ export type RegisterPayload = {
|
||||||
|
|
||||||
export class PublicApi extends BaseAPI {
|
export class PublicApi extends BaseAPI {
|
||||||
public login(username: string, password: string) {
|
public login(username: string, password: string) {
|
||||||
return this.http.post<LoginPayload, LoginResult>(
|
return this.http.post<LoginPayload, LoginResult>(UrlBuilder('/users/login'), {
|
||||||
UrlBuilder("/users/login"),
|
|
||||||
{
|
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public register(payload: RegisterPayload) {
|
public register(payload: RegisterPayload) {
|
||||||
return this.http.post<RegisterPayload, LoginResult>(
|
return this.http.post<RegisterPayload, LoginResult>(UrlBuilder('/users/register'), payload);
|
||||||
UrlBuilder("/users/register"),
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Requests } from "~~/lib/requests";
|
import { Requests } from '~~/lib/requests';
|
||||||
import { BaseAPI, UrlBuilder } from "./base";
|
import { BaseAPI, UrlBuilder } from './base';
|
||||||
import { LocationsApi } from "./classes/locations";
|
import { LabelsApi } from './classes/labels';
|
||||||
|
import { LocationsApi } from './classes/locations';
|
||||||
|
|
||||||
export type Result<T> = {
|
export type Result<T> = {
|
||||||
item: T;
|
item: T;
|
||||||
|
@ -15,20 +16,21 @@ export type User = {
|
||||||
|
|
||||||
export class UserApi extends BaseAPI {
|
export class UserApi extends BaseAPI {
|
||||||
locations: LocationsApi;
|
locations: LocationsApi;
|
||||||
|
labels: LabelsApi;
|
||||||
constructor(requests: Requests) {
|
constructor(requests: Requests) {
|
||||||
super(requests);
|
super(requests);
|
||||||
|
|
||||||
this.locations = new LocationsApi(requests);
|
this.locations = new LocationsApi(requests);
|
||||||
|
this.labels = new LabelsApi(requests);
|
||||||
|
|
||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public self() {
|
public self() {
|
||||||
return this.http.get<Result<User>>(UrlBuilder("/users/self"));
|
return this.http.get<Result<User>>(UrlBuilder('/users/self'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public logout() {
|
public logout() {
|
||||||
return this.http.post<object, void>(UrlBuilder("/users/logout"), {});
|
return this.http.post<object, void>(UrlBuilder('/users/logout'), {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Location } from '~~/lib/api/classes/locations';
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'home',
|
layout: 'home',
|
||||||
});
|
});
|
||||||
|
@ -8,17 +7,21 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const locations = ref<Location[]>([]);
|
|
||||||
onMounted(async () => {
|
const { data: locations } = useAsyncData('locations', async () => {
|
||||||
const { data } = await api.locations.getAll();
|
const { data } = await api.locations.getAll();
|
||||||
if (data) {
|
return data.items;
|
||||||
locations.value = data.items;
|
});
|
||||||
}
|
|
||||||
|
const { data: labels } = useAsyncData('labels', async () => {
|
||||||
|
const { data } = await api.labels.getAll();
|
||||||
|
return data.items;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseContainer>
|
<BaseContainer class="space-y-16">
|
||||||
|
<section>
|
||||||
<BaseSectionHeader class="mb-5"> Storage Locations </BaseSectionHeader>
|
<BaseSectionHeader class="mb-5"> Storage Locations </BaseSectionHeader>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
|
@ -35,5 +38,13 @@
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<BaseSectionHeader class="mb-5"> Labels </BaseSectionHeader>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<LabelChip v-for="label in labels" :label="label" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -105,7 +105,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<header class="sm:px-6 py-2 lg:p-14 sm:py-6">
|
<header class="sm:px-6 py-2 lg:p-14 sm:py-6">
|
||||||
<h2 class="mt-1 text-4xl font-bold tracking-tight text-base-content sm:text-5xl lg:text-6xl">Homebox</h2>
|
<h2 class="mt-1 text-4xl font-bold tracking-tight text-base-content sm:text-5xl lg:text-6xl flex">
|
||||||
|
HomeB
|
||||||
|
<AppLogo class="w-12 -mb-4" style="padding-left: 3px; padding-right: 2px" />
|
||||||
|
x
|
||||||
|
</h2>
|
||||||
<p class="ml-1 text-lg text-base-content/50">Track, Organize, and Manage your Shit.</p>
|
<p class="ml-1 text-lg text-base-content/50">Track, Organize, and Manage your Shit.</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="grid p-6 sm:place-items-center min-h-[50vh]">
|
<div class="grid p-6 sm:place-items-center min-h-[50vh]">
|
||||||
|
|
|
@ -12,9 +12,18 @@
|
||||||
|
|
||||||
const preferences = useLocationViewPreferences();
|
const preferences = useLocationViewPreferences();
|
||||||
|
|
||||||
const location = ref<Location | null>(null);
|
|
||||||
const locationId = computed<string>(() => route.params.id as string);
|
const locationId = computed<string>(() => route.params.id as string);
|
||||||
|
|
||||||
|
const { data: location } = useAsyncData(locationId.value, async () => {
|
||||||
|
const { data, error } = await api.locations.get(locationId.value);
|
||||||
|
if (error) {
|
||||||
|
toast.error('Failed to load location');
|
||||||
|
navigateTo('/home');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
function maybeTimeAgo(date?: string): string {
|
function maybeTimeAgo(date?: string): string {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return '??';
|
return '??';
|
||||||
|
@ -41,17 +50,6 @@
|
||||||
return dt;
|
return dt;
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const { data, error } = await api.locations.get(locationId.value);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
toast.error('Failed to load location');
|
|
||||||
navigateTo('/home');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
location.value = data;
|
|
||||||
});
|
|
||||||
|
|
||||||
const { reveal } = useConfirm();
|
const { reveal } = useConfirm();
|
||||||
|
|
||||||
async function confirmDelete() {
|
async function confirmDelete() {
|
||||||
|
|
Loading…
Reference in a new issue