taggart

Simple golang tagging filesystem webapp
Log | Files | Refs

commit 184a0039a129d604eadc6e30c6a6c1a7274ea6b6
parent 40d7b0a18047e7c294229d172ca0f9f59a0dc198
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Sun, 21 Sep 2025 11:05:51 +0100

Add settings feature for upload dir, db path

Diffstat:
M.gitignore | 1+
Mmain.go | 156++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtemplates/_header.html | 2+-
Atemplates/settings.html | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 212 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -2,3 +2,4 @@ uploads/ *.db +config.json diff --git a/main.go b/main.go @@ -11,6 +11,8 @@ import ( "strings" "io" "net/url" + "encoding/json" + "io/ioutil" _ "github.com/mattn/go-sqlite3" ) @@ -18,6 +20,7 @@ import ( var ( db *sql.DB tmpl *template.Template + config Config ) type File struct { @@ -27,6 +30,12 @@ type File struct { Tags map[string]string } +type Config struct { + DatabasePath string `json:"database_path"` + UploadDir string `json:"upload_dir"` + ServerPort string `json:"server_port"` +} + type TagDisplay struct { Value string Count int @@ -38,8 +47,13 @@ type PageData struct { } func main() { + // Load configuration first + if err := loadConfig(); err != nil { + log.Fatalf("Failed to load config: %v", err) + } + var err error - db, err = sql.Open("sqlite3", "./database.db") + db, err = sql.Open("sqlite3", config.DatabasePath) if err != nil { log.Fatal(err) } @@ -71,7 +85,8 @@ func main() { log.Fatal(err) } - os.MkdirAll("uploads", 0755) + // Use configured upload directory + os.MkdirAll(config.UploadDir, 0755) os.MkdirAll("static", 0755) tmpl = template.Must(template.New("").Funcs(template.FuncMap{ @@ -93,12 +108,19 @@ func main() { http.HandleFunc("/tag/", tagFilterHandler) http.HandleFunc("/untagged", untaggedFilesHandler) http.HandleFunc("/search", searchHandler) + http.HandleFunc("/settings", settingsHandler) - http.Handle("/uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir("uploads")))) + // Use configured upload directory for file serving + http.Handle("/uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir(config.UploadDir)))) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) - log.Println("Server started at http://localhost:8080") - http.ListenAndServe(":8080", nil) + log.Printf("Server started at http://localhost%s", config.ServerPort) + log.Printf("Database: %s", config.DatabasePath) + log.Printf("Upload directory: %s", config.UploadDir) + http.ListenAndServe(config.ServerPort, nil) +} + + func searchHandler(w http.ResponseWriter, r *http.Request) { query := strings.TrimSpace(r.URL.Query().Get("q")) @@ -648,4 +670,128 @@ func tagFilterHandler(w http.ResponseWriter, r *http.Request) { } tmpl.ExecuteTemplate(w, "list.html", pageData) +} + +func loadConfig() error { + // Set defaults + config = Config{ + DatabasePath: "./database.db", + UploadDir: "uploads", + ServerPort: ":8080", + } + + // Try to load existing config + if data, err := ioutil.ReadFile("config.json"); err == nil { + if err := json.Unmarshal(data, &config); err != nil { + return err + } + } + + // Ensure upload directory exists + return os.MkdirAll(config.UploadDir, 0755) +} + +func saveConfig() error { + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return err + } + return ioutil.WriteFile("config.json", data, 0644) +} + +func validateConfig(newConfig Config) error { + // Validate database path is not empty + if newConfig.DatabasePath == "" { + return fmt.Errorf("database path cannot be empty") + } + + // Validate upload directory is not empty + if newConfig.UploadDir == "" { + return fmt.Errorf("upload directory cannot be empty") + } + + // Validate server port format + if newConfig.ServerPort == "" || !strings.HasPrefix(newConfig.ServerPort, ":") { + return fmt.Errorf("server port must be in format ':8080'") + } + + // Try to create upload directory if it doesn't exist + if err := os.MkdirAll(newConfig.UploadDir, 0755); err != nil { + return fmt.Errorf("cannot create upload directory: %v", err) + } + + return nil +} + +// Add this settings handler function +func settingsHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + // Handle settings update + newConfig := Config{ + DatabasePath: strings.TrimSpace(r.FormValue("database_path")), + UploadDir: strings.TrimSpace(r.FormValue("upload_dir")), + ServerPort: strings.TrimSpace(r.FormValue("server_port")), + } + + // Validate new configuration + if err := validateConfig(newConfig); err != nil { + pageData := PageData{ + Title: "Settings", + Data: struct { + Config Config + Error string + }{config, err.Error()}, + } + tmpl.ExecuteTemplate(w, "settings.html", pageData) + return + } + + // Check if database path changed and requires restart + needsRestart := (newConfig.DatabasePath != config.DatabasePath || + newConfig.ServerPort != config.ServerPort) + + // Save new configuration + config = newConfig + if err := saveConfig(); err != nil { + pageData := PageData{ + Title: "Settings", + Data: struct { + Config Config + Error string + }{config, "Failed to save configuration: " + err.Error()}, + } + tmpl.ExecuteTemplate(w, "settings.html", pageData) + return + } + + // Show success message + var message string + if needsRestart { + message = "Settings saved successfully! Please restart the server for database/port changes to take effect." + } else { + message = "Settings saved successfully!" + } + + pageData := PageData{ + Title: "Settings", + Data: struct { + Config Config + Error string + Success string + }{config, "", message}, + } + tmpl.ExecuteTemplate(w, "settings.html", pageData) + return + } + + // Show settings form + pageData := PageData{ + Title: "Settings", + Data: struct { + Config Config + Error string + Success string + }{config, "", ""}, + } + tmpl.ExecuteTemplate(w, "settings.html", pageData) } \ No newline at end of file diff --git a/templates/_header.html b/templates/_header.html @@ -23,5 +23,5 @@ ul.tag-menu li ul li a:hover{background:#ddd} </style> </head> <body> -<p><a href="/upload">Upload new file</a> | <a href="/">Files</a> | <a href="/tags">Tags</a> | <a href="/untagged">Untagged</a> | <a href="/search">Search</a></p> +<p><a href="/upload">Upload new file</a> | <a href="/">Files</a> | <a href="/tags">Tags</a> | <a href="/untagged">Untagged</a> | <a href="/search">Search</a> | <a href="/settings">Settings</a></p> {{end}} \ No newline at end of file diff --git a/templates/settings.html b/templates/settings.html @@ -0,0 +1,58 @@ +{{template "_header" .}} +<h1>Settings</h1> + +{{if .Data.Error}} +<div style="background-color: #f8d7da; color: #721c24; padding: 10px; border: 1px solid #f5c6cb; border-radius: 4px; margin-bottom: 20px;"> + <strong>Error:</strong> {{.Data.Error}} +</div> +{{end}} + +{{if .Data.Success}} +<div style="background-color: #d4edda; color: #155724; padding: 10px; border: 1px solid #c3e6cb; border-radius: 4px; margin-bottom: 20px;"> + <strong>Success:</strong> {{.Data.Success}} +</div> +{{end}} + +<form method="post" style="max-width: 600px;"> + <div style="margin-bottom: 20px;"> + <label for="database_path" style="display: block; font-weight: bold; margin-bottom: 5px;">Database Path:</label> + <input type="text" id="database_path" name="database_path" value="{{.Data.Config.DatabasePath}}" required + style="width: 100%; padding: 8px; font-size: 14px;" + placeholder="./database.db"> + <small style="color: #666;">Path to SQLite database file (requires restart if changed)</small> + </div> + + <div style="margin-bottom: 20px;"> + <label for="upload_dir" style="display: block; font-weight: bold; margin-bottom: 5px;">Upload Directory:</label> + <input type="text" id="upload_dir" name="upload_dir" value="{{.Data.Config.UploadDir}}" required + style="width: 100%; padding: 8px; font-size: 14px;" + placeholder="uploads"> + <small style="color: #666;">Directory where uploaded files are stored</small> + </div> + + <div style="margin-bottom: 20px;"> + <label for="server_port" style="display: block; font-weight: bold; margin-bottom: 5px;">Server Port:</label> + <input type="text" id="server_port" name="server_port" value="{{.Data.Config.ServerPort}}" required + style="width: 100%; padding: 8px; font-size: 14px;" + placeholder=":8080"> + <small style="color: #666;">Port for web server (format: :8080, requires restart if changed)</small> + </div> + + <button type="submit" style="background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; font-size: 16px;"> + Save Settings + </button> +</form> + +<div style="margin-top: 40px; padding: 20px; background-color: #f8f9fa; border-radius: 5px;"> + <h3>Current Configuration:</h3> + <ul> + <li><strong>Database:</strong> {{.Data.Config.DatabasePath}}</li> + <li><strong>Upload Directory:</strong> {{.Data.Config.UploadDir}}</li> + <li><strong>Server Port:</strong> {{.Data.Config.ServerPort}}</li> + </ul> + + <h4>Configuration File:</h4> + <p>Settings are stored in <code>config.json</code> in the application directory.</p> +</div> + +{{template "_footer"}} +\ No newline at end of file