289 lines
10 KiB
Go
289 lines
10 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/administration"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity"
|
|
)
|
|
|
|
type adminHandler struct {
|
|
authorizer *PlatformAuthorizer
|
|
service *administration.Service
|
|
}
|
|
|
|
func NewAdminHandler(authorizer *PlatformAuthorizer, service *administration.Service) (http.Handler, error) {
|
|
if authorizer == nil || service == nil {
|
|
return nil, fmt.Errorf("admin HTTP: authorizer and service are required")
|
|
}
|
|
return &adminHandler{authorizer: authorizer, service: service}, nil
|
|
}
|
|
|
|
func (handler *adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api/admin/accounts/groups":
|
|
handler.serveGroups(w, r)
|
|
case "/api/admin/accounts/password":
|
|
handler.servePasswordReset(w, r)
|
|
case "/api/admin/accounts":
|
|
handler.serveAccounts(w, r)
|
|
case "/api/admin/organizations":
|
|
handler.serveOrganizations(w, r)
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}
|
|
|
|
func (handler *adminHandler) authorize(w http.ResponseWriter, r *http.Request) (identity.Session, administration.Actor, bool) {
|
|
session, err := handler.authorizer.Authorize(r, PlatformAdmin)
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return identity.Session{}, administration.Actor{}, false
|
|
}
|
|
return session, administration.Actor{ID: session.User.ID, Role: administration.Role(session.User.Role), OrganizationID: session.User.OrganizationID}, true
|
|
}
|
|
|
|
func (handler *adminHandler) serveGroups(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
adminMethodNotAllowed(w, http.MethodPost)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusGone, map[string]any{"error": "平台账号体系不再使用外部部门接口,请直接管理组织。"})
|
|
}
|
|
|
|
func (handler *adminHandler) serveAccounts(w http.ResponseWriter, r *http.Request) {
|
|
session, actor, ok := handler.authorize(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
organizationID := strings.TrimSpace(r.URL.Query().Get("organizationId"))
|
|
if actor.Role != administration.RoleSuperAdmin {
|
|
organizationID = actor.OrganizationID
|
|
}
|
|
members, err := handler.service.ListAccounts(r.Context(), actor, administration.AccountFilters{OrganizationID: organizationID, IncludeDisabled: true})
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
organizations, err := handler.service.ListOrganizations(r.Context(), actor)
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"configured": true, "currentOrganizationId": nullableAdminString(organizationID), "organizations": adminOrganizationProjections(organizations), "members": adminAccountProjections(members), "canManageOrganizations": actor.Role == administration.RoleSuperAdmin, "canAssignOrganizationAdmin": actor.Role == administration.RoleSuperAdmin, "canCreateSuperAdmin": actor.Role == administration.RoleSuperAdmin})
|
|
case http.MethodPost:
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
role := administration.Role(adminOptionalString(body["role"]))
|
|
if role == "" {
|
|
role = administration.RoleUser
|
|
}
|
|
account, err := handler.service.CreateAccount(r.Context(), actor, administration.CreateAccountInput{Phone: adminRequiredString(body["phone"]), DisplayName: adminRequiredString(body["displayName"]), Password: adminRequiredString(body["password"]), Role: role, OrganizationID: adminOptionalString(body["organizationId"]), LegacySubject: adminOptionalString(body["legacySubject"])})
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusCreated, map[string]any{"ok": true, "user": adminAccountProjection(administration.ProjectAccount(account))})
|
|
case http.MethodPatch:
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
patch := administration.UpdateAccountInput{ClearLoginLock: body["clearLoginLock"] == true}
|
|
if value, exists := body["displayName"]; exists {
|
|
v := adminOptionalString(value)
|
|
patch.DisplayName = &v
|
|
}
|
|
if value, exists := body["role"]; exists {
|
|
v := administration.Role(adminOptionalString(value))
|
|
patch.Role = &v
|
|
}
|
|
if value, exists := body["organizationId"]; exists {
|
|
v := adminOptionalString(value)
|
|
patch.OrganizationID = &v
|
|
}
|
|
if value, exists := body["status"]; exists {
|
|
v := administration.Status(adminOptionalString(value))
|
|
patch.Status = &v
|
|
}
|
|
account, err := handler.service.UpdateAccount(r.Context(), actor, adminRequiredString(body["userId"]), patch)
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"ok": true, "user": adminAccountProjection(administration.ProjectAccount(account))})
|
|
case http.MethodPut:
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
account, err := handler.service.SetAccountStatus(r.Context(), actor, adminRequiredString(body["userId"]), administration.Status(adminRequiredString(body["status"])))
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"ok": true, "user": adminAccountProjection(administration.ProjectAccount(account))})
|
|
case http.MethodDelete:
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
archive, err := handler.service.DeleteAccount(r.Context(), actor, adminRequiredString(body["userId"]))
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"ok": true, "archivedOwnerId": archive})
|
|
default:
|
|
adminMethodNotAllowed(w, "GET, POST, PATCH, PUT, DELETE")
|
|
}
|
|
_ = session
|
|
}
|
|
|
|
func (handler *adminHandler) servePasswordReset(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
adminMethodNotAllowed(w, http.MethodPost)
|
|
return
|
|
}
|
|
_, actor, ok := handler.authorize(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
account, err := handler.service.ResetPassword(r.Context(), actor, adminRequiredString(body["userId"]), adminRequiredString(body["newPassword"]))
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"ok": true, "userId": account.ID})
|
|
}
|
|
|
|
func (handler *adminHandler) serveOrganizations(w http.ResponseWriter, r *http.Request) {
|
|
_, actor, ok := handler.authorize(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
organizations, err := handler.service.ListOrganizations(r.Context(), actor)
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"organizations": adminOrganizationProjections(organizations)})
|
|
case http.MethodPost:
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
organization, err := handler.service.CreateOrganization(r.Context(), actor, adminRequiredString(body["name"]))
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusCreated, map[string]any{"ok": true, "organization": adminOrganizationProjection(administration.ProjectOrganization(organization))})
|
|
case http.MethodPatch:
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
patch := administration.UpdateOrganizationInput{}
|
|
if value, exists := body["name"]; exists {
|
|
v := adminOptionalString(value)
|
|
patch.Name = &v
|
|
}
|
|
if value, exists := body["status"]; exists {
|
|
v := administration.Status(adminOptionalString(value))
|
|
patch.Status = &v
|
|
}
|
|
organization, err := handler.service.UpdateOrganization(r.Context(), actor, adminRequiredString(body["organizationId"]), patch)
|
|
if err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"ok": true, "organization": adminOrganizationProjection(administration.ProjectOrganization(organization))})
|
|
case http.MethodDelete:
|
|
body, ok := readAdminBody(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := handler.service.DeleteOrganization(r.Context(), actor, adminRequiredString(body["organizationId"])); err != nil {
|
|
writeAdminError(w, err)
|
|
return
|
|
}
|
|
writeAdminJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
default:
|
|
adminMethodNotAllowed(w, "GET, POST, PATCH, DELETE")
|
|
}
|
|
}
|
|
|
|
func readAdminBody(w http.ResponseWriter, r *http.Request) (map[string]any, bool) {
|
|
var body map[string]any
|
|
if json.NewDecoder(r.Body).Decode(&body) != nil {
|
|
writeAdminJSON(w, http.StatusBadRequest, map[string]any{"error": "请求内容格式不正确。"})
|
|
return nil, false
|
|
}
|
|
return body, true
|
|
}
|
|
func adminOptionalString(value any) string {
|
|
text, ok := value.(string)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(text)
|
|
}
|
|
func adminRequiredString(value any) string { return adminOptionalString(value) }
|
|
func nullableAdminString(value string) any {
|
|
if value == "" {
|
|
return nil
|
|
}
|
|
return value
|
|
}
|
|
func adminMethodNotAllowed(w http.ResponseWriter, allow string) {
|
|
w.Header().Set("Allow", allow)
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
func writeAdminError(w http.ResponseWriter, err error) {
|
|
status, message := http.StatusInternalServerError, "服务器内部错误。"
|
|
var authErr *PlatformAuthError
|
|
if errors.As(err, &authErr) {
|
|
status, message = authErr.Status, authErr.Message
|
|
} else if administration.StatusCode(err) != http.StatusInternalServerError {
|
|
status, message = administration.StatusCode(err), err.Error()
|
|
}
|
|
writeAdminJSON(w, status, map[string]any{"error": message})
|
|
}
|
|
func writeAdminJSON(w http.ResponseWriter, status int, body any) { writeJSON(w, status, body) }
|
|
|
|
func adminAccountProjection(a administration.AccountProjection) map[string]any {
|
|
return map[string]any{"id": a.ID, "phone": a.Phone, "displayName": a.DisplayName, "role": a.Role, "organizationId": nullableAdminString(a.OrganizationID), "status": a.Status, "createdAt": a.CreatedAt, "lastLoginAt": a.LastLoginAt, "lockedUntil": a.LockedUntil}
|
|
}
|
|
func adminAccountProjections(values []administration.AccountProjection) []map[string]any {
|
|
out := make([]map[string]any, len(values))
|
|
for i, value := range values {
|
|
out[i] = adminAccountProjection(value)
|
|
}
|
|
return out
|
|
}
|
|
func adminOrganizationProjection(o administration.OrganizationProjection) map[string]any {
|
|
return map[string]any{"id": o.ID, "name": o.Name, "status": o.Status}
|
|
}
|
|
func adminOrganizationProjections(values []administration.OrganizationProjection) []map[string]any {
|
|
out := make([]map[string]any, len(values))
|
|
for i, value := range values {
|
|
out[i] = adminOrganizationProjection(value)
|
|
}
|
|
return out
|
|
}
|