Files

161 lines
4.8 KiB
Go

package api
import (
"encoding/json"
"net/http"
"freight-sfa-server/internal/auth"
)
// backupPayload 备份结构,与前端 exportBackup 的 JSON 保持一致
type backupPayload struct {
App string `json:"app"`
Version string `json:"version"`
Exported string `json:"exported"`
Clients []map[string]any `json:"clients"`
Inquiries []map[string]any `json:"inquiries"`
Bookings []map[string]any `json:"bookings"`
Followups []map[string]any `json:"followups"`
Options map[string][]string `json:"options"`
Owner string `json:"owner"`
Extra map[string]json.RawMessage `json:"extra,omitempty"`
}
// exportBackup 导出全量数据(团队共享账套)
func (s *Server) exportBackup(w http.ResponseWriter, r *http.Request) {
clients, _ := s.loadAllRecords("client")
inquiries, _ := s.loadAllRecords("inquiry")
bookings, _ := s.loadAllRecords("booking")
followups, _ := s.loadAllRecords("followup")
opts, _ := s.loadOptions()
p := backupPayload{
App: "freight-sfa",
Version: "1.0.0",
Exported: nowStr(),
Clients: clients,
Inquiries: inquiries,
Bookings: bookings,
Followups: followups,
Options: opts,
Owner: auth.Username(r),
}
writeJSON(w, http.StatusOK, p)
}
func (s *Server) loadOptions() (map[string][]string, error) {
var raw string
err := s.db.QueryRow(`SELECT data_json FROM options WHERE id=1`).Scan(&raw)
if err != nil {
return defaultOptions, nil
}
m := map[string][]string{}
if json.Unmarshal([]byte(raw), &m) != nil {
return defaultOptions, nil
}
for k, v := range defaultOptions {
if _, ok := m[k]; !ok || m[k] == nil {
m[k] = v
}
}
return m, nil
}
// importBackup 导入备份(全量覆盖团队数据)
func (s *Server) importBackup(w http.ResponseWriter, r *http.Request) {
var p backupPayload
if err := decodeJSON(r, &p); err != nil {
writeErr(w, http.StatusBadRequest, "备份格式错误")
return
}
tx, err := s.db.Begin()
if err != nil {
writeErr(w, http.StatusInternalServerError, "事务启动失败")
return
}
defer tx.Rollback()
now := nowStr()
clearAndInsert := func(kind string, list []map[string]any) error {
if _, err := tx.Exec(`UPDATE records SET deleted_at=? WHERE kind=?`, now, kind); err != nil {
return err
}
for _, m := range list {
raw, err := json.Marshal(m)
if err != nil {
return err
}
id := genID(kind)
uid := auth.UserID(r)
if _, err := tx.Exec(`INSERT INTO records (id, kind, owner_id, data_json, created_at, updated_at)
VALUES (?,?,?,?,?,?)`, id, kind, uid, string(raw), now, now); err != nil {
return err
}
}
return nil
}
if err := clearAndInsert("client", p.Clients); err != nil {
writeErr(w, http.StatusInternalServerError, "导入客户失败")
return
}
if err := clearAndInsert("inquiry", p.Inquiries); err != nil {
writeErr(w, http.StatusInternalServerError, "导入商机失败")
return
}
if err := clearAndInsert("booking", p.Bookings); err != nil {
writeErr(w, http.StatusInternalServerError, "导入订舱失败")
return
}
if err := clearAndInsert("followup", p.Followups); err != nil {
writeErr(w, http.StatusInternalServerError, "导入跟进失败")
return
}
if p.Options != nil {
raw, _ := json.Marshal(p.Options)
if _, err := tx.Exec(`UPDATE options SET data_json=?, updated_at=? WHERE id=1`, string(raw), now); err != nil {
return
}
}
if err := tx.Commit(); err != nil {
writeErr(w, http.StatusInternalServerError, "导入提交失败")
return
}
s.auditLog(r, "import", "backup", "", "导入备份")
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "counts": map[string]int{
"clients": len(p.Clients), "inquiries": len(p.Inquiries),
"bookings": len(p.Bookings), "followups": len(p.Followups),
}})
}
// listAuditLogs 审计日志(仅 admin 可见)
func (s *Server) listAuditLogs(w http.ResponseWriter, r *http.Request) {
if auth.Role(r) != "admin" {
writeErr(w, http.StatusForbidden, "仅管理员可查看审计日志")
return
}
rows, err := s.db.Query(`SELECT id, user_id, username, action, entity, entity_id, detail, created_at
FROM audit_logs ORDER BY id DESC LIMIT 200`)
if err != nil {
writeErr(w, http.StatusInternalServerError, "查询失败")
return
}
defer rows.Close()
out := []map[string]any{}
for rows.Next() {
var id int64
var uid int64
var user, action, entity, entityID, detail, created string
if err := rows.Scan(&id, &uid, &user, &action, &entity, &entityID, &detail, &created); err != nil {
writeErr(w, http.StatusInternalServerError, "数据读取失败")
return
}
out = append(out, map[string]any{
"id": id, "user_id": uid, "username": user, "action": action,
"entity": entity, "entity_id": entityID, "detail": detail, "created_at": created,
})
}
writeJSON(w, http.StatusOK, out)
}