From bff75aafbe5e88ac29b96f5763fe2fc3225fa04e Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 22 Jan 2026 12:50:47 +0100 Subject: [PATCH] fix: persist options when uploading documents & dynamic config OnlyAdminCanCreate Reader options (readMode, allowDownload, requireFullRead, verifyChecksum) were not being saved during document upload. SimpleAuthorizer now reads the setting dynamically from ConfigService instead of using a static value set at startup. This allows admins to toggle document creation permissions via the settings UI without requiring a server restart. Fixes #14 Fixes #15 --- .../application/services/document_service.go | 14 ++++++++-- .../presentation/api/storage/handler.go | 13 +++++++++ backend/pkg/web/auth/simple_authorizer.go | 19 +++++++++---- backend/pkg/web/server.go | 2 +- webapp/src/components/DocumentCreateForm.vue | 8 +++++- webapp/src/services/documents.ts | 28 ++++++++++++++++--- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/backend/internal/application/services/document_service.go b/backend/internal/application/services/document_service.go index 3f8ee19..2039f28 100644 --- a/backend/internal/application/services/document_service.go +++ b/backend/internal/application/services/document_service.go @@ -55,6 +55,12 @@ type CreateDocumentRequest struct { Title string `json:"title"` CreatedBy string `json:"created_by,omitempty"` + // Reader options + ReadMode string `json:"read_mode,omitempty"` + AllowDownload *bool `json:"allow_download,omitempty"` + RequireFullRead *bool `json:"require_full_read,omitempty"` + VerifyChecksum *bool `json:"verify_checksum,omitempty"` + // Storage fields for uploaded files StorageKey string `json:"storage_key,omitempty"` StorageProvider string `json:"storage_provider,omitempty"` @@ -106,8 +112,12 @@ func (s *DocumentService) CreateDocument(ctx context.Context, req CreateDocument } input := models.DocumentInput{ - Title: title, - URL: url, + Title: title, + URL: url, + ReadMode: req.ReadMode, + AllowDownload: req.AllowDownload, + RequireFullRead: req.RequireFullRead, + VerifyChecksum: req.VerifyChecksum, } // Handle storage fields if provided (for uploaded files) diff --git a/backend/internal/presentation/api/storage/handler.go b/backend/internal/presentation/api/storage/handler.go index e246521..02a65c1 100644 --- a/backend/internal/presentation/api/storage/handler.go +++ b/backend/internal/presentation/api/storage/handler.go @@ -160,6 +160,15 @@ func (h *Handler) HandleUpload(w http.ResponseWriter, r *http.Request) { title = header.Filename } + // Get reader options from form + readMode := r.FormValue("readMode") + if readMode == "" { + readMode = "integrated" + } + allowDownload := r.FormValue("allowDownload") == "true" + requireFullRead := r.FormValue("requireFullRead") == "true" + verifyChecksum := r.FormValue("verifyChecksum") != "false" // default true + // Detect content type from file content buffer := make([]byte, 512) n, err := file.Read(buffer) @@ -207,6 +216,10 @@ func (h *Handler) HandleUpload(w http.ResponseWriter, r *http.Request) { Reference: storageKey, Title: title, CreatedBy: user.Email, + ReadMode: readMode, + AllowDownload: &allowDownload, + RequireFullRead: &requireFullRead, + VerifyChecksum: &verifyChecksum, StorageKey: storageKey, StorageProvider: h.provider.Type(), FileSize: header.Size, diff --git a/backend/pkg/web/auth/simple_authorizer.go b/backend/pkg/web/auth/simple_authorizer.go index 0bd1ed3..d6f50ec 100644 --- a/backend/pkg/web/auth/simple_authorizer.go +++ b/backend/pkg/web/auth/simple_authorizer.go @@ -5,18 +5,24 @@ import ( "context" "strings" + "github.com/btouchard/ackify-ce/backend/pkg/models" "github.com/btouchard/ackify-ce/backend/pkg/providers" ) +// ConfigProvider provides access to general configuration. +type ConfigProvider interface { + GetConfig() *models.MutableConfig +} + // SimpleAuthorizer is an authorization implementation based on a list of admin emails. // This is the default authorizer for Community Edition. type SimpleAuthorizer struct { - adminEmails map[string]bool - onlyAdminCanCreate bool + adminEmails map[string]bool + configProvider ConfigProvider } // NewSimpleAuthorizer creates a new simple authorizer. -func NewSimpleAuthorizer(adminEmails []string, onlyAdminCanCreate bool) *SimpleAuthorizer { +func NewSimpleAuthorizer(adminEmails []string, configProvider ConfigProvider) *SimpleAuthorizer { emailMap := make(map[string]bool, len(adminEmails)) for _, email := range adminEmails { normalized := strings.ToLower(strings.TrimSpace(email)) @@ -25,8 +31,8 @@ func NewSimpleAuthorizer(adminEmails []string, onlyAdminCanCreate bool) *SimpleA } } return &SimpleAuthorizer{ - adminEmails: emailMap, - onlyAdminCanCreate: onlyAdminCanCreate, + adminEmails: emailMap, + configProvider: configProvider, } } @@ -38,7 +44,8 @@ func (a *SimpleAuthorizer) IsAdmin(_ context.Context, userEmail string) bool { // CanCreateDocument implements providers.Authorizer. func (a *SimpleAuthorizer) CanCreateDocument(ctx context.Context, userEmail string) bool { - if !a.onlyAdminCanCreate { + cfg := a.configProvider.GetConfig() + if !cfg.General.OnlyAdminCanCreate { return true } return a.IsAdmin(ctx, userEmail) diff --git a/backend/pkg/web/server.go b/backend/pkg/web/server.go index e7bedfc..00d396f 100644 --- a/backend/pkg/web/server.go +++ b/backend/pkg/web/server.go @@ -225,7 +225,7 @@ func (b *ServerBuilder) setDefaultProviders() { }) } if b.authorizer == nil { - b.authorizer = webauth.NewSimpleAuthorizer(b.cfg.App.AdminEmails, b.cfg.App.OnlyAdminCanCreate) + b.authorizer = webauth.NewSimpleAuthorizer(b.cfg.App.AdminEmails, b.configService) } if b.quotaEnforcer == nil { b.quotaEnforcer = NewNoLimitQuotaEnforcer() diff --git a/webapp/src/components/DocumentCreateForm.vue b/webapp/src/components/DocumentCreateForm.vue index 05a72c4..2e09e9c 100644 --- a/webapp/src/components/DocumentCreateForm.vue +++ b/webapp/src/components/DocumentCreateForm.vue @@ -159,7 +159,13 @@ async function handleSubmit() { isUploading.value = true const uploadResponse = await documentService.uploadDocument( selectedFile.value, - title.value || undefined, + { + title: title.value || undefined, + readMode: readMode.value, + allowDownload: allowDownload.value, + requireFullRead: requireFullRead.value, + verifyChecksum: verifyChecksum.value, + }, (progress) => { uploadProgress.value = progress } diff --git a/webapp/src/services/documents.ts b/webapp/src/services/documents.ts index 01ae371..2b77ea3 100644 --- a/webapp/src/services/documents.ts +++ b/webapp/src/services/documents.ts @@ -26,6 +26,14 @@ export interface UploadProgress { percent: number } +export interface UploadDocumentOptions { + title?: string + readMode?: 'integrated' | 'external' + allowDownload?: boolean + requireFullRead?: boolean + verifyChecksum?: boolean +} + export interface CreateDocumentResponse { docId: string url?: string @@ -139,13 +147,13 @@ export const documentService = { /** * Upload a file and create a document * @param file File to upload - * @param title Optional title for the document + * @param options Upload options including title and reader settings * @param onProgress Optional callback for upload progress * @returns Upload response with document info */ async uploadDocument( file: File, - title?: string, + options?: UploadDocumentOptions, onProgress?: (progress: UploadProgress) => void ): Promise { // Get CSRF token first @@ -154,8 +162,20 @@ export const documentService = { const formData = new FormData() formData.append('file', file) - if (title) { - formData.append('title', title) + if (options?.title) { + formData.append('title', options.title) + } + if (options?.readMode) { + formData.append('readMode', options.readMode) + } + if (options?.allowDownload !== undefined) { + formData.append('allowDownload', String(options.allowDownload)) + } + if (options?.requireFullRead !== undefined) { + formData.append('requireFullRead', String(options.requireFullRead)) + } + if (options?.verifyChecksum !== undefined) { + formData.append('verifyChecksum', String(options.verifyChecksum)) } const response = await axios.post>(