commit 40d7b0a18047e7c294229d172ca0f9f59a0dc198
parent dd3f7e7ff47fa8ad30cc19115bafc3b738d2bedf
Author: breadcat <breadcat@users.noreply.github.com>
Date: Sun, 21 Sep 2025 11:04:07 +0100
Rudimentary search feature
Diffstat:
3 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/main.go b/main.go
@@ -92,12 +92,54 @@ func main() {
http.HandleFunc("/tags", tagsHandler)
http.HandleFunc("/tag/", tagFilterHandler)
http.HandleFunc("/untagged", untaggedFilesHandler)
+ http.HandleFunc("/search", searchHandler)
http.Handle("/uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir("uploads"))))
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
log.Println("Server started at http://localhost:8080")
http.ListenAndServe(":8080", nil)
+func searchHandler(w http.ResponseWriter, r *http.Request) {
+ query := strings.TrimSpace(r.URL.Query().Get("q"))
+
+ var files []File
+ var searchTitle string
+
+ if query != "" {
+ // Convert wildcards to SQL LIKE pattern
+ // * becomes % and ? becomes _ (standard SQL wildcards)
+ sqlPattern := strings.ReplaceAll(query, "*", "%")
+ sqlPattern = strings.ReplaceAll(sqlPattern, "?", "_")
+
+ // Search for files matching the pattern
+ rows, err := db.Query("SELECT id, filename, path FROM files WHERE filename LIKE ? ORDER BY filename", sqlPattern)
+ if err != nil {
+ http.Error(w, "Search failed", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var f File
+ rows.Scan(&f.ID, &f.Filename, &f.Path)
+ files = append(files, f)
+ }
+
+ searchTitle = fmt.Sprintf("Search Results for: %s", query)
+ } else {
+ searchTitle = "Search Files"
+ }
+
+ // Always initialize the data structure properly
+ pageData := PageData{
+ Title: searchTitle,
+ Data: struct {
+ Files []File
+ Query string
+ }{files, query},
+ }
+
+ tmpl.ExecuteTemplate(w, "search.html", pageData)
}
// Upload file from URL
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></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></p>
{{end}}
\ No newline at end of file
diff --git a/templates/search.html b/templates/search.html
@@ -0,0 +1,37 @@
+{{template "_header" .}}
+<h1>Search Files</h1>
+
+<form method="get" action="/search" style="margin-bottom: 30px;">
+ <input type="text" name="q" value="{{.Data.Query}}" placeholder="Search filenames (use * for wildcards)" style="width: 400px; padding: 8px; font-size: 16px;">
+ <button type="submit" style="padding: 8px 16px; font-size: 16px;">Search</button>
+</form>
+
+<div style="background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px;">
+ <h3>Wildcard Examples:</h3>
+ <ul>
+ <li><code>*class*.jpg</code> - finds files containing "class" and ending with ".jpg"</li>
+ <li><code>photo*</code> - finds files starting with "photo"</li>
+ <li><code>*.pdf</code> - finds all PDF files</li>
+ <li><code>IMG_????_*.jpg</code> - finds files like "IMG_2023_vacation.jpg" (? matches single character)</li>
+ <li><code>*vacation*</code> - finds files containing "vacation" anywhere</li>
+ </ul>
+</div>
+
+{{if .Data.Files}}
+ <h2>Found {{len .Data.Files}} file{{if ne (len .Data.Files) 1}}s{{end}}</h2>
+ <div class="file-grid">
+ {{range .Data.Files}}
+ <div class="file-item">
+ <a href="/file/{{.ID}}">{{.Filename}}</a>
+ {{if hasAnySuffix .Filename ".jpg" ".jpeg" ".png" ".gif" ".webp"}}
+ <br><img src="/uploads/{{.Filename}}" style="max-width: 100%; height: auto;" />
+ {{end}}
+ </div>
+ {{end}}
+ </div>
+{{else if .Data.Query}}
+ <p>No files found matching "<strong>{{.Data.Query}}</strong>"</p>
+ <p>Try using wildcards like <code>*{{.Data.Query}}*</code> for broader results.</p>
+{{end}}
+
+{{template "_footer"}}
+\ No newline at end of file