@@ -2,21 +2,12 @@ import type { FastifyPluginAsync } from 'fastify';
22import { updateFileSchema } from '@opencoperlock/shared' ;
33import { prisma } from '../db.js' ;
44import { parseOr400 } from '../lib/validate.js' ;
5- import { newStorageKey } from '../storage/index.js' ;
6- import {
7- FileTooLargeError ,
8- InfectedFileError ,
9- ingestPlaintext ,
10- } from '../services/ingest.js' ;
5+ import { FileTooLargeError , InfectedFileError } from '../services/ingest.js' ;
6+ import { storeUserFile , QuotaExhaustedError } from '../services/upload.js' ;
117import { decryptServerFile } from '../services/download.js' ;
128import { trashFile } from '../services/trash.js' ;
13- import {
14- findVersionTarget ,
15- isVersionable ,
16- pruneVersions ,
17- snapshotVersion ,
18- } from '../services/versioning.js' ;
19- import { adjustUsage , remainingAllowance } from '../services/quota.js' ;
9+ import { pruneVersions , snapshotVersion } from '../services/versioning.js' ;
10+ import { adjustUsage } from '../services/quota.js' ;
2011import { toPublicFile } from '../lib/serialize.js' ;
2112import { audit } from '../services/audit.js' ;
2213
@@ -52,63 +43,22 @@ export const fileRoutes: FastifyPluginAsync = async (app) => {
5243 const part = await req . file ( ) ;
5344 if ( ! part ) return reply . code ( 400 ) . send ( { error : 'No file provided' } ) ;
5445
55- const allowance = await remainingAllowance ( req . user ! . id ) ;
56- if ( allowance <= 0 ) return reply . code ( 413 ) . send ( { error : 'Storage quota exhausted' } ) ;
57-
58- const storageKey = newStorageKey ( ) ;
5946 try {
60- const result = await ingestPlaintext ( app . ctx , part . file , { maxBytes : allowance , storageKey } ) ;
61-
62- // Versioning: re-uploading a text-like file under the same name keeps the old
63- // content as a version instead of creating a duplicate row.
64- const existing = isVersionable ( part . filename , part . mimetype )
65- ? await findVersionTarget ( req . user ! . id , folderId ?? null , part . filename )
66- : null ;
67-
68- if ( existing ) {
69- await snapshotVersion ( existing ) ;
70- const file = await prisma . fileObject . update ( {
71- where : { id : existing . id } ,
72- data : {
73- sizeBytes : BigInt ( result . sizeBytes ) ,
74- mimeType : part . mimetype ,
75- storageKey : result . storageKey ,
76- wrappedKey : result . wrappedKey ,
77- iv : result . iv ,
78- authTag : result . authTag ,
79- sha256 : result . sha256 ,
80- avStatus : result . avStatus ,
81- } ,
82- } ) ;
83- // New content adds to usage; the retained version keeps the old size.
84- await adjustUsage ( req . user ! . id , result . sizeBytes ) ;
85- const freed = await pruneVersions ( app . ctx , file . id ) ;
86- if ( freed > 0 ) await adjustUsage ( req . user ! . id , - freed ) ;
87- await audit ( req , 'file.version' , { target : file . id } ) ;
88- return reply . code ( 201 ) . send ( { file : toPublicFile ( file ) } ) ;
89- }
90-
91- const file = await prisma . fileObject . create ( {
92- data : {
93- ownerId : req . user ! . id ,
94- folderId : folderId ?? null ,
95- name : part . filename ,
96- sizeBytes : BigInt ( result . sizeBytes ) ,
97- mimeType : part . mimetype ,
98- storageKey : result . storageKey ,
99- encMode : 'SERVER' ,
100- wrappedKey : result . wrappedKey ,
101- iv : result . iv ,
102- authTag : result . authTag ,
103- sha256 : result . sha256 ,
104- avStatus : result . avStatus ,
105- } ,
47+ // Shared pipeline: quota + antivirus + server-side encryption + text-file versioning,
48+ // plus outgoing-webhook dispatch — identical to the REST API and WebDAV.
49+ const { file, versioned } = await storeUserFile ( app . ctx , {
50+ ownerId : req . user ! . id ,
51+ folderId : folderId ?? null ,
52+ stream : part . file ,
53+ filename : part . filename ,
54+ mimetype : part . mimetype ,
10655 } ) ;
107- await adjustUsage ( req . user ! . id , result . sizeBytes ) ;
108- await audit ( req , 'file.upload' , { target : file . id } ) ;
56+ await audit ( req , versioned ? 'file.version' : 'file.upload' , { target : file . id } ) ;
10957 return reply . code ( 201 ) . send ( { file : toPublicFile ( file ) } ) ;
11058 } catch ( err ) {
111- await app . ctx . storage . delete ( storageKey ) . catch ( ( ) => { } ) ;
59+ if ( err instanceof QuotaExhaustedError ) {
60+ return reply . code ( 413 ) . send ( { error : 'Storage quota exhausted' } ) ;
61+ }
11262 if ( err instanceof FileTooLargeError ) {
11363 return reply . code ( 413 ) . send ( { error : 'Upload exceeds your available quota' } ) ;
11464 }
0 commit comments