licensing-cotton/internal/handlers/request.go
2025-11-01 15:19:24 +08:00

214 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
"io"
"licensing-cotton/internal/config"
"licensing-cotton/internal/database"
"licensing-cotton/internal/models"
"net/http"
"time"
)
// CreateRequest 记录新的 renew 申请 (如果设备未在申请列表中)
func CreateRequest(deviceID string) error {
var existingStatus string
// 查询该设备是否已经有 `pending_requests` 记录
err := database.DB.QueryRow(`SELECT status FROM pending_requests WHERE device_id = ? ORDER BY request_time DESC LIMIT 1`, deviceID).
Scan(&existingStatus)
if err == nil {
if existingStatus == "pending" {
// 如果已有 `pending` 申请,则不允许重复提交
return errors.New("该设备已有未处理的续期申请,等待管理员审批")
}
// 如果 `status` 是 `approved` 或 `rejected`,则允许提交新申请
} else if err != sql.ErrNoRows {
// 查询时发生错误
return fmt.Errorf("查询续期申请失败: %w", err)
}
// 插入新的申请(设为 `pending`
_, err = database.DB.Exec(
`INSERT INTO pending_requests (device_id, request_time, status) VALUES (?, ?, ?)`,
deviceID, time.Now(), "pending",
)
if err != nil {
return fmt.Errorf("创建续期申请失败: %w", err)
}
return nil
}
func HandleListPendingRequests(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
return
}
// 管理员权限检查
if !isAdminRequest(w, r) {
return
}
// **查询所有未审批的请求**
rows, err := database.DB.Query(`
SELECT id, device_id, request_time, status, approved_by, approved_at, expiration
FROM pending_requests WHERE status='pending'
`)
if err != nil {
http.Error(w, "查询待审批请求失败: "+err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
pendingRequests := []models.PendingRequest{}
for rows.Next() {
var req models.PendingRequest
var approvedBy sql.NullString
var approvedAt sql.NullTime
var expiration sql.NullTime
// **扫描所有字段**
if err := rows.Scan(&req.ID, &req.DeviceID, &req.RequestTime, &req.Status, &approvedBy, &approvedAt, &expiration); err != nil {
http.Error(w, "解析查询结果失败: "+err.Error(), http.StatusInternalServerError)
return
}
// **处理 `NULL` 值**
if approvedBy.Valid {
req.ApprovedBy = &approvedBy.String
}
if approvedAt.Valid {
req.ApprovedAt = &approvedAt.Time
}
if expiration.Valid {
req.Expiration = &expiration.Time
}
pendingRequests = append(pendingRequests, req)
}
// **返回 JSON**
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pendingRequests)
}
func HandleDeviceRequest(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
return
}
if !isAdminRequest(w, r) {
return
}
var req struct {
DeviceID string `json:"device_id"`
Expiration string `json:"expiration"`
ApprovedBy string `json:"approved_by"`
Action string `json:"action"` // "approve" 或 "reject"
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "解析请求失败: "+err.Error(), http.StatusBadRequest)
return
}
switch req.Action {
case "approve":
handleApprove(w, struct{ DeviceID, Expiration, ApprovedBy, Action string }(req))
case "reject":
handleReject(w, struct{ DeviceID, Expiration, ApprovedBy, Action string }(req))
default:
http.Error(w, "无效的操作", http.StatusBadRequest)
}
}
func handleApprove(w http.ResponseWriter, req struct{ DeviceID, Expiration, ApprovedBy, Action string }) {
expirationTime, err := time.Parse(time.RFC3339, req.Expiration)
if err != nil {
http.Error(w, "无效的 expiration 格式", http.StatusBadRequest)
return
}
var exists bool
err = database.DB.QueryRow(`SELECT EXISTS(SELECT 1 FROM devices WHERE device_id = ?)`, req.DeviceID).Scan(&exists)
if err != nil {
http.Error(w, "数据库查询失败: "+err.Error(), http.StatusInternalServerError)
return
}
if !exists {
_, err = database.DB.Exec(`INSERT INTO devices (device_id, expiration) VALUES (?, ?)`, req.DeviceID, expirationTime)
} else {
_, err = database.DB.Exec(`UPDATE devices SET expiration = ? WHERE device_id = ?`, expirationTime, req.DeviceID)
}
if err != nil {
http.Error(w, "处理设备记录失败: "+err.Error(), http.StatusInternalServerError)
return
}
_, err = database.DB.Exec(`UPDATE pending_requests SET status='approved', approved_by=?, approved_at=? WHERE device_id=?`, req.ApprovedBy, time.Now(), req.DeviceID)
if err != nil {
http.Error(w, "更新请求状态失败: "+err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{
"message": fmt.Sprintf("设备 %s 已审批,授权至 %s", req.DeviceID, req.Expiration),
})
}
func handleReject(w http.ResponseWriter, req struct{ DeviceID, Expiration, ApprovedBy, Action string }) {
currentTime := time.Now()
_, err := database.DB.Exec(`UPDATE pending_requests SET status='rejected', approved_by=?, approved_at=? WHERE device_id=?`, req.ApprovedBy, currentTime, req.DeviceID)
if err != nil {
http.Error(w, "拒绝请求失败: "+err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{
"message": fmt.Sprintf("设备 %s 的请求已被拒绝", req.DeviceID),
})
}
func SetAutoRenewAllDevices(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body: "+err.Error(), http.StatusInternalServerError)
return
}
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // 重置 r.Body 供后续使用
if !isAdminRequest(w, r) {
return
}
// 解析请求体中的 "enabled" 字段
var req struct {
Enabled string `json:"enabled"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Failed to read request body: "+err.Error(), http.StatusInternalServerError)
//http.Error(w, `{"error":"解析请求失败","code":400}`, http.StatusBadRequest)
return
}
// 设置全局变量
config.AutoRenewAllDevices = req.Enabled == "true"
// 返回成功响应
msg := fmt.Sprintf(`{"message":"AutoRenewAllDevices set to %v","code":200}`, req.Enabled)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(msg))
}