Files
quyun-v2/backend/app/http/web/tenant_apply.go
2025-12-25 11:20:56 +08:00

148 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package web
import (
"errors"
"regexp"
"strings"
"quyun/v2/app/errorx"
"quyun/v2/app/http/web/dto"
"quyun/v2/app/services"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"quyun/v2/providers/jwt"
"github.com/gofiber/fiber/v3"
"go.ipao.vip/gen/types"
"gorm.io/gorm"
)
// @provider
type tenantApply struct{}
var reTenantCode = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{2,63}$`)
// Application 获取当前用户的租户申请信息(申请创作者)。
//
// @Summary 获取租户申请信息(申请创作者)
// @Tags Web
// @Accept json
// @Produce json
// @Success 200 {object} dto.TenantApplicationResponse "成功"
// @Router /v1/tenant/application [get]
func (ctl *tenantApply) application(ctx fiber.Ctx) (*dto.TenantApplicationResponse, error) {
claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims)
if !ok || claims == nil || claims.UserID <= 0 {
return nil, errorx.ErrTokenInvalid
}
m, err := services.Tenant.FindOwnedByUserID(ctx, claims.UserID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return &dto.TenantApplicationResponse{HasApplication: false}, nil
}
return nil, errorx.Wrap(err).WithMsg("查询申请信息失败,请稍后再试")
}
return &dto.TenantApplicationResponse{
HasApplication: true,
TenantID: m.ID,
TenantCode: m.Code,
TenantName: m.Name,
Status: m.Status,
StatusDescription: m.Status.Description(),
CreatedAt: m.CreatedAt,
}, nil
}
// Apply 申请创作者(创建租户申请)。
//
// @Summary 提交租户申请(申请创作者)
// @Tags Web
// @Accept json
// @Produce json
// @Param form body dto.TenantApplyForm true "form"
// @Success 200 {object} dto.TenantApplicationResponse "成功"
// @Router /v1/tenant/apply [post]
// @Bind form body
func (ctl *tenantApply) apply(ctx fiber.Ctx, form *dto.TenantApplyForm) (*dto.TenantApplicationResponse, error) {
claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims)
if !ok || claims == nil || claims.UserID <= 0 {
return nil, errorx.ErrTokenInvalid
}
code := strings.ToLower(strings.TrimSpace(form.Code))
if code == "" {
return nil, errorx.ErrMissingParameter.WithMsg("请填写租户 ID")
}
if !reTenantCode.MatchString(code) {
return nil, errorx.ErrInvalidParameter.WithMsg("租户 ID 需为 3-64 位小写字母/数字/下划线/短横线,且以字母或数字开头")
}
name := strings.TrimSpace(form.Name)
if name == "" {
return nil, errorx.ErrMissingParameter.WithMsg("请填写租户名称")
}
// 一个用户仅可申请为一个租户创作者:若已存在 owned tenant直接返回当前申请信息。
existing, err := services.Tenant.FindOwnedByUserID(ctx, claims.UserID)
if err == nil && existing != nil && existing.ID > 0 {
return &dto.TenantApplicationResponse{
HasApplication: true,
TenantID: existing.ID,
TenantCode: existing.Code,
TenantName: existing.Name,
Status: existing.Status,
StatusDescription: existing.Status.Description(),
CreatedAt: existing.CreatedAt,
}, nil
}
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.Wrap(err).WithMsg("申请校验失败,请稍后再试")
}
tenant := &models.Tenant{
UserID: claims.UserID,
Code: code,
UUID: types.NewUUIDv4(),
Name: name,
Status: consts.TenantStatusPendingVerify,
Config: types.JSON([]byte(`{}`)),
}
// NOTE: 使用全新 Session避免复用 gen.DO 内部带 Model/Statement 的 *gorm.DB 导致 schema 与 dest 不一致而触发 GORM 反射 panic。
db := models.Q.Tenant.WithContext(ctx).UnderlyingDB().Session(&gorm.Session{})
err = db.Transaction(func(tx *gorm.DB) error {
if err := tx.Omit("Users").Create(tenant).Error; err != nil {
return err
}
tenantUser := &models.TenantUser{
TenantID: tenant.ID,
UserID: claims.UserID,
Role: types.NewArray([]consts.TenantUserRole{consts.TenantUserRoleTenantAdmin}),
Status: consts.UserStatusVerified,
}
if err := tx.Create(tenantUser).Error; err != nil {
return err
}
return tx.First(tenant, tenant.ID).Error
})
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return nil, errorx.ErrRecordDuplicated.WithMsg("租户 ID 已被占用,请换一个试试")
}
return nil, errorx.Wrap(err).WithMsg("提交申请失败,请稍后再试")
}
return &dto.TenantApplicationResponse{
HasApplication: true,
TenantID: tenant.ID,
TenantCode: tenant.Code,
TenantName: tenant.Name,
Status: tenant.Status,
StatusDescription: tenant.Status.Description(),
CreatedAt: tenant.CreatedAt,
}, nil
}