mirror of
https://github.com/hay-kot/homebox.git
synced 2025-08-05 17:10:30 +00:00
group API contracts
This commit is contained in:
parent
67d421db5d
commit
9613bab0da
16 changed files with 471 additions and 71 deletions
|
@ -21,6 +21,63 @@ const docTemplate = `{
|
||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/v1/groups": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Group"
|
||||||
|
],
|
||||||
|
"summary": "Get the current user's group",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/repo.Group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Group"
|
||||||
|
],
|
||||||
|
"summary": "Updates some fields of the current users group",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "User Data",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/repo.GroupUpdate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/repo.Group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/groups/invitations": {
|
"/v1/groups/invitations": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -32,7 +89,7 @@ const docTemplate = `{
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"User"
|
"Group"
|
||||||
],
|
],
|
||||||
"summary": "Get the current user",
|
"summary": "Get the current user",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -1116,6 +1173,37 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"repo.Group": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repo.GroupUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repo.ItemAttachment": {
|
"repo.ItemAttachment": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -13,6 +13,63 @@
|
||||||
},
|
},
|
||||||
"basePath": "/api",
|
"basePath": "/api",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/v1/groups": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Group"
|
||||||
|
],
|
||||||
|
"summary": "Get the current user's group",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/repo.Group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Group"
|
||||||
|
],
|
||||||
|
"summary": "Updates some fields of the current users group",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "User Data",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/repo.GroupUpdate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/repo.Group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/groups/invitations": {
|
"/v1/groups/invitations": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -24,7 +81,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"User"
|
"Group"
|
||||||
],
|
],
|
||||||
"summary": "Get the current user",
|
"summary": "Get the current user",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -1108,6 +1165,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"repo.Group": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repo.GroupUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repo.ItemAttachment": {
|
"repo.ItemAttachment": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -9,6 +9,26 @@ definitions:
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
repo.Group:
|
||||||
|
properties:
|
||||||
|
createdAt:
|
||||||
|
type: string
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
updatedAt:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
repo.GroupUpdate:
|
||||||
|
properties:
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
repo.ItemAttachment:
|
repo.ItemAttachment:
|
||||||
properties:
|
properties:
|
||||||
createdAt:
|
createdAt:
|
||||||
|
@ -415,6 +435,40 @@ info:
|
||||||
title: Go API Templates
|
title: Go API Templates
|
||||||
version: "1.0"
|
version: "1.0"
|
||||||
paths:
|
paths:
|
||||||
|
/v1/groups:
|
||||||
|
get:
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/repo.Group'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Get the current user's group
|
||||||
|
tags:
|
||||||
|
- Group
|
||||||
|
put:
|
||||||
|
parameters:
|
||||||
|
- description: User Data
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/repo.GroupUpdate'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/repo.Group'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Updates some fields of the current users group
|
||||||
|
tags:
|
||||||
|
- Group
|
||||||
/v1/groups/invitations:
|
/v1/groups/invitations:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -435,7 +489,7 @@ paths:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Get the current user
|
summary: Get the current user
|
||||||
tags:
|
tags:
|
||||||
- User
|
- Group
|
||||||
/v1/items:
|
/v1/items:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -110,7 +110,7 @@ func run(cfg *config.Config) error {
|
||||||
|
|
||||||
app.db = c
|
app.db = c
|
||||||
app.repos = repo.New(c, cfg.Storage.Data)
|
app.repos = repo.New(c, cfg.Storage.Data)
|
||||||
app.services = services.NewServices(app.repos)
|
app.services = services.New(app.repos)
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Start Server
|
// Start Server
|
||||||
|
|
|
@ -72,6 +72,10 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
|
||||||
|
|
||||||
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
|
r.Post(v1Base("/groups/invitations"), v1Ctrl.HandleGroupInvitationsCreate())
|
||||||
|
|
||||||
|
// TODO: I don't like /groups being the URL for users
|
||||||
|
r.Get(v1Base("/groups"), v1Ctrl.HandleGroupGet())
|
||||||
|
r.Put(v1Base("/groups"), v1Ctrl.HandleGroupUpdate())
|
||||||
|
|
||||||
r.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll())
|
r.Get(v1Base("/locations"), v1Ctrl.HandleLocationGetAll())
|
||||||
r.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate())
|
r.Post(v1Base("/locations"), v1Ctrl.HandleLocationCreate())
|
||||||
r.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet())
|
r.Get(v1Base("/locations/{id}"), v1Ctrl.HandleLocationGet())
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -22,9 +23,61 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleUserSelf godoc
|
// HandleGroupGet godoc
|
||||||
|
// @Summary Get the current user's group
|
||||||
|
// @Tags Group
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} repo.Group
|
||||||
|
// @Router /v1/groups [Get]
|
||||||
|
// @Security Bearer
|
||||||
|
func (ctrl *V1Controller) HandleGroupGet() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := services.NewContext(r.Context())
|
||||||
|
|
||||||
|
group, err := ctrl.svc.Group.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to get group")
|
||||||
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Respond(w, http.StatusOK, group)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleGroupUpdate godoc
|
||||||
|
// @Summary Updates some fields of the current users group
|
||||||
|
// @Tags Group
|
||||||
|
// @Produce json
|
||||||
|
// @Param payload body repo.GroupUpdate true "User Data"
|
||||||
|
// @Success 200 {object} repo.Group
|
||||||
|
// @Router /v1/groups [Put]
|
||||||
|
// @Security Bearer
|
||||||
|
func (ctrl *V1Controller) HandleGroupUpdate() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := repo.GroupUpdate{}
|
||||||
|
|
||||||
|
if err := server.Decode(r, &data); err != nil {
|
||||||
|
server.RespondError(w, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := services.NewContext(r.Context())
|
||||||
|
|
||||||
|
group, err := ctrl.svc.Group.UpdateGroup(ctx, data)
|
||||||
|
if err != nil {
|
||||||
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Respond(w, http.StatusOK, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleGroupInvitationsCreate godoc
|
||||||
// @Summary Get the current user
|
// @Summary Get the current user
|
||||||
// @Tags User
|
// @Tags Group
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body GroupInvitationCreate true "User Data"
|
// @Param payload body GroupInvitationCreate true "User Data"
|
||||||
// @Success 200 {object} GroupInvitation
|
// @Success 200 {object} GroupInvitation
|
||||||
|
@ -36,7 +89,7 @@ func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
|
||||||
|
|
||||||
if err := server.Decode(r, &data); err != nil {
|
if err := server.Decode(r, &data); err != nil {
|
||||||
log.Err(err).Msg("failed to decode user registration data")
|
log.Err(err).Msg("failed to decode user registration data")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +99,7 @@ func (ctrl *V1Controller) HandleGroupInvitationsCreate() http.HandlerFunc {
|
||||||
|
|
||||||
ctx := services.NewContext(r.Context())
|
ctx := services.NewContext(r.Context())
|
||||||
|
|
||||||
token, err := ctrl.svc.User.NewInvitation(ctx, data.Uses, data.ExpiresAt)
|
token, err := ctrl.svc.Group.NewInvitation(ctx, data.Uses, data.ExpiresAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("failed to create new token")
|
log.Err(err).Msg("failed to create new token")
|
||||||
server.RespondError(w, http.StatusInternalServerError, err)
|
server.RespondError(w, http.StatusInternalServerError, err)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
|
"github.com/hay-kot/homebox/backend/ent/group"
|
||||||
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken"
|
"github.com/hay-kot/homebox/backend/ent/groupinvitationtoken"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,11 +16,16 @@ type GroupRepository struct {
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Group struct {
|
Group struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID `json:"id,omitempty"`
|
||||||
Name string
|
Name string `json:"name,omitempty"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||||
Currency string
|
Currency string `json:"currency,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupUpdate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupInvitationCreate struct {
|
GroupInvitationCreate struct {
|
||||||
|
@ -69,6 +75,17 @@ func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group,
|
||||||
Save(ctx))
|
Save(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data GroupUpdate) (Group, error) {
|
||||||
|
currency := group.Currency(data.Currency)
|
||||||
|
|
||||||
|
entity, err := r.db.Group.UpdateOneID(ID).
|
||||||
|
SetName(data.Name).
|
||||||
|
SetCurrency(currency).
|
||||||
|
Save(ctx)
|
||||||
|
|
||||||
|
return mapToGroupErr(entity, err)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
|
func (r *GroupRepository) GroupByID(ctx context.Context, id uuid.UUID) (Group, error) {
|
||||||
return mapToGroupErr(r.db.Group.Get(ctx, id))
|
return mapToGroupErr(r.db.Group.Get(ctx, id))
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,3 +18,16 @@ func Test_Group_Create(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, g.ID, foundGroup.ID)
|
assert.Equal(t, g.ID, foundGroup.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Group_Update(t *testing.T) {
|
||||||
|
g, err := tRepos.Groups.GroupCreate(context.Background(), "test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
g, err = tRepos.Groups.GroupUpdate(context.Background(), g.ID, GroupUpdate{
|
||||||
|
Name: "test2",
|
||||||
|
Currency: "eur",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test2", g.Name)
|
||||||
|
assert.Equal(t, "eur", g.Currency)
|
||||||
|
}
|
||||||
|
|
|
@ -4,18 +4,20 @@ import "github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
|
|
||||||
type AllServices struct {
|
type AllServices struct {
|
||||||
User *UserService
|
User *UserService
|
||||||
|
Group *GroupService
|
||||||
Location *LocationService
|
Location *LocationService
|
||||||
Labels *LabelService
|
Labels *LabelService
|
||||||
Items *ItemService
|
Items *ItemService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServices(repos *repo.AllRepos) *AllServices {
|
func New(repos *repo.AllRepos) *AllServices {
|
||||||
if repos == nil {
|
if repos == nil {
|
||||||
panic("repos cannot be nil")
|
panic("repos cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AllServices{
|
return &AllServices{
|
||||||
User: &UserService{repos},
|
User: &UserService{repos},
|
||||||
|
Group: &GroupService{repos},
|
||||||
Location: &LocationService{repos},
|
Location: &LocationService{repos},
|
||||||
Labels: &LabelService{repos},
|
Labels: &LabelService{repos},
|
||||||
Items: &ItemService{
|
Items: &ItemService{
|
||||||
|
|
|
@ -63,7 +63,7 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
tClient = client
|
tClient = client
|
||||||
tRepos = repo.New(tClient, os.TempDir()+"/homebox")
|
tRepos = repo.New(tClient, os.TempDir()+"/homebox")
|
||||||
tSvc = NewServices(tRepos)
|
tSvc = New(tRepos)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
bootstrap()
|
bootstrap()
|
||||||
|
|
44
backend/internal/services/service_group.go
Normal file
44
backend/internal/services/service_group.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hay-kot/homebox/backend/internal/repo"
|
||||||
|
"github.com/hay-kot/homebox/backend/pkgs/hasher"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupService struct {
|
||||||
|
repos *repo.AllRepos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *GroupService) Get(ctx Context) (repo.Group, error) {
|
||||||
|
return svc.repos.Groups.GroupByID(ctx.Context, ctx.GID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *GroupService) UpdateGroup(ctx Context, data repo.GroupUpdate) (repo.Group, error) {
|
||||||
|
if data.Name == "" {
|
||||||
|
data.Name = ctx.User.GroupName
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Currency == "" {
|
||||||
|
return repo.Group{}, errors.New("currency cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc.repos.Groups.GroupUpdate(ctx.Context, ctx.GID, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *GroupService) NewInvitation(ctx Context, uses int, expiresAt time.Time) (string, error) {
|
||||||
|
token := hasher.GenerateToken()
|
||||||
|
|
||||||
|
_, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GID, repo.GroupInvitationCreate{
|
||||||
|
Token: token.Hash,
|
||||||
|
Uses: uses,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Raw, nil
|
||||||
|
}
|
|
@ -186,21 +186,6 @@ func (svc *UserService) DeleteSelf(ctx context.Context, ID uuid.UUID) error {
|
||||||
return svc.repos.Users.Delete(ctx, ID)
|
return svc.repos.Users.Delete(ctx, ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *UserService) NewInvitation(ctx Context, uses int, expiresAt time.Time) (string, error) {
|
|
||||||
token := hasher.GenerateToken()
|
|
||||||
|
|
||||||
_, err := svc.repos.Groups.InvitationCreate(ctx, ctx.GID, repo.GroupInvitationCreate{
|
|
||||||
Token: token.Hash,
|
|
||||||
Uses: uses,
|
|
||||||
ExpiresAt: expiresAt,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token.Raw, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) {
|
func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) {
|
||||||
usr, err := svc.repos.Users.GetOneId(ctx, ctx.UID)
|
usr, err := svc.repos.Users.GetOneId(ctx, ctx.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { describe, test, expect } from "vitest";
|
import { describe, test, expect } from "vitest";
|
||||||
import { factories } from "./factories";
|
import { factories } from "./factories";
|
||||||
import { sharedUserClient } from "./test-utils";
|
|
||||||
|
|
||||||
describe("[GET] /api/v1/status", () => {
|
describe("[GET] /api/v1/status", () => {
|
||||||
test("server should respond", async () => {
|
test("server should respond", async () => {
|
||||||
|
@ -32,43 +31,4 @@ describe("first time user workflow (register, login, join group)", () => {
|
||||||
expect(response.status).toBe(204);
|
expect(response.status).toBe(204);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user should be able to join create join token and have user signup", async () => {
|
|
||||||
// Setup User 1 Token
|
|
||||||
|
|
||||||
const client = await sharedUserClient();
|
|
||||||
const { data: user1 } = await client.user.self();
|
|
||||||
|
|
||||||
const { response, data } = await client.group.createInvitation({
|
|
||||||
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),
|
|
||||||
uses: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(response.status).toBe(201);
|
|
||||||
expect(data.token).toBeTruthy();
|
|
||||||
|
|
||||||
// Create User 2 with token
|
|
||||||
|
|
||||||
const duplicateUser = factories.user();
|
|
||||||
duplicateUser.token = data.token;
|
|
||||||
|
|
||||||
const { response: registerResp } = await api.register(duplicateUser);
|
|
||||||
expect(registerResp.status).toBe(204);
|
|
||||||
|
|
||||||
const { response: loginResp, data: loginData } = await api.login(duplicateUser.email, duplicateUser.password);
|
|
||||||
expect(loginResp.status).toBe(200);
|
|
||||||
|
|
||||||
// Get Self and Assert
|
|
||||||
|
|
||||||
const client2 = factories.client.user(loginData.token);
|
|
||||||
|
|
||||||
const { data: user2 } = await client2.user.self();
|
|
||||||
|
|
||||||
user2.item.groupName = user1.item.groupName;
|
|
||||||
|
|
||||||
// Cleanup User 2
|
|
||||||
|
|
||||||
const { response: deleteResp } = await client2.user.delete();
|
|
||||||
expect(deleteResp.status).toBe(204);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
66
frontend/lib/api/__test__/user/group.test.ts
Normal file
66
frontend/lib/api/__test__/user/group.test.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { describe, test, expect } from "vitest";
|
||||||
|
import { factories } from "../factories";
|
||||||
|
import { sharedUserClient } from "../test-utils";
|
||||||
|
|
||||||
|
describe("first time user workflow (register, login, join group)", () => {
|
||||||
|
test("user should be able to update group", async () => {
|
||||||
|
const { client } = await factories.client.singleUse();
|
||||||
|
|
||||||
|
const name = faker.name.firstName();
|
||||||
|
|
||||||
|
const { response, data: group } = await client.group.update({
|
||||||
|
name,
|
||||||
|
currency: "eur",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(group.name).toBe(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user should be able to get own group", async () => {
|
||||||
|
const { client } = await factories.client.singleUse();
|
||||||
|
|
||||||
|
const { response, data: group } = await client.group.get();
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(group.name).toBeTruthy();
|
||||||
|
expect(group.currency).toBe("usd");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user should be able to join create join token and have user signup", async () => {
|
||||||
|
const api = factories.client.public();
|
||||||
|
|
||||||
|
// Setup User 1 Token
|
||||||
|
const client = await sharedUserClient();
|
||||||
|
const { data: user1 } = await client.user.self();
|
||||||
|
|
||||||
|
const { response, data } = await client.group.createInvitation({
|
||||||
|
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),
|
||||||
|
uses: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.status).toBe(201);
|
||||||
|
expect(data.token).toBeTruthy();
|
||||||
|
|
||||||
|
// Create User 2 with token
|
||||||
|
const duplicateUser = factories.user();
|
||||||
|
duplicateUser.token = data.token;
|
||||||
|
|
||||||
|
const { response: registerResp } = await api.register(duplicateUser);
|
||||||
|
expect(registerResp.status).toBe(204);
|
||||||
|
|
||||||
|
const { response: loginResp, data: loginData } = await api.login(duplicateUser.email, duplicateUser.password);
|
||||||
|
expect(loginResp.status).toBe(200);
|
||||||
|
|
||||||
|
// Get Self and Assert
|
||||||
|
const client2 = factories.client.user(loginData.token);
|
||||||
|
const { data: user2 } = await client2.user.self();
|
||||||
|
|
||||||
|
user2.item.groupName = user1.item.groupName;
|
||||||
|
|
||||||
|
// Cleanup User 2
|
||||||
|
const { response: deleteResp } = await client2.user.delete();
|
||||||
|
expect(deleteResp.status).toBe(204);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,5 @@
|
||||||
import { BaseAPI, route } from "../base";
|
import { BaseAPI, route } from "../base";
|
||||||
import { GroupInvitation, GroupInvitationCreate } from "../types/data-contracts";
|
import { Group, GroupInvitation, GroupInvitationCreate, GroupUpdate } from "../types/data-contracts";
|
||||||
|
|
||||||
export class GroupApi extends BaseAPI {
|
export class GroupApi extends BaseAPI {
|
||||||
createInvitation(data: GroupInvitationCreate) {
|
createInvitation(data: GroupInvitationCreate) {
|
||||||
|
@ -8,4 +8,17 @@ export class GroupApi extends BaseAPI {
|
||||||
body: data,
|
body: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update(data: GroupUpdate) {
|
||||||
|
return this.http.put<GroupUpdate, Group>({
|
||||||
|
url: route("/groups"),
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
return this.http.get<Group>({
|
||||||
|
url: route("/groups"),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,19 @@ export interface DocumentOut {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Group {
|
||||||
|
createdAt: Date;
|
||||||
|
currency: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupUpdate {
|
||||||
|
currency: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ItemAttachment {
|
export interface ItemAttachment {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
document: DocumentOut;
|
document: DocumentOut;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue