1- import fastify from 'fastify' ;
2- import rateLimit from '@fastify/rate-limit' ;
3- import multipart from '@fastify/multipart' ;
4- import * as FileType from 'file-type' ;
5- import * as stream from 'stream' ;
6- import * as crypto from 'crypto' ;
7- import * as Mega from 'megajs' ;
8-
9- import config from '../config' ;
10- import { formatBytes , runtime } from '../functions' ;
11- import MegaClient from '../mega' ;
12- import db from '../database' ;
13- import { UploadResponse , UploadMode , DetectedFileType , UploadQuery } from '../types' ;
14- import { FastifyRequest , FastifyReply } from 'fastify' ;
1+ import fastify from "fastify" ;
2+ import rateLimit from "@fastify/rate-limit" ;
3+ import multipart from "@fastify/multipart" ;
4+ import * as FileType from "file-type" ;
5+ import * as stream from "stream" ;
6+ import * as crypto from "crypto" ;
7+ import * as Mega from "megajs" ;
8+ import config from "../config" ;
9+ import { formatBytes , runtime } from "../functions" ;
10+ import MegaClient from "../mega" ;
11+ import db from "../database" ;
12+ import { UploadResponse , UploadMode , DetectedFileType , UploadQuery , } from "../types" ;
13+ import { FastifyRequest , FastifyReply } from "fastify" ;
1514
1615const app = fastify ( {
1716 logger : false ,
@@ -21,7 +20,7 @@ const app = fastify({
2120} ) ;
2221
2322app . register ( require ( "@fastify/static" ) , {
24- root : require ( "path" ) . join ( process . cwd ( ) , ' public' ) ,
23+ root : require ( "path" ) . join ( process . cwd ( ) , " public" ) ,
2524 prefix : "/" ,
2625} ) ;
2726
@@ -33,83 +32,121 @@ app.register(multipart, {
3332 } ,
3433} ) ;
3534
36- app . addHook ( ' onSend' , ( req , rep , _payload , done ) => {
37- if ( req . url . startsWith ( ' /media' ) || req . url . startsWith ( ' /file' ) ) {
38- rep . header ( ' Cache-Control' , `public, max-age=${ config . server . cacheTTL } ` ) ;
35+ app . addHook ( " onSend" , ( req , rep , _payload , done ) => {
36+ if ( req . url . startsWith ( " /media" ) || req . url . startsWith ( " /file" ) ) {
37+ rep . header ( " Cache-Control" , `public, max-age=${ config . server . cacheTTL } ` ) ;
3938 }
4039 done ( ) ;
4140} ) ;
4241
4342const authOK = ( req : FastifyRequest ) : boolean => {
4443 if ( ! config . auth ?. enable ) return true ;
45- const h = req . headers [ ' authorization' ] ;
44+ const h = req . headers [ " authorization" ] ;
4645 if ( ! h ) return false ;
47- const [ t , token ] = h . split ( ' ' ) ;
48- return t === ' Bearer' && config . auth . keys . includes ( token ) ;
46+ const [ t , token ] = h . split ( " " ) ;
47+ return t === " Bearer" && config . auth . keys . includes ( token ) ;
4948} ;
5049
5150const origin = ( req : FastifyRequest ) : string => {
52- const s = ( req . raw . socket as any ) . encrypted ? ' https' : ' http' ;
51+ const s = ( req . raw . socket as any ) . encrypted ? " https" : " http" ;
5352 const host = req . hostname ;
5453 const port = ( req . raw . socket as any ) . localPort ;
55- return host === ' localhost' ? `${ s } ://${ host } :${ port } ` : `${ s } ://${ host } ` ;
54+ return host === " localhost" ? `${ s } ://${ host } :${ port } ` : `${ s } ://${ host } ` ;
5655} ;
5756
5857const parseParts = async ( req : FastifyRequest ) => {
59- let mode = 'single' , query : UploadQuery | undefined = undefined ;
58+ let mode = "single" ,
59+ query : UploadQuery | undefined = undefined ;
6060 const files : any [ ] = [ ] ;
6161
6262 for await ( const part of req . parts ( ) ) {
63- if ( part . type === 'field' ) {
64- if ( part . fieldname === 'mode' ) mode = part . value || 'single' ;
65- if ( part . fieldname === 'email' && mode === 'dual' ) query = { email : part . value } ;
63+ if ( part . type === "field" ) {
64+ if ( part . fieldname === "mode" ) mode = part . value || "single" ;
65+ if ( part . fieldname === "email" && mode === "dual" )
66+ query = { email : part . value } ;
6667 continue ;
6768 }
6869 if ( ! part . file ) continue ;
6970
7071 const buf = await part . toBuffer ( ) ;
71- const ft = await FileType . fromBuffer ( buf ) as DetectedFileType | undefined ;
72- if ( ! ft || ! config . server . allowedTypes . includes ( ft . mime ) ) throw new Error ( 'Invalid: ' + ft ?. mime ) ;
72+ const ft = ( await FileType . fromBuffer ( buf ) ) as DetectedFileType | undefined ;
73+ if ( ! ft || ! config . server . allowedTypes . includes ( ft . mime ) )
74+ throw new Error ( "Invalid: " + ft ?. mime ) ;
7375 const d = new Date ( ) ;
7476 const name = `${ d . getDate ( ) } _${ d . getMonth ( ) + 1 } _${ d . getFullYear ( ) } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } .${ ft . ext } ` ;
75- const strm = new stream . PassThrough ( ) ; strm . end ( buf ) ;
76- files . push ( { filename : name , stream : strm , mime : ft . mime , extension : ft . ext } ) ;
77+ const strm = new stream . PassThrough ( ) ;
78+ strm . end ( buf ) ;
79+ files . push ( {
80+ filename : name ,
81+ stream : strm ,
82+ mime : ft . mime ,
83+ extension : ft . ext ,
84+ } ) ;
7785 }
7886 return { mode, query, files } ;
7987} ;
8088
8189const mega = new MegaClient ( config ) ;
8290
83- app . post ( ' /upload' , async ( req : FastifyRequest , rep : FastifyReply ) => {
91+ app . post ( " /upload" , async ( req : FastifyRequest , rep : FastifyReply ) => {
8492 if ( ! authOK ( req ) ) {
85- const h = req . headers [ 'authorization' ] ;
86- return rep . code ( h ? 403 : 401 ) . send ( { error : h ? 'Invalid Auth' : 'Missing Auth' } ) ;
93+ const h = req . headers [ "authorization" ] ;
94+ return rep
95+ . code ( h ? 403 : 401 )
96+ . send ( { error : h ? "Invalid Auth" : "Missing Auth" } ) ;
8797 }
8898 try {
8999 const { mode, query, files } = await parseParts ( req ) ;
90- const ups = await Promise . all ( files . map ( f => mega . uploadFile ( f . filename , f . stream , mode as UploadMode , query ) ) ) ;
100+ const ups = await Promise . all (
101+ files . map ( ( f ) =>
102+ mega . uploadFile ( f . filename , f . stream , mode as UploadMode , query ) ,
103+ ) ,
104+ ) ;
91105
92106 if ( config . autoDelete ?. enable ) {
93- ups . forEach ( u => mega . scheduleDelete ( u . name , config . autoDelete ! . minutes ) ) ;
107+ ups . forEach ( ( u ) =>
108+ mega . scheduleDelete ( u . name , config . autoDelete ! . minutes ) ,
109+ ) ;
94110 }
95111
96112 const o = origin ( req ) ;
97113 const out : UploadResponse = { success : true , files : [ ] } ;
98114
99115 for ( let i = 0 ; i < ups . length ; i ++ ) {
100- const u = ups [ i ] , f = files [ i ] ;
116+ const u = ups [ i ] ,
117+ f = files [ i ] ;
101118 if ( config . FILENAMES && db . supportsCustomFilenames ( ) ) {
102- const custom = crypto . randomBytes ( 4 ) . toString ( 'hex' ) + '.' + f . extension ;
103- await db . saveCustomFile ( { customFileName : custom , originalMegaUrl : u . url , fileExtension : f . extension } ) ;
104- out . files . push ( { url : `${ o } /file/${ custom } ` , name : u . name , size : u . size , formattedSize : formatBytes ( u . size ) , mime : u . mime } ) ;
119+ const custom =
120+ crypto . randomBytes ( 4 ) . toString ( "hex" ) + "." + f . extension ;
121+ await db . saveCustomFile ( {
122+ customFileName : custom ,
123+ originalMegaUrl : u . url ,
124+ fileExtension : f . extension ,
125+ } ) ;
126+ out . files . push ( {
127+ url : `${ o } /file/${ custom } ` ,
128+ name : u . name ,
129+ size : u . size ,
130+ formattedSize : formatBytes ( u . size ) ,
131+ mime : u . mime ,
132+ } ) ;
105133 } else {
106- const url = `${ o } /media/${ u . url . replace ( / ^ h t t p s : \/ \/ m e g a \. n z \/ f i l e \/ / , '' ) . replace ( '#' , '@' ) } ` ;
107- out . files . push ( { url, name : u . name , size : u . size , formattedSize : formatBytes ( u . size ) , mime : u . mime } ) ;
134+ const url = `${ o } /media/${ u . url . replace ( / ^ h t t p s : \/ \/ m e g a \. n z \/ f i l e \/ / , "" ) . replace ( "#" , "@" ) } ` ;
135+ out . files . push ( {
136+ url,
137+ name : u . name ,
138+ size : u . size ,
139+ formattedSize : formatBytes ( u . size ) ,
140+ mime : u . mime ,
141+ } ) ;
108142 }
109143 }
110144 if ( config . autoDelete ?. enable ) {
111145 const sec = config . autoDelete . minutes * 60 ;
112- out . files . forEach ( f => { f . expires = `${ sec } s` ; f . formattedExpires = runtime ( sec ) ; } ) ;
146+ out . files . forEach ( ( f ) => {
147+ f . expires = `${ sec } s` ;
148+ f . formattedExpires = runtime ( sec ) ;
149+ } ) ;
113150 }
114151 rep . send ( out ) ;
115152 } catch ( err : any ) {
@@ -118,64 +155,153 @@ app.post('/upload', async (req: FastifyRequest, rep: FastifyReply) => {
118155 }
119156} ) ;
120157
121- app . get ( ' /file/:filename' , async ( req , rep ) => {
158+ app . get ( " /file/:filename" , async ( req , rep ) => {
122159 const { filename } = req . params as any ;
123- if ( ! config . FILENAMES || ! db . supportsCustomFilenames ( ) ) return rep . code ( 404 ) . send ( { error : 'Not enabled' } ) ;
160+ if ( ! config . FILENAMES || ! db . supportsCustomFilenames ( ) )
161+ return rep . code ( 404 ) . send ( { error : "Not enabled" } ) ;
124162 const cf = await db . getCustomFile ( filename ) ;
125- if ( ! cf ) return rep . code ( 404 ) . send ( { error : 'Not found' } ) ;
126- const h = cf . originalMegaUrl . replace ( / ^ h t t p s : \/ \/ m e g a \. n z \/ f i l e \/ / , '' ) . replace ( '#' , '@' ) . replace ( '@' , '#' ) ;
127- const url = 'https://mega.nz/file/' + h ;
163+ if ( ! cf ) return rep . code ( 404 ) . send ( { error : "Not found" } ) ;
164+ const h = cf . originalMegaUrl
165+ . replace ( / ^ h t t p s : \/ \/ m e g a \. n z \/ f i l e \/ / , "" )
166+ . replace ( "#" , "@" )
167+ . replace ( "@" , "#" ) ;
168+ const url = "https://mega.nz/file/" + h ;
128169 const file = Mega . File . fromURL ( url ) ;
129170 await file . loadAttributes ( ) ;
130- rep . header ( ' Content-Type' , ( file as any ) . mime || ' application/octet-stream' ) ;
131- rep . header ( ' Content-Disposition' , `inline; filename="${ filename } "` ) ;
171+ rep . header ( " Content-Type" , ( file as any ) . mime || " application/octet-stream" ) ;
172+ rep . header ( " Content-Disposition" , `inline; filename="${ filename } "` ) ;
132173 return rep . send ( file . download ( { } ) ) ;
133174} ) ;
134175
135- app . get ( ' /media/*' , async ( req , rep ) => {
136- const h = ( req . params as any ) [ '*' ] . replace ( '@' , '#' ) ;
137- const file = Mega . File . fromURL ( ' https://mega.nz/file/' + h ) ;
176+ app . get ( " /media/*" , async ( req , rep ) => {
177+ const h = ( req . params as any ) [ "*" ] . replace ( "@" , "#" ) ;
178+ const file = Mega . File . fromURL ( " https://mega.nz/file/" + h ) ;
138179 await file . loadAttributes ( ) ;
139- rep . header ( ' Content-Type' , ( file as any ) . mime || ' application/octet-stream' ) ;
140- rep . header ( ' Content-Disposition' , `inline; filename="${ file . name } "` ) ;
180+ rep . header ( " Content-Type" , ( file as any ) . mime || " application/octet-stream" ) ;
181+ rep . header ( " Content-Disposition" , `inline; filename="${ file . name } "` ) ;
141182 return rep . send ( file . download ( { } ) ) ;
142183} ) ;
143184
144- app . get ( '/info' , ( _ , rep ) => rep . send ( {
145- request_limit : config . rateLimit . max ,
146- rate_limit : config . rateLimit . timeWindow ,
147- file_size : config . server . maxFileSize ,
148- max_files : config . server . maxFiles ,
149- ...( config . autoDelete ?. enable && { auto_delete_time : config . autoDelete . minutes } ) ,
150- } ) ) ;
151-
152- app . get ( '/health' , ( _ , rep ) => rep . send ( {
153- status : 'ok' ,
154- timestamp : new Date ( ) . toISOString ( ) ,
155- database : db . isConnected ( ) ? 'connected' : 'disconnected' ,
156- database_type : db . getDbType ( ) ,
157- custom_filenames : config . FILENAMES && db . supportsCustomFilenames ( ) ,
158- } ) ) ;
185+ app . delete ( "/delete/*" , async ( req : FastifyRequest , rep : FastifyReply ) => {
186+ if ( ! authOK ( req ) ) {
187+ const h = req . headers [ "authorization" ] ;
188+ return rep
189+ . code ( h ? 403 : 401 )
190+ . send ( { error : h ? "Invalid Auth" : "Missing Auth" } ) ;
191+ }
192+ try {
193+ const path = ( req . params as any ) [ "*" ] ;
194+ let fileName : string ;
195+ let dltdb = false ;
196+
197+ if ( path . startsWith ( "file/" ) ) {
198+ const id = path . replace ( "file/" , "" ) ;
199+
200+ if ( config . FILENAMES && db . supportsCustomFilenames ( ) ) {
201+ const customFile = await db . getCustomFile ( id ) ;
202+ if ( ! customFile ) {
203+ return rep . code ( 404 ) . send ( { error : "File not found in database" } ) ;
204+ }
205+
206+ try {
207+ fileName = await mega . getFileNameFromUrl ( customFile . originalMegaUrl ) ;
208+ const fileDeleted = await mega . deleteFileByName ( fileName ) ;
209+
210+ if ( ! fileDeleted ) {
211+ await db . deleteCustomFile ( id ) ;
212+ return rep
213+ . code ( 404 )
214+ . send ( {
215+ error : "File not found in any acc deleted it from database" ,
216+ } ) ;
217+ }
218+
219+ await db . deleteCustomFile ( id ) ;
220+ dltdb = true ;
221+ } catch ( error ) {
222+ await db . deleteCustomFile ( id ) ;
223+ return rep
224+ . code ( 404 )
225+ . send ( { error : "File not found on acc deleted it from database" } ) ;
226+ }
227+ } else {
228+ return rep . code ( 400 ) . send ( { error : "Custom filenames not enabled" } ) ;
229+ }
230+ } else if ( path . startsWith ( "media/" ) ) {
231+ const hash = path . replace ( "media/" , "" ) . replace ( "@" , "#" ) ;
232+ const megaUrl = `https://mega.nz/file/${ hash } ` ;
233+
234+ try {
235+ fileName = await mega . getFileNameFromUrl ( megaUrl ) ;
236+ const fileDeleted = await mega . deleteFileByName ( fileName ) ;
237+ if ( ! fileDeleted ) {
238+ return rep . code ( 404 ) . send ( { error : "File not found in any account" } ) ;
239+ }
240+ } catch ( error ) {
241+ return rep . code ( 404 ) . send ( { error : "File not found on Mega" } ) ;
242+ }
243+ } else {
244+ return rep . code ( 400 ) . send ( { error : "Invalid path." } ) ;
245+ }
246+
247+ rep . send ( {
248+ success : true ,
249+ message : "File deleted successfully" ,
250+ deletedFrom : { megaAcc : true , database : dltdb } ,
251+ } ) ;
252+ } catch ( err : any ) {
253+ console . error ( "Delete error:" , err ) ;
254+ rep . code ( 400 ) . send ( { error : err . message || "Failed to delete file" } ) ;
255+ }
256+ } ) ;
257+
258+ app . get ( "/info" , ( _ , rep ) =>
259+ rep . send ( {
260+ request_limit : config . rateLimit . max ,
261+ rate_limit : config . rateLimit . timeWindow ,
262+ file_size : config . server . maxFileSize ,
263+ max_files : config . server . maxFiles ,
264+ ...( config . autoDelete ?. enable && {
265+ auto_delete_time : config . autoDelete . minutes ,
266+ } ) ,
267+ } ) ,
268+ ) ;
269+
270+ app . get ( "/health" , ( _ , rep ) =>
271+ rep . send ( {
272+ status : "ok" ,
273+ timestamp : new Date ( ) . toISOString ( ) ,
274+ database : db . isConnected ( ) ? "connected" : "disconnected" ,
275+ database_type : db . getDbType ( ) ,
276+ custom_filenames : config . FILENAMES && db . supportsCustomFilenames ( ) ,
277+ } ) ,
278+ ) ;
159279
160280const shutdown = async ( s : string ) => {
161281 console . log ( `Got ${ s } , shutting down...` ) ;
162- try { await mega . cleanup ( ) ; await app . close ( ) ; process . exit ( 0 ) ; }
163- catch ( err ) { console . error ( err ) ; process . exit ( 1 ) ; }
282+ try {
283+ await mega . cleanup ( ) ;
284+ await app . close ( ) ;
285+ process . exit ( 0 ) ;
286+ } catch ( err ) {
287+ console . error ( err ) ;
288+ process . exit ( 1 ) ;
289+ }
164290} ;
165291
166- process . on ( ' SIGTERM' , ( ) => shutdown ( ' SIGTERM' ) ) ;
167- process . on ( ' SIGINT' , ( ) => shutdown ( ' SIGINT' ) ) ;
292+ process . on ( " SIGTERM" , ( ) => shutdown ( " SIGTERM" ) ) ;
293+ process . on ( " SIGINT" , ( ) => shutdown ( " SIGINT" ) ) ;
168294
169295const start = async ( ) => {
170296 try {
171297 await mega . initialize ( ) ;
172- console . log ( ' Instance ready' ) ;
173- await app . listen ( { port : config . server . port , host : ' 0.0.0.0' } ) ;
298+ console . log ( " Instance ready" ) ;
299+ await app . listen ( { port : config . server . port , host : " 0.0.0.0" } ) ;
174300 console . log ( `Server at:${ config . server . port } ` ) ;
175301 } catch ( err ) {
176302 console . error ( err ) ;
177303 process . exit ( 1 ) ;
178304 }
179305} ;
180306
181- start ( ) ;
307+ start ( ) ;
0 commit comments