mirror of
https://github.com/hay-kot/homebox.git
synced 2025-08-05 09:10:26 +00:00
implement attachment post route (WIP)
This commit is contained in:
parent
5ee9082301
commit
48036eabce
12 changed files with 225 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
# Project Specific
|
# Project Specific
|
||||||
|
backend/homebox-data/*
|
||||||
config.yml
|
config.yml
|
||||||
homebox.db
|
homebox.db
|
||||||
.idea
|
.idea
|
||||||
|
|
|
@ -224,6 +224,60 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/items/{id}/attachment": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Items"
|
||||||
|
],
|
||||||
|
"summary": "imports items into the database",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Item ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"description": "File attachment",
|
||||||
|
"name": "file",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Type of file",
|
||||||
|
"name": "type",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the file including extension",
|
||||||
|
"name": "name",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/types.ItemOut"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/labels": {
|
"/v1/labels": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -881,6 +935,9 @@ const docTemplate = `{
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,6 +216,60 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/items/{id}/attachment": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Items"
|
||||||
|
],
|
||||||
|
"summary": "imports items into the database",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Item ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"description": "File attachment",
|
||||||
|
"name": "file",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Type of file",
|
||||||
|
"name": "type",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the file including extension",
|
||||||
|
"name": "name",
|
||||||
|
"in": "formData",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/types.ItemOut"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/labels": {
|
"/v1/labels": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -873,6 +927,9 @@
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,8 @@ definitions:
|
||||||
$ref: '#/definitions/types.DocumentOut'
|
$ref: '#/definitions/types.DocumentOut'
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
@ -503,6 +505,41 @@ paths:
|
||||||
summary: updates a item
|
summary: updates a item
|
||||||
tags:
|
tags:
|
||||||
- Items
|
- Items
|
||||||
|
/v1/items/{id}/attachment:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- description: Item ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: File attachment
|
||||||
|
in: formData
|
||||||
|
name: file
|
||||||
|
required: true
|
||||||
|
type: file
|
||||||
|
- description: Type of file
|
||||||
|
in: formData
|
||||||
|
name: type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: name of the file including extension
|
||||||
|
in: formData
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/types.ItemOut'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: imports items into the database
|
||||||
|
tags:
|
||||||
|
- Items
|
||||||
/v1/items/import:
|
/v1/items/import:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -81,6 +81,8 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
|
||||||
r.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet())
|
r.Get(v1Base("/items/{id}"), v1Ctrl.HandleItemGet())
|
||||||
r.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate())
|
r.Put(v1Base("/items/{id}"), v1Ctrl.HandleItemUpdate())
|
||||||
r.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete())
|
r.Delete(v1Base("/items/{id}"), v1Ctrl.HandleItemDelete())
|
||||||
|
|
||||||
|
r.Post(v1Base("/items/{id}/attachment"), v1Ctrl.HandleItemAttachmentCreate())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,10 @@ package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/hay-kot/homebox/backend/ent/attachment"
|
||||||
"github.com/hay-kot/homebox/backend/internal/services"
|
"github.com/hay-kot/homebox/backend/internal/services"
|
||||||
"github.com/hay-kot/homebox/backend/internal/types"
|
"github.com/hay-kot/homebox/backend/internal/types"
|
||||||
"github.com/hay-kot/homebox/backend/pkgs/server"
|
"github.com/hay-kot/homebox/backend/pkgs/server"
|
||||||
|
@ -89,8 +91,8 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
|
||||||
// @Summary Gets a item and fields
|
// @Summary Gets a item and fields
|
||||||
// @Tags Items
|
// @Tags Items
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Success 200 {object} types.ItemOut
|
// @Success 200 {object} types.ItemOut
|
||||||
// @Router /v1/items/{id} [GET]
|
// @Router /v1/items/{id} [GET]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
|
||||||
|
@ -189,3 +191,64 @@ func (ctrl *V1Controller) HandleItemsImport() http.HandlerFunc {
|
||||||
server.Respond(w, http.StatusNoContent, nil)
|
server.Respond(w, http.StatusNoContent, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleItemsImport godocs
|
||||||
|
// @Summary imports items into the database
|
||||||
|
// @Tags Items
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Item ID"
|
||||||
|
// @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
|
||||||
|
// @Router /v1/items/{id}/attachment [Post]
|
||||||
|
// @Security Bearer
|
||||||
|
func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Max upload size of 10 MB - TODO: Set via config
|
||||||
|
err := r.ParseMultipartForm(10 << 20)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("failed to parse multipart form")
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, _, err := r.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("failed to get file from form")
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
attachmentName := r.FormValue("name")
|
||||||
|
if attachmentName == "" {
|
||||||
|
log.Err(err).Msg("failed to get name from form")
|
||||||
|
server.RespondError(w, http.StatusBadRequest, errors.New("name is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentType := r.FormValue("type")
|
||||||
|
if attachmentType == "" {
|
||||||
|
attachmentName = "attachment"
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, user, err := ctrl.partialParseIdAndUser(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := ctrl.svc.Items.AddAttachment(
|
||||||
|
r.Context(),
|
||||||
|
user.GroupID,
|
||||||
|
uid,
|
||||||
|
attachmentName,
|
||||||
|
attachment.Type(attachmentType),
|
||||||
|
file,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("failed to add attachment")
|
||||||
|
server.RespondServerError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Respond(w, http.StatusOK, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ func ToItemAttachment(attachment *ent.Attachment) *types.ItemAttachment {
|
||||||
ID: attachment.ID,
|
ID: attachment.ID,
|
||||||
CreatedAt: attachment.CreatedAt,
|
CreatedAt: attachment.CreatedAt,
|
||||||
UpdatedAt: attachment.UpdatedAt,
|
UpdatedAt: attachment.UpdatedAt,
|
||||||
|
Type: attachment.Type.String(),
|
||||||
Document: types.DocumentOut{
|
Document: types.DocumentOut{
|
||||||
ID: attachment.Edges.Document.ID,
|
ID: attachment.Edges.Document.ID,
|
||||||
Title: attachment.Edges.Document.Title,
|
Title: attachment.Edges.Document.Title,
|
||||||
|
|
|
@ -101,7 +101,7 @@ func (svc *ItemService) attachmentPath(gid, itemId uuid.UUID, filename string) s
|
||||||
// AddAttachment adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
|
// AddAttachment adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment
|
||||||
// Table and Items table. The file provided via the reader is stored on the file system based on the provided
|
// Table and Items table. The file provided via the reader is stored on the file system based on the provided
|
||||||
// relative path during construction of the service.
|
// relative path during construction of the service.
|
||||||
func (svc *ItemService) AddAttachment(ctx context.Context, gid, itemId uuid.UUID, filename string, file io.Reader) (*types.ItemOut, error) {
|
func (svc *ItemService) AddAttachment(ctx context.Context, gid, itemId uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (*types.ItemOut, error) {
|
||||||
// Get the Item
|
// Get the Item
|
||||||
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
item, err := svc.repo.Items.GetOne(ctx, itemId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -122,7 +122,7 @@ func (svc *ItemService) AddAttachment(ctx context.Context, gid, itemId uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the attachment
|
// Create the attachment
|
||||||
_, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachment.TypeAttachment)
|
_, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachmentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ func TestItemService_AddAttachment(t *testing.T) {
|
||||||
reader := strings.NewReader(contents)
|
reader := strings.NewReader(contents)
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
afterAttachment, err := svc.AddAttachment(context.Background(), tGroup.ID, itm.ID, "testfile.txt", reader)
|
afterAttachment, err := svc.AddAttachment(context.Background(), tGroup.ID, itm.ID, "testfile.txt", "attachment", reader)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, afterAttachment)
|
assert.NotNil(t, afterAttachment)
|
||||||
|
|
||||||
|
|
|
@ -103,5 +103,6 @@ type ItemAttachment struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
Type string `json:"type"`
|
||||||
Document DocumentOut `json:"document"`
|
Document DocumentOut `json:"document"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ type LabelUpdate struct {
|
||||||
|
|
||||||
type LabelSummary struct {
|
type LabelSummary struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
GroupID uuid.UUID `json:"groupId"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
|
|
@ -45,6 +45,7 @@ export interface ItemAttachment {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
document: DocumentOut;
|
document: DocumentOut;
|
||||||
id: string;
|
id: string;
|
||||||
|
type: string;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue