Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions platforms/esigner-api/src/controllers/FileController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Request, Response } from "express";
import { FileService, ReservedFileNameError } from "../services/FileService";
import multer from "multer";

export const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB limit

const upload = multer({
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit
limits: { fileSize: MAX_FILE_SIZE },
storage: multer.memoryStorage(),
});

Expand Down Expand Up @@ -200,7 +202,7 @@ export class FileController {
}

const signatures = await this.fileService.getFileSignatures(fileId);

res.json(signatures.map(sig => ({
id: sig.id,
userId: sig.userId,
Expand Down
32 changes: 27 additions & 5 deletions platforms/esigner-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { config } from "dotenv";
import { AppDataSource } from "./database/data-source";
import path from "path";
import { AuthController } from "./controllers/AuthController";
import { FileController } from "./controllers/FileController";
import { FileController, MAX_FILE_SIZE } from "./controllers/FileController";
import multer from "multer";
import { InvitationController } from "./controllers/InvitationController";
import { SignatureController } from "./controllers/SignatureController";
import { UserController } from "./controllers/UserController";
Expand All @@ -24,12 +25,12 @@ AppDataSource.initialize()
.then(async () => {
console.log("Database connection established");
console.log("Web3 adapter initialized");

// Initialize platform eVault for eSigner
try {
const platformService = PlatformEVaultService.getInstance();
const exists = await platformService.checkPlatformEVaultExists();

if (!exists) {
console.log("🔧 Creating platform eVault for eSigner...");
const result = await platformService.createPlatformEVault();
Expand Down Expand Up @@ -61,8 +62,8 @@ app.use(
credentials: true,
}),
);
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ limit: "50mb", extended: true }));
app.use(express.json({ limit: "20mb" }));
app.use(express.urlencoded({ limit: "20mb", extended: true }));

// Controllers
const authController = new AuthController();
Expand Down Expand Up @@ -105,6 +106,27 @@ app.post("/api/signatures/session", authGuard, signatureController.createSigning
app.get("/api/signatures/session/:id", signatureController.getSigningSessionStatus);
app.post("/api/signatures/callback", signatureController.handleSignedPayload);

// Global error handler for multer file size errors
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
if (err instanceof multer.MulterError && err.code === "LIMIT_FILE_SIZE") {
const maxSizeMB = Math.round(MAX_FILE_SIZE / (1024 * 1024));
return res.status(413).json({
error: `File size exceeds the maximum limit of ${maxSizeMB} MB`,
code: "LIMIT_FILE_SIZE",
maxSize: MAX_FILE_SIZE,
});
}
// Handle other multer errors
if (err instanceof multer.MulterError) {
return res.status(400).json({
error: err.message,
code: err.code,
});
}
// Pass other errors to the default handler
next(err);
});

// Start server
app.listen(port, () => {
console.log(`eSigner API server running on port ${port}`);
Expand Down
33 changes: 32 additions & 1 deletion platforms/esigner/src/lib/stores/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,25 @@ export const fetchDocuments = async () => {
// Keep fetchFiles alias for backward compatibility
export const fetchFiles = fetchDocuments;

const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB limit

export class FileSizeError extends Error {
constructor(public fileSize: number, public maxSize: number = MAX_FILE_SIZE) {
const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(0);
super(`File size (${fileSizeMB} MB) exceeds the maximum limit of ${maxSizeMB} MB`);
this.name = 'FileSizeError';
}
}

export const uploadFile = async (file: File, displayName?: string, description?: string) => {
// Client-side file size validation
if (file.size > MAX_FILE_SIZE) {
const err = new FileSizeError(file.size, MAX_FILE_SIZE);
error.set(err.message);
throw err;
}

try {
isLoading.set(true);
error.set(null);
Expand All @@ -84,7 +102,20 @@ export const uploadFile = async (file: File, displayName?: string, description?:
});
await fetchDocuments();
return response.data;
} catch (err) {
} catch (err: unknown) {
// Handle HTTP 413 Payload Too Large
if (err && typeof err === 'object' && 'response' in err) {
const axiosError = err as { response?: { status?: number; data?: { error?: string; maxSize?: number; fileSize?: number } } };
if (axiosError.response?.status === 413) {
const data = axiosError.response.data;
const fileSizeErr = new FileSizeError(
data?.fileSize || file.size,
data?.maxSize || MAX_FILE_SIZE
);
error.set(fileSizeErr.message);
throw fileSizeErr;
}
}
error.set(err instanceof Error ? err.message : 'Failed to upload file');
throw err;
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { isAuthenticated } from '$lib/stores/auth';
import { uploadFile } from '$lib/stores/files';
import { files, fetchFiles, uploadFile, FileSizeError } from '$lib/stores/files';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n '\b(files|fetchFiles)\b' 'platforms/esigner/src/routes/(protected)/files/new/+page.svelte' | grep -v 'import'

Repository: MetaState-Prototype-Project/prototype

Length of output: 1067


Remove unused imports files and fetchFiles.

The imports files and fetchFiles are not used in this file. Only uploadFile and FileSizeError are referenced. Remove the unused imports from line 5.

🤖 Prompt for AI Agents
In `@platforms/esigner/src/routes/`(protected)/files/new/+page.svelte at line 5,
The import line currently brings in unused symbols `files` and `fetchFiles`;
remove those two from the import so only `uploadFile` and `FileSizeError` are
imported (i.e., change the import that references `files, fetchFiles,
uploadFile, FileSizeError` to import just `uploadFile` and `FileSizeError`) to
eliminate dead imports and related lint warnings.

import { apiClient } from '$lib/utils/axios';
import { inviteSignees } from '$lib/stores/invitations';

Expand Down Expand Up @@ -59,7 +59,11 @@
}
} catch (err) {
console.error('Upload failed:', err);
alert('Failed to upload file');
if (err instanceof FileSizeError) {
alert(err.message);
} else {
alert('Failed to upload file. Please try again.');
}
throw err;
} finally {
isLoading = false;
Expand Down Expand Up @@ -126,7 +130,7 @@
alert('You cannot invite yourself. You are automatically added as a signee.');
return;
}

if (!selectedUsers.find(u => u.id === user.id)) {
selectedUsers = [...selectedUsers, user];
}
Expand All @@ -147,10 +151,10 @@
try {
isSubmitting = true;
const userIds = selectedUsers.map(u => u.id);

// Backend will automatically add owner as signee
await inviteSignees(selectedFile.id, userIds);

goto(`/files/${selectedFile.id}`);
} catch (err) {
console.error('Failed to create invitations:', err);
Expand Down