diff --git a/backend/app/api/handlers/v1/partials.go b/backend/app/api/handlers/v1/partials.go index 763805f..5c81ad5 100644 --- a/backend/app/api/handlers/v1/partials.go +++ b/backend/app/api/handlers/v1/partials.go @@ -21,7 +21,7 @@ func (ctrl *V1Controller) routeID(r *http.Request) (uuid.UUID, error) { func (ctrl *V1Controller) routeUUID(r *http.Request, key string) (uuid.UUID, error) { ID, err := uuid.Parse(chi.URLParam(r, key)) if err != nil { - return uuid.Nil, validate.NewInvalidRouteKeyError(key) + return uuid.Nil, validate.NewRouteKeyError(key) } return ID, nil } diff --git a/backend/app/api/handlers/v1/v1_ctrl_notifiers.go b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go new file mode 100644 index 0000000..7390bb5 --- /dev/null +++ b/backend/app/api/handlers/v1/v1_ctrl_notifiers.go @@ -0,0 +1,101 @@ +package v1 + +import ( + "context" + "net/http" + + "github.com/containrrr/shoutrrr" + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/core/services" + "github.com/hay-kot/homebox/backend/internal/data/repo" + "github.com/hay-kot/homebox/backend/internal/web/adapters" + "github.com/hay-kot/homebox/backend/pkgs/server" +) + +// HandleGetUserNotifiers godoc +// @Summary Get All notifier +// @Tags Notifiers +// @Produce json +// @Success 200 {object} server.Results{items=[]repo.NotifierOut} +// @Router /v1/notifiers [GET] +// @Security Bearer +func (ctrl *V1Controller) HandleGetUserNotifiers() server.HandlerFunc { + fn := func(ctx context.Context, _ struct{}) ([]repo.NotifierOut, error) { + user := services.UseUserCtx(ctx) + return ctrl.repo.Notifiers.GetByUser(ctx, user.ID) + } + + return adapters.Query(fn, http.StatusOK) +} + +// HandleCreateNotifier godoc +// @Summary Create a new notifier +// @Tags Notifiers +// @Produce json +// @Param payload body repo.NotifierCreate true "Notifier Data" +// @Success 200 {object} repo.NotifierOut +// @Router /v1/notifiers [POST] +// @Security Bearer +func (ctrl *V1Controller) HandleCreateNotifier() server.HandlerFunc { + fn := func(ctx context.Context, in repo.NotifierCreate) (repo.NotifierOut, error) { + auth := services.NewContext(ctx) + return ctrl.repo.Notifiers.Create(ctx, auth.GID, auth.UID, in) + } + + return adapters.Action(fn, http.StatusCreated) +} + +// HandleDeleteNotifier godocs +// @Summary Delete a notifier +// @Tags Notifiers +// @Param id path string true "Notifier ID" +// @Success 204 +// @Router /v1/notifiers/{id} [DELETE] +// @Security Bearer +func (ctrl *V1Controller) HandleDeleteNotifier() server.HandlerFunc { + fn := func(ctx context.Context, ID uuid.UUID) (any, error) { + auth := services.NewContext(ctx) + return nil, ctrl.repo.Notifiers.Delete(ctx, auth.UID, ID) + } + + return adapters.CommandID("id", fn, http.StatusNoContent) +} + +// HandleUpdateNotifier godocs +// @Summary Update a notifier +// @Tags Notifiers +// @Param id path string true "Notifier ID" +// @Param payload body repo.NotifierUpdate true "Notifier Data" +// @Success 200 {object} repo.NotifierOut +// @Router /v1/notifiers/{id} [PUT] +// @Security Bearer +func (ctrl *V1Controller) HandleUpdateNotifier() server.HandlerFunc { + fn := func(ctx context.Context, ID uuid.UUID, in repo.NotifierUpdate) (repo.NotifierOut, error) { + auth := services.NewContext(ctx) + return ctrl.repo.Notifiers.Update(ctx, auth.UID, ID, in) + } + + return adapters.ActionID("id", fn, http.StatusOK) +} + +// HandlerNotifierTest godoc +// @Summary Test notifier +// @Tags Notifiers +// @Produce json +// @Param id path string true "Notifier ID" +// @Param url query string true "URL" +// @Success 204 +// @Router /v1/notifiers/test [POST] +// @Security Bearer +func (ctrl *V1Controller) HandlerNotifierTest() server.HandlerFunc { + type body struct { + URL string `json:"url" validate:"required"` + } + + fn := func(ctx context.Context, q body) (any, error) { + err := shoutrrr.Send(q.URL, "Test message from Homebox") + return nil, err + } + + return adapters.Action(fn, http.StatusOK) +} diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 53083ee..e638fbe 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -126,6 +126,13 @@ func (a *app) mountRoutes(repos *repo.AllRepos) { a.server.Get(v1Base("/asset/{id}"), v1Ctrl.HandleAssetGet(), userMW...) + // Notifiers + a.server.Get(v1Base("/notifiers"), v1Ctrl.HandleGetUserNotifiers(), userMW...) + a.server.Post(v1Base("/notifiers"), v1Ctrl.HandleCreateNotifier(), userMW...) + a.server.Put(v1Base("/notifiers/{id}"), v1Ctrl.HandleUpdateNotifier(), userMW...) + a.server.Delete(v1Base("/notifiers/{id}"), v1Ctrl.HandleDeleteNotifier(), userMW...) + a.server.Post(v1Base("/notifiers/test"), v1Ctrl.HandlerNotifierTest(), userMW...) + // Asset-Like endpoints a.server.Get( v1Base("/qrcode"), diff --git a/backend/app/api/static/docs/docs.go b/backend/app/api/static/docs/docs.go index a5a117e..c959c23 100644 --- a/backend/app/api/static/docs/docs.go +++ b/backend/app/api/static/docs/docs.go @@ -1316,6 +1316,179 @@ const docTemplate = `{ } } }, + "/v1/notifiers": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifiers" + ], + "summary": "Get All notifier", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/server.Results" + }, + { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.NotifierOut" + } + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifiers" + ], + "summary": "Create a new notifier", + "parameters": [ + { + "description": "Notifier Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/repo.NotifierCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/repo.NotifierOut" + } + } + } + } + }, + "/v1/notifiers/test": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifiers" + ], + "summary": "Test notifier", + "parameters": [ + { + "type": "string", + "description": "Notifier ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "URL", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/v1/notifiers/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Notifiers" + ], + "summary": "Update a notifier", + "parameters": [ + { + "type": "string", + "description": "Notifier ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Notifier Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/repo.NotifierUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/repo.NotifierOut" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Notifiers" + ], + "summary": "Delete a notifier", + "parameters": [ + { + "type": "string", + "description": "Notifier ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/v1/qrcode": { "get": { "security": [ @@ -2293,6 +2466,72 @@ const docTemplate = `{ } } }, + "repo.NotifierCreate": { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "url": { + "type": "string" + } + } + }, + "repo.NotifierOut": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "repo.NotifierUpdate": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "url": { + "type": "string", + "x-nullable": true + } + } + }, "repo.PaginationResult-repo_ItemSummary": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.json b/backend/app/api/static/docs/swagger.json index 6ca48a0..36f8dc3 100644 --- a/backend/app/api/static/docs/swagger.json +++ b/backend/app/api/static/docs/swagger.json @@ -1308,6 +1308,179 @@ } } }, + "/v1/notifiers": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifiers" + ], + "summary": "Get All notifier", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/server.Results" + }, + { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.NotifierOut" + } + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifiers" + ], + "summary": "Create a new notifier", + "parameters": [ + { + "description": "Notifier Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/repo.NotifierCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/repo.NotifierOut" + } + } + } + } + }, + "/v1/notifiers/test": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifiers" + ], + "summary": "Test notifier", + "parameters": [ + { + "type": "string", + "description": "Notifier ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "URL", + "name": "url", + "in": "query", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/v1/notifiers/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Notifiers" + ], + "summary": "Update a notifier", + "parameters": [ + { + "type": "string", + "description": "Notifier ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Notifier Data", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/repo.NotifierUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/repo.NotifierOut" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "tags": [ + "Notifiers" + ], + "summary": "Delete a notifier", + "parameters": [ + { + "type": "string", + "description": "Notifier ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/v1/qrcode": { "get": { "security": [ @@ -2285,6 +2458,72 @@ } } }, + "repo.NotifierCreate": { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "url": { + "type": "string" + } + } + }, + "repo.NotifierOut": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "repo.NotifierUpdate": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "url": { + "type": "string", + "x-nullable": true + } + } + }, "repo.PaginationResult-repo_ItemSummary": { "type": "object", "properties": { diff --git a/backend/app/api/static/docs/swagger.yaml b/backend/app/api/static/docs/swagger.yaml index e7f4dc0..1a33418 100644 --- a/backend/app/api/static/docs/swagger.yaml +++ b/backend/app/api/static/docs/swagger.yaml @@ -451,6 +451,51 @@ definitions: itemId: type: string type: object + repo.NotifierCreate: + properties: + isActive: + type: boolean + name: + maxLength: 255 + minLength: 1 + type: string + url: + type: string + required: + - name + - url + type: object + repo.NotifierOut: + properties: + createdAt: + type: string + groupId: + type: string + id: + type: string + isActive: + type: boolean + name: + type: string + updatedAt: + type: string + userId: + type: string + type: object + repo.NotifierUpdate: + properties: + isActive: + type: boolean + name: + maxLength: 255 + minLength: 1 + type: string + url: + type: string + x-nullable: true + required: + - name + type: object repo.PaginationResult-repo_ItemSummary: properties: items: @@ -1428,6 +1473,109 @@ paths: summary: Get All Locations tags: - Locations + /v1/notifiers: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/server.Results' + - properties: + items: + items: + $ref: '#/definitions/repo.NotifierOut' + type: array + type: object + security: + - Bearer: [] + summary: Get All notifier + tags: + - Notifiers + post: + parameters: + - description: Notifier Data + in: body + name: payload + required: true + schema: + $ref: '#/definitions/repo.NotifierCreate' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/repo.NotifierOut' + security: + - Bearer: [] + summary: Create a new notifier + tags: + - Notifiers + /v1/notifiers/{id}: + delete: + parameters: + - description: Notifier ID + in: path + name: id + required: true + type: string + responses: + "204": + description: No Content + security: + - Bearer: [] + summary: Delete a notifier + tags: + - Notifiers + put: + parameters: + - description: Notifier ID + in: path + name: id + required: true + type: string + - description: Notifier Data + in: body + name: payload + required: true + schema: + $ref: '#/definitions/repo.NotifierUpdate' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/repo.NotifierOut' + security: + - Bearer: [] + summary: Update a notifier + tags: + - Notifiers + /v1/notifiers/test: + post: + parameters: + - description: Notifier ID + in: path + name: id + required: true + type: string + - description: URL + in: query + name: url + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + security: + - Bearer: [] + summary: Test notifier + tags: + - Notifiers /v1/qrcode: get: parameters: diff --git a/backend/internal/data/repo/repo_notifier.go b/backend/internal/data/repo/repo_notifier.go index 9a0567a..00446f0 100644 --- a/backend/internal/data/repo/repo_notifier.go +++ b/backend/internal/data/repo/repo_notifier.go @@ -36,13 +36,13 @@ func NewNotifierRepository(db *ent.Client) *NotifierRepository { type ( NotifierCreate struct { Name string `json:"name" validate:"required,min=1,max=255"` - IsActive bool `json:"isEnabled" validate:"required,min=1,max=255"` + IsActive bool `json:"isActive"` URL string `json:"url" validate:"required"` } NotifierUpdate struct { Name string `json:"name" validate:"required,min=1,max=255"` - IsActive bool `json:"isEnabled" validate:"required,min=1,max=255"` + IsActive bool `json:"isActive"` URL *string `json:"url" extensions:"x-nullable"` } @@ -54,13 +54,13 @@ type ( UpdatedAt time.Time `json:"updatedAt"` Name string `json:"name"` - IsActive bool `json:"isEnabled"` + IsActive bool `json:"isActive"` URL string `json:"-"` // URL field is not exposed to the client } ) func (r *NotifierRepository) GetByUser(ctx context.Context, userID uuid.UUID) ([]NotifierOut, error) { - notifier, err := r.db.Notifier.Query().Where(notifier.GroupID(userID)).All(ctx) + notifier, err := r.db.Notifier.Query().Where(notifier.UserID(userID)).All(ctx) return r.mapper.MapEachErr(notifier, err) } diff --git a/backend/internal/data/repo/repos_all.go b/backend/internal/data/repo/repos_all.go index 40748cb..9a6d9c5 100644 --- a/backend/internal/data/repo/repos_all.go +++ b/backend/internal/data/repo/repos_all.go @@ -13,6 +13,7 @@ type AllRepos struct { Docs *DocumentRepository Attachments *AttachmentRepo MaintEntry *MaintenanceEntryRepository + Notifiers *NotifierRepository } func New(db *ent.Client, root string) *AllRepos { @@ -26,5 +27,6 @@ func New(db *ent.Client, root string) *AllRepos { Docs: &DocumentRepository{db, root}, Attachments: &AttachmentRepo{db}, MaintEntry: &MaintenanceEntryRepository{db}, + Notifiers: NewNotifierRepository(db), } }