Initial commit: GovAI 政务AI平台
This commit is contained in:
@@ -0,0 +1,614 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/enterprise-ai-platform/server/internal/middleware"
|
||||
"github.com/enterprise-ai-platform/server/internal/response"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewAdminHandler(pool *pgxpool.Pool) *AdminHandler {
|
||||
return &AdminHandler{pool: pool}
|
||||
}
|
||||
|
||||
// getUserOrgID 通过当前登录用户ID查询其所属机构ID,用于多租户数据隔离
|
||||
func (h *AdminHandler) getUserOrgID(r *http.Request) (string, error) {
|
||||
userID := middleware.GetUserID(r.Context())
|
||||
var orgID string
|
||||
err := h.pool.QueryRow(r.Context(),
|
||||
`SELECT COALESCE(org_id::text, '') FROM users WHERE id = $1`, userID).Scan(&orgID)
|
||||
return orgID, err
|
||||
}
|
||||
|
||||
// --- Overview Stats ---
|
||||
|
||||
func (h *AdminHandler) Overview(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, err := h.getUserOrgID(r)
|
||||
if err != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
|
||||
var totalUsers, totalApps, activeUsers, todayConversations int
|
||||
var monthlyTokens int64
|
||||
var monthlyCost float64
|
||||
|
||||
h.pool.QueryRow(r.Context(), `SELECT COUNT(*) FROM users WHERE status = 'active' AND org_id = $1`, orgID).Scan(&totalUsers)
|
||||
h.pool.QueryRow(r.Context(), `SELECT COUNT(*) FROM applications WHERE status = 'approved' AND org_id = $1`, orgID).Scan(&totalApps)
|
||||
h.pool.QueryRow(r.Context(), `SELECT COUNT(DISTINCT l.user_id) FROM app_usage_logs l JOIN users u ON l.user_id = u.id WHERE l.created_at >= $1 AND u.org_id = $2`, todayStart, orgID).Scan(&activeUsers)
|
||||
h.pool.QueryRow(r.Context(), `SELECT COUNT(*) FROM app_usage_logs l JOIN users u ON l.user_id = u.id WHERE l.created_at >= $1 AND u.org_id = $2`, todayStart, orgID).Scan(&todayConversations)
|
||||
h.pool.QueryRow(r.Context(), `SELECT COALESCE(SUM(l.total_tokens), 0) FROM app_usage_logs l JOIN users u ON l.user_id = u.id WHERE l.created_at >= $1 AND u.org_id = $2`, monthStart, orgID).Scan(&monthlyTokens)
|
||||
h.pool.QueryRow(r.Context(), `SELECT COALESCE(SUM(l.estimated_cost), 0) FROM app_usage_logs l JOIN users u ON l.user_id = u.id WHERE l.created_at >= $1 AND u.org_id = $2`, monthStart, orgID).Scan(&monthlyCost)
|
||||
|
||||
response.JSON(w, http.StatusOK, map[string]any{
|
||||
"total_users": totalUsers,
|
||||
"total_apps": totalApps,
|
||||
"active_users": activeUsers,
|
||||
"total_conversations": todayConversations,
|
||||
"monthly_tokens": monthlyTokens,
|
||||
"monthly_cost": monthlyCost,
|
||||
})
|
||||
}
|
||||
|
||||
// --- User Management ---
|
||||
|
||||
func (h *AdminHandler) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, err := h.getUserOrgID(r)
|
||||
if err != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
page, _ := strconv.Atoi(q.Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize := 20
|
||||
offset := (page - 1) * pageSize
|
||||
search := q.Get("q")
|
||||
roleFilter := q.Get("role")
|
||||
statusFilter := q.Get("status")
|
||||
|
||||
query := `SELECT id, name, email, avatar_url, role, status, employee_id, last_login_at, login_count, created_at
|
||||
FROM users WHERE org_id = $1`
|
||||
args := []any{orgID}
|
||||
argIdx := 2
|
||||
|
||||
if search != "" {
|
||||
query += ` AND (name ILIKE '%' || $` + strconv.Itoa(argIdx) + ` || '%' OR email ILIKE '%' || $` + strconv.Itoa(argIdx) + ` || '%')`
|
||||
args = append(args, search)
|
||||
argIdx++
|
||||
}
|
||||
if roleFilter != "" {
|
||||
query += ` AND role = $` + strconv.Itoa(argIdx)
|
||||
args = append(args, roleFilter)
|
||||
argIdx++
|
||||
}
|
||||
if statusFilter != "" {
|
||||
query += ` AND status = $` + strconv.Itoa(argIdx)
|
||||
args = append(args, statusFilter)
|
||||
argIdx++
|
||||
}
|
||||
|
||||
query += ` ORDER BY created_at DESC LIMIT $` + strconv.Itoa(argIdx) + ` OFFSET $` + strconv.Itoa(argIdx+1)
|
||||
args = append(args, pageSize, offset)
|
||||
|
||||
rows, err := h.pool.Query(r.Context(), query, args...)
|
||||
if err != nil {
|
||||
response.InternalError(w, "查询用户失败")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []map[string]any
|
||||
for rows.Next() {
|
||||
var (
|
||||
id, name, email, role, status string
|
||||
avatarURL, employeeID *string
|
||||
lastLoginAt *time.Time
|
||||
loginCount int
|
||||
createdAt time.Time
|
||||
)
|
||||
if err := rows.Scan(&id, &name, &email, &avatarURL, &role, &status, &employeeID, &lastLoginAt, &loginCount, &createdAt); err != nil {
|
||||
continue
|
||||
}
|
||||
users = append(users, map[string]any{
|
||||
"id": id, "name": name, "email": email,
|
||||
"avatar_url": avatarURL, "role": role, "status": status,
|
||||
"employee_id": employeeID, "last_login_at": lastLoginAt,
|
||||
"login_count": loginCount, "created_at": createdAt,
|
||||
})
|
||||
}
|
||||
if users == nil {
|
||||
users = []map[string]any{}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]any{"items": users, "page": page})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) UpdateUserRole(w http.ResponseWriter, r *http.Request) {
|
||||
userID := chi.URLParam(r, "id")
|
||||
operatorRole := middleware.GetRole(r.Context())
|
||||
|
||||
var req struct {
|
||||
Role string `json:"role"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
response.BadRequest(w, "无效的请求格式")
|
||||
return
|
||||
}
|
||||
|
||||
validRoles := map[string]bool{"user": true, "creator": true, "admin": true, "super_admin": true}
|
||||
if !validRoles[req.Role] {
|
||||
response.BadRequest(w, "无效的角色")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Role == "super_admin" && operatorRole != "super_admin" {
|
||||
response.Forbidden(w, "只有超级管理员才能设置超级管理员角色")
|
||||
return
|
||||
}
|
||||
|
||||
// 确保只能修改本机构用户
|
||||
orgID, orgErr := h.getUserOrgID(r)
|
||||
if orgErr != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := h.pool.Exec(r.Context(), `UPDATE users SET role = $2 WHERE id = $1 AND org_id = $3`, userID, req.Role, orgID)
|
||||
if err != nil {
|
||||
response.InternalError(w, "更新角色失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, nil)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) UpdateUserStatus(w http.ResponseWriter, r *http.Request) {
|
||||
userID := chi.URLParam(r, "id")
|
||||
|
||||
var req struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
response.BadRequest(w, "无效的请求格式")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status != "active" && req.Status != "disabled" {
|
||||
response.BadRequest(w, "无效的状态")
|
||||
return
|
||||
}
|
||||
|
||||
// 确保只能修改本机构用户
|
||||
orgID, orgErr := h.getUserOrgID(r)
|
||||
if orgErr != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := h.pool.Exec(r.Context(), `UPDATE users SET status = $2 WHERE id = $1 AND org_id = $3`, userID, req.Status, orgID)
|
||||
if err != nil {
|
||||
response.InternalError(w, "更新状态失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, nil)
|
||||
}
|
||||
|
||||
// --- App Management ---
|
||||
|
||||
func (h *AdminHandler) ListAllApps(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, err := h.getUserOrgID(r)
|
||||
if err != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
page, _ := strconv.Atoi(q.Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
offset := (page - 1) * 20
|
||||
statusFilter := q.Get("status")
|
||||
|
||||
query := `SELECT a.id, a.name, a.slug, a.description, a.icon_url,
|
||||
c.name as category_name, u.name as creator_name,
|
||||
a.dify_app_type, a.status, a.visibility, a.usage_count, a.created_at
|
||||
FROM applications a
|
||||
LEFT JOIN categories c ON a.category_id = c.id
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
WHERE a.org_id = $1`
|
||||
args := []any{orgID}
|
||||
argIdx := 2
|
||||
|
||||
if statusFilter != "" {
|
||||
query += ` AND a.status = $` + strconv.Itoa(argIdx)
|
||||
args = append(args, statusFilter)
|
||||
argIdx++
|
||||
}
|
||||
|
||||
query += ` ORDER BY a.created_at DESC LIMIT $` + strconv.Itoa(argIdx) + ` OFFSET $` + strconv.Itoa(argIdx+1)
|
||||
args = append(args, 20, offset)
|
||||
|
||||
rows, err := h.pool.Query(r.Context(), query, args...)
|
||||
if err != nil {
|
||||
response.InternalError(w, "查询应用失败")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var apps []map[string]any
|
||||
for rows.Next() {
|
||||
var (
|
||||
id, name, slug, status, visibility string
|
||||
desc, iconURL, catName, creator *string
|
||||
appType *string
|
||||
usageCount int64
|
||||
createdAt time.Time
|
||||
)
|
||||
if err := rows.Scan(&id, &name, &slug, &desc, &iconURL, &catName, &creator,
|
||||
&appType, &status, &visibility, &usageCount, &createdAt); err != nil {
|
||||
continue
|
||||
}
|
||||
apps = append(apps, map[string]any{
|
||||
"id": id, "name": name, "slug": slug, "description": desc,
|
||||
"icon_url": iconURL, "category_name": catName, "creator_name": creator,
|
||||
"dify_app_type": appType,
|
||||
"status": status, "visibility": visibility, "usage_count": usageCount,
|
||||
"created_at": createdAt,
|
||||
})
|
||||
}
|
||||
if apps == nil {
|
||||
apps = []map[string]any{}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]any{"items": apps, "page": page})
|
||||
}
|
||||
|
||||
// --- Audit Logs ---
|
||||
|
||||
func (h *AdminHandler) ListAuditLogs(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, orgErr := h.getUserOrgID(r)
|
||||
if orgErr != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
page, _ := strconv.Atoi(q.Get("page"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
offset := (page - 1) * 50
|
||||
|
||||
rows, err := h.pool.Query(r.Context(), `
|
||||
SELECT al.id, al.action, al.resource_type, al.resource_id,
|
||||
al.details, al.ip_address, al.created_at,
|
||||
u.name as user_name
|
||||
FROM audit_logs al
|
||||
LEFT JOIN users u ON al.user_id = u.id
|
||||
WHERE u.org_id = $2
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT 50 OFFSET $1`, offset, orgID)
|
||||
if err != nil {
|
||||
response.InternalError(w, "查询审计日志失败")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var logs []map[string]any
|
||||
for rows.Next() {
|
||||
var (
|
||||
id, action, resType string
|
||||
resID *string
|
||||
details json.RawMessage
|
||||
ipAddr *string
|
||||
createdAt time.Time
|
||||
userName *string
|
||||
)
|
||||
if err := rows.Scan(&id, &action, &resType, &resID, &details, &ipAddr, &createdAt, &userName); err != nil {
|
||||
continue
|
||||
}
|
||||
logs = append(logs, map[string]any{
|
||||
"id": id, "action": action, "resource_type": resType,
|
||||
"resource_id": resID, "details": details,
|
||||
"ip_address": ipAddr, "created_at": createdAt, "user_name": userName,
|
||||
})
|
||||
}
|
||||
if logs == nil {
|
||||
logs = []map[string]any{}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]any{"items": logs, "page": page})
|
||||
}
|
||||
|
||||
// --- Usage Analytics ---
|
||||
|
||||
func (h *AdminHandler) UsageAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, orgErr := h.getUserOrgID(r)
|
||||
if orgErr != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
days := 7
|
||||
if d, err := strconv.Atoi(r.URL.Query().Get("days")); err == nil && d > 0 && d <= 90 {
|
||||
days = d
|
||||
}
|
||||
|
||||
rows, err := h.pool.Query(r.Context(), `
|
||||
SELECT DATE(l.created_at) as day, COUNT(*) as count, COALESCE(SUM(l.total_tokens), 0) as tokens
|
||||
FROM app_usage_logs l
|
||||
JOIN users u ON l.user_id = u.id
|
||||
WHERE l.created_at >= NOW() - $1::interval AND u.org_id = $2
|
||||
GROUP BY DATE(l.created_at)
|
||||
ORDER BY day`, strconv.Itoa(days)+" days", orgID)
|
||||
if err != nil {
|
||||
response.InternalError(w, "查询使用统计失败")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var dailyStats []map[string]any
|
||||
for rows.Next() {
|
||||
var (
|
||||
day time.Time
|
||||
count int
|
||||
tokens int64
|
||||
)
|
||||
if err := rows.Scan(&day, &count, &tokens); err != nil {
|
||||
continue
|
||||
}
|
||||
dailyStats = append(dailyStats, map[string]any{
|
||||
"date": day.Format("2006-01-02"),
|
||||
"count": count,
|
||||
"total_tokens": tokens,
|
||||
})
|
||||
}
|
||||
if dailyStats == nil {
|
||||
dailyStats = []map[string]any{}
|
||||
}
|
||||
|
||||
// Top apps
|
||||
topRows, err := h.pool.Query(r.Context(), `
|
||||
SELECT a.name, COUNT(l.id) as usage_count
|
||||
FROM app_usage_logs l
|
||||
JOIN applications a ON l.app_id = a.id
|
||||
WHERE l.created_at >= NOW() - $1::interval AND a.org_id = $2
|
||||
GROUP BY a.name
|
||||
ORDER BY usage_count DESC LIMIT 10`, strconv.Itoa(days)+" days", orgID)
|
||||
if err != nil {
|
||||
response.JSON(w, http.StatusOK, map[string]any{"daily": dailyStats})
|
||||
return
|
||||
}
|
||||
defer topRows.Close()
|
||||
|
||||
var topApps []map[string]any
|
||||
for topRows.Next() {
|
||||
var name string
|
||||
var count int
|
||||
if err := topRows.Scan(&name, &count); err != nil {
|
||||
continue
|
||||
}
|
||||
topApps = append(topApps, map[string]any{"name": name, "count": count})
|
||||
}
|
||||
if topApps == nil {
|
||||
topApps = []map[string]any{}
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, map[string]any{
|
||||
"daily": dailyStats,
|
||||
"top_apps": topApps,
|
||||
})
|
||||
}
|
||||
|
||||
// --- Review Management ---
|
||||
|
||||
func (h *AdminHandler) ListPendingReviews(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, orgErr := h.getUserOrgID(r)
|
||||
if orgErr != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := h.pool.Query(r.Context(), `
|
||||
SELECT r.id, r.app_id, r.version, r.submit_comment, r.submitted_at,
|
||||
a.name as app_name, a.description as app_description, a.icon_url,
|
||||
u.name as submitter_name
|
||||
FROM app_reviews r
|
||||
JOIN applications a ON r.app_id = a.id
|
||||
JOIN users u ON r.submitter_id = u.id
|
||||
WHERE r.status = 'pending' AND a.org_id = $1
|
||||
ORDER BY r.submitted_at ASC LIMIT 50`, orgID)
|
||||
if err != nil {
|
||||
response.InternalError(w, "查询审核列表失败")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var reviews []map[string]any
|
||||
for rows.Next() {
|
||||
var (
|
||||
id, appID, version string
|
||||
comment *string
|
||||
submittedAt time.Time
|
||||
appName, appDesc *string
|
||||
appIcon *string
|
||||
submitterName string
|
||||
)
|
||||
if err := rows.Scan(&id, &appID, &version, &comment, &submittedAt,
|
||||
&appName, &appDesc, &appIcon, &submitterName); err != nil {
|
||||
continue
|
||||
}
|
||||
reviews = append(reviews, map[string]any{
|
||||
"id": id, "app_id": appID, "version": version,
|
||||
"submit_comment": comment, "submitted_at": submittedAt,
|
||||
"app_name": appName, "app_description": appDesc, "app_icon": appIcon,
|
||||
"submitter_name": submitterName,
|
||||
})
|
||||
}
|
||||
if reviews == nil {
|
||||
reviews = []map[string]any{}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, reviews)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) ApproveReview(w http.ResponseWriter, r *http.Request) {
|
||||
reviewID := chi.URLParam(r, "id")
|
||||
reviewerID := middleware.GetUserID(r.Context())
|
||||
|
||||
var req struct {
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
// Get app_id from review
|
||||
var appID string
|
||||
err := h.pool.QueryRow(r.Context(),
|
||||
`SELECT app_id FROM app_reviews WHERE id = $1 AND status = 'pending'`, reviewID).Scan(&appID)
|
||||
if err != nil {
|
||||
response.NotFound(w, "审核记录不存在或已处理")
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := h.pool.Begin(r.Context())
|
||||
if err != nil {
|
||||
response.InternalError(w, "事务开始失败")
|
||||
return
|
||||
}
|
||||
defer tx.Rollback(r.Context())
|
||||
|
||||
tx.Exec(r.Context(), `
|
||||
UPDATE app_reviews SET status = 'approved', reviewer_id = $2, review_comment = $3, reviewed_at = NOW()
|
||||
WHERE id = $1`, reviewID, reviewerID, req.Comment)
|
||||
|
||||
tx.Exec(r.Context(), `
|
||||
UPDATE applications SET status = 'approved', published_at = NOW()
|
||||
WHERE id = $1`, appID)
|
||||
|
||||
if err := tx.Commit(r.Context()); err != nil {
|
||||
response.InternalError(w, "审核通过失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, nil)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) RejectReview(w http.ResponseWriter, r *http.Request) {
|
||||
reviewID := chi.URLParam(r, "id")
|
||||
reviewerID := middleware.GetUserID(r.Context())
|
||||
|
||||
var req struct {
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Comment == "" {
|
||||
response.BadRequest(w, "驳回必须填写原因")
|
||||
return
|
||||
}
|
||||
|
||||
var appID string
|
||||
err := h.pool.QueryRow(r.Context(),
|
||||
`SELECT app_id FROM app_reviews WHERE id = $1 AND status = 'pending'`, reviewID).Scan(&appID)
|
||||
if err != nil {
|
||||
response.NotFound(w, "审核记录不存在或已处理")
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := h.pool.Begin(r.Context())
|
||||
if err != nil {
|
||||
response.InternalError(w, "事务开始失败")
|
||||
return
|
||||
}
|
||||
defer tx.Rollback(r.Context())
|
||||
|
||||
tx.Exec(r.Context(), `
|
||||
UPDATE app_reviews SET status = 'rejected', reviewer_id = $2, review_comment = $3, reviewed_at = NOW()
|
||||
WHERE id = $1`, reviewID, reviewerID, req.Comment)
|
||||
|
||||
tx.Exec(r.Context(), `
|
||||
UPDATE applications SET status = 'rejected'
|
||||
WHERE id = $1`, appID)
|
||||
|
||||
if err := tx.Commit(r.Context()); err != nil {
|
||||
response.InternalError(w, "驳回失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, nil)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) DelistApp(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, orgErr := h.getUserOrgID(r)
|
||||
if orgErr != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
appID := chi.URLParam(r, "id")
|
||||
|
||||
var status string
|
||||
err := h.pool.QueryRow(r.Context(),
|
||||
`SELECT status FROM applications WHERE id = $1 AND org_id = $2`, appID, orgID).Scan(&status)
|
||||
if err != nil {
|
||||
response.NotFound(w, "应用不存在")
|
||||
return
|
||||
}
|
||||
if status != "approved" {
|
||||
response.BadRequest(w, "只有已上架的应用可以撤架")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.pool.Exec(r.Context(),
|
||||
`UPDATE applications SET status = 'archived' WHERE id = $1`, appID)
|
||||
if err != nil {
|
||||
response.InternalError(w, "撤架失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, map[string]string{"message": "已撤架"})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) RelistApp(w http.ResponseWriter, r *http.Request) {
|
||||
orgID, orgErr := h.getUserOrgID(r)
|
||||
if orgErr != nil || orgID == "" {
|
||||
response.InternalError(w, "无法确定当前机构")
|
||||
return
|
||||
}
|
||||
|
||||
appID := chi.URLParam(r, "id")
|
||||
|
||||
var status string
|
||||
err := h.pool.QueryRow(r.Context(),
|
||||
`SELECT status FROM applications WHERE id = $1 AND org_id = $2`, appID, orgID).Scan(&status)
|
||||
if err != nil {
|
||||
response.NotFound(w, "应用不存在")
|
||||
return
|
||||
}
|
||||
if status != "archived" {
|
||||
response.BadRequest(w, "只有已归档的应用可以重新上架")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.pool.Exec(r.Context(),
|
||||
`UPDATE applications SET status = 'approved', published_at = NOW() WHERE id = $1`, appID)
|
||||
if err != nil {
|
||||
response.InternalError(w, "重新上架失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, map[string]string{"message": "已重新上架"})
|
||||
}
|
||||
Reference in New Issue
Block a user