From b609421a6e7bccb0ad543850c0737e518f587459 Mon Sep 17 00:00:00 2001 From: Yi-Ting Shih Date: Sun, 7 Dec 2025 20:34:53 +0800 Subject: [PATCH] Feat: finish putImageAliases --- Makefile | 9 ++- docs/docs.go | 4 +- docs/swagger.json | 4 +- docs/swagger.yaml | 4 +- handlers/api/deleteAlias.go | 1 + handlers/api/deleteImage.go | 1 + handlers/api/getAliasUpdate.go | 1 + handlers/api/getAliases.go | 1 - handlers/api/getImages.go | 67 +++++++++++++++--- handlers/api/postImage.go | 11 +++ handlers/api/putImageAliases.go | 49 +++++++++++++- implements/bunDatabase.go | 116 +++++++++++++++++++++++++++----- interfaces/database.go | 12 ++++ models/image.go | 2 + utils/initDB.go | 2 +- 15 files changed, 242 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 2b12abc..d12e35c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ -.PHONY: all swagger docker install test bench +.PHONY: all swagger docker install test SWAG ?= go run github.com/swaggo/swag/cmd/swag@v1.16.4 +DOCKER ?= docker GO_ENV += CGO_ENABLED=0 SOURCE := $(shell find . -type f -name '*.go') @@ -9,7 +10,7 @@ TARGET := backend all: swagger docker docker: $(TARGET) - docker compose up -d --force-recreate --build backend + $(DOCKER) compose up -d --force-recreate --build backend install: $(GO_ENV) go install @@ -17,6 +18,10 @@ install: test: go test -v ./tests -count=1 +postgres: + $(DOCKER) compose exec postgres psql \ + postgres://go2025:go2025@postgres:5432/go2025?sslmode=disable + swagger: $(SWAG) fmt $(SWAG) init -o docs -g cmds/serve.go -pdl 1 diff --git a/docs/docs.go b/docs/docs.go index 4449bab..1da2f4c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -268,10 +268,10 @@ const docTemplate = `{ "api.getImagesOutputImage": { "type": "object", "properties": { - "aliases": { + "aliasesIds": { "type": "array", "items": { - "type": "string" + "type": "integer" } }, "id": { diff --git a/docs/swagger.json b/docs/swagger.json index 06035e5..f098f7e 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -260,10 +260,10 @@ "api.getImagesOutputImage": { "type": "object", "properties": { - "aliases": { + "aliasesIds": { "type": "array", "items": { - "type": "string" + "type": "integer" } }, "id": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 779a0d6..c3db4bb 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -9,9 +9,9 @@ definitions: type: object api.getImagesOutputImage: properties: - aliases: + aliasesIds: items: - type: string + type: integer type: array id: type: integer diff --git a/handlers/api/deleteAlias.go b/handlers/api/deleteAlias.go index 9c5a7e5..8cd61ec 100644 --- a/handlers/api/deleteAlias.go +++ b/handlers/api/deleteAlias.go @@ -19,5 +19,6 @@ import ( func (self *Handlers) DeleteAlias( w http.ResponseWriter, req bunrouter.Request, ) error { + // TODO return utils.Success(w) } diff --git a/handlers/api/deleteImage.go b/handlers/api/deleteImage.go index 382bae2..816ccb7 100644 --- a/handlers/api/deleteImage.go +++ b/handlers/api/deleteImage.go @@ -19,5 +19,6 @@ import ( func (self *Handlers) DeleteImage( w http.ResponseWriter, req bunrouter.Request, ) error { + // TODO return utils.Success(w) } diff --git a/handlers/api/getAliasUpdate.go b/handlers/api/getAliasUpdate.go index 1c81676..207e12d 100644 --- a/handlers/api/getAliasUpdate.go +++ b/handlers/api/getAliasUpdate.go @@ -15,5 +15,6 @@ import ( func (self *Handlers) GetAliasUpdate( w http.ResponseWriter, req bunrouter.Request, ) error { + // TODO return utils.Success(w) } diff --git a/handlers/api/getAliases.go b/handlers/api/getAliases.go index 5ef139f..7442ff3 100644 --- a/handlers/api/getAliases.go +++ b/handlers/api/getAliases.go @@ -23,7 +23,6 @@ type getAliasesOutputAlias struct { func (self *Handlers) GetAliases( w http.ResponseWriter, req bunrouter.Request, ) error { - // mock output ctx := req.Context() aliases, err := self.db.GetAliases(ctx) diff --git a/handlers/api/getImages.go b/handlers/api/getImages.go index adfd4ed..4f0a614 100644 --- a/handlers/api/getImages.go +++ b/handlers/api/getImages.go @@ -2,18 +2,18 @@ package api import ( "net/http" + "strconv" "strings" "gitea.konchin.com/go2025/backend/middlewares" - "gitea.konchin.com/go2025/backend/utils" "github.com/uptrace/bunrouter" ) type getImagesOutputImage struct { - Id int64 `json:"id"` - Uploader string `json:"uploadedUserId"` - UploadTS int64 `json:"uploadedAt"` - Aliases []string `json:"aliases"` + Id int64 `json:"id"` + Uploader string `json:"uploadedUserId"` + UploadTS int64 `json:"uploadedAt"` + AliasesIds []int64 `json:"aliasesIds"` } // GetImages @@ -27,18 +27,63 @@ type getImagesOutputImage struct { func (self *Handlers) GetImages( w http.ResponseWriter, req bunrouter.Request, ) error { - // ctx := req.Context() + ctx := req.Context() - images := strings.Split(req.Param("images"), ",") - aliases := strings.Split(req.Param("aliases"), ",") + rawReqImages := strings.Split(req.Param("images"), ",") + rawReqAliases := strings.Split(req.Param("aliases"), ",") - if (len(images) == 0 && len(aliases) == 0) || - (len(images) > 0 && len(aliases) > 0) { + if (len(rawReqImages) == 0 && len(rawReqAliases) == 0) || + (len(rawReqImages) > 0 && len(rawReqAliases) > 0) { return middlewares.HTTPError{ StatusCode: http.StatusBadRequest, Message: "images and aliases should exist exactly one", } } - return utils.Success(w) + var reqImages, reqAliases []int64 + for _, img := range rawReqImages { + imgId, err := strconv.ParseInt(img, 10, 64) + if err != nil { + return middlewares.HTTPError{ + StatusCode: http.StatusBadRequest, + Message: "failed to parse image ids", + } + } + reqImages = append(reqImages, imgId) + } + for _, ali := range rawReqAliases { + aliId, err := strconv.ParseInt(ali, 10, 64) + if err != nil { + return middlewares.HTTPError{ + StatusCode: http.StatusBadRequest, + Message: "failed to parse alias ids", + } + } + reqAliases = append(reqAliases, aliId) + } + + images, err := self.db.GetImages(ctx, reqImages, reqAliases) + if err != nil { + return middlewares.HTTPError{ + StatusCode: http.StatusInternalServerError, + Message: "failed to get images", + OriginError: err, + } + } + + var output []getImagesOutputImage + for _, img := range images { + var aliases []int64 + for _, alias := range img.Aliases { + aliases = append(aliases, alias.Id) + } + output = append(output, getImagesOutputImage{ + Id: img.Id, + Uploader: img.Uploader, + UploadTS: img.UploadTS.Unix(), + AliasesIds: aliases, + }) + } + + return bunrouter.JSON(w, output) } diff --git a/handlers/api/postImage.go b/handlers/api/postImage.go index a2b1099..e39749d 100644 --- a/handlers/api/postImage.go +++ b/handlers/api/postImage.go @@ -2,7 +2,9 @@ package api import ( "net/http" + "strings" + "gitea.konchin.com/go2025/backend/middlewares" "gitea.konchin.com/go2025/backend/utils" "github.com/uptrace/bunrouter" ) @@ -19,5 +21,14 @@ import ( func (self *Handlers) PostImage( w http.ResponseWriter, req bunrouter.Request, ) error { + typeHeader := strings.Split(req.Header.Get("Content-Type"), "/") + if len(typeHeader) != 2 || typeHeader[0] != "image" { + return middlewares.HTTPError{ + StatusCode: http.StatusBadRequest, + Message: "incorrect 'Content-Type' header", + } + } + + // TODO return utils.Success(w) } diff --git a/handlers/api/putImageAliases.go b/handlers/api/putImageAliases.go index c21954e..ccc331f 100644 --- a/handlers/api/putImageAliases.go +++ b/handlers/api/putImageAliases.go @@ -1,18 +1,23 @@ package api import ( + "encoding/json" + "io" "net/http" + "gitea.konchin.com/go2025/backend/middlewares" "gitea.konchin.com/go2025/backend/utils" "github.com/uptrace/bunrouter" ) -type putImageAliasesInputAlias = string +type putImageAliasesInput struct { + Aliases []string `json:"aliases"` +} // PutImageAliases // -// @param id path int64 true "Image Id" -// @param payload body []putImageAliasesInputAlias true "Payload" +// @param id path int64 true "Image Id" +// @param payload body putImageAliasesInput true "Payload" // @success 200 // @failure 401 // @failure 404 @@ -20,5 +25,43 @@ type putImageAliasesInputAlias = string func (self *Handlers) PutImageAliases( w http.ResponseWriter, req bunrouter.Request, ) error { + ctx := req.Context() + + imgId, err := req.Params().Int64("id") + if err != nil { + return middlewares.HTTPError{ + StatusCode: http.StatusBadRequest, + Message: "falied to parse image id in path", + OriginError: err, + } + } + + b, err := io.ReadAll(req.Body) + if err != nil { + return middlewares.HTTPError{ + StatusCode: http.StatusBadRequest, + Message: "failed to read payload", + OriginError: err, + } + } + + var input putImageAliasesInput + if err := json.Unmarshal(b, &input); err != nil { + return middlewares.HTTPError{ + StatusCode: http.StatusBadRequest, + Message: "failed to unmarshal json", + OriginError: err, + } + } + + err = self.db.UpdateAliases(ctx, imgId, input.Aliases) + if err != nil { + return middlewares.HTTPError{ + StatusCode: http.StatusInternalServerError, + Message: "failed to update aliases", + OriginError: err, + } + } + return utils.Success(w) } diff --git a/implements/bunDatabase.go b/implements/bunDatabase.go index 545b2d8..f783d34 100644 --- a/implements/bunDatabase.go +++ b/implements/bunDatabase.go @@ -42,26 +42,34 @@ func (self *BunDatabase) UpdateRefreshToken( ret := models.Session{ UserId: userId, } - err := self.db.NewSelect(). - Model(&ret). - WherePK(). - Scan(ctx) - if err != nil { - return models.Session{}, err - } - if err := ret.RotateRefreshToken(); err != nil { - tracing.Logger.Ctx(ctx). - Error("failed to rotate refresh token", - zap.Error(err)) - return models.Session{}, err - } + err := self.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + err := self.db.NewSelect(). + Model(&ret). + WherePK(). + Scan(ctx) + if err != nil { + return err + } - _, err = self.db.NewUpdate(). - Model((*models.Session)(nil)). - Set("refresh_token = ?", ret.RefreshToken). - Where("user_id = ?", userId). - Exec(ctx) + if err := ret.RotateRefreshToken(); err != nil { + tracing.Logger.Ctx(ctx). + Error("failed to rotate refresh token", + zap.Error(err)) + return err + } + + _, err = self.db.NewUpdate(). + Model((*models.Session)(nil)). + Set("refresh_token = ?", ret.RefreshToken). + Where("user_id = ?", userId). + Exec(ctx) + if err != nil { + return err + } + + return nil + }) if err != nil { return models.Session{}, err } @@ -104,3 +112,75 @@ func (self *BunDatabase) GetAliases( } return ret, nil } + +func (self *BunDatabase) GetImages( + ctx context.Context, + imageIds []int64, + aliasIds []int64, +) ([]models.Image, error) { + if len(aliasIds) > 0 { + var rels []models.AliasImage + err := self.db.NewSelect(). + Model(&rels). + Where("alias_id IN (?)", bun.In(aliasIds)). + Scan(ctx) + if err != nil { + return []models.Image{}, err + } + for _, rel := range rels { + imageIds = append(imageIds, rel.ImageId) + } + } + + var ret []models.Image + if len(imageIds) > 0 { + err := self.db.NewSelect(). + Model(&ret). + Where("image_id IN (?)", bun.In(imageIds)). + Relation("Aliases"). + Scan(ctx) + if err != nil { + return []models.Image{}, err + } + } + return ret, nil +} + +func (self *BunDatabase) UpdateAliases( + ctx context.Context, + imageId int64, + aliasNames []string, +) error { + return self.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + var aliases []models.Alias + for _, ali := range aliasNames { + aliases = append(aliases, models.Alias{ + Name: ali, + }) + } + _, err := self.db.NewInsert(). + Model(&aliases). + On("CONFLICT (id) DO NOTHING"). + Exec(ctx) + if err != nil { + return err + } + + var rels []models.AliasImage + for _, alias := range aliases { + rels = append(rels, models.AliasImage{ + AliasId: alias.Id, + ImageId: imageId, + }) + } + _, err = self.db.NewInsert(). + Model(&rels). + On("CONFLICT (alias_id, image_id) DO NOTHING"). + Exec(ctx) + if err != nil { + return err + } + + return nil + }) +} diff --git a/interfaces/database.go b/interfaces/database.go index bab9baa..7d1cbd5 100644 --- a/interfaces/database.go +++ b/interfaces/database.go @@ -25,4 +25,16 @@ type Database interface { GetAliases( ctx context.Context, ) ([]models.Alias, error) + + GetImages( + ctx context.Context, + imageIds []int64, + aliasIds []int64, + ) ([]models.Image, error) + + UpdateAliases( + ctx context.Context, + imageId int64, + aliasNames []string, + ) error } diff --git a/models/image.go b/models/image.go index 394f501..4e11175 100644 --- a/models/image.go +++ b/models/image.go @@ -12,4 +12,6 @@ type Image struct { Id int64 `bun:"id,pk,autoincrement"` Uploader string `bun:"uploader"` UploadTS time.Time `bun:"upload_timestamp"` + + Aliases []Alias `bun:"m2m:alias_image,join:Image=Alias"` } diff --git a/utils/initDB.go b/utils/initDB.go index 4290e16..4005b2d 100644 --- a/utils/initDB.go +++ b/utils/initDB.go @@ -8,8 +8,8 @@ import ( ) func InitDB(ctx context.Context, db *bun.DB) error { - db.RegisterModel((*models.AliasImage)(nil)) return db.ResetModel(ctx, + (*models.AliasImage)(nil), (*models.Alias)(nil), (*models.Image)(nil), (*models.Session)(nil),