tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

include-admin.go (6994B)


      1 package main
      2 
      3 import (
      4 	"database/sql"
      5 	"fmt"
      6 	"io"
      7 	"net/http"
      8 	"os"
      9 	"path/filepath"
     10 	"strconv"
     11 	"strings"
     12 	"time"
     13 )
     14 
     15 type AdminPageData struct {
     16 	Config            Config
     17 	Error             string
     18 	Success           string
     19 	OrphanData        OrphanData
     20 	ActiveTab         string
     21 	MissingThumbnails []VideoFile
     22 }
     23 
     24 func renderAdminPage(w http.ResponseWriter, r *http.Request, data AdminPageData) {
     25 	if data.ActiveTab == "" {
     26 		data.ActiveTab = r.FormValue("active_tab")
     27 	}
     28 	pageData := buildPageData("Admin", data)
     29 	renderTemplate(w, "admin.html", pageData)
     30 }
     31 
     32 func currentAdminState(r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) AdminPageData {
     33 	return AdminPageData{
     34 		Config:            config,
     35 		OrphanData:        orphanData,
     36 		ActiveTab:         r.FormValue("active_tab"),
     37 		MissingThumbnails: missingThumbnails,
     38 	}
     39 }
     40 
     41 func validateConfig(newConfig Config) error {
     42 	if newConfig.GallerySize == "" {
     43 		return fmt.Errorf("gallery size cannot be empty")
     44 	}
     45 	if newConfig.ItemsPerPage == "" {
     46 		return fmt.Errorf("items per page cannot be empty")
     47 	}
     48 	return nil
     49 }
     50 
     51 func adminHandler(w http.ResponseWriter, r *http.Request) {
     52 	// Get orphaned files
     53 	orphanData, _ := getOrphanedFiles(config.UploadDir)
     54 
     55 	// Get video files for thumbnails
     56 	missingThumbnails, _ := getMissingThumbnailVideos()
     57 
     58 	switch r.Method {
     59 	case http.MethodPost:
     60 		switch r.FormValue("action") {
     61 		case "save", "":
     62 			handleSaveSettings(w, r, orphanData, missingThumbnails)
     63 
     64 		case "backup":
     65 			err := backupDatabase(config.DatabasePath)
     66 			data := currentAdminState(r, orphanData, missingThumbnails)
     67 			data.Error = errorString(err)
     68 			data.Success = successString(err, "Database backup created successfully!")
     69 			renderAdminPage(w, r, data)
     70 
     71 		case "vacuum":
     72 			err := vacuumDatabase(config.DatabasePath)
     73 			data := currentAdminState(r, orphanData, missingThumbnails)
     74 			data.Error = errorString(err)
     75 			data.Success = successString(err, "Database vacuum completed successfully!")
     76 			renderAdminPage(w, r, data)
     77 
     78 		case "save_aliases":
     79 			handleSaveAliases(w, r, orphanData, missingThumbnails)
     80 
     81 		case "save_sed_rules":
     82 			handleSaveSedRules(w, r, orphanData, missingThumbnails)
     83 
     84 		case "compute_properties":
     85 			handleComputeProperties(w, r, orphanData, missingThumbnails)
     86 		}
     87 
     88 	default:
     89 		renderAdminPage(w, r, currentAdminState(r, orphanData, missingThumbnails))
     90 	}
     91 }
     92 
     93 func parseAliasesFromForm(r *http.Request) []TagAliasGroup {
     94 	var groups []TagAliasGroup
     95 	for i := 0; ; i++ {
     96 		category := strings.TrimSpace(r.FormValue(fmt.Sprintf("aliases[%d][category]", i)))
     97 		if category == "" {
     98 			break
     99 		}
    100 		var aliases []string
    101 		for j := 0; ; j++ {
    102 			v := strings.TrimSpace(r.FormValue(fmt.Sprintf("aliases[%d][aliases][%d]", i, j)))
    103 			if v == "" {
    104 				break
    105 			}
    106 			aliases = append(aliases, v)
    107 		}
    108 		if len(aliases) >= 2 {
    109 			groups = append(groups, TagAliasGroup{Category: category, Aliases: aliases})
    110 		}
    111 	}
    112 	return groups
    113 }
    114 
    115 func parseSedRulesFromForm(r *http.Request) ([]SedRule, error) {
    116 	var rules []SedRule
    117 	for i := 0; ; i++ {
    118 		name := strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][name]", i)))
    119 		if name == "" {
    120 			break
    121 		}
    122 		command := strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][command]", i)))
    123 		if command == "" {
    124 			return nil, fmt.Errorf("rule %s is missing a command", strconv.Itoa(i+1))
    125 		}
    126 		rules = append(rules, SedRule{
    127 			Name:        name,
    128 			Description: strings.TrimSpace(r.FormValue(fmt.Sprintf("sed_rules[%d][description]", i))),
    129 			Command:     command,
    130 		})
    131 	}
    132 	return rules, nil
    133 }
    134 
    135 func handleSaveAliases(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) {
    136 	config.TagAliases = parseAliasesFromForm(r)
    137 
    138 	if err := SaveConfig(db, config); err != nil {
    139 		data := currentAdminState(r, orphanData, missingThumbnails)
    140 		data.Error = "Failed to save configuration: " + err.Error()
    141 		renderAdminPage(w, r, data)
    142 		return
    143 	}
    144 
    145 	data := currentAdminState(r, orphanData, missingThumbnails)
    146 	data.Success = "Tag aliases saved successfully!"
    147 	renderAdminPage(w, r, data)
    148 }
    149 
    150 func handleSaveSettings(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) {
    151 	newConfig := config // preserve runtime fields
    152 	newConfig.GallerySize = strings.TrimSpace(r.FormValue("gallery_size"))
    153 	newConfig.ItemsPerPage = strings.TrimSpace(r.FormValue("items_per_page"))
    154 
    155 	if err := validateConfig(newConfig); err != nil {
    156 		data := currentAdminState(r, orphanData, missingThumbnails)
    157 		data.Error = err.Error()
    158 		renderAdminPage(w, r, data)
    159 		return
    160 	}
    161 
    162 	config = newConfig
    163 
    164 	if err := SaveConfig(db, config); err != nil {
    165 		data := currentAdminState(r, orphanData, missingThumbnails)
    166 		data.Error = "Failed to save configuration: " + err.Error()
    167 		renderAdminPage(w, r, data)
    168 		return
    169 	}
    170 
    171 	data := currentAdminState(r, orphanData, missingThumbnails)
    172 	data.Success = "Settings saved successfully!"
    173 	renderAdminPage(w, r, data)
    174 }
    175 
    176 func handleSaveSedRules(w http.ResponseWriter, r *http.Request, orphanData OrphanData, missingThumbnails []VideoFile) {
    177 	rules, err := parseSedRulesFromForm(r)
    178 	if err != nil {
    179 		data := currentAdminState(r, orphanData, missingThumbnails)
    180 		data.Error = err.Error()
    181 		renderAdminPage(w, r, data)
    182 		return
    183 	}
    184 
    185 	config.SedRules = rules
    186 
    187 	if err := SaveConfig(db, config); err != nil {
    188 		data := currentAdminState(r, orphanData, missingThumbnails)
    189 		data.Error = "Failed to save configuration: " + err.Error()
    190 		renderAdminPage(w, r, data)
    191 		return
    192 	}
    193 
    194 	data := currentAdminState(r, orphanData, missingThumbnails)
    195 	data.Success = "Sed rules saved successfully!"
    196 	renderAdminPage(w, r, data)
    197 }
    198 
    199 func backupDatabase(dbPath string) error {
    200 	if dbPath == "" {
    201 		return fmt.Errorf("database path not configured")
    202 	}
    203 
    204 	timestamp := time.Now().Format("20060102_150405")
    205 	backupPath := fmt.Sprintf("%s_backup_%s.db", strings.TrimSuffix(dbPath, filepath.Ext(dbPath)), timestamp)
    206 
    207 	input, err := os.Open(dbPath)
    208 	if err != nil {
    209 		return fmt.Errorf("failed to open database: %w", err)
    210 	}
    211 	defer input.Close()
    212 
    213 	output, err := os.Create(backupPath)
    214 	if err != nil {
    215 		return fmt.Errorf("failed to create backup file: %w", err)
    216 	}
    217 	defer output.Close()
    218 
    219 	if _, err := io.Copy(output, input); err != nil {
    220 		return fmt.Errorf("failed to copy database: %w", err)
    221 	}
    222 
    223 	return nil
    224 }
    225 
    226 func vacuumDatabase(dbPath string) error {
    227 	db, err := sql.Open("sqlite3", dbPath)
    228 	if err != nil {
    229 		return fmt.Errorf("failed to open database: %w", err)
    230 	}
    231 	defer db.Close()
    232 
    233 	if _, err = db.Exec("VACUUM;"); err != nil {
    234 		return fmt.Errorf("VACUUM failed: %w", err)
    235 	}
    236 
    237 	return nil
    238 }
    239 
    240 func getFilesInDB() (map[string]bool, error) {
    241 	rows, err := db.Query("SELECT filename FROM files")
    242 	if err != nil {
    243 		return nil, err
    244 	}
    245 	defer rows.Close()
    246 
    247 	fileMap := make(map[string]bool)
    248 	for rows.Next() {
    249 		var name string
    250 		if err := rows.Scan(&name); err != nil {
    251 			return nil, err
    252 		}
    253 		fileMap[name] = true
    254 	}
    255 	if err := rows.Err(); err != nil {
    256 		return nil, err
    257 	}
    258 	return fileMap, nil
    259 }