From a3bc83db8a1805c8a5a6690b7c0512155273cfdb Mon Sep 17 00:00:00 2001
From: Stefan Hardegger
Date: Thu, 30 Oct 2025 13:11:40 +0100
Subject: [PATCH] file size limits, keep active filters in session
---
.../service/DatabaseManagementService.java | 22 ++++--
backend/src/main/resources/application.yml | 6 +-
docker-compose.yml | 6 +-
frontend/src/app/library/LibraryContent.tsx | 27 +++++---
frontend/src/hooks/useLibraryFilters.ts | 68 +++++++++++++++++++
frontend/tsconfig.tsbuildinfo | 2 +-
nginx.conf | 6 +-
7 files changed, 112 insertions(+), 25 deletions(-)
create mode 100644 frontend/src/hooks/useLibraryFilters.ts
diff --git a/backend/src/main/java/com/storycove/service/DatabaseManagementService.java b/backend/src/main/java/com/storycove/service/DatabaseManagementService.java
index 547930a..66d4694 100644
--- a/backend/src/main/java/com/storycove/service/DatabaseManagementService.java
+++ b/backend/src/main/java/com/storycove/service/DatabaseManagementService.java
@@ -354,14 +354,24 @@ public class DatabaseManagementService implements ApplicationContextAware {
Path tempBackupFile = Files.createTempFile("storycove_restore_", ".sql");
try {
- // Write backup stream to temporary file
+ // Write backup stream to temporary file, filtering out incompatible commands
System.err.println("Writing backup data to temporary file...");
try (InputStream input = backupStream;
- OutputStream output = Files.newOutputStream(tempBackupFile)) {
- byte[] buffer = new byte[8192];
- int bytesRead;
- while ((bytesRead = input.read(buffer)) != -1) {
- output.write(buffer, 0, bytesRead);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ BufferedWriter writer = Files.newBufferedWriter(tempBackupFile, StandardCharsets.UTF_8)) {
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ // Skip DROP DATABASE and CREATE DATABASE commands - we're already connected to the DB
+ // Also skip database connection commands as we're already connected
+ if (line.trim().startsWith("DROP DATABASE") ||
+ line.trim().startsWith("CREATE DATABASE") ||
+ line.trim().startsWith("\\connect")) {
+ System.err.println("Skipping incompatible command: " + line.substring(0, Math.min(50, line.length())));
+ continue;
+ }
+ writer.write(line);
+ writer.newLine();
}
}
diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml
index 0d4205b..439ce23 100644
--- a/backend/src/main/resources/application.yml
+++ b/backend/src/main/resources/application.yml
@@ -21,8 +21,8 @@ spring:
servlet:
multipart:
- max-file-size: 600MB # Increased for large backup restore (425MB+)
- max-request-size: 610MB # Slightly higher to account for form data
+ max-file-size: 2048MB # 2GB for large backup restore
+ max-request-size: 2100MB # Slightly higher to account for form data
jackson:
serialization:
@@ -33,7 +33,7 @@ spring:
server:
port: 8080
tomcat:
- max-http-request-size: 650MB # Tomcat HTTP request size limit (separate from multipart)
+ max-http-request-size: 2150MB # Tomcat HTTP request size limit (2GB + overhead)
storycove:
app:
diff --git a/docker-compose.yml b/docker-compose.yml
index e664c42..2a71f05 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -124,7 +124,7 @@ configs:
}
server {
listen 80;
- client_max_body_size 600M;
+ client_max_body_size 2048M;
location / {
proxy_pass http://frontend;
proxy_http_version 1.1;
@@ -145,8 +145,8 @@ configs:
proxy_connect_timeout 900s;
proxy_send_timeout 900s;
proxy_read_timeout 900s;
- # Large upload settings
- client_max_body_size 600M;
+ # Large upload settings (2GB for backups)
+ client_max_body_size 2048M;
proxy_request_buffering off;
proxy_max_temp_file_size 0;
}
diff --git a/frontend/src/app/library/LibraryContent.tsx b/frontend/src/app/library/LibraryContent.tsx
index e979474..67f8e43 100644
--- a/frontend/src/app/library/LibraryContent.tsx
+++ b/frontend/src/app/library/LibraryContent.tsx
@@ -13,6 +13,7 @@ import SidebarLayout from '../../components/library/SidebarLayout';
import ToolbarLayout from '../../components/library/ToolbarLayout';
import MinimalLayout from '../../components/library/MinimalLayout';
import { useLibraryLayout } from '../../hooks/useLibraryLayout';
+import { useLibraryFilters, clearLibraryFilters } from '../../hooks/useLibraryFilters';
type ViewMode = 'grid' | 'list';
type SortOption = 'createdAt' | 'title' | 'authorName' | 'rating' | 'wordCount' | 'lastReadAt';
@@ -26,17 +27,21 @@ export default function LibraryContent() {
const [loading, setLoading] = useState(false);
const [searchLoading, setSearchLoading] = useState(false);
const [randomLoading, setRandomLoading] = useState(false);
- const [searchQuery, setSearchQuery] = useState('');
- const [selectedTags, setSelectedTags] = useState([]);
- const [viewMode, setViewMode] = useState('list');
- const [sortOption, setSortOption] = useState('lastReadAt');
- const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
+
+ // Persisted filter state (survives navigation within session)
+ const [searchQuery, setSearchQuery] = useLibraryFilters('searchQuery', '');
+ const [selectedTags, setSelectedTags] = useLibraryFilters('selectedTags', []);
+ const [viewMode, setViewMode] = useLibraryFilters('viewMode', 'list');
+ const [sortOption, setSortOption] = useLibraryFilters('sortOption', 'lastReadAt');
+ const [sortDirection, setSortDirection] = useLibraryFilters<'asc' | 'desc'>('sortDirection', 'desc');
+ const [advancedFilters, setAdvancedFilters] = useLibraryFilters('advancedFilters', {});
+
+ // Non-persisted state (resets on navigation)
const [page, setPage] = useState(0);
const [totalPages, setTotalPages] = useState(1);
const [totalElements, setTotalElements] = useState(0);
const [refreshTrigger, setRefreshTrigger] = useState(0);
const [urlParamsProcessed, setUrlParamsProcessed] = useState(false);
- const [advancedFilters, setAdvancedFilters] = useState({});
// Initialize filters from URL parameters
useEffect(() => {
@@ -209,11 +214,15 @@ export default function LibraryContent() {
}
};
- const clearFilters = () => {
+ const handleClearFilters = () => {
+ // Clear state
setSearchQuery('');
setSelectedTags([]);
setAdvancedFilters({});
setPage(0);
+ // Clear sessionStorage
+ clearLibraryFilters();
+ // Trigger refresh
setRefreshTrigger(prev => prev + 1);
};
@@ -266,7 +275,7 @@ export default function LibraryContent() {
onSortDirectionToggle: handleSortDirectionToggle,
onAdvancedFiltersChange: handleAdvancedFiltersChange,
onRandomStory: handleRandomStory,
- onClearFilters: clearFilters,
+ onClearFilters: handleClearFilters,
};
const renderContent = () => {
@@ -280,7 +289,7 @@ export default function LibraryContent() {
}
{searchQuery || selectedTags.length > 0 || Object.values(advancedFilters).some(v => v !== undefined && v !== '' && v !== 'all' && v !== false) ? (
-