feat: add tenant admin invite management, ledger overview, order details, and order management features
- Implemented Invite management with creation, searching, and disabling functionalities. - Added Ledger overview for financial transactions with filtering options. - Developed Order Detail view for individual order insights and refund capabilities. - Created Orders management page with search, reset, and pagination features. - Enhanced user experience with toast notifications for actions and error handling.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"quyun/v2/app/http/tenant_join"
|
||||
"quyun/v2/app/http/tenant_media"
|
||||
"quyun/v2/app/http/tenant_public"
|
||||
"quyun/v2/app/http/web"
|
||||
"quyun/v2/app/jobs"
|
||||
"quyun/v2/app/middlewares"
|
||||
"quyun/v2/app/services"
|
||||
@@ -60,7 +61,7 @@ func Command() atom.Option {
|
||||
tenant_public.Provide,
|
||||
tenant_media.Provide,
|
||||
// {Provider: api.Provide},
|
||||
// {Provider: web.Provide},
|
||||
web.Provide,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/app/http/tenant/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/app/services"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
@@ -28,6 +29,45 @@ func requireTenantAdmin(tenantUser *models.TenantUser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// list
|
||||
//
|
||||
// @Summary 内容列表(租户管理)
|
||||
// @Tags Tenant
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param tenantCode path string true "Tenant Code"
|
||||
// @Param filter query dto.AdminContentListFilter true "Filter"
|
||||
// @Success 200 {object} requests.Pager{items=dto.AdminContentItem}
|
||||
//
|
||||
// @Router /t/:tenantCode/v1/admin/contents [get]
|
||||
// @Bind tenant local key(tenant)
|
||||
// @Bind tenantUser local key(tenant_user)
|
||||
// @Bind filter query
|
||||
func (*contentAdmin) list(ctx fiber.Ctx, tenant *models.Tenant, tenantUser *models.TenantUser, filter *dto.AdminContentListFilter) (*requests.Pager, error) {
|
||||
if err := requireTenantAdmin(tenantUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter == nil {
|
||||
filter = &dto.AdminContentListFilter{}
|
||||
}
|
||||
filter.Pagination.Format()
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"tenant_id": tenant.ID,
|
||||
"user_id": tenantUser.UserID,
|
||||
"query_user_id": filter.UserID,
|
||||
"keyword": filter.KeywordTrimmed(),
|
||||
"status": filter.Status,
|
||||
"visibility": filter.Visibility,
|
||||
"published_at_from": filter.PublishedAtFrom,
|
||||
"published_at_to": filter.PublishedAtTo,
|
||||
"created_at_from": filter.CreatedAtFrom,
|
||||
"created_at_to": filter.CreatedAtTo,
|
||||
}).Info("tenant.admin.contents.list")
|
||||
|
||||
return services.Content.AdminContentPage(ctx.Context(), tenant.ID, filter)
|
||||
}
|
||||
|
||||
// create
|
||||
//
|
||||
// @Summary 创建内容(草稿)
|
||||
|
||||
56
backend/app/http/tenant/dto/content_admin_list.go
Normal file
56
backend/app/http/tenant/dto/content_admin_list.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen/types"
|
||||
)
|
||||
|
||||
// AdminContentListFilter 租户管理员查询“内容列表(含草稿/已发布/已下架等)”的过滤条件。
|
||||
type AdminContentListFilter struct {
|
||||
requests.Pagination `json:",inline" query:",inline"`
|
||||
requests.SortQueryFilter `json:",inline" query:",inline"`
|
||||
|
||||
ID *int64 `json:"id,omitempty" query:"id"`
|
||||
|
||||
UserID *int64 `json:"user_id,omitempty" query:"user_id"`
|
||||
|
||||
Keyword *string `json:"keyword,omitempty" query:"keyword"`
|
||||
|
||||
Status *consts.ContentStatus `json:"status,omitempty" query:"status"`
|
||||
Visibility *consts.ContentVisibility `json:"visibility,omitempty" query:"visibility"`
|
||||
|
||||
PublishedAtFrom *time.Time `json:"published_at_from,omitempty" query:"published_at_from"`
|
||||
PublishedAtTo *time.Time `json:"published_at_to,omitempty" query:"published_at_to"`
|
||||
|
||||
CreatedAtFrom *time.Time `json:"created_at_from,omitempty" query:"created_at_from"`
|
||||
CreatedAtTo *time.Time `json:"created_at_to,omitempty" query:"created_at_to"`
|
||||
}
|
||||
|
||||
func (f *AdminContentListFilter) KeywordTrimmed() string {
|
||||
if f == nil || f.Keyword == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(*f.Keyword)
|
||||
}
|
||||
|
||||
type AdminContentOwnerLite struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Status consts.UserStatus `json:"status"`
|
||||
Roles types.Array[consts.Role] `json:"roles"`
|
||||
}
|
||||
|
||||
type AdminContentItem struct {
|
||||
Content *models.Content `json:"content,omitempty"`
|
||||
Price *models.ContentPrice `json:"price,omitempty"`
|
||||
Owner *AdminContentOwnerLite `json:"owner,omitempty"`
|
||||
|
||||
StatusDescription string `json:"status_description,omitempty"`
|
||||
VisibilityDescription string `json:"visibility_description,omitempty"`
|
||||
}
|
||||
@@ -82,6 +82,13 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
PathParam[int64]("contentID"),
|
||||
))
|
||||
// Register routes for controller: contentAdmin
|
||||
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/admin/contents -> contentAdmin.list")
|
||||
router.Get("/t/:tenantCode/v1/admin/contents"[len(r.Path()):], DataFunc3(
|
||||
r.contentAdmin.list,
|
||||
Local[*models.Tenant]("tenant"),
|
||||
Local[*models.TenantUser]("tenant_user"),
|
||||
Query[dto.AdminContentListFilter]("filter"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Patch /t/:tenantCode/v1/admin/contents/:contentID -> contentAdmin.update")
|
||||
router.Patch("/t/:tenantCode/v1/admin/contents/:contentID"[len(r.Path()):], DataFunc4(
|
||||
r.contentAdmin.update,
|
||||
|
||||
69
backend/app/http/web/auth.go
Normal file
69
backend/app/http/web/auth.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/app/http/web/dto"
|
||||
"quyun/v2/app/services"
|
||||
"quyun/v2/pkg/consts"
|
||||
"quyun/v2/providers/jwt"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type auth struct {
|
||||
jwt *jwt.JWT
|
||||
}
|
||||
|
||||
// Login 用户登录(平台侧,非超级管理员)。
|
||||
//
|
||||
// @Summary 用户登录
|
||||
// @Tags Web
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param form body dto.LoginForm true "form"
|
||||
// @Success 200 {object} dto.LoginResponse "成功"
|
||||
// @Router /v1/auth/login [post]
|
||||
// @Bind form body
|
||||
func (ctl *auth) login(ctx fiber.Ctx, form *dto.LoginForm) (*dto.LoginResponse, error) {
|
||||
m, err := services.User.FindByUsername(ctx, form.Username)
|
||||
if err != nil {
|
||||
return nil, errorx.Wrap(err).WithMsg("用户名或密码错误")
|
||||
}
|
||||
if ok := m.ComparePassword(ctx, form.Password); !ok {
|
||||
return nil, errorx.Wrap(errorx.ErrInvalidCredentials).WithMsg("用户名或密码错误")
|
||||
}
|
||||
|
||||
token, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{
|
||||
UserID: m.ID,
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, errorx.Wrap(err).WithMsg("登录凭证生成失败")
|
||||
}
|
||||
|
||||
return &dto.LoginResponse{Token: token}, nil
|
||||
}
|
||||
|
||||
// Token 刷新登录凭证。
|
||||
//
|
||||
// @Summary 刷新 Token
|
||||
// @Tags Web
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.LoginResponse "成功"
|
||||
// @Router /v1/auth/token [get]
|
||||
func (ctl *auth) token(ctx fiber.Ctx) (*dto.LoginResponse, error) {
|
||||
claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims)
|
||||
if !ok || claims == nil || claims.UserID <= 0 {
|
||||
return nil, errorx.ErrTokenInvalid
|
||||
}
|
||||
|
||||
token, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{
|
||||
UserID: claims.UserID,
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, errorx.Wrap(err).WithMsg("登录凭证生成失败")
|
||||
}
|
||||
|
||||
return &dto.LoginResponse{Token: token}, nil
|
||||
}
|
||||
16
backend/app/http/web/dto/auth.go
Normal file
16
backend/app/http/web/dto/auth.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package dto
|
||||
|
||||
// LoginForm 平台侧用户登录表单(用于获取 JWT 访问凭证)。
|
||||
// 注意:此登录是“用户身份”登录(非超级管理员),用于进入租户管理后台前的身份校验与租户列表查询。
|
||||
type LoginForm struct {
|
||||
// Username 用户名;必须与数据库 users.username 精确匹配。
|
||||
Username string `json:"username,omitempty"`
|
||||
// Password 明文密码;后端会与 users.password 的 bcrypt hash 做比对。
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// LoginResponse 登录响应。
|
||||
type LoginResponse struct {
|
||||
// Token JWT 访问令牌;前端应以 `Authorization: Bearer <token>` 方式携带。
|
||||
Token string `json:"token"`
|
||||
}
|
||||
51
backend/app/http/web/dto/me.go
Normal file
51
backend/app/http/web/dto/me.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen/types"
|
||||
)
|
||||
|
||||
// MeResponse 当前登录用户信息(脱敏)。
|
||||
type MeResponse struct {
|
||||
// ID 用户ID(全局唯一)。
|
||||
ID int64 `json:"id"`
|
||||
// Username 用户名。
|
||||
Username string `json:"username"`
|
||||
// Roles 用户全局角色数组(如 user/super_admin 等)。
|
||||
Roles types.Array[consts.Role] `json:"roles"`
|
||||
// Status 用户状态(active/verified/banned 等)。
|
||||
Status consts.UserStatus `json:"status"`
|
||||
// StatusDescription 用户状态描述(便于前端展示)。
|
||||
StatusDescription string `json:"status_description"`
|
||||
// CreatedAt 用户创建时间。
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// UpdatedAt 用户更新时间。
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// MyTenantItem 当前用户可进入的租户条目(用于“选择租户进入后台”页面)。
|
||||
type MyTenantItem struct {
|
||||
// TenantID 租户ID(数值型主键)。
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
// TenantCode 租户Code(路由使用:/t/:tenantCode/...)。
|
||||
TenantCode string `json:"tenant_code"`
|
||||
// TenantName 租户名称。
|
||||
TenantName string `json:"tenant_name"`
|
||||
// TenantStatus 租户状态(pending/verified/expired 等)。
|
||||
TenantStatus consts.TenantStatus `json:"tenant_status"`
|
||||
// TenantStatusDescription 租户状态描述(便于前端展示)。
|
||||
TenantStatusDescription string `json:"tenant_status_description"`
|
||||
|
||||
// IsOwner 是否为租户Owner(tenants.user_id == 当前用户)。
|
||||
// 说明:Owner 通常也在 tenant_users 里具备 tenant_admin 角色,但此字段更直观。
|
||||
IsOwner bool `json:"is_owner"`
|
||||
// MemberRoles 当前用户在该租户下的角色(tenant_admin/member 等)。
|
||||
MemberRoles types.Array[consts.TenantUserRole] `json:"member_roles"`
|
||||
// MemberStatus 当前用户在该租户下的成员状态。
|
||||
MemberStatus consts.UserStatus `json:"member_status"`
|
||||
// JoinedAt 加入租户时间(tenant_users.created_at)。
|
||||
JoinedAt time.Time `json:"joined_at"`
|
||||
}
|
||||
61
backend/app/http/web/me.go
Normal file
61
backend/app/http/web/me.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/app/http/web/dto"
|
||||
"quyun/v2/app/services"
|
||||
"quyun/v2/pkg/consts"
|
||||
"quyun/v2/providers/jwt"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type me struct{}
|
||||
|
||||
// Me 获取当前登录用户信息(脱敏)。
|
||||
//
|
||||
// @Summary 当前用户信息
|
||||
// @Tags Web
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.MeResponse "成功"
|
||||
// @Router /v1/me [get]
|
||||
func (ctl *me) me(ctx fiber.Ctx) (*dto.MeResponse, error) {
|
||||
claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims)
|
||||
if !ok || claims == nil || claims.UserID <= 0 {
|
||||
return nil, errorx.ErrTokenInvalid
|
||||
}
|
||||
|
||||
m, err := services.User.FindByID(ctx, claims.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.MeResponse{
|
||||
ID: m.ID,
|
||||
Username: m.Username,
|
||||
Roles: m.Roles,
|
||||
Status: m.Status,
|
||||
StatusDescription: m.Status.Description(),
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MyTenants 获取当前用户可进入的租户列表。
|
||||
//
|
||||
// @Summary 我的租户列表
|
||||
// @Tags Web
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} dto.MyTenantItem "成功"
|
||||
// @Router /v1/me/tenants [get]
|
||||
func (ctl *me) myTenants(ctx fiber.Ctx) ([]*dto.MyTenantItem, error) {
|
||||
claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims)
|
||||
if !ok || claims == nil || claims.UserID <= 0 {
|
||||
return nil, errorx.ErrTokenInvalid
|
||||
}
|
||||
|
||||
return services.Tenant.UserTenants(ctx, claims.UserID)
|
||||
}
|
||||
51
backend/app/http/web/provider.gen.go
Executable file
51
backend/app/http/web/provider.gen.go
Executable file
@@ -0,0 +1,51 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"quyun/v2/app/middlewares"
|
||||
"quyun/v2/providers/jwt"
|
||||
|
||||
"go.ipao.vip/atom"
|
||||
"go.ipao.vip/atom/container"
|
||||
"go.ipao.vip/atom/contracts"
|
||||
"go.ipao.vip/atom/opt"
|
||||
)
|
||||
|
||||
func Provide(opts ...opt.Option) error {
|
||||
if err := container.Container.Provide(func(
|
||||
jwt *jwt.JWT,
|
||||
) (*auth, error) {
|
||||
obj := &auth{
|
||||
jwt: jwt,
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func() (*me, error) {
|
||||
obj := &me{}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
auth *auth,
|
||||
me *me,
|
||||
middlewares *middlewares.Middlewares,
|
||||
) (contracts.HttpRoute, error) {
|
||||
obj := &Routes{
|
||||
auth: auth,
|
||||
me: me,
|
||||
middlewares: middlewares,
|
||||
}
|
||||
if err := obj.Prepare(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}, atom.GroupRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
66
backend/app/http/web/routes.gen.go
Normal file
66
backend/app/http/web/routes.gen.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Code generated by atomctl. DO NOT EDIT.
|
||||
|
||||
// Package web provides HTTP route definitions and registration
|
||||
// for the quyun/v2 application.
|
||||
package web
|
||||
|
||||
import (
|
||||
"quyun/v2/app/http/web/dto"
|
||||
"quyun/v2/app/middlewares"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "go.ipao.vip/atom"
|
||||
_ "go.ipao.vip/atom/contracts"
|
||||
. "go.ipao.vip/atom/fen"
|
||||
)
|
||||
|
||||
// Routes implements the HttpRoute contract and provides route registration
|
||||
// for all controllers in the web module.
|
||||
//
|
||||
// @provider contracts.HttpRoute atom.GroupRoutes
|
||||
type Routes struct {
|
||||
log *log.Entry `inject:"false"`
|
||||
middlewares *middlewares.Middlewares
|
||||
// Controller instances
|
||||
auth *auth
|
||||
me *me
|
||||
}
|
||||
|
||||
// Prepare initializes the routes provider with logging configuration.
|
||||
func (r *Routes) Prepare() error {
|
||||
r.log = log.WithField("module", "routes.web")
|
||||
r.log.Info("Initializing routes module")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the unique identifier for this routes provider.
|
||||
func (r *Routes) Name() string {
|
||||
return "web"
|
||||
}
|
||||
|
||||
// Register registers all HTTP routes with the provided fiber router.
|
||||
// Each route is registered with its corresponding controller action and parameter bindings.
|
||||
func (r *Routes) Register(router fiber.Router) {
|
||||
// Register routes for controller: auth
|
||||
r.log.Debugf("Registering route: Get /v1/auth/token -> auth.token")
|
||||
router.Get("/v1/auth/token"[len(r.Path()):], DataFunc0(
|
||||
r.auth.token,
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /v1/auth/login -> auth.login")
|
||||
router.Post("/v1/auth/login"[len(r.Path()):], DataFunc1(
|
||||
r.auth.login,
|
||||
Body[dto.LoginForm]("form"),
|
||||
))
|
||||
// Register routes for controller: me
|
||||
r.log.Debugf("Registering route: Get /v1/me -> me.me")
|
||||
router.Get("/v1/me"[len(r.Path()):], DataFunc0(
|
||||
r.me.me,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/me/tenants -> me.myTenants")
|
||||
router.Get("/v1/me/tenants"[len(r.Path()):], DataFunc0(
|
||||
r.me.myTenants,
|
||||
))
|
||||
|
||||
r.log.Info("Successfully registered all routes")
|
||||
}
|
||||
11
backend/app/http/web/routes.manual.go
Normal file
11
backend/app/http/web/routes.manual.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package web
|
||||
|
||||
func (r *Routes) Path() string {
|
||||
return "/v1"
|
||||
}
|
||||
|
||||
func (r *Routes) Middlewares() []any {
|
||||
return []any{
|
||||
r.middlewares.UserAuth,
|
||||
}
|
||||
}
|
||||
57
backend/app/middlewares/user.go
Normal file
57
backend/app/middlewares/user.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/pkg/consts"
|
||||
"quyun/v2/providers/jwt"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func shouldSkipUserJWTAuth(path string) bool {
|
||||
// 登录接口无需鉴权。
|
||||
if strings.Contains(path, "/v1/auth/login") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UserAuth 为平台通用(非租户域)接口提供 JWT 校验,并写入 claims 到 ctx locals。
|
||||
func (f *Middlewares) UserAuth(c fiber.Ctx) error {
|
||||
if shouldSkipUserJWTAuth(c.Path()) {
|
||||
f.log.Debug("middlewares.user.auth.skipped")
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
authHeader := c.Get(jwt.HttpHeader)
|
||||
if authHeader == "" {
|
||||
f.log.Info("middlewares.user.auth.missing_token")
|
||||
return errorx.ErrTokenMissing
|
||||
}
|
||||
|
||||
claims, err := f.jwt.Parse(authHeader)
|
||||
if err != nil {
|
||||
f.log.WithError(err).Warn("middlewares.user.auth.invalid_token")
|
||||
switch err {
|
||||
case jwt.TokenExpired:
|
||||
return errorx.ErrTokenExpired
|
||||
case jwt.TokenMalformed, jwt.TokenNotValidYet, jwt.TokenInvalid:
|
||||
return errorx.ErrTokenInvalid
|
||||
default:
|
||||
return errorx.ErrTokenInvalid
|
||||
}
|
||||
}
|
||||
if claims.UserID == 0 {
|
||||
f.log.Warn("middlewares.user.auth.missing_user_id")
|
||||
return errorx.ErrTokenInvalid
|
||||
}
|
||||
|
||||
f.log.WithFields(map[string]any{
|
||||
"user_id": claims.UserID,
|
||||
}).Info("middlewares.user.auth.ok")
|
||||
|
||||
c.Locals(consts.CtxKeyClaims, claims)
|
||||
return c.Next()
|
||||
}
|
||||
180
backend/app/services/content_admin.go
Normal file
180
backend/app/services/content_admin.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
tenantdto "quyun/v2/app/http/tenant/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/database"
|
||||
"quyun/v2/database/models"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/samber/lo"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.ipao.vip/gen"
|
||||
"go.ipao.vip/gen/field"
|
||||
)
|
||||
|
||||
// AdminContentPage returns contents list for tenant admin (includes drafts/unpublished/etc).
|
||||
func (s *content) AdminContentPage(ctx context.Context, tenantID int64, filter *tenantdto.AdminContentListFilter) (*requests.Pager, error) {
|
||||
if tenantID <= 0 {
|
||||
return nil, errors.New("tenant_id must be > 0")
|
||||
}
|
||||
if filter == nil {
|
||||
filter = &tenantdto.AdminContentListFilter{}
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"page": filter.Page,
|
||||
"limit": filter.Limit,
|
||||
"user_id": lo.FromPtr(filter.UserID),
|
||||
"keyword": filter.KeywordTrimmed(),
|
||||
"status": lo.FromPtr(filter.Status),
|
||||
}).Info("services.content.admin.page")
|
||||
|
||||
filter.Pagination.Format()
|
||||
|
||||
cTbl, query := models.ContentQuery.QueryContext(ctx)
|
||||
query = query.Select(cTbl.ALL)
|
||||
|
||||
conds := []gen.Condition{
|
||||
cTbl.TenantID.Eq(tenantID),
|
||||
cTbl.DeletedAt.IsNull(),
|
||||
}
|
||||
if filter.ID != nil && *filter.ID > 0 {
|
||||
conds = append(conds, cTbl.ID.Eq(*filter.ID))
|
||||
}
|
||||
if filter.UserID != nil && *filter.UserID > 0 {
|
||||
conds = append(conds, cTbl.UserID.Eq(*filter.UserID))
|
||||
}
|
||||
if kw := strings.TrimSpace(filter.KeywordTrimmed()); kw != "" {
|
||||
conds = append(conds, cTbl.Title.Like(database.WrapLike(kw)))
|
||||
}
|
||||
if filter.Status != nil {
|
||||
conds = append(conds, cTbl.Status.Eq(*filter.Status))
|
||||
}
|
||||
if filter.Visibility != nil {
|
||||
conds = append(conds, cTbl.Visibility.Eq(*filter.Visibility))
|
||||
}
|
||||
if filter.PublishedAtFrom != nil {
|
||||
conds = append(conds, cTbl.PublishedAt.Gte(*filter.PublishedAtFrom))
|
||||
}
|
||||
if filter.PublishedAtTo != nil {
|
||||
conds = append(conds, cTbl.PublishedAt.Lte(*filter.PublishedAtTo))
|
||||
}
|
||||
if filter.CreatedAtFrom != nil {
|
||||
conds = append(conds, cTbl.CreatedAt.Gte(*filter.CreatedAtFrom))
|
||||
}
|
||||
if filter.CreatedAtTo != nil {
|
||||
conds = append(conds, cTbl.CreatedAt.Lte(*filter.CreatedAtTo))
|
||||
}
|
||||
|
||||
orderBys := make([]field.Expr, 0, 6)
|
||||
allowedAsc := map[string]field.Expr{
|
||||
"id": cTbl.ID.Asc(),
|
||||
"title": cTbl.Title.Asc(),
|
||||
"user_id": cTbl.UserID.Asc(),
|
||||
"status": cTbl.Status.Asc(),
|
||||
"visibility": cTbl.Visibility.Asc(),
|
||||
"published_at": cTbl.PublishedAt.Asc(),
|
||||
"created_at": cTbl.CreatedAt.Asc(),
|
||||
"updated_at": cTbl.UpdatedAt.Asc(),
|
||||
}
|
||||
allowedDesc := map[string]field.Expr{
|
||||
"id": cTbl.ID.Desc(),
|
||||
"title": cTbl.Title.Desc(),
|
||||
"user_id": cTbl.UserID.Desc(),
|
||||
"status": cTbl.Status.Desc(),
|
||||
"visibility": cTbl.Visibility.Desc(),
|
||||
"published_at": cTbl.PublishedAt.Desc(),
|
||||
"created_at": cTbl.CreatedAt.Desc(),
|
||||
"updated_at": cTbl.UpdatedAt.Desc(),
|
||||
}
|
||||
for _, f := range filter.AscFields() {
|
||||
f = strings.TrimSpace(f)
|
||||
if f == "" {
|
||||
continue
|
||||
}
|
||||
if ob, ok := allowedAsc[f]; ok {
|
||||
orderBys = append(orderBys, ob)
|
||||
}
|
||||
}
|
||||
for _, f := range filter.DescFields() {
|
||||
f = strings.TrimSpace(f)
|
||||
if f == "" {
|
||||
continue
|
||||
}
|
||||
if ob, ok := allowedDesc[f]; ok {
|
||||
orderBys = append(orderBys, ob)
|
||||
}
|
||||
}
|
||||
if len(orderBys) == 0 {
|
||||
orderBys = append(orderBys, cTbl.ID.Desc())
|
||||
} else {
|
||||
orderBys = append(orderBys, cTbl.ID.Desc())
|
||||
}
|
||||
|
||||
items, total, err := query.Where(conds...).Order(orderBys...).FindByPage(int(filter.Offset()), int(filter.Limit))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contentIDs := lo.Uniq(lo.FilterMap(items, func(item *models.Content, _ int) (int64, bool) {
|
||||
if item == nil || item.ID <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
return item.ID, true
|
||||
}))
|
||||
ownerIDs := lo.Uniq(lo.FilterMap(items, func(item *models.Content, _ int) (int64, bool) {
|
||||
if item == nil || item.UserID <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
return item.UserID, true
|
||||
}))
|
||||
|
||||
priceByContent, err := s.contentPriceMapping(ctx, tenantID, contentIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ownerMap := map[int64]*tenantdto.AdminContentOwnerLite{}
|
||||
if len(ownerIDs) > 0 {
|
||||
uTbl, uQuery := models.UserQuery.QueryContext(ctx)
|
||||
users, err := uQuery.Where(uTbl.ID.In(ownerIDs...)).Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, u := range users {
|
||||
if u == nil {
|
||||
continue
|
||||
}
|
||||
ownerMap[u.ID] = &tenantdto.AdminContentOwnerLite{
|
||||
ID: u.ID,
|
||||
Username: u.Username,
|
||||
Status: u.Status,
|
||||
Roles: u.Roles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
respItems := lo.Map(items, func(model *models.Content, _ int) *tenantdto.AdminContentItem {
|
||||
if model == nil {
|
||||
return nil
|
||||
}
|
||||
return &tenantdto.AdminContentItem{
|
||||
Content: model,
|
||||
Price: priceByContent[model.ID],
|
||||
Owner: ownerMap[model.UserID],
|
||||
StatusDescription: model.Status.Description(),
|
||||
VisibilityDescription: model.Visibility.Description(),
|
||||
}
|
||||
})
|
||||
|
||||
return &requests.Pager{
|
||||
Pagination: filter.Pagination,
|
||||
Total: total,
|
||||
Items: respItems,
|
||||
}, nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
superdto "quyun/v2/app/http/super/dto"
|
||||
tenantdto "quyun/v2/app/http/tenant/dto"
|
||||
web_dto "quyun/v2/app/http/web/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/database"
|
||||
"quyun/v2/database/models"
|
||||
@@ -26,6 +27,72 @@ import (
|
||||
// @provider
|
||||
type tenant struct{}
|
||||
|
||||
// UserTenants 查询“当前用户可进入的租户列表”(平台通用域 /v1)。
|
||||
// - 返回 tenant_users 维度的角色与加入时间,便于前端做“选择租户进入后台”的交互。
|
||||
// - 不做租户状态过滤:前端可以根据 TenantStatusDescription 做提示或禁用进入按钮。
|
||||
func (t *tenant) UserTenants(ctx context.Context, userID int64) ([]*web_dto.MyTenantItem, error) {
|
||||
if userID <= 0 {
|
||||
return nil, errors.New("user_id must be > 0")
|
||||
}
|
||||
|
||||
// 先查 tenant_users,拿到用户加入的租户ID与角色信息(避免 join scan 冲突/字段覆盖问题)。
|
||||
tuTbl, tuQuery := models.TenantUserQuery.QueryContext(ctx)
|
||||
tenantUsers, err := tuQuery.Where(tuTbl.UserID.Eq(userID)).Order(tuTbl.ID.Desc()).Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tenantUsers) == 0 {
|
||||
return []*web_dto.MyTenantItem{}, nil
|
||||
}
|
||||
|
||||
tenantIDs := lo.Uniq(lo.FilterMap(tenantUsers, func(tu *models.TenantUser, _ int) (int64, bool) {
|
||||
if tu == nil || tu.TenantID <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
return tu.TenantID, true
|
||||
}))
|
||||
if len(tenantIDs) == 0 {
|
||||
return []*web_dto.MyTenantItem{}, nil
|
||||
}
|
||||
|
||||
// 再查 tenants(批量),并构建映射,保持输出顺序以 tenant_users 为准。
|
||||
teTbl, teQuery := models.TenantQuery.QueryContext(ctx)
|
||||
tenants, err := teQuery.Where(teTbl.ID.In(tenantIDs...)).Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenantMap := make(map[int64]*models.Tenant, len(tenants))
|
||||
for _, te := range tenants {
|
||||
if te == nil {
|
||||
continue
|
||||
}
|
||||
tenantMap[te.ID] = te
|
||||
}
|
||||
|
||||
items := make([]*web_dto.MyTenantItem, 0, len(tenantUsers))
|
||||
for _, tu := range tenantUsers {
|
||||
if tu == nil {
|
||||
continue
|
||||
}
|
||||
te := tenantMap[tu.TenantID]
|
||||
if te == nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, &web_dto.MyTenantItem{
|
||||
TenantID: te.ID,
|
||||
TenantCode: te.Code,
|
||||
TenantName: te.Name,
|
||||
TenantStatus: te.Status,
|
||||
TenantStatusDescription: te.Status.Description(),
|
||||
IsOwner: te.UserID == userID,
|
||||
MemberRoles: tu.Role,
|
||||
MemberStatus: tu.Status,
|
||||
JoinedAt: tu.CreatedAt,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// SuperDetail 查询单个租户详情(平台侧)。
|
||||
func (t *tenant) SuperDetail(ctx context.Context, tenantID int64) (*superdto.TenantItem, error) {
|
||||
if tenantID <= 0 {
|
||||
|
||||
@@ -42,7 +42,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.LoginForm"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_super_dto.LoginForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -50,7 +50,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.LoginResponse"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_super_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.LoginResponse"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_super_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1429,6 +1429,140 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/admin/contents": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "内容列表(租户管理)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Tenant Code",
|
||||
"name": "tenantCode",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Asc specifies comma-separated field names to sort ascending by.",
|
||||
"name": "asc",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "created_at_from",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "created_at_to",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Desc specifies comma-separated field names to sort descending by.",
|
||||
"name": "desc",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "keyword",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "published_at_from",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "published_at_to",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"draft",
|
||||
"reviewing",
|
||||
"published",
|
||||
"unpublished",
|
||||
"blocked"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
"ContentStatusDraft",
|
||||
"ContentStatusReviewing",
|
||||
"ContentStatusPublished",
|
||||
"ContentStatusUnpublished",
|
||||
"ContentStatusBlocked"
|
||||
],
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"public",
|
||||
"tenant_only",
|
||||
"private"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
"ContentVisibilityPublic",
|
||||
"ContentVisibilityTenantOnly",
|
||||
"ContentVisibilityPrivate"
|
||||
],
|
||||
"name": "visibility",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.AdminContentItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
@@ -3295,7 +3429,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.MeResponse"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_tenant_dto.MeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3717,6 +3851,108 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/auth/login": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "用户登录",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.LoginForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/auth/token": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "刷新 Token",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/me": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "当前用户信息",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.MeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/me/tenants": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "我的租户列表",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.MyTenantItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -3958,6 +4194,46 @@ const docTemplate = `{
|
||||
"UserStatusBanned"
|
||||
]
|
||||
},
|
||||
"dto.AdminContentItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"$ref": "#/definitions/models.Content"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/definitions/dto.AdminContentOwnerLite"
|
||||
},
|
||||
"price": {
|
||||
"$ref": "#/definitions/models.ContentPrice"
|
||||
},
|
||||
"status_description": {
|
||||
"type": "string"
|
||||
},
|
||||
"visibility_description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.AdminContentOwnerLite": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/consts.Role"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/consts.UserStatus"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.AdminLedgerItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4435,25 +4711,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.LoginForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MeBalanceResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4479,35 +4736,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tenant": {
|
||||
"description": "Tenant is the resolved tenant by ` + "`" + `tenantCode` + "`" + `.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Tenant"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_user": {
|
||||
"description": "TenantUser is the membership record of the authenticated user within the tenant.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TenantUser"
|
||||
}
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
"description": "User is the authenticated user derived from JWT ` + "`" + `user_id` + "`" + `.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.User"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MyLedgerItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4525,6 +4753,58 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MyTenantItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"is_owner": {
|
||||
"description": "IsOwner 是否为租户Owner(tenants.user_id == 当前用户)。\n说明:Owner 通常也在 tenant_users 里具备 tenant_admin 角色,但此字段更直观。",
|
||||
"type": "boolean"
|
||||
},
|
||||
"joined_at": {
|
||||
"description": "JoinedAt 加入租户时间(tenant_users.created_at)。",
|
||||
"type": "string"
|
||||
},
|
||||
"member_roles": {
|
||||
"description": "MemberRoles 当前用户在该租户下的角色(tenant_admin/member 等)。",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/consts.TenantUserRole"
|
||||
}
|
||||
},
|
||||
"member_status": {
|
||||
"description": "MemberStatus 当前用户在该租户下的成员状态。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.UserStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 租户Code(路由使用:/t/:tenantCode/...)。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 租户ID(数值型主键)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_status": {
|
||||
"description": "TenantStatus 租户状态(pending/verified/expired 等)。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.TenantStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_status_description": {
|
||||
"description": "TenantStatusDescription 租户状态描述(便于前端展示)。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OrderBuyerLite": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5899,6 +6179,116 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_super_dto.LoginForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_super_dto.LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_tenant_dto.MeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tenant": {
|
||||
"description": "Tenant is the resolved tenant by ` + "`" + `tenantCode` + "`" + `.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Tenant"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_user": {
|
||||
"description": "TenantUser is the membership record of the authenticated user within the tenant.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TenantUser"
|
||||
}
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
"description": "User is the authenticated user derived from JWT ` + "`" + `user_id` + "`" + `.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.User"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_web_dto.LoginForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"description": "Password 明文密码;后端会与 users.password 的 bcrypt hash 做比对。",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username 用户名;必须与数据库 users.username 精确匹配。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_web_dto.LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"description": "Token JWT 访问令牌;前端应以 ` + "`" + `Authorization: Bearer \u003ctoken\u003e` + "`" + ` 方式携带。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_web_dto.MeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"description": "CreatedAt 用户创建时间。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 用户ID(全局唯一)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"roles": {
|
||||
"description": "Roles 用户全局角色数组(如 user/super_admin 等)。",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/consts.Role"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"description": "Status 用户状态(active/verified/banned 等)。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.UserStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status_description": {
|
||||
"description": "StatusDescription 用户状态描述(便于前端展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "UpdatedAt 用户更新时间。",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username 用户名。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requests.KV": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.LoginForm"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_super_dto.LoginForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -44,7 +44,7 @@
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.LoginResponse"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_super_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.LoginResponse"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_super_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1423,6 +1423,140 @@
|
||||
}
|
||||
},
|
||||
"/t/{tenantCode}/v1/admin/contents": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Tenant"
|
||||
],
|
||||
"summary": "内容列表(租户管理)",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Tenant Code",
|
||||
"name": "tenantCode",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Asc specifies comma-separated field names to sort ascending by.",
|
||||
"name": "asc",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "created_at_from",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "created_at_to",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Desc specifies comma-separated field names to sort descending by.",
|
||||
"name": "desc",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "keyword",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Limit is page size; only values in {10,20,50,100} are accepted (otherwise defaults to 10).",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page is 1-based page index; values \u003c= 0 are normalized to 1.",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "published_at_from",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "published_at_to",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"draft",
|
||||
"reviewing",
|
||||
"published",
|
||||
"unpublished",
|
||||
"blocked"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
"ContentStatusDraft",
|
||||
"ContentStatusReviewing",
|
||||
"ContentStatusPublished",
|
||||
"ContentStatusUnpublished",
|
||||
"ContentStatusBlocked"
|
||||
],
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"public",
|
||||
"tenant_only",
|
||||
"private"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
"ContentVisibilityPublic",
|
||||
"ContentVisibilityTenantOnly",
|
||||
"ContentVisibilityPrivate"
|
||||
],
|
||||
"name": "visibility",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/requests.Pager"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.AdminContentItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
@@ -3289,7 +3423,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.MeResponse"
|
||||
"$ref": "#/definitions/quyun_v2_app_http_tenant_dto.MeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3711,6 +3845,108 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/auth/login": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "用户登录",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "form",
|
||||
"name": "form",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.LoginForm"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/auth/token": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "刷新 Token",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/me": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "当前用户信息",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/quyun_v2_app_http_web_dto.MeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/me/tenants": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Web"
|
||||
],
|
||||
"summary": "我的租户列表",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dto.MyTenantItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -3952,6 +4188,46 @@
|
||||
"UserStatusBanned"
|
||||
]
|
||||
},
|
||||
"dto.AdminContentItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"$ref": "#/definitions/models.Content"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/definitions/dto.AdminContentOwnerLite"
|
||||
},
|
||||
"price": {
|
||||
"$ref": "#/definitions/models.ContentPrice"
|
||||
},
|
||||
"status_description": {
|
||||
"type": "string"
|
||||
},
|
||||
"visibility_description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.AdminContentOwnerLite": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/consts.Role"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/definitions/consts.UserStatus"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.AdminLedgerItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4429,25 +4705,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.LoginForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MeBalanceResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4473,35 +4730,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tenant": {
|
||||
"description": "Tenant is the resolved tenant by `tenantCode`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Tenant"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_user": {
|
||||
"description": "TenantUser is the membership record of the authenticated user within the tenant.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TenantUser"
|
||||
}
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
"description": "User is the authenticated user derived from JWT `user_id`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.User"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MyLedgerItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4519,6 +4747,58 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.MyTenantItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"is_owner": {
|
||||
"description": "IsOwner 是否为租户Owner(tenants.user_id == 当前用户)。\n说明:Owner 通常也在 tenant_users 里具备 tenant_admin 角色,但此字段更直观。",
|
||||
"type": "boolean"
|
||||
},
|
||||
"joined_at": {
|
||||
"description": "JoinedAt 加入租户时间(tenant_users.created_at)。",
|
||||
"type": "string"
|
||||
},
|
||||
"member_roles": {
|
||||
"description": "MemberRoles 当前用户在该租户下的角色(tenant_admin/member 等)。",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/consts.TenantUserRole"
|
||||
}
|
||||
},
|
||||
"member_status": {
|
||||
"description": "MemberStatus 当前用户在该租户下的成员状态。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.UserStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_code": {
|
||||
"description": "TenantCode 租户Code(路由使用:/t/:tenantCode/...)。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_id": {
|
||||
"description": "TenantID 租户ID(数值型主键)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"tenant_name": {
|
||||
"description": "TenantName 租户名称。",
|
||||
"type": "string"
|
||||
},
|
||||
"tenant_status": {
|
||||
"description": "TenantStatus 租户状态(pending/verified/expired 等)。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.TenantStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_status_description": {
|
||||
"description": "TenantStatusDescription 租户状态描述(便于前端展示)。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OrderBuyerLite": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5893,6 +6173,116 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_super_dto.LoginForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_super_dto.LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_tenant_dto.MeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tenant": {
|
||||
"description": "Tenant is the resolved tenant by `tenantCode`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Tenant"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tenant_user": {
|
||||
"description": "TenantUser is the membership record of the authenticated user within the tenant.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TenantUser"
|
||||
}
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
"description": "User is the authenticated user derived from JWT `user_id`.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.User"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_web_dto.LoginForm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"description": "Password 明文密码;后端会与 users.password 的 bcrypt hash 做比对。",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username 用户名;必须与数据库 users.username 精确匹配。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_web_dto.LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"token": {
|
||||
"description": "Token JWT 访问令牌;前端应以 `Authorization: Bearer \u003ctoken\u003e` 方式携带。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quyun_v2_app_http_web_dto.MeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"description": "CreatedAt 用户创建时间。",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID 用户ID(全局唯一)。",
|
||||
"type": "integer"
|
||||
},
|
||||
"roles": {
|
||||
"description": "Roles 用户全局角色数组(如 user/super_admin 等)。",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/consts.Role"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"description": "Status 用户状态(active/verified/banned 等)。",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/consts.UserStatus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status_description": {
|
||||
"description": "StatusDescription 用户状态描述(便于前端展示)。",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "UpdatedAt 用户更新时间。",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username 用户名。",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requests.KV": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -184,6 +184,32 @@ definitions:
|
||||
- UserStatusPendingVerify
|
||||
- UserStatusVerified
|
||||
- UserStatusBanned
|
||||
dto.AdminContentItem:
|
||||
properties:
|
||||
content:
|
||||
$ref: '#/definitions/models.Content'
|
||||
owner:
|
||||
$ref: '#/definitions/dto.AdminContentOwnerLite'
|
||||
price:
|
||||
$ref: '#/definitions/models.ContentPrice'
|
||||
status_description:
|
||||
type: string
|
||||
visibility_description:
|
||||
type: string
|
||||
type: object
|
||||
dto.AdminContentOwnerLite:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
roles:
|
||||
items:
|
||||
$ref: '#/definitions/consts.Role'
|
||||
type: array
|
||||
status:
|
||||
$ref: '#/definitions/consts.UserStatus'
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
dto.AdminLedgerItem:
|
||||
properties:
|
||||
ledger:
|
||||
@@ -531,18 +557,6 @@ definitions:
|
||||
description: Reason 申请原因(可选):用于向租户管理员说明申请加入的目的。
|
||||
type: string
|
||||
type: object
|
||||
dto.LoginForm:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
dto.LoginResponse:
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
dto.MeBalanceResponse:
|
||||
properties:
|
||||
balance:
|
||||
@@ -559,22 +573,6 @@ definitions:
|
||||
description: UpdatedAt 更新时间:余额变更时更新。
|
||||
type: string
|
||||
type: object
|
||||
dto.MeResponse:
|
||||
properties:
|
||||
tenant:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Tenant'
|
||||
description: Tenant is the resolved tenant by `tenantCode`.
|
||||
tenant_user:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.TenantUser'
|
||||
description: TenantUser is the membership record of the authenticated user
|
||||
within the tenant.
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.User'
|
||||
description: User is the authenticated user derived from JWT `user_id`.
|
||||
type: object
|
||||
dto.MyLedgerItem:
|
||||
properties:
|
||||
ledger:
|
||||
@@ -585,6 +583,42 @@ definitions:
|
||||
description: TypeDescription 流水类型中文说明(用于前端展示)。
|
||||
type: string
|
||||
type: object
|
||||
dto.MyTenantItem:
|
||||
properties:
|
||||
is_owner:
|
||||
description: |-
|
||||
IsOwner 是否为租户Owner(tenants.user_id == 当前用户)。
|
||||
说明:Owner 通常也在 tenant_users 里具备 tenant_admin 角色,但此字段更直观。
|
||||
type: boolean
|
||||
joined_at:
|
||||
description: JoinedAt 加入租户时间(tenant_users.created_at)。
|
||||
type: string
|
||||
member_roles:
|
||||
description: MemberRoles 当前用户在该租户下的角色(tenant_admin/member 等)。
|
||||
items:
|
||||
$ref: '#/definitions/consts.TenantUserRole'
|
||||
type: array
|
||||
member_status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/consts.UserStatus'
|
||||
description: MemberStatus 当前用户在该租户下的成员状态。
|
||||
tenant_code:
|
||||
description: TenantCode 租户Code(路由使用:/t/:tenantCode/...)。
|
||||
type: string
|
||||
tenant_id:
|
||||
description: TenantID 租户ID(数值型主键)。
|
||||
type: integer
|
||||
tenant_name:
|
||||
description: TenantName 租户名称。
|
||||
type: string
|
||||
tenant_status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/consts.TenantStatus'
|
||||
description: TenantStatus 租户状态(pending/verified/expired 等)。
|
||||
tenant_status_description:
|
||||
description: TenantStatusDescription 租户状态描述(便于前端展示)。
|
||||
type: string
|
||||
type: object
|
||||
dto.OrderBuyerLite:
|
||||
properties:
|
||||
id:
|
||||
@@ -1503,6 +1537,76 @@ definitions:
|
||||
verified_at:
|
||||
type: string
|
||||
type: object
|
||||
quyun_v2_app_http_super_dto.LoginForm:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
quyun_v2_app_http_super_dto.LoginResponse:
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
quyun_v2_app_http_tenant_dto.MeResponse:
|
||||
properties:
|
||||
tenant:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Tenant'
|
||||
description: Tenant is the resolved tenant by `tenantCode`.
|
||||
tenant_user:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.TenantUser'
|
||||
description: TenantUser is the membership record of the authenticated user
|
||||
within the tenant.
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.User'
|
||||
description: User is the authenticated user derived from JWT `user_id`.
|
||||
type: object
|
||||
quyun_v2_app_http_web_dto.LoginForm:
|
||||
properties:
|
||||
password:
|
||||
description: Password 明文密码;后端会与 users.password 的 bcrypt hash 做比对。
|
||||
type: string
|
||||
username:
|
||||
description: Username 用户名;必须与数据库 users.username 精确匹配。
|
||||
type: string
|
||||
type: object
|
||||
quyun_v2_app_http_web_dto.LoginResponse:
|
||||
properties:
|
||||
token:
|
||||
description: 'Token JWT 访问令牌;前端应以 `Authorization: Bearer <token>` 方式携带。'
|
||||
type: string
|
||||
type: object
|
||||
quyun_v2_app_http_web_dto.MeResponse:
|
||||
properties:
|
||||
created_at:
|
||||
description: CreatedAt 用户创建时间。
|
||||
type: string
|
||||
id:
|
||||
description: ID 用户ID(全局唯一)。
|
||||
type: integer
|
||||
roles:
|
||||
description: Roles 用户全局角色数组(如 user/super_admin 等)。
|
||||
items:
|
||||
$ref: '#/definitions/consts.Role'
|
||||
type: array
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/consts.UserStatus'
|
||||
description: Status 用户状态(active/verified/banned 等)。
|
||||
status_description:
|
||||
description: StatusDescription 用户状态描述(便于前端展示)。
|
||||
type: string
|
||||
updated_at:
|
||||
description: UpdatedAt 用户更新时间。
|
||||
type: string
|
||||
username:
|
||||
description: Username 用户名。
|
||||
type: string
|
||||
type: object
|
||||
requests.KV:
|
||||
properties:
|
||||
key:
|
||||
@@ -1558,14 +1662,14 @@ paths:
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.LoginForm'
|
||||
$ref: '#/definitions/quyun_v2_app_http_super_dto.LoginForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
$ref: '#/definitions/dto.LoginResponse'
|
||||
$ref: '#/definitions/quyun_v2_app_http_super_dto.LoginResponse'
|
||||
tags:
|
||||
- Super
|
||||
/super/v1/auth/token:
|
||||
@@ -1578,7 +1682,7 @@ paths:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
$ref: '#/definitions/dto.LoginResponse'
|
||||
$ref: '#/definitions/quyun_v2_app_http_super_dto.LoginResponse'
|
||||
tags:
|
||||
- Super
|
||||
/super/v1/contents:
|
||||
@@ -2465,6 +2569,95 @@ paths:
|
||||
tags:
|
||||
- Super
|
||||
/t/{tenantCode}/v1/admin/contents:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: Tenant Code
|
||||
in: path
|
||||
name: tenantCode
|
||||
required: true
|
||||
type: string
|
||||
- description: Asc specifies comma-separated field names to sort ascending by.
|
||||
in: query
|
||||
name: asc
|
||||
type: string
|
||||
- in: query
|
||||
name: created_at_from
|
||||
type: string
|
||||
- in: query
|
||||
name: created_at_to
|
||||
type: string
|
||||
- description: Desc specifies comma-separated field names to sort descending
|
||||
by.
|
||||
in: query
|
||||
name: desc
|
||||
type: string
|
||||
- in: query
|
||||
name: id
|
||||
type: integer
|
||||
- in: query
|
||||
name: keyword
|
||||
type: string
|
||||
- description: Limit is page size; only values in {10,20,50,100} are accepted
|
||||
(otherwise defaults to 10).
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- description: Page is 1-based page index; values <= 0 are normalized to 1.
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- in: query
|
||||
name: published_at_from
|
||||
type: string
|
||||
- in: query
|
||||
name: published_at_to
|
||||
type: string
|
||||
- enum:
|
||||
- draft
|
||||
- reviewing
|
||||
- published
|
||||
- unpublished
|
||||
- blocked
|
||||
in: query
|
||||
name: status
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- ContentStatusDraft
|
||||
- ContentStatusReviewing
|
||||
- ContentStatusPublished
|
||||
- ContentStatusUnpublished
|
||||
- ContentStatusBlocked
|
||||
- in: query
|
||||
name: user_id
|
||||
type: integer
|
||||
- enum:
|
||||
- public
|
||||
- tenant_only
|
||||
- private
|
||||
in: query
|
||||
name: visibility
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- ContentVisibilityPublic
|
||||
- ContentVisibilityTenantOnly
|
||||
- ContentVisibilityPrivate
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/requests.Pager'
|
||||
- properties:
|
||||
items:
|
||||
$ref: '#/definitions/dto.AdminContentItem'
|
||||
type: object
|
||||
summary: 内容列表(租户管理)
|
||||
tags:
|
||||
- Tenant
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
@@ -3717,7 +3910,7 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.MeResponse'
|
||||
$ref: '#/definitions/quyun_v2_app_http_tenant_dto.MeResponse'
|
||||
summary: 当前租户上下文信息
|
||||
tags:
|
||||
- Tenant
|
||||
@@ -3994,6 +4187,71 @@ paths:
|
||||
summary: 获取公开试看资源(preview role)
|
||||
tags:
|
||||
- TenantPublic
|
||||
/v1/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: form
|
||||
in: body
|
||||
name: form
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/quyun_v2_app_http_web_dto.LoginForm'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
$ref: '#/definitions/quyun_v2_app_http_web_dto.LoginResponse'
|
||||
summary: 用户登录
|
||||
tags:
|
||||
- Web
|
||||
/v1/auth/token:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
$ref: '#/definitions/quyun_v2_app_http_web_dto.LoginResponse'
|
||||
summary: 刷新 Token
|
||||
tags:
|
||||
- Web
|
||||
/v1/me:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
$ref: '#/definitions/quyun_v2_app_http_web_dto.MeResponse'
|
||||
summary: 当前用户信息
|
||||
tags:
|
||||
- Web
|
||||
/v1/me/tenants:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 成功
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/dto.MyTenantItem'
|
||||
type: array
|
||||
summary: 我的租户列表
|
||||
tags:
|
||||
- Web
|
||||
securityDefinitions:
|
||||
BasicAuth:
|
||||
type: basic
|
||||
|
||||
Reference in New Issue
Block a user