@@ -8,6 +8,7 @@ import { Client } from "@gradio/client";
88import yazl from "yazl" ;
99import { downloadFile } from "$lib/server/files/downloadFile" ;
1010import mimeTypes from "mime-types" ;
11+ import { logger } from "$lib/server/logger" ;
1112
1213export interface FeatureFlags {
1314 searchEnabled : boolean ;
@@ -120,6 +121,32 @@ export const misc = new Elysia()
120121 throw new Error ( "Data export is not enabled" ) ;
121122 }
122123
124+ const nExports = await collections . messageEvents . countDocuments ( {
125+ userId : locals . user . _id ,
126+ type : "export" ,
127+ expiresAt : { $gt : new Date ( ) } ,
128+ } ) ;
129+
130+ if ( nExports >= 1 ) {
131+ throw new Error (
132+ "You have already exported your data recently. Please wait 1 hour before exporting again."
133+ ) ;
134+ }
135+
136+ const stats : {
137+ nConversations : number ;
138+ nMessages : number ;
139+ nAssistants : number ;
140+ nAvatars : number ;
141+ nFiles : number ;
142+ } = {
143+ nConversations : 0 ,
144+ nMessages : 0 ,
145+ nFiles : 0 ,
146+ nAssistants : 0 ,
147+ nAvatars : 0 ,
148+ } ;
149+
123150 const zipfile = new yazl . ZipFile ( ) ;
124151
125152 const promises = [
@@ -129,8 +156,10 @@ export const misc = new Elysia()
129156 . then ( async ( conversations ) => {
130157 const formattedConversations = await Promise . all (
131158 conversations . map ( async ( conversation ) => {
159+ stats . nConversations ++ ;
132160 const hashes : string [ ] = [ ] ;
133161 conversation . messages . forEach ( async ( message ) => {
162+ stats . nMessages ++ ;
134163 if ( message . files ) {
135164 message . files . forEach ( ( file ) => {
136165 hashes . push ( file . value ) ;
@@ -152,12 +181,13 @@ export const misc = new Elysia()
152181 files . forEach ( ( file ) => {
153182 if ( ! file ) return ;
154183
155- const extension = mimeTypes . extension ( file . mime ) || "bin" ;
184+ const extension = mimeTypes . extension ( file . mime ) || null ;
156185 const convId = conversation . _id . toString ( ) ;
157186 const fileId = file . name . split ( "-" ) [ 1 ] . slice ( 0 , 8 ) ;
158- const fileName = `file-${ convId } -${ fileId } .${ extension } ` ;
187+ const fileName = `file-${ convId } -${ fileId } ` + ( extension ? ` .${ extension } ` : "" ) ;
159188 filenames . push ( fileName ) ;
160189 zipfile . addBuffer ( Buffer . from ( file . value , "base64" ) , fileName ) ;
190+ stats . nFiles ++ ;
161191 } ) ;
162192
163193 return {
@@ -212,8 +242,11 @@ export const misc = new Elysia()
212242 if ( ! content ) return ;
213243
214244 zipfile . addBuffer ( content , `avatar-${ assistant . _id . toString ( ) } .jpg` ) ;
245+ stats . nAvatars ++ ;
215246 }
216247
248+ stats . nAssistants ++ ;
249+
217250 return {
218251 _id : assistant . _id . toString ( ) ,
219252 name : assistant . name ,
@@ -241,9 +274,24 @@ export const misc = new Elysia()
241274 } ) ,
242275 ] ;
243276
244- await Promise . all ( promises ) ;
245-
246- zipfile . end ( ) ;
277+ Promise . all ( promises ) . then ( async ( ) => {
278+ logger . info (
279+ {
280+ userId : locals . user ?. _id ,
281+ ...stats ,
282+ } ,
283+ "Exported user data"
284+ ) ;
285+ zipfile . end ( ) ;
286+ if ( locals . user ?. _id ) {
287+ await collections . messageEvents . insertOne ( {
288+ userId : locals . user ?. _id ,
289+ type : "export" ,
290+ createdAt : new Date ( ) ,
291+ expiresAt : new Date ( Date . now ( ) + 1000 * 60 * 60 ) , // 1 hour
292+ } ) ;
293+ }
294+ } ) ;
247295
248296 // @ts -expect-error - zipfile.outputStream is not typed correctly
249297 return new Response ( zipfile . outputStream , {
0 commit comments