@@ -8,6 +8,7 @@ import { Client } from "@gradio/client";
8
8
import yazl from "yazl" ;
9
9
import { downloadFile } from "$lib/server/files/downloadFile" ;
10
10
import mimeTypes from "mime-types" ;
11
+ import { logger } from "$lib/server/logger" ;
11
12
12
13
export interface FeatureFlags {
13
14
searchEnabled : boolean ;
@@ -120,6 +121,32 @@ export const misc = new Elysia()
120
121
throw new Error ( "Data export is not enabled" ) ;
121
122
}
122
123
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
+
123
150
const zipfile = new yazl . ZipFile ( ) ;
124
151
125
152
const promises = [
@@ -129,8 +156,10 @@ export const misc = new Elysia()
129
156
. then ( async ( conversations ) => {
130
157
const formattedConversations = await Promise . all (
131
158
conversations . map ( async ( conversation ) => {
159
+ stats . nConversations ++ ;
132
160
const hashes : string [ ] = [ ] ;
133
161
conversation . messages . forEach ( async ( message ) => {
162
+ stats . nMessages ++ ;
134
163
if ( message . files ) {
135
164
message . files . forEach ( ( file ) => {
136
165
hashes . push ( file . value ) ;
@@ -152,12 +181,13 @@ export const misc = new Elysia()
152
181
files . forEach ( ( file ) => {
153
182
if ( ! file ) return ;
154
183
155
- const extension = mimeTypes . extension ( file . mime ) || "bin" ;
184
+ const extension = mimeTypes . extension ( file . mime ) || null ;
156
185
const convId = conversation . _id . toString ( ) ;
157
186
const fileId = file . name . split ( "-" ) [ 1 ] . slice ( 0 , 8 ) ;
158
- const fileName = `file-${ convId } -${ fileId } .${ extension } ` ;
187
+ const fileName = `file-${ convId } -${ fileId } ` + ( extension ? ` .${ extension } ` : "" ) ;
159
188
filenames . push ( fileName ) ;
160
189
zipfile . addBuffer ( Buffer . from ( file . value , "base64" ) , fileName ) ;
190
+ stats . nFiles ++ ;
161
191
} ) ;
162
192
163
193
return {
@@ -212,8 +242,11 @@ export const misc = new Elysia()
212
242
if ( ! content ) return ;
213
243
214
244
zipfile . addBuffer ( content , `avatar-${ assistant . _id . toString ( ) } .jpg` ) ;
245
+ stats . nAvatars ++ ;
215
246
}
216
247
248
+ stats . nAssistants ++ ;
249
+
217
250
return {
218
251
_id : assistant . _id . toString ( ) ,
219
252
name : assistant . name ,
@@ -241,9 +274,24 @@ export const misc = new Elysia()
241
274
} ) ,
242
275
] ;
243
276
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
+ } ) ;
247
295
248
296
// @ts -expect-error - zipfile.outputStream is not typed correctly
249
297
return new Response ( zipfile . outputStream , {
0 commit comments