package httpapi import ( "encoding/json" "net/http" "strconv" "strings" "time" "git.118im.com/im_team/im_workbench/internal/store" ) func (a *API) adminWorkbenchApps(w http.ResponseWriter, r *http.Request) { st, ok := a.internalAdminStore(w, r) if !ok { return } switch r.Method { case http.MethodGet: page := queryInt(r, "page", 1) perPage := queryInt(r, "per_page", 20) var enabled *int if raw := strings.TrimSpace(r.URL.Query().Get("enabled")); raw != "" { if value, err := strconv.Atoi(raw); err == nil { enabled = &value } } items, total, err := st.AdminWorkbenchModules(r.Context(), page, perPage, r.URL.Query().Get("code"), r.URL.Query().Get("name"), enabled) if err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, map[string]any{"items": adminWorkbenchModulePayloads(items), "total": total, "page": page, "per_page": perPage}) case http.MethodPost: var body adminWorkbenchModuleRequest if !decodeJSON(w, r, &body) { return } item, err := st.UpsertAdminWorkbenchModule(r.Context(), store.AdminWorkbenchModuleInput{ ID: body.ID, Code: body.Code, Name: body.Name, IconFileID: body.IconFileID, Route: body.Route, SortOrder: body.SortOrder, UnreadKey: body.UnreadKey, Enabled: body.Enabled, Permission: body.Permission, }) if err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, adminWorkbenchModulePayload(*item)) default: methodNotAllowed(w, r) } } func (a *API) adminWorkbenchAppDisable(w http.ResponseWriter, r *http.Request) { st, ok := a.internalAdminStore(w, r) if !ok { return } if r.Method != http.MethodPost { methodNotAllowed(w, r) return } var body struct { ID int64 `json:"id"` Code string `json:"code"` } if !decodeJSON(w, r, &body) { return } if err := st.DisableAdminWorkbenchModule(r.Context(), body.ID, body.Code); err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, map[string]any{"ok": true}) } func (a *API) adminWorkbenchAppsReorder(w http.ResponseWriter, r *http.Request) { st, ok := a.internalAdminStore(w, r) if !ok { return } if r.Method != http.MethodPost { methodNotAllowed(w, r) return } var body struct { Items []struct { Code string `json:"code"` SortOrder int `json:"sort_order"` } `json:"items"` } if !decodeJSON(w, r, &body) { return } items := make([]store.AdminWorkbenchReorderItem, 0, len(body.Items)) for _, item := range body.Items { items = append(items, store.AdminWorkbenchReorderItem{Code: item.Code, SortOrder: item.SortOrder}) } if err := st.ReorderAdminWorkbenchModules(r.Context(), items); err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, map[string]any{"ok": true}) } func (a *API) adminOrganizationDepartments(w http.ResponseWriter, r *http.Request) { st, ok := a.internalAdminStore(w, r) if !ok { return } if r.Method != http.MethodGet { methodNotAllowed(w, r) return } page := queryInt(r, "page", 1) perPage := queryInt(r, "per_page", 20) items, total, err := st.AdminOrganizationDepartments(r.Context(), page, perPage, r.URL.Query().Get("parent_id"), r.URL.Query().Get("name"), r.URL.Query().Get("state")) if err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, map[string]any{"items": adminOrganizationDepartmentPayloads(items), "total": total, "page": page, "per_page": perPage}) } func (a *API) adminOrganizationMembers(w http.ResponseWriter, r *http.Request) { st, ok := a.internalAdminStore(w, r) if !ok { return } if r.Method != http.MethodGet { methodNotAllowed(w, r) return } page := queryInt(r, "page", 1) perPage := queryInt(r, "per_page", 20) userID, _ := strconv.ParseInt(strings.TrimSpace(r.URL.Query().Get("user_id")), 10, 64) items, total, err := st.AdminOrganizationMembers(r.Context(), page, perPage, r.URL.Query().Get("department_id"), firstNonEmpty(r.URL.Query().Get("q"), r.URL.Query().Get("keyword")), r.URL.Query().Get("employment_state"), userID) if err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, map[string]any{"items": adminOrganizationMemberPayloads(items), "total": total, "page": page, "per_page": perPage}) } func (a *API) adminMailboxConfig(w http.ResponseWriter, r *http.Request) { st, ok := a.internalAdminStore(w, r) if !ok { return } switch r.Method { case http.MethodGet: cfg, err := st.AdminMailboxConfig(r.Context()) if err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, adminMailboxConfigPayload(*cfg)) case http.MethodPost: var body adminMailboxConfigRequest if !decodeJSON(w, r, &body) { return } if err := st.UpdateAdminMailboxConfig(r.Context(), store.AdminMailboxConfig{ Enabled: body.Enabled, AllowAnonymous: body.AllowAnonymous, RequiredFields: body.RequiredFields, MaxLength: body.MaxLength, AttachmentPolicy: body.AttachmentPolicy, NoticeTitle: body.NoticeTitle, NoticeContent: body.NoticeContent, }); err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, map[string]any{"ok": true}) default: methodNotAllowed(w, r) } } func (a *API) adminFeedbackConfig(w http.ResponseWriter, r *http.Request) { st, ok := a.internalAdminStore(w, r) if !ok { return } switch r.Method { case http.MethodGet: cfg, err := st.AdminFeedbackConfig(r.Context()) if err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, adminFeedbackConfigPayload(*cfg)) case http.MethodPost: var body adminFeedbackConfigRequest if !decodeJSON(w, r, &body) { return } if err := st.UpdateAdminFeedbackConfig(r.Context(), store.AdminFeedbackConfig{ Categories: body.Categories, MaxLength: body.MaxLength, AttachmentPolicy: body.AttachmentPolicy, AllowContact: body.AllowContact, DiagnosticsNotice: body.DiagnosticsNotice, }); err != nil { writeStoreError(w, r, err) return } writeSuccess(w, r, map[string]any{"ok": true}) default: methodNotAllowed(w, r) } } func (a *API) internalAdminStore(w http.ResponseWriter, r *http.Request) (*store.Store, bool) { tenantID, _ := strconv.ParseInt(firstNonEmpty(r.Header.Get("X-Tenant-ID"), r.URL.Query().Get("tenant_id")), 10, 64) tenantCode := firstNonEmpty(r.Header.Get("X-Tenant-Code"), r.URL.Query().Get("tenant_code"), "default") st, err := a.store.ForTenant(r.Context(), tenantID, tenantCode) if err != nil { writeStoreError(w, r, err) return nil, false } return st, true } type adminWorkbenchModuleRequest struct { ID int64 `json:"id"` Code string `json:"code"` Name string `json:"name"` IconFileID string `json:"icon_file_id"` Route string `json:"route"` SortOrder int `json:"sort_order"` UnreadKey string `json:"unread_key"` Enabled int `json:"enabled"` Permission string `json:"permission"` } type adminMailboxConfigRequest struct { Enabled int `json:"enabled"` AllowAnonymous int `json:"allow_anonymous"` RequiredFields string `json:"required_fields"` MaxLength int `json:"max_length"` AttachmentPolicy string `json:"attachment_policy"` NoticeTitle string `json:"notice_title"` NoticeContent string `json:"notice_content"` } type adminFeedbackConfigRequest struct { Categories string `json:"categories"` MaxLength int `json:"max_length"` AttachmentPolicy string `json:"attachment_policy"` AllowContact int `json:"allow_contact"` DiagnosticsNotice string `json:"diagnostics_notice"` } func adminWorkbenchModulePayloads(items []store.AdminWorkbenchModule) []map[string]any { res := make([]map[string]any, 0, len(items)) for _, item := range items { res = append(res, adminWorkbenchModulePayload(item)) } return res } func adminWorkbenchModulePayload(item store.AdminWorkbenchModule) map[string]any { return map[string]any{ "id": item.ID, "code": item.Code, "name": item.Name, "iconFileId": item.IconFileID, "route": item.Route, "sortOrder": item.SortOrder, "unreadKey": item.UnreadKey, "enabled": item.Enabled, "permission": item.Permission, "createdAt": formatAdminTime(item.CreatedAt), "updatedAt": formatAdminTime(item.UpdatedAt), } } func adminOrganizationDepartmentPayloads(items []store.OrganizationDepartment) []map[string]any { res := make([]map[string]any, 0, len(items)) for _, item := range items { res = append(res, map[string]any{ "department_id": item.DepartmentID, "name": item.Name, "parent_id": item.ParentID, "child_count": item.ChildCount, "member_count": item.MemberCount, "version": item.Version, }) } return res } func adminOrganizationMemberPayloads(items []store.OrganizationMember) []map[string]any { res := make([]map[string]any, 0, len(items)) for _, item := range items { res = append(res, map[string]any{ "user_id": item.UserID, "department_id": item.DepartmentID, "display_name": item.DisplayName, "position": item.Position, "mobile": item.Mobile, "email": item.Email, "avatar_file_id": item.AvatarFileID, "status": item.Status, "contactable": item.Contactable, "version": item.Version, }) } return res } func adminMailboxConfigPayload(cfg store.AdminMailboxConfig) map[string]any { return map[string]any{ "id": cfg.ID, "enabled": cfg.Enabled, "allowAnonymous": cfg.AllowAnonymous, "requiredFields": cfg.RequiredFields, "maxLength": cfg.MaxLength, "attachmentPolicy": cfg.AttachmentPolicy, "noticeTitle": cfg.NoticeTitle, "noticeContent": cfg.NoticeContent, "noticeUpdatedAt": formatAdminTime(cfg.NoticeUpdatedAt), "updatedAt": formatAdminTime(cfg.UpdatedAt), } } func adminFeedbackConfigPayload(cfg store.AdminFeedbackConfig) map[string]any { return map[string]any{ "id": cfg.ID, "categories": cfg.Categories, "maxLength": cfg.MaxLength, "attachmentPolicy": cfg.AttachmentPolicy, "allowContact": cfg.AllowContact, "diagnosticsNotice": cfg.DiagnosticsNotice, "updatedAt": formatAdminTime(cfg.UpdatedAt), } } func decodeJSON(w http.ResponseWriter, r *http.Request, out any) bool { if err := json.NewDecoder(r.Body).Decode(out); err != nil { writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", nil) return false } return true } func queryInt(r *http.Request, key string, fallback int) int { value, err := strconv.Atoi(strings.TrimSpace(r.URL.Query().Get(key))) if err != nil || value <= 0 { return fallback } return value } func formatAdminTime(t time.Time) string { if t.IsZero() { return "" } return t.UTC().Format(time.RFC3339Nano) }