From 14bad8e7ef260fc0d1fcfd72e1841da63395bb71 Mon Sep 17 00:00:00 2001 From: Nikolai Papin Date: Mon, 1 Sep 2025 18:46:08 +0300 Subject: [PATCH] feat: Implemented create wish method for wish list service --- backend/internal/database/query.sql.go | 22 ++++ backend/internal/services/s3.go | 2 +- backend/internal/services/setup.go | 3 +- backend/internal/services/wishlist.go | 154 +++++++++++++++++++++++++ sqlc/query.sql | 7 ++ 5 files changed, 186 insertions(+), 2 deletions(-) diff --git a/backend/internal/database/query.sql.go b/backend/internal/database/query.sql.go index 8049fb7..932a18e 100644 --- a/backend/internal/database/query.sql.go +++ b/backend/internal/database/query.sql.go @@ -1054,6 +1054,28 @@ func (q *Queries) GetWishByGuid(ctx context.Context, guid string) (Wish, error) return i, err } +const getWishListOwnerByGuid = `-- name: GetWishListOwnerByGuid :one +SELECT u.id, u.username, u.verified, u.registration_date, u.role, u.deleted +FROM wish_lists wl +JOIN profiles p ON wl.profile_id = p.id +JOIN users u ON p.user_id = u.id +WHERE wl.guid = ($1::text)::uuid +` + +func (q *Queries) GetWishListOwnerByGuid(ctx context.Context, guid string) (User, error) { + row := q.db.QueryRow(ctx, getWishListOwnerByGuid, guid) + var i User + err := row.Scan( + &i.ID, + &i.Username, + &i.Verified, + &i.RegistrationDate, + &i.Role, + &i.Deleted, + ) + return i, err +} + const getWishlistByGuid = `-- name: GetWishlistByGuid :one SELECT id, guid, profile_id, hidden, name, icon_name, color, color_grad, deleted FROM wish_lists wl WHERE wl.guid = ($1::text)::uuid diff --git a/backend/internal/services/s3.go b/backend/internal/services/s3.go index 145c07f..596fbc2 100644 --- a/backend/internal/services/s3.go +++ b/backend/internal/services/s3.go @@ -48,7 +48,7 @@ type s3ServiceImpl struct { imagePolicy minio.PostPolicy } -func NewUploadService(_minio *minio.Client, _log *zap.Logger) S3Service { +func NewS3Service(_minio *minio.Client, _log *zap.Logger) S3Service { service := s3ServiceImpl{ minio: _minio, log: _log, diff --git a/backend/internal/services/setup.go b/backend/internal/services/setup.go index e734cfd..d4ac9f8 100644 --- a/backend/internal/services/setup.go +++ b/backend/internal/services/setup.go @@ -23,9 +23,10 @@ import ( var Module = fx.Module("services", fx.Provide( - NewUploadService, + NewS3Service, NewSmtpService, NewAuthService, NewProfileService, + NewWishListService, ), ) diff --git a/backend/internal/services/wishlist.go b/backend/internal/services/wishlist.go index 69264cb..4b44ba3 100644 --- a/backend/internal/services/wishlist.go +++ b/backend/internal/services/wishlist.go @@ -18,10 +18,26 @@ package services import ( + "easywish/internal/database" "easywish/internal/dto" + errs "easywish/internal/errors" + "easywish/internal/utils" "easywish/internal/utils/enums" + mapspecial "easywish/internal/utils/mapSpecial" + "errors" + + "github.com/go-redis/redis/v8" + "github.com/jackc/pgx/v5" + "go.uber.org/zap" ) +type wishListServiceImpl struct { + log *zap.Logger + dbctx database.DbContext + redis *redis.Client + s3 S3Service +} + type WishListService interface { CreateWishList(cinfo dto.ClientInfo, object dto.NewWishListDto) (*dto.WishListDto, error) UpdateWishListByGuid(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error) @@ -31,6 +47,144 @@ type WishListService interface { CreateWish(cinfo dto.ClientInfo, object dto.NewWishDto) (*dto.WishDto, error) UpdateWish(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error) + MoveWishToWishList(cinfo dto.ClientInfo, wishGuid string, wishListGuid string) (bool, error) GetWishByGuid(cinfo dto.ClientInfo, guid string) (*dto.WishDto, error) GetWishesByWishListGuidPaginated(cinfo dto.ClientInfo, guid string, amount int, page int, sorting enums.Sorting, sortOrder enums.SortOrder) (*[]dto.WishDto, error) } + +func NewWishListService(_log *zap.Logger, _dbctx database.DbContext, _redis *redis.Client) WishListService { + // XXX: fill this in + return wishListServiceImpl{} +} + +func (w wishListServiceImpl) CreateWish(cinfo dto.ClientInfo, object dto.NewWishDto) (*dto.WishDto, error) { + helper, db, err := database.NewDbHelperTransaction(w.dbctx); if err != nil { + w.log.Error( + "Failed to open transaction", + zap.Error(err)) + return nil, errs.ErrServerError + } + defer helper.Rollback() + + // Check if wish list exists + wishList, err := db.TXQueries.GetWishlistByGuid(db.CTX, object.WishListGuid); if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + w.log.Warn( + "Attempted to create a wish for a wish list that does not exist", + zap.String("username", cinfo.Username), + zap.String("wish_list_guid", object.WishListGuid), + zap.Error(err)) + return nil, errs.ErrNotFound + } + + w.log.Error( + "Failed to get wishlist for the new wish", + zap.String("username", cinfo.Username), + zap.String("wish_list_guid", object.WishListGuid), + zap.Error(err)) + return nil, errs.ErrServerError + } + + wishListOwnerUser, err := db.TXQueries.GetWishListOwnerByGuid(db.CTX, wishList.Guid.String()); if err != nil { + w.log.Error( + "Failed to get wish list owner", + zap.String("username", cinfo.Username), + zap.String("wish_list_guid", wishList.Guid.String()), + zap.Error(err)) + return nil, errs.ErrServerError + } + + if wishListOwnerUser.Username != cinfo.Username { + w.log.Warn( + "Attempt to create wish in a wish list the user does not own", + zap.String("owner_username", wishListOwnerUser.Username), + zap.String("username", cinfo.Username), + zap.String("wish_list_guid", object.WishListGuid)) + + // As usual, we will pretend that it does not exist + return nil, errs.ErrNotFound + } + + var avatarUrl *string + if object.PictureUploadId != "" { + key, err := w.s3.SaveUpload(object.PictureUploadId, "images"); if err != nil { + if errors.Is(err, errs.ErrFileNotFound) { + return nil, err + } + + w.log.Error( + "Failed to save image", + zap.String("upload_id", object.PictureUploadId), + zap.String("username", cinfo.Username), + zap.Error(err)) + return nil, errs.ErrServerError + } + + urlObj := w.s3.GetLocalizedFileUrl(*key, "images") + avatarUrl = utils.NewPointer(urlObj.String()) + } else { + avatarUrl = utils.NewPointer("") + } + + newWish, err := db.TXQueries.CreateWish(db.CTX, database.CreateWishParams{ + WishListGuid: object.WishListGuid, + Name: object.Name, + Description: object.Description, + PictureUrl: *avatarUrl, + Stars: int16(object.Stars), + }); if err != nil { + w.log.Error( + "Failed to create a new wish", + zap.String("username", cinfo.Username), + zap.String("wish_list_guid", object.WishListGuid), + zap.Error(err)) + return nil, errs.ErrServerError + } + + err = helper.Commit(); if err != nil { + w.log.Error( + "Failed to commit transaction", + zap.Error(err)) + return nil, errs.ErrServerError + } + + wishDto := &dto.WishDto{} + mapspecial.MapWishDto(newWish, wishDto) + return wishDto, nil +} + +func (w wishListServiceImpl) CreateWishList(cinfo dto.ClientInfo, object dto.NewWishListDto) (*dto.WishListDto, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) DeleteWishListByGuid(cinfo dto.ClientInfo, guid string) (bool, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) GetUserWishListsPaginated(cinfo dto.ClientInfo, amount int, page int, sorting enums.Sorting, sortOrder enums.SortOrder) (*[]dto.WishListDto, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) GetWishByGuid(cinfo dto.ClientInfo, guid string) (*dto.WishDto, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) GetWishListByGuid(cinfo dto.ClientInfo, guid string) (*dto.WishListDto, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) GetWishesByWishListGuidPaginated(cinfo dto.ClientInfo, guid string, amount int, page int, sorting enums.Sorting, sortOrder enums.SortOrder) (*[]dto.WishDto, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) MoveWishToWishList(cinfo dto.ClientInfo, wishGuid string, wishListGuid string) (bool, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) UpdateWish(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error) { + panic("unimplemented") +} + +func (w wishListServiceImpl) UpdateWishListByGuid(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error) { + panic("unimplemented") +} diff --git a/sqlc/query.sql b/sqlc/query.sql index e8300d3..578b585 100644 --- a/sqlc/query.sql +++ b/sqlc/query.sql @@ -393,6 +393,13 @@ WHERE wl.guid = (@guid::text)::uuid; SELECT * FROM wish_lists wl WHERE wl.guid = (@guid::text)::uuid; +;-- name: GetWishListOwnerByGuid :one +SELECT u.* +FROM wish_lists wl +JOIN profiles p ON wl.profile_id = p.id +JOIN users u ON p.user_id = u.id +WHERE wl.guid = (@guid::text)::uuid; + ;-- name: GetWishlistsByUsername :many SELECT * FROM wish_lists wl JOIN profiles p ON p.id = wl.profile_id