include-admin.go (8583B)
1 package main 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "path/filepath" 12 "strings" 13 "time" 14 ) 15 16 func loadConfig() error { 17 config = Config{ 18 DatabasePath: "./database.db", 19 UploadDir: "uploads", 20 ServerPort: ":8080", 21 InstanceName: "Tagliatelle", 22 GallerySize: "400px", 23 ItemsPerPage: "100", 24 TagAliases: []TagAliasGroup{}, 25 } 26 27 if data, err := ioutil.ReadFile("config.json"); err == nil { 28 if err := json.Unmarshal(data, &config); err != nil { 29 return err 30 } 31 } 32 33 return os.MkdirAll(config.UploadDir, 0755) 34 } 35 36 func saveConfig() error { 37 data, err := json.MarshalIndent(config, "", " ") 38 if err != nil { 39 return err 40 } 41 return ioutil.WriteFile("config.json", data, 0644) 42 } 43 44 func validateConfig(newConfig Config) error { 45 if newConfig.DatabasePath == "" { 46 return fmt.Errorf("database path cannot be empty") 47 } 48 49 if newConfig.UploadDir == "" { 50 return fmt.Errorf("upload directory cannot be empty") 51 } 52 53 if newConfig.ServerPort == "" || !strings.HasPrefix(newConfig.ServerPort, ":") { 54 return fmt.Errorf("server port must be in format ':8080'") 55 } 56 57 if err := os.MkdirAll(newConfig.UploadDir, 0755); err != nil { 58 return fmt.Errorf("cannot create upload directory: %v", err) 59 } 60 61 return nil 62 } 63 64 func adminHandler(w http.ResponseWriter, r *http.Request) { 65 // Get orphaned files 66 orphans, _ := getOrphanedFiles(config.UploadDir) 67 68 // Get video files for thumbnails 69 missingThumbnails, _ := getMissingThumbnailVideos() 70 71 switch r.Method { 72 case http.MethodPost: 73 action := r.FormValue("action") 74 75 switch action { 76 case "save", "": 77 handleSaveSettings(w, r, orphans, missingThumbnails) 78 return 79 80 case "backup": 81 err := backupDatabase(config.DatabasePath) 82 pageData := buildPageData("Admin", struct { 83 Config Config 84 Error string 85 Success string 86 Orphans []string 87 MissingThumbnails []VideoFile 88 }{ 89 Config: config, 90 Error: errorString(err), 91 Success: successString(err, "Database backup created successfully!"), 92 Orphans: orphans, 93 MissingThumbnails: missingThumbnails, 94 }) 95 renderTemplate(w, "admin.html", pageData) 96 return 97 98 case "vacuum": 99 err := vacuumDatabase(config.DatabasePath) 100 pageData := buildPageData("Admin", struct { 101 Config Config 102 Error string 103 Success string 104 Orphans []string 105 MissingThumbnails []VideoFile 106 }{ 107 Config: config, 108 Error: errorString(err), 109 Success: successString(err, "Database vacuum completed successfully!"), 110 Orphans: orphans, 111 MissingThumbnails: missingThumbnails, 112 }) 113 renderTemplate(w, "admin.html", pageData) 114 return 115 116 case "save_aliases": 117 handleSaveAliases(w, r, orphans, missingThumbnails) 118 return 119 } 120 121 default: 122 pageData := buildPageData("Admin", struct { 123 Config Config 124 Error string 125 Success string 126 Orphans []string 127 MissingThumbnails []VideoFile 128 }{ 129 Config: config, 130 Error: "", 131 Success: "", 132 Orphans: orphans, 133 MissingThumbnails: missingThumbnails, 134 }) 135 renderTemplate(w, "admin.html", pageData) 136 } 137 } 138 139 func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphans []string, missingThumbnails []VideoFile) { 140 aliasesJSON := r.FormValue("aliases_json") 141 142 var aliases []TagAliasGroup 143 if aliasesJSON != "" { 144 if err := json.Unmarshal([]byte(aliasesJSON), &aliases); err != nil { 145 pageData := buildPageData("Admin", struct { 146 Config Config 147 Error string 148 Success string 149 Orphans []string 150 MissingThumbnails []VideoFile 151 }{ 152 Config: config, 153 Error: "Invalid aliases JSON: " + err.Error(), 154 Success: "", 155 Orphans: orphans, 156 MissingThumbnails: missingThumbnails, 157 }) 158 renderTemplate(w, "admin.html", pageData) 159 return 160 } 161 } 162 163 config.TagAliases = aliases 164 165 if err := saveConfig(); err != nil { 166 pageData := buildPageData("Admin", struct { 167 Config Config 168 Error string 169 Success string 170 Orphans []string 171 MissingThumbnails []VideoFile 172 }{ 173 Config: config, 174 Error: "Failed to save configuration: " + err.Error(), 175 Success: "", 176 Orphans: orphans, 177 MissingThumbnails: missingThumbnails, 178 }) 179 renderTemplate(w, "admin.html", pageData) 180 return 181 } 182 183 pageData := buildPageData("Admin", struct { 184 Config Config 185 Error string 186 Success string 187 Orphans []string 188 MissingThumbnails []VideoFile 189 }{ 190 Config: config, 191 Error: "", 192 Success: "Tag aliases saved successfully!", 193 Orphans: orphans, 194 MissingThumbnails: missingThumbnails, 195 }) 196 renderTemplate(w, "admin.html", pageData) 197 } 198 199 func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphans []string, missingThumbnails []VideoFile) { 200 newConfig := Config{ 201 DatabasePath: strings.TrimSpace(r.FormValue("database_path")), 202 UploadDir: strings.TrimSpace(r.FormValue("upload_dir")), 203 ServerPort: strings.TrimSpace(r.FormValue("server_port")), 204 InstanceName: strings.TrimSpace(r.FormValue("instance_name")), 205 GallerySize: strings.TrimSpace(r.FormValue("gallery_size")), 206 ItemsPerPage: strings.TrimSpace(r.FormValue("items_per_page")), 207 TagAliases: config.TagAliases, // Preserve existing aliases 208 } 209 210 if err := validateConfig(newConfig); err != nil { 211 pageData := buildPageData("Admin", struct { 212 Config Config 213 Error string 214 Success string 215 Orphans []string 216 MissingThumbnails []VideoFile 217 }{ 218 Config: config, 219 Error: err.Error(), 220 Success: "", 221 Orphans: orphans, 222 MissingThumbnails: missingThumbnails, 223 }) 224 renderTemplate(w, "admin.html", pageData) 225 return 226 } 227 228 needsRestart := (newConfig.DatabasePath != config.DatabasePath || 229 newConfig.ServerPort != config.ServerPort) 230 231 config = newConfig 232 if err := saveConfig(); err != nil { 233 pageData := buildPageData("Admin", struct { 234 Config Config 235 Error string 236 Success string 237 Orphans []string 238 MissingThumbnails []VideoFile 239 }{ 240 Config: config, 241 Error: "Failed to save configuration: " + err.Error(), 242 Success: "", 243 Orphans: orphans, 244 MissingThumbnails: missingThumbnails, 245 }) 246 renderTemplate(w, "admin.html", pageData) 247 return 248 } 249 250 var message string 251 if needsRestart { 252 message = "Settings saved successfully! Please restart the server for database/port changes to take effect." 253 } else { 254 message = "Settings saved successfully!" 255 } 256 257 pageData := buildPageData("Admin", struct { 258 Config Config 259 Error string 260 Success string 261 Orphans []string 262 MissingThumbnails []VideoFile 263 }{ 264 Config: config, 265 Error: "", 266 Success: message, 267 Orphans: orphans, 268 MissingThumbnails: missingThumbnails, 269 }) 270 renderTemplate(w, "admin.html", pageData) 271 } 272 273 func backupDatabase(dbPath string) error { 274 if dbPath == "" { 275 return fmt.Errorf("database path not configured") 276 } 277 278 timestamp := time.Now().Format("20060102_150405") 279 backupPath := fmt.Sprintf("%s_backup_%s.db", strings.TrimSuffix(dbPath, filepath.Ext(dbPath)), timestamp) 280 281 input, err := os.Open(dbPath) 282 if err != nil { 283 return fmt.Errorf("failed to open database: %w", err) 284 } 285 defer input.Close() 286 287 output, err := os.Create(backupPath) 288 if err != nil { 289 return fmt.Errorf("failed to create backup file: %w", err) 290 } 291 defer output.Close() 292 293 if _, err := io.Copy(output, input); err != nil { 294 return fmt.Errorf("failed to copy database: %w", err) 295 } 296 297 return nil 298 } 299 300 func vacuumDatabase(dbPath string) error { 301 db, err := sql.Open("sqlite3", dbPath) 302 if err != nil { 303 return fmt.Errorf("failed to open database: %w", err) 304 } 305 defer db.Close() 306 307 _, err = db.Exec("VACUUM;") 308 if err != nil { 309 return fmt.Errorf("VACUUM failed: %w", err) 310 } 311 312 return nil 313 } 314 315 func getFilesInDB() (map[string]bool, error) { 316 rows, err := db.Query(`SELECT filename FROM files`) 317 if err != nil { 318 return nil, err 319 } 320 defer rows.Close() 321 322 fileMap := make(map[string]bool) 323 for rows.Next() { 324 var name string 325 rows.Scan(&name) 326 fileMap[name] = true 327 } 328 return fileMap, nil 329 }