From 339d55d92f82efaa37bfc60451150d409e7a10cd Mon Sep 17 00:00:00 2001 From: m Date: Tue, 15 Sep 2026 13:57:57 +0200 Subject: [PATCH] add version and translate all pages --- VERSION | 1 + backend/cmd/server/main.go | 9 +- backend/cmd/server/main_test.go | 18 +-- backend/config/config.go | 10 +- backend/config/config_test.go | 8 +- backend/handlers/auth_test.go | 2 +- backend/handlers/router.go | 8 -- backend/handlers/store.go | 2 +- backend/models/clients.go | 6 +- backend/models/documents.go | 10 +- backend/models/users.go | 6 +- backend/service/folders.go | 8 +- backend/service/move.go | 13 ++- backend/service/move_test.go | 34 ++++-- backend/service/resources.go | 6 +- backend/service/upload.go | 2 +- mise.toml | 26 +++++ .../mobile/ui/components/FileThumbnail.kt | 1 + .../app/src/main/res/values-en/strings.xml | 105 +++++++++++++++++- 19 files changed, 205 insertions(+), 70 deletions(-) create mode 100644 VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 8743b75..00becc3 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "os" "github.com/gin-gonic/gin" "github.com/vaultdrop/backend/config" @@ -14,6 +15,8 @@ import ( "github.com/vaultdrop/backend/service" ) +var version = "dev" + func newRouter() *gin.Engine { r := gin.Default() handlers.RegisterRoutes(r) @@ -21,8 +24,12 @@ func newRouter() *gin.Engine { } func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Printf("vaultdrop-server %s\n", version) + os.Exit(0) + } - err, cfg := config.LoadApplicationConfig() + cfg, err := config.LoadApplicationConfig() if err != nil { log.Fatalln(err) diff --git a/backend/cmd/server/main_test.go b/backend/cmd/server/main_test.go index d2caeb3..67d6fca 100644 --- a/backend/cmd/server/main_test.go +++ b/backend/cmd/server/main_test.go @@ -9,14 +9,14 @@ import ( func TestUserCreation(t *testing.T) { - err, userAntoine := models.NewUser("antoine") + userAntoine, err := models.NewUser("antoine") if err != nil { - t.Errorf(`Error creating antoine user %v`, err) + t.Fatalf("creating antoine user: %v", err) } - err, userBob := models.NewUser("bob") + userBob, err := models.NewUser("bob") if err != nil { - t.Errorf(`Error creating bob user %v`, err) + t.Fatalf("creating bob user: %v", err) } fmt.Println(userAntoine, userBob) @@ -25,16 +25,16 @@ func TestUserCreation(t *testing.T) { func TestCreateDocument(t *testing.T) { - err, document := models.NewDocument("paper.pdf", models.FILE, 1) + document, err := models.NewDocument("paper.pdf", models.FILE, 1) if err != nil { - t.Errorf(`Error creating document %v`, err) + t.Fatalf("creating document: %v", err) } - err, directory := models.NewDocument("bob", models.DIRECTORY, 1) + directory, err := models.NewDocument("bob", models.DIRECTORY, 1) if err != nil { - t.Errorf(`Error creating bob directory %v`, err) + t.Fatalf("creating bob directory: %v", err) } fmt.Println(document, directory) -} +} \ No newline at end of file diff --git a/backend/config/config.go b/backend/config/config.go index e3c78c9..8ba18ab 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -18,22 +18,22 @@ type ApplicationConfig struct { AdminPassword string } -func LoadApplicationConfig() (error, *ApplicationConfig) { +func LoadApplicationConfig() (*ApplicationConfig, error) { // .env optionnel — les défauts suffisent pour le dev local. _ = godotenv.Load() port, err := getInt("PORT", 8080) if err != nil { - return err, nil + return nil, err } maxSize, err := getInt("MAX_FILE_SIZE_MB", 50) if err != nil { - return err, nil + return nil, err } - return nil, &ApplicationConfig{ + return &ApplicationConfig{ Port: port, DatabaseURL: get("DATABASE_URL", "postgres://vaultdrop:vaultdrop@localhost:5432/vaultdrop_dev?sslmode=disable"), UploadDir: get("UPLOAD_DIR", "./uploads"), @@ -42,7 +42,7 @@ func LoadApplicationConfig() (error, *ApplicationConfig) { AuthSecret: get("AUTH_SECRET", "dev-secret-change-me"), AdminUsername: get("ADMIN_USERNAME", ""), AdminPassword: get("ADMIN_PASSWORD", ""), - } + }, nil } diff --git a/backend/config/config_test.go b/backend/config/config_test.go index 2e1c383..b765079 100644 --- a/backend/config/config_test.go +++ b/backend/config/config_test.go @@ -18,7 +18,7 @@ func clearEnv(t *testing.T) { func TestLoadApplicationDefaults(t *testing.T) { clearEnv(t) - err, cfg := LoadApplicationConfig() + cfg, err := LoadApplicationConfig() if err != nil { t.Fatalf("LoadApplicationConfig: %v", err) } @@ -56,7 +56,7 @@ func TestLoadApplicationEnvOverrides(t *testing.T) { t.Setenv("ADMIN_USERNAME", "root") t.Setenv("ADMIN_PASSWORD", "toor") - err, cfg := LoadApplicationConfig() + cfg, err := LoadApplicationConfig() if err != nil { t.Fatalf("LoadApplicationConfig: %v", err) } @@ -71,13 +71,13 @@ func TestLoadApplicationRejectsInvalidInt(t *testing.T) { clearEnv(t) t.Setenv("PORT", "not-a-number") - if err, _ := LoadApplicationConfig(); err == nil { + if _, err := LoadApplicationConfig(); err == nil { t.Error("PORT invalide : attendu une erreur") } clearEnv(t) t.Setenv("MAX_FILE_SIZE_MB", "99999999999999999999999") - if err, _ := LoadApplicationConfig(); err == nil { + if _, err := LoadApplicationConfig(); err == nil { t.Error("MAX_FILE_SIZE_MB invalide : attendu une erreur") } } diff --git a/backend/handlers/auth_test.go b/backend/handlers/auth_test.go index 7a03585..06d7871 100644 --- a/backend/handlers/auth_test.go +++ b/backend/handlers/auth_test.go @@ -311,7 +311,7 @@ func TestResolveUser(t *testing.T) { t.Errorf("resolve: %+v", env.Data) } // Jamais email ni is_admin - if len(rec.Body.Bytes()) < 0 || strings.Contains(rec.Body.String(), "is_admin") || strings.Contains(rec.Body.String(), "email") { + if len(rec.Body.Bytes()) == 0 || strings.Contains(rec.Body.String(), "is_admin") || strings.Contains(rec.Body.String(), "email") { t.Error("resolve ne doit pas exposer email/is_admin") } diff --git a/backend/handlers/router.go b/backend/handlers/router.go index 2b75b6a..a775aac 100644 --- a/backend/handlers/router.go +++ b/backend/handlers/router.go @@ -2,7 +2,6 @@ package handlers import ( "net/http" - "strings" "github.com/gin-gonic/gin" @@ -47,10 +46,3 @@ func RegisterRoutes(r *gin.Engine) { api.Error(c, http.StatusNotFound, "NOT_FOUND", "route not found") }) } - -// userID lit la clé de scoping posée par RequireAuth. Un empty string est -// impossible en conditions normales (le middleware l'a validé) ; garde-fou -// pour un appel direct dans les tests. -func userID(c *gin.Context) string { - return strings.TrimSpace(c.GetString(UserIDKey)) -} diff --git a/backend/handlers/store.go b/backend/handlers/store.go index 86f459a..56a1fd4 100644 --- a/backend/handlers/store.go +++ b/backend/handlers/store.go @@ -25,7 +25,7 @@ func writeError(c *gin.Context, err error) { api.Error(c, 404, "GRANTEE_NOT_FOUND", "the specified user does not exist") case errors.Is(err, repository.ErrNameConflict): api.Error(c, 409, "NAME_CONFLICT", "a resource with this name already exists here") - case errors.Is(err, service.FileTooLargeError): + case errors.Is(err, service.ErrFileTooLarge): api.Error(c, 413, "FILE_TOO_LARGE", "file exceeds the maximum allowed size") default: api.Error(c, 500, "INTERNAL", err.Error()) diff --git a/backend/models/clients.go b/backend/models/clients.go index 6e6b68a..3f81967 100644 --- a/backend/models/clients.go +++ b/backend/models/clients.go @@ -8,19 +8,19 @@ type Client interface { GetClientVersion() int } -func NewClient(clientVersion int) (error, Client) { +func NewClient(clientVersion int) (Client, error) { isCompatible := ServerClientIsCompatibleWithClient(clientVersion) if !isCompatible { - return fmt.Errorf("You client version %d is not compatible with the server verison %d", clientVersion, SERVER_VERSION), nil + return nil, fmt.Errorf("client version %d is not compatible with the server version %d", clientVersion, SERVER_VERSION) } client := ClientVerisonOne{ Version: clientVersion, } - return nil, &client + return &client, nil } diff --git a/backend/models/documents.go b/backend/models/documents.go index d3efc73..34e9c36 100644 --- a/backend/models/documents.go +++ b/backend/models/documents.go @@ -1,7 +1,7 @@ package models import ( - "fmt" + "errors" ) type Document struct { @@ -37,19 +37,19 @@ func IsDirectory(documentType int) bool { return DIRECTORY == documentType } -func NewDocument(documentName string, documentType int, documentVersion int) (error, *Document) { +func NewDocument(documentName string, documentType int, documentVersion int) (*Document, error) { documentTypeIsValid := IsDocumentTypeValid(documentType) if !documentTypeIsValid { - return fmt.Errorf(ERROR_DOCUMENT_TYPE), nil + return nil, errors.New(ERROR_DOCUMENT_TYPE) } - return nil, &Document{ + return &Document{ Name: &documentName, Type: documentType, Version: documentVersion, - } + }, nil } diff --git a/backend/models/users.go b/backend/models/users.go index bcab8ff..cc0bf11 100644 --- a/backend/models/users.go +++ b/backend/models/users.go @@ -4,8 +4,8 @@ type User struct { Username string } -func NewUser(username string) (error, *User) { - return nil, &User{ +func NewUser(username string) (*User, error) { + return &User{ Username: username, - } + }, nil } diff --git a/backend/service/folders.go b/backend/service/folders.go index 367ebfe..85b9495 100644 --- a/backend/service/folders.go +++ b/backend/service/folders.go @@ -2,13 +2,13 @@ package service import "github.com/vaultdrop/backend/models" -func (s *Services) CreateFolder(folderName string, destination *models.Document) (error, *models.Document) { +func (s *Services) CreateFolder(folderName string, destination *models.Document) (*models.Document, error) { - err, directory := models.NewDocument(folderName, models.DIRECTORY, 1) + directory, err := models.NewDocument(folderName, models.DIRECTORY, 1) if err != nil { - return err, nil + return nil, err } - return nil, directory + return directory, nil } diff --git a/backend/service/move.go b/backend/service/move.go index 0e9ec85..682a517 100644 --- a/backend/service/move.go +++ b/backend/service/move.go @@ -1,6 +1,7 @@ package service import ( + "errors" "fmt" "github.com/vaultdrop/backend/models" @@ -9,19 +10,19 @@ import ( func (s *Services) MoveDocumentIntoDocument(from *models.Document, to *models.Document) error { if from.UUID == nil { - return fmt.Errorf(models.ERROR_DOCUMENT_NOT_PERCISTED) + return errors.New(models.ERROR_DOCUMENT_NOT_PERCISTED) } if to.Type != models.DIRECTORY { - return fmt.Errorf("The destination document must be a directory") + return errors.New("the destination document must be a directory") } - if canEdit, _ := UserCanEditDocument(s.ConnectedUser, from); canEdit == false { - return fmt.Errorf("You can not edit this folder") + if canEdit, _ := UserCanEditDocument(s.ConnectedUser, from); !canEdit { + return errors.New("you cannot edit this folder") } - if canEdit, _ := UserCanEditDocument(s.ConnectedUser, to); canEdit == false { - return fmt.Errorf("You can not edit this folder") + if canEdit, _ := UserCanEditDocument(s.ConnectedUser, to); !canEdit { + return errors.New("you cannot edit this folder") } // Check if document exist and move the document into document if is directory diff --git a/backend/service/move_test.go b/backend/service/move_test.go index da137fe..c678e06 100644 --- a/backend/service/move_test.go +++ b/backend/service/move_test.go @@ -8,23 +8,27 @@ import ( func TestMoveDocument(t *testing.T) { - err, userAntoine := models.NewUser("antoine") + userAntoine, err := models.NewUser("antoine") if err != nil { - t.Errorf(`Error creating antoine user %v`, err) + t.Fatalf("creating antoine user: %v", err) } fakeUUID := "dsqdsq" - err, document := models.NewDocument("test.pdf", models.FILE, 1) + document, err := models.NewDocument("test.pdf", models.FILE, 1) + if err != nil { + t.Fatalf("creating document: %v", err) + } document.UUID = &fakeUUID - err, folder := models.NewDocument("orga", models.DIRECTORY, 1) + folder, err := models.NewDocument("orga", models.DIRECTORY, 1) + if err != nil { + t.Fatalf("creating folder: %v", err) + } folder.UUID = &fakeUUID service := New(userAntoine) - err = service.MoveDocumentIntoDocument(document, folder) - - if err != nil { + if err := service.MoveDocumentIntoDocument(document, folder); err != nil { t.Error(err) } @@ -32,14 +36,20 @@ func TestMoveDocument(t *testing.T) { func TestMoveDocumentToAFile(t *testing.T) { - err, userAntoine := models.NewUser("antoine") + userAntoine, err := models.NewUser("antoine") if err != nil { - t.Errorf(`Error creating antoine user %v`, err) + t.Fatalf("creating antoine user: %v", err) } - err, document := models.NewDocument("test.pdf", models.FILE, 1) + document, err := models.NewDocument("test.pdf", models.FILE, 1) + if err != nil { + t.Fatalf("creating document: %v", err) + } - err, notAFolder := models.NewDocument("orga", models.FILE, 1) + notAFolder, err := models.NewDocument("orga", models.FILE, 1) + if err != nil { + t.Fatalf("creating file: %v", err) + } service := New(userAntoine) @@ -49,4 +59,4 @@ func TestMoveDocumentToAFile(t *testing.T) { t.Error(err) } -} +} \ No newline at end of file diff --git a/backend/service/resources.go b/backend/service/resources.go index 430e475..60039ef 100644 --- a/backend/service/resources.go +++ b/backend/service/resources.go @@ -13,8 +13,8 @@ import ( "github.com/vaultdrop/backend/repository" ) -// FileTooLargeError signals an upload above MaxFileSize. -var FileTooLargeError = errors.New("file too large") +// ErrFileTooLarge signals an upload above MaxFileSize. +var ErrFileTooLarge = errors.New("file too large") // FileDTO serializes exactly as mobile/api/types.ts FileDto. type FileDTO struct { @@ -106,7 +106,7 @@ func (s *Resources) SearchFiles(ownerID, q string, page, pageSize int) ([]FileDT // if metadata persistence fails (e.g. name conflict). func (s *Resources) Upload(ownerID string, file *multipart.FileHeader, folderID string) (FileDTO, error) { if file.Size > s.MaxFileSize { - return FileDTO{}, FileTooLargeError + return FileDTO{}, ErrFileTooLarge } id := repository.NewID() diff --git a/backend/service/upload.go b/backend/service/upload.go index ae3af36..c7fd0e2 100644 --- a/backend/service/upload.go +++ b/backend/service/upload.go @@ -9,7 +9,7 @@ import ( func (s *Services) UploadDocument(file *string, destination *models.Document) error { if canEdit, _ := UserCanEditDocument(s.ConnectedUser, destination); !canEdit { - return fmt.Errorf("Can not edit") + return fmt.Errorf("cannot edit document") } models.NewDocument("test", models.FILE, 1) diff --git a/mise.toml b/mise.toml index c01c758..ad618be 100644 --- a/mise.toml +++ b/mise.toml @@ -25,4 +25,30 @@ if [ "${usage_target?}" = "emulator" ]; then else adb -d install -r "$APK" fi +''' + +[tasks.test] +description = "Run all tests (Go + Kotlin)" +run = ''' +set -euo pipefail +cd backend && go test ./... +cd ../mobile-kotlin && ./gradlew :app:testDebugUnitTest -q +''' + +[tasks.build] +description = "Build all artifacts (Go binary + Android APK)" +run = ''' +set -euo pipefail +VERSION=$(cat VERSION) +mkdir -p backend/bin +cd backend && go build -ldflags "-X main.version=$VERSION" -o bin/vaultdrop-server cmd/server/main.go +cd ../mobile-kotlin && ./gradlew :app:assembleDebug -q +''' + +[tasks.lint] +description = "Lint all code (staticcheck + Android Lint)" +run = ''' +set -euo pipefail +cd backend && staticcheck ./... +cd ../mobile-kotlin && ./gradlew :app:lint -q ''' \ No newline at end of file diff --git a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/components/FileThumbnail.kt b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/components/FileThumbnail.kt index 86e8d9c..a04d1ee 100644 --- a/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/components/FileThumbnail.kt +++ b/mobile-kotlin/app/src/main/java/com/vaultdrop/mobile/ui/components/FileThumbnail.kt @@ -51,6 +51,7 @@ fun FileThumbnail( val context = LocalContext.current val thumbnailStore = rememberThumbnailStore(context) + @Suppress("ProduceStateDoesNotAssignValue") val thumb by produceState(null, file.resourceId, file.lastModified) { value = withContext(Dispatchers.IO) { thumbnailStore.ensure(file) } } diff --git a/mobile-kotlin/app/src/main/res/values-en/strings.xml b/mobile-kotlin/app/src/main/res/values-en/strings.xml index 0d23980..59a744d 100644 --- a/mobile-kotlin/app/src/main/res/values-en/strings.xml +++ b/mobile-kotlin/app/src/main/res/values-en/strings.xml @@ -1,19 +1,37 @@ VaultDrop + + Choose your VaultDrop folder + VaultDrop needs a folder to store all the PDFs it creates. You can also create a new VaultDrop folder right in the picker. This step is required, once only. + Choose my VaultDrop folder + VaultDrop Files Folder Files Folders Dashboard coming soon + %1$d document(s) to review + Review them in swipe mode: keep (and sync) or delete. + All up to date — nothing to review. + + + New documents + %1$d remaining + KEEP + keep + sync + DELETE + All documents have been reviewed. + Finish + Mark all as reviewed + Mark all as reviewed? + The remaining documents will be marked as reviewed and synced to the cloud without being checked. + Could not load the documents to review. + Could not delete this document — permission denied. Up Move here (%1$d) Move %1$d file(s)… File(s) moved - Choose your VaultDrop folder - VaultDrop needs a folder to store all the PDFs it creates. You can also create a new VaultDrop folder right in the picker. This step is required, once only. - Choose my VaultDrop folder - VaultDrop Add a folder Create a folder Folder name @@ -40,6 +58,7 @@ No folders yet. Tap "Add a folder" to sync a folder. No files yet. Tap "Add a folder" to sync your files. Empty folder — next sync will refresh it. + To review B KB MB @@ -117,6 +136,33 @@ Continue without account Local mode — your folders stay on this device and are not synced. + + Online + Offline + Checking… + Local mode + Server status — tap to check + + + Syncing… + %1$d pending + 1 pending + Up to date + Sync — tap to view the operations list + Sync list + No operations yet. + Close + %1$d operations + %1$d pending + %1$d failed + Pending + Synced + Failed + Create resource + Move + Delete + Update + %1$d selected Assemble PDF @@ -153,8 +199,44 @@ e.g. january_invoices Save PDF created + Preview + Close preview Generated PDFs + + Scan + Capture + %1$d page(s) + View pages + Frame the document, then capture. + Allow camera access to scan documents. + Allow camera + Crop + Confirm page + Retake + Color + B&W + High-contrast B&W + Add a page + Scanned pages + Delete + Move up + Move down + No pages yet.\nCapture your first page. + Export to the VaultDrop folder + Save as PDF + Exporting… + Abandon + Abandon the scan? + Captured pages will be deleted. + Confirm + Cancel + Scan session unavailable. + Could not use this capture. + Could not save the page. + Could not export the pages. + Pages exported + Delete Delete locally @@ -168,4 +250,19 @@ Confirm Cancel Error while deleting + + + Share with a user + Recipient\'s username + Access level + View (viewer) + Comment (commenter) + Edit (editor) + Share + Cancel + No user found with this username. + Could not share right now. + Share this folder + Share + More actions \ No newline at end of file