feat(api): GET /files/search (ILIKE substring insensible casse, wildcards échappés, scope owner, q obligatoire)

This commit is contained in:
m
2026-09-10 07:45:53 +02:00
parent 14c058fddb
commit 37ad2d4275
5 changed files with 118 additions and 2 deletions
+24 -1
View File
@@ -4,6 +4,7 @@ import (
"errors"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/vaultdrop/backend/pkg/api"
@@ -123,4 +124,26 @@ func FoldersList(c *gin.Context) {
api.OK(c, folders)
}
func FilesSearch(c *gin.Context) { api.NotImplemented(c) }
func FilesSearch(c *gin.Context) {
if Store == nil {
api.Error(c, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "backend not initialized")
return
}
deviceID := c.GetString(DeviceIDKey)
q := strings.TrimSpace(c.Query("q"))
if q == "" {
api.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "missing required query param `q`")
return
}
page := intParam(c.Query("page"), 1)
pageSize := intParam(c.Query("pageSize"), 50)
if pageSize > 200 {
pageSize = 200
}
files, total, err := Store.SearchFiles(deviceID, q, page, pageSize)
if err != nil {
writeError(c, err)
return
}
api.OKList(c, files, page, pageSize, total)
}
+45
View File
@@ -242,6 +242,51 @@ func TestFilesFlow(t *testing.T) {
expectError(t, rec, http.StatusNotFound, "NOT_FOUND", "get-after-delete")
}
func TestSearchFiles(t *testing.T) {
r, _, repo := setup(t)
device := repository.NewID()
token := registerDevice(t, r, device)
if err := repo.Resources.InsertFile(device, repository.NewID(), "vacances-août.jpg", "", 100, nil, nil); err != nil {
t.Fatalf("insert: %v", err)
}
if err := repo.Resources.InsertFile(device, repository.NewID(), "rapport-q3.pdf", "", 100, nil, nil); err != nil {
t.Fatalf("insert: %v", err)
}
if err := repo.Resources.InsertFile(device, repository.NewID(), "toto.txt", "", 100, nil, nil); err != nil {
t.Fatalf("insert: %v", err)
}
// q obligatoire
rec, _ := doRequest(t, r, http.MethodGet, "/api/v1/files/search", token, nil, "")
expectError(t, rec, http.StatusBadRequest, "INVALID_REQUEST", "search-no-q")
// insensible à la casse + sous-chaîne
rec, _ = doRequest(t, r, http.MethodGet, "/api/v1/files/search?q=APORT", token, nil, "")
env := expectOK(t, rec, "search")
var files []fileDTO
if err := json.Unmarshal(env.Data, &files); err != nil {
t.Fatalf("search: unmarshal: %v", err)
}
if len(files) != 1 || files[0].Name != "rapport-q3.pdf" {
t.Errorf("search 'APORT': %+v", files)
}
if env.Meta == nil || env.Meta.Total != 1 {
t.Errorf("meta search: %+v", env.Meta)
}
// wildcards neutralisés (trouve que "toto", pas tous les fichiers)
rec, _ = doRequest(t, r, http.MethodGet, "/api/v1/files/search?q=%25", token, nil, "")
env = expectOK(t, rec, "search-escaped")
files = nil
if err := json.Unmarshal(env.Data, &files); err != nil {
t.Fatalf("search-escaped: unmarshal: %v", err)
}
if len(files) != 0 {
t.Errorf("q=%% doit ne rien matcher, got %+v", files)
}
}
func TestUploadTooLarge(t *testing.T) {
r, _, _ := setup(t)
device := repository.NewID()