암호화 마이그레이션 기능 삭제

This commit is contained in:
static
2026-03-10 19:33:12 +09:00
parent 3c1e98c872
commit c2874035ba
13 changed files with 141 additions and 589 deletions

View File

@@ -18,7 +18,7 @@
"devDependencies": {
"@eslint/compat": "^2.0.3",
"@eslint/js": "^9.39.4",
"@iconify-json/material-symbols": "^1.2.60",
"@iconify-json/material-symbols": "^1.2.61",
"@noble/hashes": "^2.0.1",
"@sveltejs/adapter-node": "^5.5.4",
"@sveltejs/kit": "^2.53.4",
@@ -35,7 +35,7 @@
"es-hangul": "^2.3.8",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.15.0",
"eslint-plugin-svelte": "^3.15.2",
"eslint-plugin-tailwindcss": "^3.18.2",
"exifreader": "^4.36.2",
"file-saver": "^2.0.5",
@@ -48,11 +48,11 @@
"prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.5.1",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.53.7",
"svelte": "^5.53.9",
"svelte-check": "^4.4.5",
"tailwindcss": "^3.4.19",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1",
"typescript-eslint": "^8.57.0",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1"
},

260
pnpm-lock.yaml generated
View File

@@ -43,23 +43,23 @@ importers:
specifier: ^9.39.4
version: 9.39.4
'@iconify-json/material-symbols':
specifier: ^1.2.60
version: 1.2.60
specifier: ^1.2.61
version: 1.2.61
'@noble/hashes':
specifier: ^2.0.1
version: 2.0.1
'@sveltejs/adapter-node':
specifier: ^5.5.4
version: 5.5.4(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))
version: 5.5.4(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.9)(typescript@5.9.3)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))
'@sveltejs/kit':
specifier: ^2.53.4
version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))
version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.9)(typescript@5.9.3)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))
'@sveltejs/vite-plugin-svelte':
specifier: ^6.2.4
version: 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))
version: 6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))
'@tanstack/svelte-virtual':
specifier: ^3.13.21
version: 3.13.21(svelte@5.53.7)
version: 3.13.21(svelte@5.53.9)
'@trpc/client':
specifier: ^11.12.0
version: 11.12.0(@trpc/server@11.12.0(typescript@5.9.3))(typescript@5.9.3)
@@ -94,8 +94,8 @@ importers:
specifier: ^10.1.8
version: 10.1.8(eslint@9.39.4(jiti@1.21.7))
eslint-plugin-svelte:
specifier: ^3.15.0
version: 3.15.0(eslint@9.39.4(jiti@1.21.7))(svelte@5.53.7)
specifier: ^3.15.2
version: 3.15.2(eslint@9.39.4(jiti@1.21.7))(svelte@5.53.9)
eslint-plugin-tailwindcss:
specifier: ^3.18.2
version: 3.18.2(tailwindcss@3.4.19(yaml@2.8.0))
@@ -128,16 +128,16 @@ importers:
version: 3.8.1
prettier-plugin-svelte:
specifier: ^3.5.1
version: 3.5.1(prettier@3.8.1)(svelte@5.53.7)
version: 3.5.1(prettier@3.8.1)(svelte@5.53.9)
prettier-plugin-tailwindcss:
specifier: ^0.7.2
version: 0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.7))(prettier@3.8.1)
version: 0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.9))(prettier@3.8.1)
svelte:
specifier: ^5.53.7
version: 5.53.7
specifier: ^5.53.9
version: 5.53.9
svelte-check:
specifier: ^4.4.5
version: 4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3)
version: 4.4.5(picomatch@4.0.3)(svelte@5.53.9)(typescript@5.9.3)
tailwindcss:
specifier: ^3.4.19
version: 3.4.19(yaml@2.8.0)
@@ -145,14 +145,14 @@ importers:
specifier: ^5.9.3
version: 5.9.3
typescript-eslint:
specifier: ^8.56.1
version: 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
specifier: ^8.57.0
version: 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
unplugin-icons:
specifier: ^23.0.1
version: 23.0.1(svelte@5.53.7)
version: 23.0.1(svelte@5.53.9)
vite:
specifier: ^7.3.1
version: 7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)
version: 7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)
packages:
@@ -389,8 +389,8 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@iconify-json/material-symbols@1.2.60':
resolution: {integrity: sha512-bmVrnz/9KJNh/j+nO3pyxzo0h8iENIKxv5RASDURhjKdPI53rRcKN7E2Gmwi25k9CBJ+IC6L9l0AGgngeqyRuw==}
'@iconify-json/material-symbols@1.2.61':
resolution: {integrity: sha512-zzk6utjXKTyUZxbtv6GQ6227WZDinN6xKcdMIM9F6CS+bmt1Zcqs8PKWKChcvexDkEe5zqkVqUsUhhNW/WaGzQ==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -692,8 +692,8 @@ packages:
'@types/node-schedule@2.1.8':
resolution: {integrity: sha512-k00g6Yj/oUg/CDC+MeLHUzu0+OFxWbIqrFfDiLi6OPKxTujvpv29mHGM8GtKr7B+9Vv92FcK/8mRqi1DK5f3hA==}
'@types/node@25.3.5':
resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==}
'@types/node@25.4.0':
resolution: {integrity: sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==}
'@types/pg@8.18.0':
resolution: {integrity: sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==}
@@ -704,63 +704,63 @@ packages:
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@typescript-eslint/eslint-plugin@8.56.1':
resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==}
'@typescript-eslint/eslint-plugin@8.57.0':
resolution: {integrity: sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.56.1
'@typescript-eslint/parser': ^8.57.0
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/parser@8.56.1':
resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==}
'@typescript-eslint/parser@8.57.0':
resolution: {integrity: sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/project-service@8.56.1':
resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==}
'@typescript-eslint/project-service@8.57.0':
resolution: {integrity: sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/scope-manager@8.56.1':
resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==}
'@typescript-eslint/scope-manager@8.57.0':
resolution: {integrity: sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.56.1':
resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==}
'@typescript-eslint/tsconfig-utils@8.57.0':
resolution: {integrity: sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/type-utils@8.56.1':
resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==}
'@typescript-eslint/type-utils@8.57.0':
resolution: {integrity: sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/types@8.56.1':
resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==}
'@typescript-eslint/types@8.57.0':
resolution: {integrity: sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.56.1':
resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==}
'@typescript-eslint/typescript-estree@8.57.0':
resolution: {integrity: sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/utils@8.56.1':
resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==}
'@typescript-eslint/utils@8.57.0':
resolution: {integrity: sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
'@typescript-eslint/visitor-keys@8.56.1':
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
'@typescript-eslint/visitor-keys@8.57.0':
resolution: {integrity: sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@xmldom/xmldom@0.9.8':
@@ -1047,8 +1047,8 @@ packages:
peerDependencies:
eslint: '>=7.0.0'
eslint-plugin-svelte@3.15.0:
resolution: {integrity: sha512-QKB7zqfuB8aChOfBTComgDptMf2yxiJx7FE04nneCmtQzgTHvY8UJkuh8J2Rz7KB9FFV9aTHX6r7rdYGvG8T9Q==}
eslint-plugin-svelte@3.15.2:
resolution: {integrity: sha512-k4Nsjs3bHujeEnnckoTM4mFYR1e8Mb9l2rTwNdmYiamA+Tjzn8X+2F+fuSP2w4VbXYhn2bmySyACQYdmUDW2Cg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.1 || ^9.0.0 || ^10.0.0
@@ -1168,8 +1168,8 @@ packages:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
flatted@3.4.0:
resolution: {integrity: sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==}
flatted@3.4.1:
resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==}
follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
@@ -1886,8 +1886,8 @@ packages:
svelte:
optional: true
svelte@5.53.7:
resolution: {integrity: sha512-uxck1KI7JWtlfP3H6HOWi/94soAl23jsGJkBzN2BAWcQng0+lTrRNhxActFqORgnO9BHVd1hKJhG+ljRuIUWfQ==}
svelte@5.53.9:
resolution: {integrity: sha512-MwDfWsN8qZzeP0jlQsWF4k/4B3csb3IbzCRggF+L/QqY7T8bbKvnChEo1cPZztF51HJQhilDbevWYl2LvXbquA==}
engines: {node: '>=18'}
tailwindcss@3.4.19:
@@ -1941,8 +1941,8 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
typescript-eslint@8.56.1:
resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==}
typescript-eslint@8.57.0:
resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -2239,7 +2239,7 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@iconify-json/material-symbols@1.2.60':
'@iconify-json/material-symbols@1.2.61':
dependencies:
'@iconify/types': 2.0.0
@@ -2405,19 +2405,19 @@ snapshots:
dependencies:
acorn: 8.16.0
'@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))':
'@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.9)(typescript@5.9.3)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))':
dependencies:
'@rollup/plugin-commonjs': 29.0.2(rollup@4.59.0)
'@rollup/plugin-json': 6.1.0(rollup@4.59.0)
'@rollup/plugin-node-resolve': 16.0.3(rollup@4.59.0)
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.9)(typescript@5.9.3)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))
rollup: 4.59.0
'@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))':
'@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.9)(typescript@5.9.3)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))':
dependencies:
'@standard-schema/spec': 1.1.0
'@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0)
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))
'@types/cookie': 0.6.0
acorn: 8.16.0
cookie: 0.6.0
@@ -2428,32 +2428,32 @@ snapshots:
mrmime: 2.0.1
set-cookie-parser: 3.0.1
sirv: 3.0.2
svelte: 5.53.7
vite: 7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)
svelte: 5.53.9
vite: 7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)
optionalDependencies:
typescript: 5.9.3
'@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))':
'@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))':
dependencies:
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))
obug: 2.1.1
svelte: 5.53.7
vite: 7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)
svelte: 5.53.9
vite: 7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)
'@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))':
'@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))':
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))
'@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)))(svelte@5.53.9)(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))
deepmerge: 4.3.1
magic-string: 0.30.21
obug: 2.1.1
svelte: 5.53.7
vite: 7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)
vitefu: 1.1.2(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0))
svelte: 5.53.9
vite: 7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)
vitefu: 1.1.2(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0))
'@tanstack/svelte-virtual@3.13.21(svelte@5.53.7)':
'@tanstack/svelte-virtual@3.13.21(svelte@5.53.9)':
dependencies:
'@tanstack/virtual-core': 3.13.21
svelte: 5.53.7
svelte: 5.53.9
'@tanstack/virtual-core@3.13.21': {}
@@ -2478,15 +2478,15 @@ snapshots:
'@types/node-schedule@2.1.8':
dependencies:
'@types/node': 25.3.5
'@types/node': 25.4.0
'@types/node@25.3.5':
'@types/node@25.4.0':
dependencies:
undici-types: 7.18.2
'@types/pg@8.18.0':
dependencies:
'@types/node': 25.3.5
'@types/node': 25.4.0
pg-protocol: 1.13.0
pg-types: 2.2.0
@@ -2494,14 +2494,14 @@ snapshots:
'@types/trusted-types@2.0.7': {}
'@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
'@typescript-eslint/eslint-plugin@8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.56.1
'@typescript-eslint/type-utils': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.56.1
'@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.57.0
'@typescript-eslint/type-utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.57.0
eslint: 9.39.4(jiti@1.21.7)
ignore: 7.0.5
natural-compare: 1.4.0
@@ -2510,41 +2510,41 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
'@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.56.1
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.56.1
'@typescript-eslint/scope-manager': 8.57.0
'@typescript-eslint/types': 8.57.0
'@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.57.0
debug: 4.4.3
eslint: 9.39.4(jiti@1.21.7)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.56.1(typescript@5.9.3)':
'@typescript-eslint/project-service@8.57.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3)
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3)
'@typescript-eslint/types': 8.57.0
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.56.1':
'@typescript-eslint/scope-manager@8.57.0':
dependencies:
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/visitor-keys': 8.56.1
'@typescript-eslint/types': 8.57.0
'@typescript-eslint/visitor-keys': 8.57.0
'@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)':
'@typescript-eslint/tsconfig-utils@8.57.0(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
'@typescript-eslint/type-utils@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/types': 8.57.0
'@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
debug: 4.4.3
eslint: 9.39.4(jiti@1.21.7)
ts-api-utils: 2.4.0(typescript@5.9.3)
@@ -2552,14 +2552,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.56.1': {}
'@typescript-eslint/types@8.57.0': {}
'@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)':
'@typescript-eslint/typescript-estree@8.57.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.56.1(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3)
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/visitor-keys': 8.56.1
'@typescript-eslint/project-service': 8.57.0(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.57.0(typescript@5.9.3)
'@typescript-eslint/types': 8.57.0
'@typescript-eslint/visitor-keys': 8.57.0
debug: 4.4.3
minimatch: 10.2.4
semver: 7.7.4
@@ -2569,20 +2569,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
'@typescript-eslint/utils@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
'@typescript-eslint/scope-manager': 8.56.1
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.57.0
'@typescript-eslint/types': 8.57.0
'@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
eslint: 9.39.4(jiti@1.21.7)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.56.1':
'@typescript-eslint/visitor-keys@8.57.0':
dependencies:
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/types': 8.57.0
eslint-visitor-keys: 5.0.1
'@xmldom/xmldom@0.9.8':
@@ -2865,7 +2865,7 @@ snapshots:
dependencies:
eslint: 9.39.4(jiti@1.21.7)
eslint-plugin-svelte@3.15.0(eslint@9.39.4(jiti@1.21.7))(svelte@5.53.7):
eslint-plugin-svelte@3.15.2(eslint@9.39.4(jiti@1.21.7))(svelte@5.53.9):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
'@jridgewell/sourcemap-codec': 1.5.5
@@ -2877,9 +2877,9 @@ snapshots:
postcss-load-config: 3.1.4(postcss@8.5.8)
postcss-safe-parser: 7.0.1(postcss@8.5.8)
semver: 7.7.4
svelte-eslint-parser: 1.6.0(svelte@5.53.7)
svelte-eslint-parser: 1.6.0(svelte@5.53.9)
optionalDependencies:
svelte: 5.53.7
svelte: 5.53.9
transitivePeerDependencies:
- ts-node
@@ -3012,10 +3012,10 @@ snapshots:
flat-cache@4.0.1:
dependencies:
flatted: 3.4.0
flatted: 3.4.1
keyv: 4.5.4
flatted@3.4.0: {}
flatted@3.4.1: {}
follow-redirects@1.15.11: {}
@@ -3457,16 +3457,16 @@ snapshots:
prelude-ls@1.2.1: {}
prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.7):
prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.9):
dependencies:
prettier: 3.8.1
svelte: 5.53.7
svelte: 5.53.9
prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.7))(prettier@3.8.1):
prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.9))(prettier@3.8.1):
dependencies:
prettier: 3.8.1
optionalDependencies:
prettier-plugin-svelte: 3.5.1(prettier@3.8.1)(svelte@5.53.7)
prettier-plugin-svelte: 3.5.1(prettier@3.8.1)(svelte@5.53.9)
prettier@3.8.1: {}
@@ -3590,19 +3590,19 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
svelte-check@4.4.5(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3):
svelte-check@4.4.5(picomatch@4.0.3)(svelte@5.53.9)(typescript@5.9.3):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
chokidar: 4.0.3
fdir: 6.5.0(picomatch@4.0.3)
picocolors: 1.1.1
sade: 1.8.1
svelte: 5.53.7
svelte: 5.53.9
typescript: 5.9.3
transitivePeerDependencies:
- picomatch
svelte-eslint-parser@1.6.0(svelte@5.53.7):
svelte-eslint-parser@1.6.0(svelte@5.53.9):
dependencies:
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -3612,9 +3612,9 @@ snapshots:
postcss-selector-parser: 7.1.1
semver: 7.7.4
optionalDependencies:
svelte: 5.53.7
svelte: 5.53.9
svelte@5.53.7:
svelte@5.53.9:
dependencies:
'@jridgewell/remapping': 2.3.5
'@jridgewell/sourcemap-codec': 1.5.5
@@ -3696,12 +3696,12 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
typescript-eslint@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3):
typescript-eslint@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/parser': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.57.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.57.0(eslint@9.39.4(jiti@1.21.7))(typescript@5.9.3)
eslint: 9.39.4(jiti@1.21.7)
typescript: 5.9.3
transitivePeerDependencies:
@@ -3713,7 +3713,7 @@ snapshots:
undici-types@7.18.2: {}
unplugin-icons@23.0.1(svelte@5.53.7):
unplugin-icons@23.0.1(svelte@5.53.9):
dependencies:
'@antfu/install-pkg': 1.1.0
'@iconify/utils': 3.1.0
@@ -3721,7 +3721,7 @@ snapshots:
obug: 2.1.1
unplugin: 2.3.11
optionalDependencies:
svelte: 5.53.7
svelte: 5.53.9
unplugin@2.3.11:
dependencies:
@@ -3744,7 +3744,7 @@ snapshots:
uuid@13.0.0: {}
vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0):
vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0):
dependencies:
esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.3)
@@ -3753,14 +3753,14 @@ snapshots:
rollup: 4.59.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 25.3.5
'@types/node': 25.4.0
fsevents: 2.3.3
jiti: 1.21.7
yaml: 2.8.0
vitefu@1.1.2(vite@7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)):
vitefu@1.1.2(vite@7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)):
optionalDependencies:
vite: 7.3.1(@types/node@25.3.5)(jiti@1.21.7)(yaml@2.8.0)
vite: 7.3.1(@types/node@25.4.0)(jiti@1.21.7)(yaml@2.8.0)
webpack-virtual-modules@0.6.2: {}

