fix: schema init, duplicate routes, and image preview bugs

- Move pg_trgm extension creation before CalDAV indexes that depend on it
- Remove duplicate app-password route registration that caused panic
- Fix missing comma in language selector array (Dutch entry)
- Await async canEdit() in file click handler (Promise was always truthy)
- Detect images by extension fallback when mime_type is octet-stream
  (files uploaded via Nextcloud WebDAV API lack correct mime types)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zjean
2026-03-04 15:14:07 +01:00
parent 54eedf5483
commit 40b269c4eb
7 changed files with 20 additions and 28 deletions
+3 -4
View File
@@ -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)
-- ============================================================
+1 -1
View File
@@ -18,7 +18,7 @@ services:
interval: 5s
timeout: 5s
retries: 5
oxicloud:
image: oxicloud
restart: always
+1 -1
View File
@@ -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,
-12
View File
@@ -185,7 +185,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
))
.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<dyn std::error::Error>> {
.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
+4 -2
View File
@@ -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 {
+1 -1
View File
@@ -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: '🇳🇱' }
];
}
+10 -7
View File
@@ -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';