feat(tenant): per-tenant brand and question limit overrides
Add nullable brand_limit / question_limit columns on tenants so ops admins can override the plan-derived brand-library quotas per account. When set, these take precedence over config.yml plan defaults across the brand-library summary, question materialization, and the daily monitoring worker's primary-client plan resolution. Ops side exposes them on admin-user create plus a new PUT /admin-users/:id/limits endpoint, invalidating the brand-library summary cache on change. The ops-web AdminUsers view gains matching inputs on the create and edit modals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,14 @@ import (
|
||||
)
|
||||
|
||||
type createAdminUserRequest struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
PlanCode string `json:"plan_code"`
|
||||
Role string `json:"role"`
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
PlanCode string `json:"plan_code"`
|
||||
Role string `json:"role"`
|
||||
BrandLimit *int `json:"brand_limit"`
|
||||
QuestionLimit *int `json:"question_limit"`
|
||||
}
|
||||
|
||||
type updateAdminUserRequest struct {
|
||||
@@ -25,6 +27,11 @@ type updateAdminUserRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type updateAdminUserLimitsRequest struct {
|
||||
BrandLimit *int `json:"brand_limit"`
|
||||
QuestionLimit *int `json:"question_limit"`
|
||||
}
|
||||
|
||||
type changeAdminUserPlanRequest struct {
|
||||
PlanCode string `json:"plan_code" binding:"required"`
|
||||
}
|
||||
@@ -86,12 +93,14 @@ func createAdminUserHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
detail, err := svc.Create(c.Request.Context(), actorFromGin(c), app.CreateAdminUserInput{
|
||||
Phone: body.Phone,
|
||||
Email: body.Email,
|
||||
Name: body.Name,
|
||||
Password: body.Password,
|
||||
PlanCode: body.PlanCode,
|
||||
Role: body.Role,
|
||||
Phone: body.Phone,
|
||||
Email: body.Email,
|
||||
Name: body.Name,
|
||||
Password: body.Password,
|
||||
PlanCode: body.PlanCode,
|
||||
Role: body.Role,
|
||||
BrandLimit: body.BrandLimit,
|
||||
QuestionLimit: body.QuestionLimit,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
@@ -141,6 +150,30 @@ func updateAdminUserHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func updateAdminUserLimitsHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body updateAdminUserLimitsRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
detail, err := svc.UpdateLimits(c.Request.Context(), actorFromGin(c), id, app.UpdateAdminUserLimitsInput{
|
||||
BrandLimit: body.BrandLimit,
|
||||
QuestionLimit: body.QuestionLimit,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, detail)
|
||||
}
|
||||
}
|
||||
|
||||
func changeAdminUserPlanHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
|
||||
Reference in New Issue
Block a user