@@ -25,6 +25,10 @@ import {
2525 isAgeArmor ,
2626} from '@/utils/age' ;
2727
28+ const GIST_TOKEN_PATH = 'settings.gistToken' ;
29+ const GIST_DOWNLOAD_TOKEN_STRATEGY_PATH =
30+ 'settings.gistDownloadTokenStrategy' ;
31+
2832export default function register ( $app ) {
2933 // utils
3034 $app . get ( '/api/utils/env' , getEnv ) ; // get runtime environment
@@ -211,9 +215,36 @@ async function decryptGistBackupContent(content, settings, encoding) {
211215 return decryptArmorIfPresent ( content , ageSecretKey ) ;
212216}
213217
214- async function gistBackupAction ( action , keep , encode ) {
215- // read token
216- const { gistToken, syncPlatform } = $ . read ( SETTINGS_KEY ) ;
218+ function resolveGistDownloadTokenStrategy (
219+ storedStrategy ,
220+ queryStrategy ,
221+ keep ,
222+ ) {
223+ if ( queryStrategy !== undefined ) {
224+ if ( queryStrategy !== 'overwrite' && queryStrategy !== 'keep' ) {
225+ throw new RequestInvalidError (
226+ 'INVALID_GIST_DOWNLOAD_TOKEN_STRATEGY' ,
227+ 'Token 处理方式仅支持 overwrite 或 keep' ,
228+ ) ;
229+ }
230+ return queryStrategy ;
231+ }
232+
233+ if ( keep !== undefined ) {
234+ return String ( keep ) . split ( ',' ) . includes ( GIST_TOKEN_PATH )
235+ ? 'keep'
236+ : 'overwrite' ;
237+ }
238+
239+ return storedStrategy === 'keep' ? 'keep' : 'overwrite' ;
240+ }
241+
242+ async function gistBackupAction (
243+ action ,
244+ { keep, encode, tokenStrategy : queryTokenStrategy } = { } ,
245+ ) {
246+ const settings = $ . read ( SETTINGS_KEY ) ;
247+ const { gistToken, syncPlatform } = settings ;
217248 if ( ! gistToken ) throw new Error ( 'GitHub Token is required for backup!' ) ;
218249
219250 const gist = new Gist ( {
@@ -223,14 +254,21 @@ async function gistBackupAction(action, keep, encode) {
223254 } ) ;
224255 let currentContent = readCurrentBackupContent ( ) ;
225256 let content ;
226- const settings = $ . read ( SETTINGS_KEY ) ;
227257 const updated = settings . syncTime ;
228258
229259 const encoding = normalizeGistBackupEncoding (
230260 encode || settings . gistUpload || 'base64' ,
231261 ) ;
262+ const tokenStrategy =
263+ action === 'download'
264+ ? resolveGistDownloadTokenStrategy (
265+ settings . gistDownloadTokenStrategy ,
266+ queryTokenStrategy ,
267+ keep ,
268+ )
269+ : undefined ;
232270 $ . info (
233- `Gist backup action: ${ action } , keep: ${ keep } , encode: ${ encode } , settings encode: ${ settings . gistUpload } , final encoding: ${ encoding } ` ,
271+ `Gist backup action: ${ action } , keep: ${ keep } , token strategy: ${ queryTokenStrategy } , settings token strategy: ${ settings . gistDownloadTokenStrategy } , final token strategy: ${ tokenStrategy } , encode: ${ encode } , settings encode: ${ settings . gistUpload } , final encoding: ${ encoding } ` ,
234272 ) ;
235273 switch ( action ) {
236274 case 'upload' :
@@ -288,7 +326,7 @@ async function gistBackupAction(action, keep, encode) {
288326 throw err ;
289327 }
290328 break ;
291- case 'download' :
329+ case 'download' : {
292330 $ . info ( `还原备份中...` ) ;
293331 content = await gist . download ( GIST_BACKUP_FILE_NAME ) ;
294332 content = await decryptGistBackupContent (
@@ -316,12 +354,32 @@ async function gistBackupAction(action, keep, encode) {
316354 throw new Error ( 'Gist 备份文件校验失败, 无法还原' ) ;
317355 }
318356 }
319- if ( keep ) {
320- $ . info ( `保留原有设置 ${ keep } ` ) ;
321- keep . split ( ',' ) . forEach ( ( path ) => {
322- _ . set ( content , path , _ . get ( currentContent , path ) ) ;
323- } ) ;
357+ const keepPaths = keep
358+ ? String ( keep )
359+ . split ( ',' )
360+ . map ( ( path ) => path . trim ( ) )
361+ . filter ( Boolean )
362+ : [ ] ;
363+ const tokenPathIndex = keepPaths . indexOf ( GIST_TOKEN_PATH ) ;
364+ if ( tokenStrategy === 'keep' && tokenPathIndex === - 1 ) {
365+ keepPaths . push ( GIST_TOKEN_PATH ) ;
366+ } else if (
367+ tokenStrategy === 'overwrite' &&
368+ tokenPathIndex !== - 1
369+ ) {
370+ keepPaths . splice ( tokenPathIndex , 1 ) ;
324371 }
372+ if ( ! keepPaths . includes ( GIST_DOWNLOAD_TOKEN_STRATEGY_PATH ) ) {
373+ keepPaths . push ( GIST_DOWNLOAD_TOKEN_STRATEGY_PATH ) ;
374+ }
375+ $ . info ( `保留原有设置 ${ keepPaths } ` ) ;
376+ keepPaths . forEach ( ( path ) => {
377+ if ( _ . has ( currentContent , path ) ) {
378+ _ . set ( content , path , _ . get ( currentContent , path ) ) ;
379+ } else {
380+ _ . unset ( content , path ) ;
381+ }
382+ } ) ;
325383 // restore settings
326384 $ . write ( JSON . stringify ( content , null , ` ` ) , '#sub-store' ) ;
327385 if ( $ . env . isNode ) {
@@ -333,10 +391,11 @@ async function gistBackupAction(action, keep, encode) {
333391 $ . info ( `migration completed` ) ;
334392 $ . info ( `还原备份完成` ) ;
335393 break ;
394+ }
336395 }
337396}
338397async function gistBackup ( req , res ) {
339- const { action, keep, encode } = req . query ;
398+ const { action, keep, encode, tokenStrategy } = req . query ;
340399 // read token
341400 const { gistToken } = $ . read ( SETTINGS_KEY ) ;
342401 if ( ! gistToken ) {
@@ -349,19 +408,25 @@ async function gistBackup(req, res) {
349408 ) ;
350409 } else {
351410 try {
352- await gistBackupAction ( action , keep , encode ) ;
411+ await gistBackupAction ( action , {
412+ keep,
413+ encode,
414+ tokenStrategy,
415+ } ) ;
353416 success ( res ) ;
354417 } catch ( err ) {
355418 $ . error (
356419 `Failed to ${ action } gist data.\nReason: ${ err . message ?? err } ` ,
357420 ) ;
358421 failed (
359422 res ,
360- new InternalServerError (
361- 'BACKUP_FAILED' ,
362- `Failed to ${ action } gist data!` ,
363- `Reason: ${ err . message ?? err } ` ,
364- ) ,
423+ err instanceof RequestInvalidError
424+ ? err
425+ : new InternalServerError (
426+ 'BACKUP_FAILED' ,
427+ `Failed to ${ action } gist data!` ,
428+ `Reason: ${ err . message ?? err } ` ,
429+ ) ,
365430 ) ;
366431 }
367432 }
0 commit comments