diff --git a/app/shop/api/internal/handler/routes.go b/app/shop/api/internal/handler/routes.go index 71f0597..2ab658f 100644 --- a/app/shop/api/internal/handler/routes.go +++ b/app/shop/api/internal/handler/routes.go @@ -75,6 +75,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/shops/:id/invitations", Handler: shop.InvitePlayerHandler(serverCtx), }, + { + // 获取店铺邀请列表 + Method: http.MethodGet, + Path: "/shops/:id/invitations", + Handler: shop.ListShopInvitationsHandler(serverCtx), + }, { // 移除打手 Method: http.MethodDelete, @@ -99,6 +105,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/shops/invitations/:id/accept", Handler: shop.AcceptInvitationHandler(serverCtx), }, + { + // 获取我收到的邀请 + Method: http.MethodGet, + Path: "/shops/invitations/mine", + Handler: shop.MyInvitationsHandler(serverCtx), + }, { // 获取当前用户的店铺 Method: http.MethodGet, diff --git a/app/shop/api/internal/handler/shop/listShopInvitationsHandler.go b/app/shop/api/internal/handler/shop/listShopInvitationsHandler.go new file mode 100644 index 0000000..d1fa040 --- /dev/null +++ b/app/shop/api/internal/handler/shop/listShopInvitationsHandler.go @@ -0,0 +1,32 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.10.1 + +package shop + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/shop/api/internal/logic/shop" + "juwan-backend/app/shop/api/internal/svc" + "juwan-backend/app/shop/api/internal/types" +) + +// 获取店铺邀请列表 +func ListShopInvitationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ShopIdReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := shop.NewListShopInvitationsLogic(r.Context(), svcCtx) + resp, err := l.ListShopInvitations(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/shop/api/internal/handler/shop/myInvitationsHandler.go b/app/shop/api/internal/handler/shop/myInvitationsHandler.go new file mode 100644 index 0000000..d14965b --- /dev/null +++ b/app/shop/api/internal/handler/shop/myInvitationsHandler.go @@ -0,0 +1,25 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.10.1 + +package shop + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "juwan-backend/app/shop/api/internal/logic/shop" + "juwan-backend/app/shop/api/internal/svc" +) + +// 获取我收到的邀请 +func MyInvitationsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := shop.NewMyInvitationsLogic(r.Context(), svcCtx) + resp, err := l.MyInvitations() + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/app/shop/api/internal/logic/shop/listShopInvitationsLogic.go b/app/shop/api/internal/logic/shop/listShopInvitationsLogic.go new file mode 100644 index 0000000..53c8bd3 --- /dev/null +++ b/app/shop/api/internal/logic/shop/listShopInvitationsLogic.go @@ -0,0 +1,72 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.10.1 + +package shop + +import ( + "context" + "errors" + + "juwan-backend/app/shop/api/internal/svc" + "juwan-backend/app/shop/api/internal/types" + "juwan-backend/app/shop/rpc/pb" + "juwan-backend/common/utils/contextj" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListShopInvitationsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 获取店铺邀请列表 +func NewListShopInvitationsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListShopInvitationsLogic { + return &ListShopInvitationsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ListShopInvitationsLogic) ListShopInvitations(req *types.ShopIdReq) (resp *types.ShopInvitationListResp, err error) { + userID, err := contextj.UserIDFrom(l.ctx) + if err != nil { + return nil, err + } + + shop, err := l.svcCtx.ShopRpc.GetShopsById(l.ctx, &pb.GetShopsByIdReq{Id: req.Id}) + if err != nil { + return nil, err + } + if shop.Shops == nil { + return nil, errors.New("shop not found") + } + if shop.Shops.OwnerId != userID { + return nil, contextj.ERRILLEGALUSER + } + + result, err := l.svcCtx.ShopRpc.SearchShopInvitations(l.ctx, &pb.SearchShopInvitationsReq{ + ShopId: req.Id, + Limit: 100, + }) + if err != nil { + return nil, err + } + + items := make([]types.ShopInvitation, 0, len(result.ShopInvitations)) + for _, inv := range result.ShopInvitations { + items = append(items, types.ShopInvitation{ + Id: inv.Id, + ShopId: inv.ShopId, + PlayerId: inv.PlayerId, + Status: inv.Status, + InvitedBy: inv.InvitedBy, + CreatedAt: inv.CreatedAt, + RespondedAt: inv.RespondedAt, + }) + } + + return &types.ShopInvitationListResp{Items: items}, nil +} diff --git a/app/shop/api/internal/logic/shop/myInvitationsLogic.go b/app/shop/api/internal/logic/shop/myInvitationsLogic.go new file mode 100644 index 0000000..7f82de3 --- /dev/null +++ b/app/shop/api/internal/logic/shop/myInvitationsLogic.go @@ -0,0 +1,70 @@ +// Code scaffolded by goctl. Safe to edit. +// goctl 1.10.1 + +package shop + +import ( + "context" + + "juwan-backend/app/player/rpc/playerservice" + "juwan-backend/app/shop/api/internal/svc" + "juwan-backend/app/shop/api/internal/types" + "juwan-backend/app/shop/rpc/pb" + "juwan-backend/common/utils/contextj" + + "github.com/zeromicro/go-zero/core/logx" +) + +type MyInvitationsLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// 获取我收到的邀请 +func NewMyInvitationsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MyInvitationsLogic { + return &MyInvitationsLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *MyInvitationsLogic) MyInvitations() (resp *types.ShopInvitationListResp, err error) { + userID, err := contextj.UserIDFrom(l.ctx) + if err != nil { + return nil, err + } + + playerResp, err := l.svcCtx.PlayerRpc.GetPlayerByUserId(l.ctx, &playerservice.SearchPlayersReq{UserId: &userID}) + if err != nil { + return nil, err + } + player := playerResp.GetPlayers() + if player == nil { + return &types.ShopInvitationListResp{Items: []types.ShopInvitation{}}, nil + } + + result, err := l.svcCtx.ShopRpc.SearchShopInvitations(l.ctx, &pb.SearchShopInvitationsReq{ + PlayerId: player.Id, + Limit: 100, + }) + if err != nil { + return nil, err + } + + items := make([]types.ShopInvitation, 0, len(result.ShopInvitations)) + for _, inv := range result.ShopInvitations { + items = append(items, types.ShopInvitation{ + Id: inv.Id, + ShopId: inv.ShopId, + PlayerId: inv.PlayerId, + Status: inv.Status, + InvitedBy: inv.InvitedBy, + CreatedAt: inv.CreatedAt, + RespondedAt: inv.RespondedAt, + }) + } + + return &types.ShopInvitationListResp{Items: items}, nil +} diff --git a/app/shop/api/internal/types/types.go b/app/shop/api/internal/types/types.go index 8a48d4f..e58a005 100644 --- a/app/shop/api/internal/types/types.go +++ b/app/shop/api/internal/types/types.go @@ -55,6 +55,20 @@ type ShopIdReq struct { Id int64 `path:"id"` } +type ShopInvitation struct { + Id int64 `json:"id"` + ShopId int64 `json:"shopId"` + PlayerId int64 `json:"playerId"` + Status string `json:"status"` + InvitedBy int64 `json:"invitedBy"` + CreatedAt int64 `json:"createdAt"` + RespondedAt int64 `json:"respondedAt,optional"` +} + +type ShopInvitationListResp struct { + Items []ShopInvitation `json:"items"` +} + type ShopListResp struct { Items []ShopProfile `json:"items"` Meta PageMeta `json:"meta"` diff --git a/app/shop/rpc/internal/logic/updateShopInvitationsLogic.go b/app/shop/rpc/internal/logic/updateShopInvitationsLogic.go index 143773a..958e32e 100644 --- a/app/shop/rpc/internal/logic/updateShopInvitationsLogic.go +++ b/app/shop/rpc/internal/logic/updateShopInvitationsLogic.go @@ -6,6 +6,7 @@ import ( "juwan-backend/app/shop/rpc/internal/models/shopinvitations" "juwan-backend/app/shop/rpc/internal/svc" "juwan-backend/app/shop/rpc/pb" + "time" "github.com/jinzhu/copier" "github.com/zeromicro/go-zero/core/logx" @@ -26,9 +27,13 @@ func NewUpdateShopInvitationsLogic(ctx context.Context, svcCtx *svc.ServiceConte } func (l *UpdateShopInvitationsLogic) UpdateShopInvitations(in *pb.UpdateShopInvitationsReq) (*pb.UpdateShopInvitationsResp, error) { - update, err := l.svcCtx.ShopModelRW.ShopInvitations.UpdateOneID(in.Id). + builder := l.svcCtx.ShopModelRW.ShopInvitations.UpdateOneID(in.Id). Where(shopinvitations.PlayerIDEQ(in.PlayerId)). - SetStatus(in.Status).Save(l.ctx) + SetStatus(in.Status) + if in.RespondedAt > 0 { + builder = builder.SetRespondedAt(time.Unix(in.RespondedAt, 0)) + } + update, err := builder.Save(l.ctx) if err != nil { logx.Errorf("failed to update shop invitation: %v", err) return nil, errors.New("failed to update shop invitation") diff --git a/deploy/dev/test_all_apis.py b/deploy/dev/test_all_apis.py index d8f354f..4aa4a59 100644 --- a/deploy/dev/test_all_apis.py +++ b/deploy/dev/test_all_apis.py @@ -647,7 +647,7 @@ def phase6_player(s: Session, game_id): return player_id, service_id -def phase7_shop(s_owner: Session, owner_user_id, invited_player_id): +def phase7_shop(s_owner: Session, s_invited_player: Session, owner_user_id, invited_player_id): print("\n=== Phase 7: Shop ===") s_owner.post( @@ -751,18 +751,55 @@ def phase7_shop(s_owner: Session, owner_user_id, invited_player_id): headers=csrf, ) report(f"POST /shops/{shop_id}/invitations", code, body) - skip( - "POST /shops/invitations/:id/accept", - "create invitation does not return invitation id", + + code, body, _ = s_owner.get( + f"{GATEWAY}/api/v1/shops/{shop_id}/invitations", ) - skip( - "DELETE /shops/invitations/:id", - "create invitation does not return invitation id", + report(f"GET /shops/{shop_id}/invitations", code, body) + + invitation_id = 0 + for item in pick_items(body): + if as_int(item.get("playerId")) == invited_player_id: + invitation_id = as_int(item.get("id")) + break + report_check( + "locate invitation id", + bool(invitation_id), + {"id": invitation_id}, ) - skip( - f"DELETE /shops/{shop_id}/players/{invited_player_id}", - "player removal depends on accepted invitation flow", + + s_invited_player.post( + f"{GATEWAY}/api/v1/users/me/switch-role", + json_body={"role": "player"}, + headers=s_invited_player.csrf_headers(), ) + code, body, _ = s_invited_player.get( + f"{GATEWAY}/api/v1/shops/invitations/mine", + ) + report("GET /shops/invitations/mine", code, body) + + if invitation_id: + inv_csrf = s_invited_player.csrf_headers() + code, body, _ = s_invited_player.post( + f"{GATEWAY}/api/v1/shops/invitations/{invitation_id}/accept", + json_body={}, + headers=inv_csrf, + ) + report( + f"POST /shops/invitations/{invitation_id}/accept", + code, + body, + ) + + code, body, _ = s_owner.delete( + f"{GATEWAY}/api/v1/shops/{shop_id}/players/{invited_player_id}", + headers=csrf, + ) + report( + f"DELETE /shops/{shop_id}/players/{invited_player_id}", + code, + body, + ) code, body, _ = s_owner.get(f"{GATEWAY}/api/v1/shops/mine") report("GET /shops/mine", code, body) @@ -1169,7 +1206,7 @@ def main(): game_id = phase5_games(s_user, s_admin) player_id, service_id = phase6_player(s_user, game_id) - shop_id = phase7_shop(s_user, user_id, invited_player_id) + shop_id = phase7_shop(s_user, s_consumer, user_id, invited_player_id) phase8_order(s_consumer, s_user, player_id, service_id, shop_id) phase9_wallet(s_user) diff --git a/desc/api/shop.api b/desc/api/shop.api index f623827..6bb9684 100644 --- a/desc/api/shop.api +++ b/desc/api/shop.api @@ -93,6 +93,18 @@ type ( AcceptInvitationReq { Id int64 `path:"id"` } + ShopInvitation { + Id int64 `json:"id"` + ShopId int64 `json:"shopId"` + PlayerId int64 `json:"playerId"` + Status string `json:"status"` + InvitedBy int64 `json:"invitedBy"` + CreatedAt int64 `json:"createdAt"` + RespondedAt int64 `json:"respondedAt,optional"` + } + ShopInvitationListResp { + Items []ShopInvitation `json:"items"` + } ) @server ( @@ -128,6 +140,14 @@ service shop-api { @handler InvitePlayer post /shops/:id/invitations (InvitationReq) returns (EmptyResp) + @doc "获取店铺邀请列表" + @handler ListShopInvitations + get /shops/:id/invitations (ShopIdReq) returns (ShopInvitationListResp) + + @doc "获取我收到的邀请" + @handler MyInvitations + get /shops/invitations/mine returns (ShopInvitationListResp) + @doc "接受邀请" @handler AcceptInvitation post /shops/invitations/:id/accept (AcceptInvitationReq) returns (EmptyResp)