add version and translate all pages
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/vaultdrop/backend/config"
|
"github.com/vaultdrop/backend/config"
|
||||||
@@ -14,6 +15,8 @@ import (
|
|||||||
"github.com/vaultdrop/backend/service"
|
"github.com/vaultdrop/backend/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
func newRouter() *gin.Engine {
|
func newRouter() *gin.Engine {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
handlers.RegisterRoutes(r)
|
handlers.RegisterRoutes(r)
|
||||||
@@ -21,8 +24,12 @@ func newRouter() *gin.Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
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 {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import (
|
|||||||
|
|
||||||
func TestUserCreation(t *testing.T) {
|
func TestUserCreation(t *testing.T) {
|
||||||
|
|
||||||
err, userAntoine := models.NewUser("antoine")
|
userAntoine, err := models.NewUser("antoine")
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
t.Errorf(`Error creating bob user %v`, err)
|
t.Fatalf("creating bob user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(userAntoine, userBob)
|
fmt.Println(userAntoine, userBob)
|
||||||
@@ -25,16 +25,16 @@ func TestUserCreation(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateDocument(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 {
|
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 {
|
if err != nil {
|
||||||
t.Errorf(`Error creating bob directory %v`, err)
|
t.Fatalf("creating bob directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(document, directory)
|
fmt.Println(document, directory)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -18,22 +18,22 @@ type ApplicationConfig struct {
|
|||||||
AdminPassword string
|
AdminPassword string
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadApplicationConfig() (error, *ApplicationConfig) {
|
func LoadApplicationConfig() (*ApplicationConfig, error) {
|
||||||
|
|
||||||
// .env optionnel — les défauts suffisent pour le dev local.
|
// .env optionnel — les défauts suffisent pour le dev local.
|
||||||
_ = godotenv.Load()
|
_ = godotenv.Load()
|
||||||
|
|
||||||
port, err := getInt("PORT", 8080)
|
port, err := getInt("PORT", 8080)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
maxSize, err := getInt("MAX_FILE_SIZE_MB", 50)
|
maxSize, err := getInt("MAX_FILE_SIZE_MB", 50)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, &ApplicationConfig{
|
return &ApplicationConfig{
|
||||||
Port: port,
|
Port: port,
|
||||||
DatabaseURL: get("DATABASE_URL", "postgres://vaultdrop:vaultdrop@localhost:5432/vaultdrop_dev?sslmode=disable"),
|
DatabaseURL: get("DATABASE_URL", "postgres://vaultdrop:vaultdrop@localhost:5432/vaultdrop_dev?sslmode=disable"),
|
||||||
UploadDir: get("UPLOAD_DIR", "./uploads"),
|
UploadDir: get("UPLOAD_DIR", "./uploads"),
|
||||||
@@ -42,7 +42,7 @@ func LoadApplicationConfig() (error, *ApplicationConfig) {
|
|||||||
AuthSecret: get("AUTH_SECRET", "dev-secret-change-me"),
|
AuthSecret: get("AUTH_SECRET", "dev-secret-change-me"),
|
||||||
AdminUsername: get("ADMIN_USERNAME", ""),
|
AdminUsername: get("ADMIN_USERNAME", ""),
|
||||||
AdminPassword: get("ADMIN_PASSWORD", ""),
|
AdminPassword: get("ADMIN_PASSWORD", ""),
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func clearEnv(t *testing.T) {
|
|||||||
func TestLoadApplicationDefaults(t *testing.T) {
|
func TestLoadApplicationDefaults(t *testing.T) {
|
||||||
clearEnv(t)
|
clearEnv(t)
|
||||||
|
|
||||||
err, cfg := LoadApplicationConfig()
|
cfg, err := LoadApplicationConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("LoadApplicationConfig: %v", err)
|
t.Fatalf("LoadApplicationConfig: %v", err)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ func TestLoadApplicationEnvOverrides(t *testing.T) {
|
|||||||
t.Setenv("ADMIN_USERNAME", "root")
|
t.Setenv("ADMIN_USERNAME", "root")
|
||||||
t.Setenv("ADMIN_PASSWORD", "toor")
|
t.Setenv("ADMIN_PASSWORD", "toor")
|
||||||
|
|
||||||
err, cfg := LoadApplicationConfig()
|
cfg, err := LoadApplicationConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("LoadApplicationConfig: %v", err)
|
t.Fatalf("LoadApplicationConfig: %v", err)
|
||||||
}
|
}
|
||||||
@@ -71,13 +71,13 @@ func TestLoadApplicationRejectsInvalidInt(t *testing.T) {
|
|||||||
clearEnv(t)
|
clearEnv(t)
|
||||||
|
|
||||||
t.Setenv("PORT", "not-a-number")
|
t.Setenv("PORT", "not-a-number")
|
||||||
if err, _ := LoadApplicationConfig(); err == nil {
|
if _, err := LoadApplicationConfig(); err == nil {
|
||||||
t.Error("PORT invalide : attendu une erreur")
|
t.Error("PORT invalide : attendu une erreur")
|
||||||
}
|
}
|
||||||
|
|
||||||
clearEnv(t)
|
clearEnv(t)
|
||||||
t.Setenv("MAX_FILE_SIZE_MB", "99999999999999999999999")
|
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")
|
t.Error("MAX_FILE_SIZE_MB invalide : attendu une erreur")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ func TestResolveUser(t *testing.T) {
|
|||||||
t.Errorf("resolve: %+v", env.Data)
|
t.Errorf("resolve: %+v", env.Data)
|
||||||
}
|
}
|
||||||
// Jamais email ni is_admin
|
// 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")
|
t.Error("resolve ne doit pas exposer email/is_admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@@ -47,10 +46,3 @@ func RegisterRoutes(r *gin.Engine) {
|
|||||||
api.Error(c, http.StatusNotFound, "NOT_FOUND", "route not found")
|
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))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func writeError(c *gin.Context, err error) {
|
|||||||
api.Error(c, 404, "GRANTEE_NOT_FOUND", "the specified user does not exist")
|
api.Error(c, 404, "GRANTEE_NOT_FOUND", "the specified user does not exist")
|
||||||
case errors.Is(err, repository.ErrNameConflict):
|
case errors.Is(err, repository.ErrNameConflict):
|
||||||
api.Error(c, 409, "NAME_CONFLICT", "a resource with this name already exists here")
|
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")
|
api.Error(c, 413, "FILE_TOO_LARGE", "file exceeds the maximum allowed size")
|
||||||
default:
|
default:
|
||||||
api.Error(c, 500, "INTERNAL", err.Error())
|
api.Error(c, 500, "INTERNAL", err.Error())
|
||||||
|
|||||||
@@ -8,19 +8,19 @@ type Client interface {
|
|||||||
GetClientVersion() int
|
GetClientVersion() int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(clientVersion int) (error, Client) {
|
func NewClient(clientVersion int) (Client, error) {
|
||||||
|
|
||||||
isCompatible := ServerClientIsCompatibleWithClient(clientVersion)
|
isCompatible := ServerClientIsCompatibleWithClient(clientVersion)
|
||||||
|
|
||||||
if !isCompatible {
|
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{
|
client := ClientVerisonOne{
|
||||||
Version: clientVersion,
|
Version: clientVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, &client
|
return &client, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Document struct {
|
type Document struct {
|
||||||
@@ -37,19 +37,19 @@ func IsDirectory(documentType int) bool {
|
|||||||
return DIRECTORY == documentType
|
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)
|
documentTypeIsValid := IsDocumentTypeValid(documentType)
|
||||||
|
|
||||||
if !documentTypeIsValid {
|
if !documentTypeIsValid {
|
||||||
return fmt.Errorf(ERROR_DOCUMENT_TYPE), nil
|
return nil, errors.New(ERROR_DOCUMENT_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, &Document{
|
return &Document{
|
||||||
Name: &documentName,
|
Name: &documentName,
|
||||||
Type: documentType,
|
Type: documentType,
|
||||||
Version: documentVersion,
|
Version: documentVersion,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ type User struct {
|
|||||||
Username string
|
Username string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(username string) (error, *User) {
|
func NewUser(username string) (*User, error) {
|
||||||
return nil, &User{
|
return &User{
|
||||||
Username: username,
|
Username: username,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package service
|
|||||||
|
|
||||||
import "github.com/vaultdrop/backend/models"
|
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 {
|
if err != nil {
|
||||||
return err, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, directory
|
return directory, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/vaultdrop/backend/models"
|
"github.com/vaultdrop/backend/models"
|
||||||
@@ -9,19 +10,19 @@ import (
|
|||||||
func (s *Services) MoveDocumentIntoDocument(from *models.Document, to *models.Document) error {
|
func (s *Services) MoveDocumentIntoDocument(from *models.Document, to *models.Document) error {
|
||||||
|
|
||||||
if from.UUID == nil {
|
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 {
|
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 {
|
if canEdit, _ := UserCanEditDocument(s.ConnectedUser, from); !canEdit {
|
||||||
return fmt.Errorf("You can not edit this folder")
|
return errors.New("you cannot edit this folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
if canEdit, _ := UserCanEditDocument(s.ConnectedUser, to); canEdit == false {
|
if canEdit, _ := UserCanEditDocument(s.ConnectedUser, to); !canEdit {
|
||||||
return fmt.Errorf("You can not edit this folder")
|
return errors.New("you cannot edit this folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if document exist and move the document into document if is directory
|
// Check if document exist and move the document into document if is directory
|
||||||
|
|||||||
@@ -8,23 +8,27 @@ import (
|
|||||||
|
|
||||||
func TestMoveDocument(t *testing.T) {
|
func TestMoveDocument(t *testing.T) {
|
||||||
|
|
||||||
err, userAntoine := models.NewUser("antoine")
|
userAntoine, err := models.NewUser("antoine")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf(`Error creating antoine user %v`, err)
|
t.Fatalf("creating antoine user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeUUID := "dsqdsq"
|
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
|
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
|
folder.UUID = &fakeUUID
|
||||||
|
|
||||||
service := New(userAntoine)
|
service := New(userAntoine)
|
||||||
|
|
||||||
err = service.MoveDocumentIntoDocument(document, folder)
|
if err := service.MoveDocumentIntoDocument(document, folder); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,14 +36,20 @@ func TestMoveDocument(t *testing.T) {
|
|||||||
|
|
||||||
func TestMoveDocumentToAFile(t *testing.T) {
|
func TestMoveDocumentToAFile(t *testing.T) {
|
||||||
|
|
||||||
err, userAntoine := models.NewUser("antoine")
|
userAntoine, err := models.NewUser("antoine")
|
||||||
if err != nil {
|
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)
|
service := New(userAntoine)
|
||||||
|
|
||||||
@@ -49,4 +59,4 @@ func TestMoveDocumentToAFile(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"github.com/vaultdrop/backend/repository"
|
"github.com/vaultdrop/backend/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileTooLargeError signals an upload above MaxFileSize.
|
// ErrFileTooLarge signals an upload above MaxFileSize.
|
||||||
var FileTooLargeError = errors.New("file too large")
|
var ErrFileTooLarge = errors.New("file too large")
|
||||||
|
|
||||||
// FileDTO serializes exactly as mobile/api/types.ts FileDto.
|
// FileDTO serializes exactly as mobile/api/types.ts FileDto.
|
||||||
type FileDTO struct {
|
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).
|
// if metadata persistence fails (e.g. name conflict).
|
||||||
func (s *Resources) Upload(ownerID string, file *multipart.FileHeader, folderID string) (FileDTO, error) {
|
func (s *Resources) Upload(ownerID string, file *multipart.FileHeader, folderID string) (FileDTO, error) {
|
||||||
if file.Size > s.MaxFileSize {
|
if file.Size > s.MaxFileSize {
|
||||||
return FileDTO{}, FileTooLargeError
|
return FileDTO{}, ErrFileTooLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
id := repository.NewID()
|
id := repository.NewID()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
func (s *Services) UploadDocument(file *string, destination *models.Document) error {
|
func (s *Services) UploadDocument(file *string, destination *models.Document) error {
|
||||||
|
|
||||||
if canEdit, _ := UserCanEditDocument(s.ConnectedUser, destination); !canEdit {
|
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)
|
models.NewDocument("test", models.FILE, 1)
|
||||||
|
|||||||
@@ -25,4 +25,30 @@ if [ "${usage_target?}" = "emulator" ]; then
|
|||||||
else
|
else
|
||||||
adb -d install -r "$APK"
|
adb -d install -r "$APK"
|
||||||
fi
|
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
|
||||||
'''
|
'''
|
||||||
@@ -51,6 +51,7 @@ fun FileThumbnail(
|
|||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val thumbnailStore = rememberThumbnailStore(context)
|
val thumbnailStore = rememberThumbnailStore(context)
|
||||||
|
@Suppress("ProduceStateDoesNotAssignValue")
|
||||||
val thumb by produceState<File?>(null, file.resourceId, file.lastModified) {
|
val thumb by produceState<File?>(null, file.resourceId, file.lastModified) {
|
||||||
value = withContext(Dispatchers.IO) { thumbnailStore.ensure(file) }
|
value = withContext(Dispatchers.IO) { thumbnailStore.ensure(file) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">VaultDrop</string>
|
<string name="app_name">VaultDrop</string>
|
||||||
|
|
||||||
|
<!-- Files screen -->
|
||||||
|
<string name="default_root_title">Choose your VaultDrop folder</string>
|
||||||
|
<string name="default_root_message">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.</string>
|
||||||
|
<string name="default_root_pick">Choose my VaultDrop folder</string>
|
||||||
|
<string name="default_root_folder_label">VaultDrop</string>
|
||||||
<string name="files">Files</string>
|
<string name="files">Files</string>
|
||||||
<string name="folder">Folder</string>
|
<string name="folder">Folder</string>
|
||||||
<string name="view_files">Files</string>
|
<string name="view_files">Files</string>
|
||||||
<string name="view_folders">Folders</string>
|
<string name="view_folders">Folders</string>
|
||||||
<string name="dashboard_coming_soon">Dashboard coming soon</string>
|
<string name="dashboard_coming_soon">Dashboard coming soon</string>
|
||||||
|
<string name="dashboard_review_title">%1$d document(s) to review</string>
|
||||||
|
<string name="dashboard_review_hint">Review them in swipe mode: keep (and sync) or delete.</string>
|
||||||
|
<string name="dashboard_all_processed">All up to date — nothing to review.</string>
|
||||||
|
|
||||||
|
<!-- Review mode (swipe) -->
|
||||||
|
<string name="review_title">New documents</string>
|
||||||
|
<string name="review_remaining">%1$d remaining</string>
|
||||||
|
<string name="review_keep">KEEP</string>
|
||||||
|
<string name="review_keep_hint">keep + sync</string>
|
||||||
|
<string name="review_delete">DELETE</string>
|
||||||
|
<string name="review_done">All documents have been reviewed.</string>
|
||||||
|
<string name="review_finish">Finish</string>
|
||||||
|
<string name="review_mark_all">Mark all as reviewed</string>
|
||||||
|
<string name="review_mark_all_title">Mark all as reviewed?</string>
|
||||||
|
<string name="review_mark_all_message">The remaining documents will be marked as reviewed and synced to the cloud without being checked.</string>
|
||||||
|
<string name="review_load_error">Could not load the documents to review.</string>
|
||||||
|
<string name="review_delete_error">Could not delete this document — permission denied.</string>
|
||||||
<string name="browse_up">Up</string>
|
<string name="browse_up">Up</string>
|
||||||
<string name="move_here_count">Move here (%1$d)</string>
|
<string name="move_here_count">Move here (%1$d)</string>
|
||||||
<string name="move_pick_title">Move %1$d file(s)…</string>
|
<string name="move_pick_title">Move %1$d file(s)…</string>
|
||||||
<string name="move_success">File(s) moved</string>
|
<string name="move_success">File(s) moved</string>
|
||||||
<string name="default_root_title">Choose your VaultDrop folder</string>
|
|
||||||
<string name="default_root_message">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.</string>
|
|
||||||
<string name="default_root_pick">Choose my VaultDrop folder</string>
|
|
||||||
<string name="default_root_folder_label">VaultDrop</string>
|
|
||||||
<string name="add_folder">Add a folder</string>
|
<string name="add_folder">Add a folder</string>
|
||||||
<string name="create_folder">Create a folder</string>
|
<string name="create_folder">Create a folder</string>
|
||||||
<string name="new_folder_name_title">Folder name</string>
|
<string name="new_folder_name_title">Folder name</string>
|
||||||
@@ -40,6 +58,7 @@
|
|||||||
<string name="no_folders_yet">No folders yet. Tap "Add a folder" to sync a folder.</string>
|
<string name="no_folders_yet">No folders yet. Tap "Add a folder" to sync a folder.</string>
|
||||||
<string name="no_files_yet">No files yet. Tap "Add a folder" to sync your files.</string>
|
<string name="no_files_yet">No files yet. Tap "Add a folder" to sync your files.</string>
|
||||||
<string name="empty_folder">Empty folder — next sync will refresh it.</string>
|
<string name="empty_folder">Empty folder — next sync will refresh it.</string>
|
||||||
|
<string name="file_pending_review">To review</string>
|
||||||
<string name="unit_bytes">B</string>
|
<string name="unit_bytes">B</string>
|
||||||
<string name="unit_kilobytes">KB</string>
|
<string name="unit_kilobytes">KB</string>
|
||||||
<string name="unit_megabytes">MB</string>
|
<string name="unit_megabytes">MB</string>
|
||||||
@@ -117,6 +136,33 @@
|
|||||||
<string name="login_skip">Continue without account</string>
|
<string name="login_skip">Continue without account</string>
|
||||||
<string name="skip_message">Local mode — your folders stay on this device and are not synced.</string>
|
<string name="skip_message">Local mode — your folders stay on this device and are not synced.</string>
|
||||||
|
|
||||||
|
<!-- Server status (header badge) -->
|
||||||
|
<string name="status_online">Online</string>
|
||||||
|
<string name="status_offline">Offline</string>
|
||||||
|
<string name="status_checking">Checking…</string>
|
||||||
|
<string name="status_local">Local mode</string>
|
||||||
|
<string name="status_tap_to_recheck">Server status — tap to check</string>
|
||||||
|
|
||||||
|
<!-- Sync status (badge + outbox list) -->
|
||||||
|
<string name="sync_syncing">Syncing…</string>
|
||||||
|
<string name="sync_pending_count">%1$d pending</string>
|
||||||
|
<string name="sync_pending_count_one">1 pending</string>
|
||||||
|
<string name="sync_idle">Up to date</string>
|
||||||
|
<string name="sync_tap_to_view">Sync — tap to view the operations list</string>
|
||||||
|
<string name="sync_list_title">Sync list</string>
|
||||||
|
<string name="sync_list_empty">No operations yet.</string>
|
||||||
|
<string name="sync_list_close">Close</string>
|
||||||
|
<string name="sync_summary_total">%1$d operations</string>
|
||||||
|
<string name="sync_summary_pending">%1$d pending</string>
|
||||||
|
<string name="sync_summary_failed">%1$d failed</string>
|
||||||
|
<string name="sync_status_pending">Pending</string>
|
||||||
|
<string name="sync_status_synced">Synced</string>
|
||||||
|
<string name="sync_status_failed">Failed</string>
|
||||||
|
<string name="sync_op_create">Create resource</string>
|
||||||
|
<string name="sync_op_move">Move</string>
|
||||||
|
<string name="sync_op_delete">Delete</string>
|
||||||
|
<string name="sync_op_update">Update</string>
|
||||||
|
|
||||||
<!-- Multi-select + PDF assembly -->
|
<!-- Multi-select + PDF assembly -->
|
||||||
<string name="selection_count">%1$d selected</string>
|
<string name="selection_count">%1$d selected</string>
|
||||||
<string name="selection_assemble">Assemble PDF</string>
|
<string name="selection_assemble">Assemble PDF</string>
|
||||||
@@ -153,8 +199,44 @@
|
|||||||
<string name="pdf_builder_name_hint">e.g. january_invoices</string>
|
<string name="pdf_builder_name_hint">e.g. january_invoices</string>
|
||||||
<string name="pdf_builder_name_save">Save</string>
|
<string name="pdf_builder_name_save">Save</string>
|
||||||
<string name="pdf_builder_saved">PDF created</string>
|
<string name="pdf_builder_saved">PDF created</string>
|
||||||
|
<string name="pdf_builder_preview">Preview</string>
|
||||||
|
<string name="pdf_builder_close_preview">Close preview</string>
|
||||||
<string name="pdf_generated_folder">Generated PDFs</string>
|
<string name="pdf_generated_folder">Generated PDFs</string>
|
||||||
|
|
||||||
|
<!-- Camera scanner -->
|
||||||
|
<string name="scan_title">Scan</string>
|
||||||
|
<string name="scan_capture">Capture</string>
|
||||||
|
<string name="scan_pages_count">%1$d page(s)</string>
|
||||||
|
<string name="scan_view_session">View pages</string>
|
||||||
|
<string name="scan_camera_hint">Frame the document, then capture.</string>
|
||||||
|
<string name="scan_permission_rationale">Allow camera access to scan documents.</string>
|
||||||
|
<string name="scan_permission_grant">Allow camera</string>
|
||||||
|
<string name="scan_crop_title">Crop</string>
|
||||||
|
<string name="scan_confirm_page">Confirm page</string>
|
||||||
|
<string name="scan_retake">Retake</string>
|
||||||
|
<string name="scan_render_color">Color</string>
|
||||||
|
<string name="scan_render_gray">B&W</string>
|
||||||
|
<string name="scan_render_high">High-contrast B&W</string>
|
||||||
|
<string name="scan_add_page">Add a page</string>
|
||||||
|
<string name="scan_session_title">Scanned pages</string>
|
||||||
|
<string name="scan_delete_page">Delete</string>
|
||||||
|
<string name="scan_move_up">Move up</string>
|
||||||
|
<string name="scan_move_down">Move down</string>
|
||||||
|
<string name="scan_empty">No pages yet.\nCapture your first page.</string>
|
||||||
|
<string name="scan_export">Export to the VaultDrop folder</string>
|
||||||
|
<string name="scan_export_pdf">Save as PDF</string>
|
||||||
|
<string name="scan_exporting">Exporting…</string>
|
||||||
|
<string name="scan_abandon">Abandon</string>
|
||||||
|
<string name="scan_abandon_title">Abandon the scan?</string>
|
||||||
|
<string name="scan_abandon_message">Captured pages will be deleted.</string>
|
||||||
|
<string name="scan_confirm">Confirm</string>
|
||||||
|
<string name="scan_cancel">Cancel</string>
|
||||||
|
<string name="scan_error_session">Scan session unavailable.</string>
|
||||||
|
<string name="scan_error_capture">Could not use this capture.</string>
|
||||||
|
<string name="scan_error_page">Could not save the page.</string>
|
||||||
|
<string name="scan_error_export">Could not export the pages.</string>
|
||||||
|
<string name="scan_export_done">Pages exported</string>
|
||||||
|
|
||||||
<!-- Delete multi-select -->
|
<!-- Delete multi-select -->
|
||||||
<string name="delete">Delete</string>
|
<string name="delete">Delete</string>
|
||||||
<string name="delete_locally">Delete locally</string>
|
<string name="delete_locally">Delete locally</string>
|
||||||
@@ -168,4 +250,19 @@
|
|||||||
<string name="delete_confirm_button">Confirm</string>
|
<string name="delete_confirm_button">Confirm</string>
|
||||||
<string name="delete_cancel">Cancel</string>
|
<string name="delete_cancel">Cancel</string>
|
||||||
<string name="delete_error">Error while deleting</string>
|
<string name="delete_error">Error while deleting</string>
|
||||||
|
|
||||||
|
<!-- Sharing (share grants) -->
|
||||||
|
<string name="share_title">Share with a user</string>
|
||||||
|
<string name="share_username_hint">Recipient\'s username</string>
|
||||||
|
<string name="share_access_label">Access level</string>
|
||||||
|
<string name="share_access_viewer">View (viewer)</string>
|
||||||
|
<string name="share_access_commenter">Comment (commenter)</string>
|
||||||
|
<string name="share_access_editor">Edit (editor)</string>
|
||||||
|
<string name="share_submit">Share</string>
|
||||||
|
<string name="share_cancel">Cancel</string>
|
||||||
|
<string name="share_error_not_found">No user found with this username.</string>
|
||||||
|
<string name="share_error_generic">Could not share right now.</string>
|
||||||
|
<string name="share_content_description">Share this folder</string>
|
||||||
|
<string name="share_action">Share</string>
|
||||||
|
<string name="more_actions">More actions</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user