diff --git a/docs/docs.go b/docs/docs.go index 19f8529..b79cf31 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -86,7 +86,10 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/api.postImageOutput" + } }, "401": { "description": "Unauthorized" @@ -264,6 +267,9 @@ const docTemplate = `{ "type": "integer" } }, + "extension": { + "type": "string" + }, "id": { "type": "integer" }, @@ -275,6 +281,23 @@ const docTemplate = `{ } } }, + "api.postImageOutput": { + "type": "object", + "properties": { + "extension": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "uploadedAt": { + "type": "string" + }, + "uploadedUserId": { + "type": "string" + } + } + }, "api.putImageAliasesInput": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 526842a..0e07c19 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -78,7 +78,10 @@ ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/api.postImageOutput" + } }, "401": { "description": "Unauthorized" @@ -256,6 +259,9 @@ "type": "integer" } }, + "extension": { + "type": "string" + }, "id": { "type": "integer" }, @@ -267,6 +273,23 @@ } } }, + "api.postImageOutput": { + "type": "object", + "properties": { + "extension": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "uploadedAt": { + "type": "string" + }, + "uploadedUserId": { + "type": "string" + } + } + }, "api.putImageAliasesInput": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 16d6f8c..081a8c7 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -13,6 +13,8 @@ definitions: items: type: integer type: array + extension: + type: string id: type: integer uploadedAt: @@ -20,6 +22,17 @@ definitions: uploadedUserId: type: string type: object + api.postImageOutput: + properties: + extension: + type: string + id: + type: integer + uploadedAt: + type: string + uploadedUserId: + type: string + type: object api.putImageAliasesInput: properties: aliases: @@ -97,6 +110,8 @@ paths: responses: "200": description: OK + schema: + $ref: '#/definitions/api.postImageOutput' "401": description: Unauthorized /api/image/{id}: diff --git a/handlers/api/getImages.go b/handlers/api/getImages.go index 2b474bf..c9f77b3 100644 --- a/handlers/api/getImages.go +++ b/handlers/api/getImages.go @@ -1,16 +1,21 @@ package api import ( + "fmt" "net/http" + "slices" "strconv" "strings" "gitea.konchin.com/go2025/backend/middlewares" + "gitea.konchin.com/go2025/backend/tracing" "github.com/uptrace/bunrouter" + "go.uber.org/zap" ) type getImagesOutputImage struct { Id int64 `json:"id"` + Extension string `json:"extension"` Uploader string `json:"uploadedUserId"` UploadTS int64 `json:"uploadedAt"` AliasesIds []int64 `json:"aliasesIds"` @@ -29,11 +34,18 @@ func (self *Handlers) GetImages( ) error { ctx := req.Context() + rmf := func(s string) bool { return len(s) == 0 } rawReqImages := strings.Split(req.URL.Query().Get("images"), ",") + rawReqImages = slices.DeleteFunc(rawReqImages, rmf) rawReqAliases := strings.Split(req.URL.Query().Get("aliases"), ",") + rawReqAliases = slices.DeleteFunc(rawReqAliases, rmf) if (len(rawReqImages) == 0 && len(rawReqAliases) == 0) || (len(rawReqImages) > 0 && len(rawReqAliases) > 0) { + tracing.Logger.Ctx(ctx). + Debug("rawReq", + zap.String("rawReqImages", fmt.Sprintf("%+v", rawReqImages)), + zap.String("rawReqAlias", fmt.Sprintf("%+v", rawReqAliases))) return middlewares.HTTPError{ StatusCode: http.StatusBadRequest, Message: "images and aliases should exist exactly one", @@ -79,6 +91,7 @@ func (self *Handlers) GetImages( } output = append(output, getImagesOutputImage{ Id: img.Id, + Extension: img.Extension, Uploader: img.Uploader, UploadTS: img.UploadTS.Unix(), AliasesIds: aliases, diff --git a/handlers/api/postImage.go b/handlers/api/postImage.go index ad93173..01bbf77 100644 --- a/handlers/api/postImage.go +++ b/handlers/api/postImage.go @@ -8,17 +8,23 @@ import ( "gitea.konchin.com/go2025/backend/middlewares" "gitea.konchin.com/go2025/backend/models" "gitea.konchin.com/go2025/backend/types" - "gitea.konchin.com/go2025/backend/utils" "github.com/uptrace/bunrouter" ) +type postImageOutput struct { + Id int64 `json:"id"` + Extension string `json:"extension"` + Uploader string `json:"uploadedUserId"` + UploadTS time.Time `json:"uploadedAt"` +} + // PostImage // // @param userinfo header string true "userinfo from /auth/gen-login-url" // @accept image/png // @accept image/jpeg // @accept image/gif -// @success 200 +// @success 200 {object} postImageOutput // @failure 401 // @router /api/image [post] func (self *Handlers) PostImage( @@ -65,5 +71,10 @@ func (self *Handlers) PostImage( OriginError: err, } } - return utils.Success(w) + return bunrouter.JSON(w, postImageOutput{ + Id: image.Id, + Extension: image.Extension, + Uploader: image.Uploader, + UploadTS: image.UploadTS, + }) } diff --git a/implements/bunDatabase.go b/implements/bunDatabase.go index ea87285..4c4289a 100644 --- a/implements/bunDatabase.go +++ b/implements/bunDatabase.go @@ -154,7 +154,7 @@ func (self *BunDatabase) GetImages( if len(imageIds) > 0 { err := self.db.NewSelect(). Model(&ret). - Where("image_id IN (?)", bun.In(imageIds)). + Where("id IN (?)", bun.In(imageIds)). Relation("Aliases"). Scan(ctx) if err != nil { @@ -178,7 +178,7 @@ func (self *BunDatabase) UpdateAliases( } _, err := self.db.NewInsert(). Model(&aliases). - On("CONFLICT (id) DO NOTHING"). + On("CONFLICT (name) DO NOTHING"). Exec(ctx) if err != nil { return err @@ -193,7 +193,7 @@ func (self *BunDatabase) UpdateAliases( } _, err = self.db.NewInsert(). Model(&rels). - On("CONFLICT (alias_id, image_id) DO NOTHING"). + On(`CONFLICT ("alias_id", "image_id") DO NOTHING`). Exec(ctx) if err != nil { return err diff --git a/models/alias.go b/models/alias.go index 6fc519a..f9c5fd6 100644 --- a/models/alias.go +++ b/models/alias.go @@ -6,7 +6,7 @@ type Alias struct { bun.BaseModel `bun:"table:alias"` Id int64 `bun:"id,pk,autoincrement"` - Name string `bun:"name,notnull"` + Name string `bun:"name,unique"` Images []Image `bun:"m2m:alias_image,join:Alias=Image"` } diff --git a/tests/01_login_test.go b/tests/01_login_test.go index c04bbd8..190099f 100644 --- a/tests/01_login_test.go +++ b/tests/01_login_test.go @@ -1,4 +1,4 @@ -package main +package tests import ( "net/http" diff --git a/tests/02_getAliases_test.go b/tests/02_getAliases_test.go deleted file mode 100644 index 1d758f6..0000000 --- a/tests/02_getAliases_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "net/http" - "testing" -) - -func Test_02_GetImages(t *testing.T) { - resp, err := client.R(). - Get("http://localhost:8080/api/aliases") - if err != nil || resp.StatusCode() != http.StatusOK { - t.Logf("%+v", resp) - t.Fatal("failed to fetch aliases") - } -} diff --git a/tests/02_postImage_test.go b/tests/02_postImage_test.go new file mode 100644 index 0000000..1a0555a --- /dev/null +++ b/tests/02_postImage_test.go @@ -0,0 +1,37 @@ +package tests + +import ( + "io" + "net/http" + "os" + "testing" +) + +type postImagePayload struct { + Id int64 `json:"id"` + Extension string `json:"extension"` +} + +var rawImage []byte +var image postImagePayload + +func Test_02_PostImage(t *testing.T) { + f, err := os.Open("resources/huh.png") + if err != nil { + t.Fatal("failed to open test png") + } + rawImage, err = io.ReadAll(f) + if err != nil { + t.Fatal("failed to read from file") + } + + resp, err := client.R(). + SetBody(rawImage). + SetResult(&image). + Post("http://localhost:8080/api/image") + + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to post image") + } +} diff --git a/tests/03_putImageAliases_test.go b/tests/03_putImageAliases_test.go new file mode 100644 index 0000000..64d3d79 --- /dev/null +++ b/tests/03_putImageAliases_test.go @@ -0,0 +1,25 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" +) + +type putImageAliasPayload struct { + Aliases []string `json:"aliases"` +} + +func Test_03_PutImageAliases(t *testing.T) { + payload := putImageAliasPayload{ + Aliases: []string{"huh"}, + } + resp, err := client.R(). + SetBody(payload). + Put(fmt.Sprintf("http://localhost:8080/api/image/%d/aliases", + image.Id)) + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to put image alias") + } +} diff --git a/tests/04_getAliases_test.go b/tests/04_getAliases_test.go new file mode 100644 index 0000000..d73ca24 --- /dev/null +++ b/tests/04_getAliases_test.go @@ -0,0 +1,26 @@ +package tests + +import ( + "net/http" + "testing" +) + +type aliasPayload struct { + Id int64 `json:"id"` + Name string `json:"name"` +} + +var aliases []aliasPayload + +func Test_04_GetAliases(t *testing.T) { + resp, err := client.R(). + SetResult(&aliases). + Get("http://localhost:8080/api/aliases") + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to get aliases") + } + if len(aliases) == 0 { + t.Fatal("no aliases") + } +} diff --git a/tests/05_getImages_test.go b/tests/05_getImages_test.go new file mode 100644 index 0000000..2078fa9 --- /dev/null +++ b/tests/05_getImages_test.go @@ -0,0 +1,28 @@ +package tests + +import ( + "net/http" + "strconv" + "testing" +) + +func Test_05_GetImages(t *testing.T) { + t.Run("check with alias id", func(t *testing.T) { + resp, err := client.R(). + SetQueryParam("aliases", strconv.FormatInt(aliases[0].Id, 10)). + Get("http://localhost:8080/api/images") + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to get images by alias id") + } + }) + t.Run("check with image id", func(t *testing.T) { + resp, err := client.R(). + SetQueryParam("images", strconv.FormatInt(image.Id, 10)). + Get("http://localhost:8080/api/images") + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to get images by image id") + } + }) +} diff --git a/tests/06_getImage_test.go b/tests/06_getImage_test.go new file mode 100644 index 0000000..1816c8d --- /dev/null +++ b/tests/06_getImage_test.go @@ -0,0 +1,21 @@ +package tests + +import ( + "bytes" + "fmt" + "net/http" + "testing" +) + +func Test_06_GetImage(t *testing.T) { + resp, err := client.R(). + Get(fmt.Sprintf("http://localhost:8080/img/%d.%s", + image.Id, image.Extension)) + if err != nil || resp.StatusCode() != http.StatusOK { + t.Fatal("failed to get image") + } + + if bytes.Compare(rawImage, resp.Body()) != 0 { + t.Fatal("image differ") + } +} diff --git a/tests/07_deleteAlias_test.go b/tests/07_deleteAlias_test.go new file mode 100644 index 0000000..b23561d --- /dev/null +++ b/tests/07_deleteAlias_test.go @@ -0,0 +1,29 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" +) + +func Test_07_DeleteAlias(t *testing.T) { + resp, err := client.R(). + Delete(fmt.Sprintf("http://localhost:8080/api/alias/%d", + aliases[0].Id)) + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to delete alias") + } + + resp, err = client.R(). + SetResult(&aliases). + Get("http://localhost:8080/api/aliases") + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to get aliases") + } + + if len(aliases) > 0 { + t.Fatal("alias not deleted") + } +} diff --git a/tests/08_deleteImage_test.go b/tests/08_deleteImage_test.go new file mode 100644 index 0000000..eedb8c6 --- /dev/null +++ b/tests/08_deleteImage_test.go @@ -0,0 +1,46 @@ +package tests + +import ( + "fmt" + "net/http" + "strconv" + "testing" +) + +type getImagesPayload struct { + Id int64 `json:"id"` +} + +func Test_08_DeleteImage(t *testing.T) { + resp, err := client.R(). + SetBody(rawImage). + SetResult(&image). + Post("http://localhost:8080/api/image") + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to post image") + } + + resp, err = client.R(). + Delete(fmt.Sprintf("http://localhost:8080/api/image/%d", + image.Id)) + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to delete image") + } + + var imgs []getImagesPayload + + resp, err = client.R(). + SetResult(&imgs). + SetQueryParam("images", strconv.FormatInt(image.Id, 10)). + Get("http://localhost:8080/api/images") + if err != nil || resp.StatusCode() != http.StatusOK { + t.Logf("%+v", resp) + t.Fatal("failed to get image by image id") + } + + if len(imgs) > 0 { + t.Fatal("image not deleted") + } +} diff --git a/tests/resources/huh.png b/tests/resources/huh.png new file mode 100644 index 0000000..91d7d65 Binary files /dev/null and b/tests/resources/huh.png differ