add gitlab ci

This commit is contained in:
m
2026-09-16 14:47:10 +02:00
parent 906d48702c
commit cd4f0179fc
32 changed files with 1462 additions and 40 deletions
+23
View File
@@ -59,6 +59,29 @@ func FilesGet(c *gin.Context) {
api.OK(c, file)
}
// FilesGetOcr expose l'extrait OCR server-side d'un fichier visible (viewer+,
// own ou shared) : GET /files/:id/ocr. Réponse { text } ("" si pas encore
// extrait) + updatedAt. Permet à un second device de récupérer le texte déjà
// calculé sans re-téléverser les octets (cf. docs/api-v1.md §5).
func FilesGetOcr(c *gin.Context) {
if Store == nil {
api.Error(c, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "backend not initialized")
return
}
userID := c.GetString(UserIDKey)
id := c.Param("id")
if !deviceIDPattern.MatchString(id) {
api.Error(c, http.StatusNotFound, "NOT_FOUND", "file not found")
return
}
dto, err := Store.GetFileOcr(userID, id)
if err != nil {
writeError(c, err)
return
}
api.OK(c, dto)
}
func FilesDelete(c *gin.Context) {
if Store == nil {
api.Error(c, http.StatusServiceUnavailable, "SERVICE_UNAVAILABLE", "backend not initialized")
+56
View File
@@ -39,6 +39,11 @@ type fileDTO struct {
CreatedAt string `json:"createdAt"`
}
type fileOcrDTO struct {
Text string `json:"text"`
UpdatedAt string `json:"updatedAt"`
}
type apiError struct {
Error struct {
Code string `json:"code"`
@@ -577,3 +582,54 @@ func TestFilesGetDeleteRejectInvalidID(t *testing.T) {
rec, _ = doRequest(t, r, http.MethodGet, "/api/v1/files/"+fileID, otherToken, nil, "")
expectError(t, rec, http.StatusNotFound, "NOT_FOUND", "get-cross-user")
}
func TestFilesGetOcrOwnerShareAndScoping(t *testing.T) {
r, _, repo := setup(t)
ownerDevice := repository.NewID()
ownerToken, owner := registerAndLogin(t, r, repo, testUserUsername(ownerDevice, "oc"), "ocr-get-test-password", ownerDevice)
granteeDevice := repository.NewID()
granteeToken, grantee := registerAndLogin(t, r, repo, testUserUsername(granteeDevice, "og"), "ocr-get-test-password-b", granteeDevice)
outsiderDevice := repository.NewID()
outsiderToken, _ := registerAndLogin(t, r, repo, testUserUsername(outsiderDevice, "oh"), "ocr-get-test-password-c", outsiderDevice)
fileID := repository.NewID()
if err := repo.Resources.InsertFile(owner, fileID, "scan.png", "", 128, nil, nil); err != nil {
t.Fatalf("insert file: %v", err)
}
if err := repo.Resources.UpdateOcrText(owner, fileID, "extrait du scan"); err != nil {
t.Fatalf("write ocr_text: %v", err)
}
// Owner : texte + updatedAt.
rec, _ := doRequest(t, r, http.MethodGet, "/api/v1/files/"+fileID+"/ocr", ownerToken, nil, "")
envOcr := expectOK(t, rec, "ocr-owner")
var ownerOcr fileOcrDTO
if err := json.Unmarshal(envOcr.Data, &ownerOcr); err != nil {
t.Fatalf("ocr-owner: unmarshal: %v", err)
}
if ownerOcr.Text != "extrait du scan" || ownerOcr.UpdatedAt == "" {
t.Errorf("ocr owner = %+v, attendu texte+updatedAt", ownerOcr)
}
// Grantée viewer+ : même texte (partage rend l'OCR lisible).
if err := repo.Shares.Upsert(fileID, grantee, "viewer", false, nil, owner); err != nil {
t.Fatalf("share: %v", err)
}
rec, _ = doRequest(t, r, http.MethodGet, "/api/v1/files/"+fileID+"/ocr", granteeToken, nil, "")
envOcr = expectOK(t, rec, "ocr-grantee")
var granteeOcr fileOcrDTO
if err := json.Unmarshal(envOcr.Data, &granteeOcr); err != nil {
t.Fatalf("ocr-grantee: unmarshal: %v", err)
}
if granteeOcr.Text != "extrait du scan" {
t.Errorf("ocr grantee = %+v, attendu texte partagé", granteeOcr)
}
// Outsider : invisible (404).
rec, _ = doRequest(t, r, http.MethodGet, "/api/v1/files/"+fileID+"/ocr", outsiderToken, nil, "")
expectError(t, rec, http.StatusNotFound, "NOT_FOUND", "ocr-outsider")
// ID invalide → 404.
rec, _ = doRequest(t, r, http.MethodGet, "/api/v1/files/not-hex/ocr", ownerToken, nil, "")
expectError(t, rec, http.StatusNotFound, "NOT_FOUND", "ocr-invalid-id")
}
+1
View File
@@ -27,6 +27,7 @@ func RegisterRoutes(r *gin.Engine) {
protected.GET("/files", FilesList)
protected.GET("/files/search", FilesSearch)
protected.GET("/files/:id", FilesGet)
protected.GET("/files/:id/ocr", FilesGetOcr)
protected.DELETE("/files/:id", FilesDelete)
protected.GET("/files/folders", FoldersList)
protected.POST("/files/upload", FilesUpload)