backup_handler.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package admin
  2. import (
  3. "github.com/Wei-Shaw/sub2api/internal/pkg/response"
  4. "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  5. "github.com/Wei-Shaw/sub2api/internal/service"
  6. "github.com/gin-gonic/gin"
  7. )
  8. type BackupHandler struct {
  9. backupService *service.BackupService
  10. userService *service.UserService
  11. imageStorage *service.ImageStorageSettingService
  12. }
  13. func NewBackupHandler(backupService *service.BackupService, userService *service.UserService, imageStorage *service.ImageStorageSettingService) *BackupHandler {
  14. return &BackupHandler{
  15. backupService: backupService,
  16. userService: userService,
  17. imageStorage: imageStorage,
  18. }
  19. }
  20. // ─── S3 配置 ───
  21. func (h *BackupHandler) GetS3Config(c *gin.Context) {
  22. cfg, err := h.backupService.GetS3Config(c.Request.Context())
  23. if err != nil {
  24. response.ErrorFrom(c, err)
  25. return
  26. }
  27. response.Success(c, cfg)
  28. }
  29. func (h *BackupHandler) UpdateS3Config(c *gin.Context) {
  30. var req service.BackupS3Config
  31. if err := c.ShouldBindJSON(&req); err != nil {
  32. response.BadRequest(c, "Invalid request: "+err.Error())
  33. return
  34. }
  35. cfg, err := h.backupService.UpdateS3Config(c.Request.Context(), req)
  36. if err != nil {
  37. response.ErrorFrom(c, err)
  38. return
  39. }
  40. response.Success(c, cfg)
  41. }
  42. func (h *BackupHandler) TestS3Connection(c *gin.Context) {
  43. var req service.BackupS3Config
  44. if err := c.ShouldBindJSON(&req); err != nil {
  45. response.BadRequest(c, "Invalid request: "+err.Error())
  46. return
  47. }
  48. err := h.backupService.TestS3Connection(c.Request.Context(), req)
  49. if err != nil {
  50. response.Success(c, gin.H{"ok": false, "message": err.Error()})
  51. return
  52. }
  53. response.Success(c, gin.H{"ok": true, "message": "connection successful"})
  54. }
  55. // ─── 定时备份 ───
  56. func (h *BackupHandler) GetSchedule(c *gin.Context) {
  57. cfg, err := h.backupService.GetSchedule(c.Request.Context())
  58. if err != nil {
  59. response.ErrorFrom(c, err)
  60. return
  61. }
  62. response.Success(c, cfg)
  63. }
  64. func (h *BackupHandler) UpdateSchedule(c *gin.Context) {
  65. var req service.BackupScheduleConfig
  66. if err := c.ShouldBindJSON(&req); err != nil {
  67. response.BadRequest(c, "Invalid request: "+err.Error())
  68. return
  69. }
  70. cfg, err := h.backupService.UpdateSchedule(c.Request.Context(), req)
  71. if err != nil {
  72. response.ErrorFrom(c, err)
  73. return
  74. }
  75. response.Success(c, cfg)
  76. }
  77. // ─── 备份操作 ───
  78. type CreateBackupRequest struct {
  79. ExpireDays *int `json:"expire_days"` // nil=使用默认值14,0=永不过期
  80. }
  81. func (h *BackupHandler) CreateBackup(c *gin.Context) {
  82. var req CreateBackupRequest
  83. _ = c.ShouldBindJSON(&req) // 允许空 body
  84. expireDays := 14 // 默认14天过期
  85. if req.ExpireDays != nil {
  86. expireDays = *req.ExpireDays
  87. }
  88. record, err := h.backupService.StartBackup(c.Request.Context(), "manual", expireDays)
  89. if err != nil {
  90. response.ErrorFrom(c, err)
  91. return
  92. }
  93. response.Accepted(c, record)
  94. }
  95. func (h *BackupHandler) ListBackups(c *gin.Context) {
  96. records, err := h.backupService.ListBackups(c.Request.Context())
  97. if err != nil {
  98. response.ErrorFrom(c, err)
  99. return
  100. }
  101. if records == nil {
  102. records = []service.BackupRecord{}
  103. }
  104. response.Success(c, gin.H{"items": records})
  105. }
  106. func (h *BackupHandler) GetBackup(c *gin.Context) {
  107. backupID := c.Param("id")
  108. if backupID == "" {
  109. response.BadRequest(c, "backup ID is required")
  110. return
  111. }
  112. record, err := h.backupService.GetBackupRecord(c.Request.Context(), backupID)
  113. if err != nil {
  114. response.ErrorFrom(c, err)
  115. return
  116. }
  117. response.Success(c, record)
  118. }
  119. func (h *BackupHandler) DeleteBackup(c *gin.Context) {
  120. backupID := c.Param("id")
  121. if backupID == "" {
  122. response.BadRequest(c, "backup ID is required")
  123. return
  124. }
  125. if err := h.backupService.DeleteBackup(c.Request.Context(), backupID); err != nil {
  126. response.ErrorFrom(c, err)
  127. return
  128. }
  129. response.Success(c, gin.H{"deleted": true})
  130. }
  131. func (h *BackupHandler) GetDownloadURL(c *gin.Context) {
  132. backupID := c.Param("id")
  133. if backupID == "" {
  134. response.BadRequest(c, "backup ID is required")
  135. return
  136. }
  137. download, err := h.backupService.GetBackupDownloadURL(c.Request.Context(), backupID)
  138. if err != nil {
  139. response.ErrorFrom(c, err)
  140. return
  141. }
  142. response.Success(c, download)
  143. }
  144. // ─── 恢复操作(需要重新输入管理员密码) ───
  145. type RestoreBackupRequest struct {
  146. Password string `json:"password" binding:"required"`
  147. }
  148. func (h *BackupHandler) RestoreBackup(c *gin.Context) {
  149. backupID := c.Param("id")
  150. if backupID == "" {
  151. response.BadRequest(c, "backup ID is required")
  152. return
  153. }
  154. var req RestoreBackupRequest
  155. if err := c.ShouldBindJSON(&req); err != nil {
  156. response.BadRequest(c, "password is required for restore operation")
  157. return
  158. }
  159. // 从上下文获取当前管理员用户 ID
  160. sub, ok := middleware.GetAuthSubjectFromContext(c)
  161. if !ok {
  162. response.Unauthorized(c, "unauthorized")
  163. return
  164. }
  165. // 获取管理员用户并验证密码
  166. user, err := h.userService.GetByID(c.Request.Context(), sub.UserID)
  167. if err != nil {
  168. response.ErrorFrom(c, err)
  169. return
  170. }
  171. if !user.CheckPassword(req.Password) {
  172. response.BadRequest(c, "incorrect admin password")
  173. return
  174. }
  175. record, err := h.backupService.StartRestore(c.Request.Context(), backupID)
  176. if err != nil {
  177. response.ErrorFrom(c, err)
  178. return
  179. }
  180. response.Accepted(c, record)
  181. }
  182. // ─── 异步生图对象存储配置 ───
  183. //
  184. // 与备份共用一套 S3 客户端构造,因此放在同一个页面下:勾选"复用备份 S3"即可直接
  185. // 借用备份已配置的端点与密钥,只用不同的前缀区分对象(备份走 backups/,图片走 images/)。
  186. func (h *BackupHandler) GetImageStorageConfig(c *gin.Context) {
  187. ctx := c.Request.Context()
  188. cfg, err := h.imageStorage.Get(ctx)
  189. if err != nil {
  190. response.ErrorFrom(c, err)
  191. return
  192. }
  193. response.Success(c, gin.H{
  194. "config": cfg,
  195. "secret_configured": h.imageStorage.SecretConfigured(ctx),
  196. })
  197. }
  198. func (h *BackupHandler) UpdateImageStorageConfig(c *gin.Context) {
  199. var req service.ImageStorageSettings
  200. if err := c.ShouldBindJSON(&req); err != nil {
  201. response.BadRequest(c, "Invalid request: "+err.Error())
  202. return
  203. }
  204. cfg, err := h.imageStorage.Update(c.Request.Context(), req)
  205. if err != nil {
  206. response.ErrorFrom(c, err)
  207. return
  208. }
  209. response.Success(c, cfg)
  210. }
  211. func (h *BackupHandler) TestImageStorageConnection(c *gin.Context) {
  212. var req service.ImageStorageSettings
  213. if err := c.ShouldBindJSON(&req); err != nil {
  214. response.BadRequest(c, "Invalid request: "+err.Error())
  215. return
  216. }
  217. if err := h.imageStorage.TestConnection(c.Request.Context(), req); err != nil {
  218. response.Success(c, gin.H{"ok": false, "message": err.Error()})
  219. return
  220. }
  221. response.Success(c, gin.H{"ok": true, "message": "connection successful"})
  222. }