View File

@@ -11,7 +11,6 @@ type IntegrityErrorMessages =
| "Directory already favorited"
| "Directory not favorited"
| "File not found"
| "File is not legacy"
| "File not found in category"
| "File already added to category"
| "File already favorited"

View File

@@ -141,17 +141,6 @@ export const getAllFileIds = async (userId: number) => {
return files.map(({ id }) => id);
};
export const getLegacyFiles = async (userId: number, limit: number = 100) => {
const files = await db
.selectFrom("file")
.selectAll()
.where("user_id", "=", userId)
.where("encrypted_content_iv", "is not", null)
.limit(limit)
.execute();
return files.map(toFile);
};
export const getFilesWithoutThumbnail = async (userId: number, limit: number = 100) => {
const files = await db
.selectFrom("file")
@@ -416,51 +405,6 @@ export const unregisterFile = async (userId: number, fileId: number) => {
});
};
export const migrateFileContent = async (
trx: typeof db,
userId: number,
fileId: number,
newPath: string,
dekVersion: Date,
encContentHash: string,
) => {
const file = await trx
.selectFrom("file")
.select(["path", "data_encryption_key_version", "encrypted_content_iv"])
.where("id", "=", fileId)
.where("user_id", "=", userId)
.limit(1)
.forUpdate()
.executeTakeFirst();
if (!file) {
throw new IntegrityError("File not found");
} else if (file.data_encryption_key_version.getTime() !== dekVersion.getTime()) {
throw new IntegrityError("Invalid DEK version");
} else if (!file.encrypted_content_iv) {
throw new IntegrityError("File is not legacy");
}
await trx
.updateTable("file")
.set({
path: newPath,
encrypted_content_iv: null,
encrypted_content_hash: encContentHash,
})
.where("id", "=", fileId)
.where("user_id", "=", userId)
.execute();
await trx
.insertInto("file_log")
.values({
file_id: fileId,
timestamp: new Date(),
action: "migrate",
})
.execute();
return { oldPath: file.path };
};
export const addFileToCategory = async (fileId: number, categoryId: number) => {
await db.transaction().execute(async (trx) => {
try {

View File

@@ -3,7 +3,7 @@ import type { Ciphertext } from "./utils";
export interface UploadSessionTable {
id: string;
type: "file" | "thumbnail" | "migration";
type: "file" | "thumbnail";
user_id: number;
path: string;
bitmap: Buffer;

View File

@@ -26,8 +26,8 @@ interface FileUploadSession extends BaseUploadSession {
encLastModifiedAt: Ciphertext;
}
interface ThumbnailOrMigrationUploadSession extends BaseUploadSession {
type: "thumbnail" | "migration";
interface ThumbnailUploadSession extends BaseUploadSession {
type: "thumbnail";
fileId: number;
dekVersion: Date;
}
@@ -86,8 +86,8 @@ export const createFileUploadSession = async (
});
};
export const createThumbnailOrMigrationUploadSession = async (
params: Omit<ThumbnailOrMigrationUploadSession, "bitmap" | "uploadedChunks">,
export const createThumbnailUploadSession = async (
params: Omit<ThumbnailUploadSession, "bitmap" | "uploadedChunks">,
) => {
await db.transaction().execute(async (trx) => {
const file = await trx
@@ -164,7 +164,7 @@ export const getUploadSession = async (sessionId: string, userId: number) => {
expiresAt: session.expires_at,
fileId: session.file_id!,
dekVersion: session.data_encryption_key_version!,
} satisfies ThumbnailOrMigrationUploadSession;
} satisfies ThumbnailUploadSession;
}
};

View File

@@ -1,7 +0,0 @@
import { createCaller } from "$trpc/router.server";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async (event) => {
const files = await createCaller(event).file.listLegacy();
return { files };
};

View File

@@ -1,82 +0,0 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms";
import { TopBar } from "$lib/components/molecules";
import type { MaybeFileInfo } from "$lib/modules/filesystem";
import { masterKeyStore } from "$lib/stores";
import { sortEntries } from "$lib/utils";
import File from "./File.svelte";
import {
getMigrationState,
clearMigrationStates,
requestLegacyFiles,
requestFileMigration,
} from "./service.svelte";
let { data } = $props();
let fileInfos: MaybeFileInfo[] = $state([]);
let files = $derived(
fileInfos
.map((info) => ({
info,
state: getMigrationState(info.id),
}))
.filter((file) => file.state?.status !== "uploaded"),
);
const migrateAllFiles = () => {
files.forEach(({ info }) => {
if (info.exists) {
requestFileMigration(info);
}
});
};
onMount(async () => {
fileInfos = sortEntries(await requestLegacyFiles(data.files, $masterKeyStore?.get(1)?.key!));
});
$effect(() => clearMigrationStates);
</script>
<svelte:head>
<title>암호화 마이그레이션</title>
</svelte:head>
<TopBar title="암호화 마이그레이션" />
<FullscreenDiv>
{#if files.length > 0}
<div class="space-y-4 pb-4">
<p class="break-keep text-gray-800">
이전 버전의 ArkVault에서 업로드된 {files.length}개 파일을 다시 암호화할 수 있어요.
</p>
<div class="space-y-2">
{#each files as { info, state } (info.id)}
{#if info.exists}
<File
{info}
{state}
onclick={({ id }) => goto(`/file/${id}`)}
onMigrateClick={requestFileMigration}
/>
{/if}
{/each}
</div>
</div>
<BottomDiv>
<Button onclick={migrateAllFiles} class="w-full">모두 다시 암호화하기</Button>
</BottomDiv>
{:else}
<div class="flex flex-grow items-center justify-center">
<p class="text-gray-500">
{#if data.files.length === 0}
마이그레이션할 파일이 없어요.
{:else}
파일 목록을 불러오고 있어요.
{/if}
</p>
</div>
{/if}
</FullscreenDiv>

View File

@@ -1,52 +0,0 @@
<script module lang="ts">
const subtexts = {
queued: "대기 중",
downloading: "다운로드하는 중",
"upload-pending": "업로드를 기다리는 중",
uploaded: "",
error: "실패",
} as const;
</script>
<script lang="ts">
import { ActionEntryButton } from "$lib/components/atoms";
import { DirectoryEntryLabel } from "$lib/components/molecules";
import type { FileInfo } from "$lib/modules/filesystem";
import { formatDateTime, formatNetworkSpeed } from "$lib/utils";
import type { MigrationState } from "./service.svelte";
import IconSync from "~icons/material-symbols/sync";
type FileInfoWithExists = FileInfo & { exists: true };
interface Props {
info: FileInfoWithExists;
onclick: (file: FileInfo) => void;
onMigrateClick: (file: FileInfoWithExists) => void;
state: MigrationState | undefined;
}
let { info, onclick, onMigrateClick, state }: Props = $props();
let subtext = $derived.by(() => {
if (!state) {
return formatDateTime(info.createdAt ?? info.lastModifiedAt);
}
if (state.status === "uploading") {
const progress = Math.floor((state.progress ?? 0) * 100);
const speed = formatNetworkSpeed((state.rate ?? 0) * 8);
return `전송됨 ${progress}% · ${speed}`;
}
return subtexts[state.status] ?? state.status;
});
</script>
<ActionEntryButton
class="h-14"
onclick={() => onclick(info)}
actionButtonIcon={!state || state.status === "error" ? IconSync : undefined}
onActionButtonClick={() => onMigrateClick(info)}
actionButtonClass="text-gray-800"
>
<DirectoryEntryLabel type="file" name={info.name} {subtext} />
</ActionEntryButton>

View File

@@ -1,116 +0,0 @@
import { limitFunction } from "p-limit";
import { SvelteMap } from "svelte/reactivity";
import { CHUNK_SIZE } from "$lib/constants";
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
import { uploadBlob } from "$lib/modules/upload";
import { requestFileDownload } from "$lib/services/file";
import { HybridPromise, Scheduler } from "$lib/utils";
import { trpc } from "$trpc/client";
import type { RouterOutputs } from "$trpc/router.server";
export type MigrationStatus =
| "queued"
| "downloading"
| "upload-pending"
| "uploading"
| "uploaded"
| "error";
export interface MigrationState {
status: MigrationStatus;
progress?: number;
rate?: number;
}
const scheduler = new Scheduler();
const states = new SvelteMap<number, MigrationState>();
export const requestLegacyFiles = async (
filesRaw: RouterOutputs["file"]["listLegacy"],
masterKey: CryptoKey,
) => {
const files = await HybridPromise.all(
filesRaw.map((file) => getFileInfo(file.id, masterKey, { serverResponse: file })),
);
return files;
};
const createState = (status: MigrationStatus): MigrationState => {
const state = $state({ status });
return state;
};
export const getMigrationState = (fileId: number) => {
return states.get(fileId);
};
export const clearMigrationStates = () => {
for (const [id, state] of states) {
if (state.status === "uploaded" || state.status === "error") {
states.delete(id);
}
}
};
const requestFileUpload = limitFunction(
async (
state: MigrationState,
fileId: number,
fileBuffer: ArrayBuffer,
dataKey: CryptoKey,
dataKeyVersion: Date,
) => {
state.status = "uploading";
const { uploadId } = await trpc().upload.startMigrationUpload.mutate({
file: fileId,
chunks: Math.ceil(fileBuffer.byteLength / CHUNK_SIZE),
dekVersion: dataKeyVersion,
});
await uploadBlob(uploadId, new Blob([fileBuffer]), dataKey, {
onProgress(s) {
state.progress = s.progress;
state.rate = s.rate;
},
});
await trpc().upload.completeMigrationUpload.mutate({ uploadId });
state.status = "uploaded";
},
{ concurrency: 1 },
);
export const requestFileMigration = async (fileInfo: FileInfo) => {
let state = states.get(fileInfo.id);
if (state) {
if (state.status !== "error") return;
state.status = "queued";
state.progress = undefined;
state.rate = undefined;
} else {
state = createState("queued");
states.set(fileInfo.id, state);
}
try {
const dataKey = fileInfo.dataKey;
if (!dataKey) {
throw new Error("Data key not available");
}
let fileBuffer: ArrayBuffer | undefined;
await scheduler.schedule(
async () => {
state.status = "downloading";
fileBuffer = await requestFileDownload(fileInfo.id, dataKey.key, true);
return fileBuffer.byteLength;
},
() => requestFileUpload(state, fileInfo.id, fileBuffer!, dataKey.key, dataKey.version),
);
} catch (e) {
state.status = "error";
throw e;
}
};

View File

@@ -5,7 +5,6 @@
import IconStorage from "~icons/material-symbols/storage";
import IconImage from "~icons/material-symbols/image";
import IconLockReset from "~icons/material-symbols/lock-reset";
import IconPassword from "~icons/material-symbols/password";
import IconLogout from "~icons/material-symbols/logout";
@@ -42,13 +41,6 @@
>
썸네일
</MenuEntryButton>
<MenuEntryButton
onclick={() => goto("/settings/migration")}
icon={IconLockReset}
iconColor="text-teal-500"
>
암호화 마이그레이션
</MenuEntryButton>
</div>
<div class="space-y-2">
<p class="font-semibold">보안</p>

View File

@@ -118,26 +118,6 @@ const fileRouter = router({
}));
}),
listLegacy: roleProcedure["activeClient"].query(async ({ ctx }) => {
const files = await FileRepo.getLegacyFiles(ctx.session.userId);
return files.map((file) => ({
id: file.id,
isLegacy: true,
parent: file.parentId,
mekVersion: file.mekVersion,
dek: file.encDek,
dekVersion: file.dekVersion,
contentType: file.contentType,
name: file.encName.ciphertext,
nameIv: file.encName.iv,
createdAt: file.encCreatedAt?.ciphertext,
createdAtIv: file.encCreatedAt?.iv,
lastModifiedAt: file.encLastModifiedAt.ciphertext,
lastModifiedAtIv: file.encLastModifiedAt.iv,
isFavorite: file.isFavorite,
}));
}),
rename: roleProcedure["activeClient"]
.input(
z.object({

View File

@@ -173,7 +173,7 @@ const uploadRouter = router({
const { id, path } = await generateSessionId();
try {
await UploadRepo.createThumbnailOrMigrationUploadSession({
await UploadRepo.createThumbnailUploadSession({
id,
type: "thumbnail",
userId: ctx.session.userId,
@@ -251,112 +251,6 @@ const uploadRouter = router({
sessionLocks.delete(uploadId);
}
}),
startMigrationUpload: roleProcedure["activeClient"]
.input(
z.object({
file: z.int().positive(),
chunks: z.int().positive(),
dekVersion: z.date(),
}),
)
.mutation(async ({ ctx, input }) => {
const { id, path } = await generateSessionId();
try {
await UploadRepo.createThumbnailOrMigrationUploadSession({
id,
type: "migration",
userId: ctx.session.userId,
path,
totalChunks: input.chunks,
expiresAt: new Date(Date.now() + UPLOADS_EXPIRES),
fileId: input.file,
dekVersion: input.dekVersion,
});
return { uploadId: id };
} catch (e) {
await safeRecursiveRm(path);
if (e instanceof IntegrityError) {
if (e.message === "File not found") {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid file id" });
} else if (e.message === "File is not legacy") {
throw new TRPCError({ code: "BAD_REQUEST", message: e.message });
}
}
throw e;
}
}),
completeMigrationUpload: roleProcedure["activeClient"]
.input(
z.object({
uploadId: z.uuidv4(),
}),
)
.mutation(async ({ ctx, input }) => {
const { uploadId } = input;
if (sessionLocks.has(uploadId)) {
throw new TRPCError({ code: "CONFLICT", message: "Completion already in progress" });
} else {
sessionLocks.add(uploadId);
}
let filePath = "";
try {
const session = await UploadRepo.getUploadSession(uploadId, ctx.session.userId);
if (session?.type !== "migration") {
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid upload id" });
} else if (session.uploadedChunks < session.totalChunks) {
throw new TRPCError({ code: "BAD_REQUEST", message: "Upload not completed" });
}
filePath = `${env.libraryPath}/${ctx.session.userId}/${uuidv4()}`;
await mkdir(dirname(filePath), { recursive: true });
const hashStream = createHash("sha256");
const writeStream = createWriteStream(filePath, { flags: "wx", mode: 0o600 });
for (let i = 1; i <= session.totalChunks; i++) {
for await (const chunk of createReadStream(`${session.path}/${i}`)) {
hashStream.update(chunk);
writeStream.write(chunk);
}
}
await new Promise<void>((resolve, reject) => {
writeStream.end((e: any) => (e ? reject(e) : resolve()));
});
const hash = hashStream.digest("base64");
const oldPath = await db.transaction().execute(async (trx) => {
const { oldPath } = await FileRepo.migrateFileContent(
trx,
ctx.session.userId,
session.fileId,
filePath,
session.dekVersion!,
hash,
);
await UploadRepo.deleteUploadSession(trx, uploadId);
return oldPath;
});
await Promise.all([safeUnlink(oldPath), safeRecursiveRm(session.path)]);
} catch (e) {
await safeUnlink(filePath);
if (e instanceof IntegrityError && e.message === "File is not legacy") {
// File migrated after this upload started
throw new TRPCError({ code: "CONFLICT", message: e.message });
}
throw e;
} finally {
sessionLocks.delete(uploadId);
}
}),
});
export default uploadRouter;