feat: add tenant membership flow

This commit is contained in:
2026-01-13 10:26:11 +08:00
parent fa43c0355f
commit 2a670b3a78
11 changed files with 1719 additions and 0 deletions

View File

@@ -29,6 +29,46 @@ func (c *Creator) Apply(ctx fiber.Ctx, user *models.User, form *dto.ApplyForm) e
return services.Creator.Apply(ctx, tenantID, user.ID, form)
}
// Review join request
//
// @Router /t/:tenantCode/v1/creator/members/:id<int>/review [post]
// @Summary Review join request
// @Description Approve or reject a tenant join request
// @Tags CreatorCenter
// @Accept json
// @Produce json
// @Param id path int64 true "Join request ID"
// @Param form body dto.TenantJoinReviewForm true "Review form"
// @Success 200 {string} string "Reviewed"
// @Bind user local key(__ctx_user)
// @Bind id path
// @Bind form body
func (c *Creator) ReviewMember(ctx fiber.Ctx, user *models.User, id int64, form *dto.TenantJoinReviewForm) error {
tenantID := getTenantID(ctx)
return services.Tenant.ReviewJoin(ctx, tenantID, user.ID, id, form)
}
// Create member invite
//
// @Router /t/:tenantCode/v1/creator/members/invite [post]
// @Summary Create member invite
// @Description Create an invite for tenant members
// @Tags CreatorCenter
// @Accept json
// @Produce json
// @Param form body dto.TenantInviteCreateForm true "Invite form"
// @Success 200 {object} dto.TenantInviteItem
// @Bind user local key(__ctx_user)
// @Bind form body
func (c *Creator) CreateMemberInvite(
ctx fiber.Ctx,
user *models.User,
form *dto.TenantInviteCreateForm,
) (*dto.TenantInviteItem, error) {
tenantID := getTenantID(ctx)
return services.Tenant.CreateInvite(ctx, tenantID, user.ID, form)
}
// Get creator dashboard stats
//
// @Router /t/:tenantCode/v1/creator/dashboard [get]

View File

@@ -0,0 +1,46 @@
package dto
type TenantJoinApplyForm struct {
// Reason 申请加入原因(可选,空值会使用默认文案)。
Reason string `json:"reason"`
}
type TenantJoinReviewForm struct {
// Action 审核动作approve/reject
Action string `json:"action"`
// Reason 审核说明(可选,用于展示驳回原因或备注)。
Reason string `json:"reason"`
}
type TenantInviteCreateForm struct {
// MaxUses 最大可使用次数(<=0 默认 1
MaxUses int32 `json:"max_uses"`
// ExpiresAt 过期时间RFC3339可选空值使用默认过期时间
ExpiresAt *string `json:"expires_at"`
// Remark 备注说明(可选)。
Remark string `json:"remark"`
}
type TenantInviteAcceptForm struct {
// Code 邀请码(必填)。
Code string `json:"code"`
}
type TenantInviteItem struct {
// ID 邀请记录ID。
ID int64 `json:"id"`
// Code 邀请码。
Code string `json:"code"`
// Status 邀请状态active/disabled/expired
Status string `json:"status"`
// MaxUses 最大可使用次数。
MaxUses int32 `json:"max_uses"`
// UsedCount 已使用次数。
UsedCount int32 `json:"used_count"`
// ExpiresAt 过期时间RFC3339空字符串表示不限制
ExpiresAt string `json:"expires_at"`
// CreatedAt 创建时间RFC3339
CreatedAt string `json:"created_at"`
// Remark 备注说明。
Remark string `json:"remark"`
}

View File

