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 }