diff --git a/db/schema.sql b/db/schema.sql index 58b83abe..4c71e001 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -6,6 +6,9 @@ -- All tables use IF NOT EXISTS for idempotent re-runs. -- ============================================================ +-- ── Extensions required by indexes below ── +CREATE EXTENSION IF NOT EXISTS pg_trgm; + -- ============================================================ -- 1. AUTH SCHEMA -- ============================================================ @@ -287,10 +290,6 @@ COMMENT ON TABLE caldav.calendar_events IS 'Calendar events (VEVENT) stored with COMMENT ON TABLE caldav.calendar_shares IS 'Calendar sharing permissions between users'; COMMENT ON TABLE caldav.calendar_properties IS 'Custom WebDAV properties on calendars'; --- ── pg_trgm extension for GIN trigram indexes (ILIKE / LIKE substring search) ── --- Required before creating any gin_trgm_ops indexes below. -CREATE EXTENSION IF NOT EXISTS pg_trgm; - -- ============================================================ -- 3. CARDDAV SCHEMA (RFC 6352) -- ============================================================ diff --git a/docker-compose.yml b/docker-compose.yml index a0074d28..067ef3e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: interval: 5s timeout: 5s retries: 5 - + oxicloud: image: oxicloud restart: always diff --git a/src/common/config.rs b/src/common/config.rs index 6ca8f580..7e9fe775 100644 --- a/src/common/config.rs +++ b/src/common/config.rs @@ -238,7 +238,7 @@ impl Default for DatabaseConfig { fn default() -> Self { Self { // Updated connection string with default credentials that PostgreSQL often uses - connection_string: "postgres://postgres:postgres@localhost:5439/oxicloud".to_string(), + connection_string: "postgres://postgres:postgres@localhost:5432/oxicloud".to_string(), max_connections: 20, min_connections: 5, connect_timeout_secs: 10, diff --git a/src/main.rs b/src/main.rs index 6f0152a1..9e1b19a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -185,7 +185,6 @@ async fn main() -> Result<(), Box> { auth_protected_routes, auth_public_routes, login_route, refresh_route, register_route, setup_route, }; - use oxicloud::interfaces::api::handlers::app_password_handler; use oxicloud::interfaces::api::handlers::device_auth_handler; use oxicloud::interfaces::middleware::auth::auth_middleware; use oxicloud::interfaces::middleware::csrf::csrf_middleware; @@ -270,15 +269,6 @@ async fn main() -> Result<(), Box> { )) .with_state(app_state.clone()); - // App Password management endpoints (protected — require JWT) - let app_password_protected = app_password_handler::app_password_routes() - .layer(axum::middleware::from_fn(csrf_middleware)) - .layer(axum::middleware::from_fn_with_state( - app_state.clone(), - auth_middleware, - )) - .with_state(app_state.clone()); - // Protected API routes — require valid JWT token let protected_api = api_routes .layer(axum::middleware::from_fn(csrf_middleware)) @@ -316,8 +306,6 @@ async fn main() -> Result<(), Box> { .nest("/api/auth/device", device_public) // Device Auth Grant protected endpoints (verify + device management) .nest("/api/auth/device", device_protected) - // App Password management endpoints (create, list, revoke) - .nest("/api/auth", app_password_protected) // Public API routes (share access, i18n) — no auth required .nest("/api", public_api_routes) // All other API routes are protected by auth middleware diff --git a/static/js/app/ui.js b/static/js/app/ui.js index 94c47dc7..f6979195 100644 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -754,12 +754,14 @@ const ui = { } // WOPI editor intercept: open Office documents in the WOPI editor // But NOT image files - those should be previewed in the inline viewer - const isImage = file.mime_type && file.mime_type.startsWith('image/'); + const ext = (file.name || '').split('.').pop().toLowerCase(); + const imageExts = ['jpg','jpeg','png','gif','svg','webp','bmp','ico','heic','heif','avif','tiff']; + const isImage = (file.mime_type && file.mime_type.startsWith('image/')) || imageExts.includes(ext); if (!isImage && window.wopiEditor && await window.wopiEditor.canEdit(file.name)) { window.wopiEditor.openInModal(file.id, file.name, 'edit'); return; } - if (self.isViewableFile(file)) { + if (self.isViewableFile(file) || isImage) { if (window.inlineViewer) window.inlineViewer.openFile(file); else window.fileOps.downloadFile(file.id, file.name); } else { diff --git a/static/js/core/languageSelector.js b/static/js/core/languageSelector.js index 848be211..6075c6d0 100644 --- a/static/js/core/languageSelector.js +++ b/static/js/core/languageSelector.js @@ -17,7 +17,7 @@ function getAvailableLanguages() { { code: 'fr', name: 'Français', flag: '🇫🇷' }, { code: 'de', name: 'Deutsch', flag: '🇩🇪' }, { code: 'pt', name: 'Português', flag: '🇧🇷' }, - { code: 'it', name: 'Italiano', flag: '🇮🇹' } + { code: 'it', name: 'Italiano', flag: '🇮🇹' }, { code: 'nl', name: 'Nederlands', flag: '🇳🇱' } ]; } diff --git a/static/js/features/files/inlineViewer.js b/static/js/features/files/inlineViewer.js index dc8821c1..ad4a0187 100644 --- a/static/js/features/files/inlineViewer.js +++ b/static/js/features/files/inlineViewer.js @@ -93,30 +93,33 @@ class InlineViewer { // WOPI editor intercept: open Office documents in the WOPI editor // But NOT image files - those should be previewed in the inline viewer - const isImage = file.mime_type && file.mime_type.startsWith('image/'); + // Detect images by mime type OR extension (uploads via WebDAV may lack correct mime) + const ext = (file.name || '').split('.').pop().toLowerCase(); + const imageExts = ['jpg','jpeg','png','gif','svg','webp','bmp','ico','heic','heif','avif','tiff']; + const isImage = (file.mime_type && file.mime_type.startsWith('image/')) || imageExts.includes(ext); if (!isImage && window.wopiEditor && await window.wopiEditor.canEdit(file.name)) { window.wopiEditor.openInModal(file.id, file.name, 'edit'); return; } this.currentFile = file; - + // Get container const modal = document.getElementById('inline-viewer-modal'); const container = modal.querySelector('.inline-viewer-container'); const title = modal.querySelector('.inline-viewer-title'); - + // Clear container container.innerHTML = ''; - + // Set title title.textContent = file.name; - + // Set controls visibility const controls = modal.querySelector('.inline-viewer-controls'); - + // Show viewer based on file type - if (file.mime_type && file.mime_type.startsWith('image/')) { + if (isImage) { // Show zoom controls controls.style.display = 'flex';