@@ -208,6 +208,19 @@ func (r *Routes) Register(router fiber.Router) {
Local[*models.User]("__ctx_user"),
Body[dto.ContentCreateForm]("form"),
))
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/members/:id<int>/review -> creator.ReviewMember")
router.Post("/t/:tenantCode/v1/creator/members/:id<int>/review"[len(r.Path()):], Func3(
r.creator.ReviewMember,
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
Body[dto.TenantJoinReviewForm]("form"),
))
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/members/invite -> creator.CreateMemberInvite")
router.Post("/t/:tenantCode/v1/creator/members/invite"[len(r.Path()):], DataFunc2(
r.creator.CreateMemberInvite,
Local[*models.User]("__ctx_user"),
Body[dto.TenantInviteCreateForm]("form"),
))
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/creator/orders/:id<int>/refund -> creator.Refund")
router.Post("/t/:tenantCode/v1/creator/orders/:id<int>/refund"[len(r.Path()):], Func3(
r.creator.Refund,
@@ -260,6 +273,11 @@ func (r *Routes) Register(router fiber.Router) {
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
))
r.log.Debugf("Registering route: Delete /t/:tenantCode/v1/tenants/:id<int>/join -> tenant.CancelJoin")
router.Delete("/t/:tenantCode/v1/tenants/:id<int>/join"[len(r.Path()):], Func1(
r.tenant.CancelJoin,
PathParam[int64]("id"),
))
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/creators/:id<int>/contents -> tenant.ListContents")
router.Get("/t/:tenantCode/v1/creators/:id<int>/contents"[len(r.Path()):], DataFunc2(
r.tenant.ListContents,
@@ -283,6 +301,18 @@ func (r *Routes) Register(router fiber.Router) {
Local[*models.User]("__ctx_user"),
PathParam[int64]("id"),
))
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/tenants/:id<int>/invites/accept -> tenant.AcceptInvite")
router.Post("/t/:tenantCode/v1/tenants/:id<int>/invites/accept"[len(r.Path()):], Func2(
r.tenant.AcceptInvite,
PathParam[int64]("id"),
Body[dto.TenantInviteAcceptForm]("form"),
))
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/tenants/:id<int>/join -> tenant.ApplyJoin")
router.Post("/t/:tenantCode/v1/tenants/:id<int>/join"[len(r.Path()):], Func2(
r.tenant.ApplyJoin,
PathParam[int64]("id"),
Body[dto.TenantJoinApplyForm]("form"),
))
// Register routes for controller: Transaction
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/orders/:id<int>/status -> transaction.Status")
router.Get("/t/:tenantCode/v1/orders/:id<int>/status"[len(r.Path()):], DataFunc2(

View File

@@ -120,3 +120,67 @@ func (t *Tenant) Unfollow(ctx fiber.Ctx, user *models.User, id int64) error {
}
return services.Tenant.Unfollow(ctx, tenantID, user.ID)
}
// Apply to join a tenant
//
// @Router /t/:tenantCode/v1/tenants/:id<int>/join [post]
// @Summary Apply to join tenant
// @Description Submit join request for a tenant
// @Tags TenantPublic
// @Accept json
// @Produce json
// @Param id path int64 true "Tenant ID"
// @Param form body dto.TenantJoinApplyForm true "Join form"
// @Success 200 {string} string "Applied"
// @Bind id path
// @Bind form body
func (t *Tenant) ApplyJoin(ctx fiber.Ctx, id int64, form *dto.TenantJoinApplyForm) error {
tenantID := getTenantID(ctx)
if tenantID > 0 && id != tenantID {
return errorx.ErrForbidden.WithMsg("租户不匹配")
}
userID := getUserID(ctx)
return services.Tenant.ApplyJoin(ctx, id, userID, form)
}
// Cancel join request
//
// @Router /t/:tenantCode/v1/tenants/:id<int>/join [delete]
// @Summary Cancel join request
// @Description Cancel join request for a tenant
// @Tags TenantPublic
// @Accept json
// @Produce json
// @Param id path int64 true "Tenant ID"
// @Success 200 {string} string "Canceled"
// @Bind id path
func (t *Tenant) CancelJoin(ctx fiber.Ctx, id int64) error {
tenantID := getTenantID(ctx)
if tenantID > 0 && id != tenantID {
return errorx.ErrForbidden.WithMsg("租户不匹配")
}
userID := getUserID(ctx)
return services.Tenant.CancelJoin(ctx, id, userID)
}
// Accept tenant invite
//
// @Router /t/:tenantCode/v1/tenants/:id<int>/invites/accept [post]
// @Summary Accept tenant invite
// @Description Accept a tenant invite by code
// @Tags TenantPublic
// @Accept json
// @Produce json
// @Param id path int64 true "Tenant ID"
// @Param form body dto.TenantInviteAcceptForm true "Invite form"
// @Success 200 {string} string "Accepted"
// @Bind id path
// @Bind form body
func (t *Tenant) AcceptInvite(ctx fiber.Ctx, id int64, form *dto.TenantInviteAcceptForm) error {
tenantID := getTenantID(ctx)
if tenantID > 0 && id != tenantID {
return errorx.ErrForbidden.WithMsg("租户不匹配")
}
userID := getUserID(ctx)
return services.Tenant.AcceptInvite(ctx, id, userID, form)
}