Files
accounted/scripts/backfill-sie-files.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

131 lines
3.7 KiB
TypeScript

#!/usr/bin/env npx tsx
/**
* Backfill SIE file archival for existing imports.
*
* Accepts a directory of SIE files, computes SHA-256 hashes, matches against
* sie_imports.file_hash, uploads to Supabase Storage, and populates
* file_storage_path.
*
* Usage: npx tsx scripts/backfill-sie-files.ts <directory-of-sie-files>
*/
import { config } from 'dotenv'
config({ path: '.env.local' })
import { createClient } from '@supabase/supabase-js'
import { readdir, readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { createHash } from 'node:crypto'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!supabaseUrl || !serviceRoleKey) {
console.error('Missing NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY in .env')
process.exit(1)
}
const supabase = createClient(supabaseUrl, serviceRoleKey)
async function calculateFileHash(content: string): Promise<string> {
const hash = createHash('sha256')
hash.update(content)
return hash.digest('hex')
}
async function main() {
const dir = process.argv[2]
if (!dir) {
console.error('Usage: npx tsx scripts/backfill-sie-files.ts <directory-of-sie-files>')
process.exit(1)
}
// 1. Read all SIE files from directory
const files = await readdir(dir)
const sieFiles = files.filter(f => f.toLowerCase().endsWith('.se') || f.toLowerCase().endsWith('.si'))
if (sieFiles.length === 0) {
console.error(`No .se/.si files found in ${dir}`)
process.exit(1)
}
console.log(`Found ${sieFiles.length} SIE files in ${dir}`)
// 2. Get all existing imports without file_storage_path
const { data: imports, error: importError } = await supabase
.from('sie_imports')
.select('id, user_id, file_hash, filename, file_storage_path')
.is('file_storage_path', null)
if (importError) {
console.error('Failed to fetch imports:', importError.message)
process.exit(1)
}
if (!imports || imports.length === 0) {
console.log('No imports need backfilling.')
return
}
console.log(`Found ${imports.length} imports without archived files`)
// Build hash→import mapping
const hashToImport = new Map<string, typeof imports[number]>()
for (const imp of imports) {
if (imp.file_hash) {
hashToImport.set(imp.file_hash, imp)
}
}
// 3. Match files by hash and upload
let matched = 0
let uploaded = 0
for (const filename of sieFiles) {
const filePath = join(dir, filename)
const content = await readFile(filePath, 'utf-8')
const hash = await calculateFileHash(content)
const imp = hashToImport.get(hash)
if (!imp) {
console.log(` ${filename}: no matching import (hash: ${hash.substring(0, 12)}...)`)
continue
}
matched++
console.log(` ${filename} → import ${imp.id} (${imp.filename})`)
// Upload to storage
const storagePath = `${imp.user_id}/${imp.id}.se`
const fileBlob = new Blob([content], { type: 'text/plain; charset=cp437' })
const { error: uploadError } = await supabase.storage
.from('sie-files')
.upload(storagePath, fileBlob, { upsert: false })
if (uploadError) {
console.error(` Upload failed: ${uploadError.message}`)
continue
}
// Update import record
const { error: updateError } = await supabase
.from('sie_imports')
.update({ file_storage_path: storagePath })
.eq('id', imp.id)
if (updateError) {
console.error(` DB update failed: ${updateError.message}`)
continue
}
uploaded++
console.log(` Archived to ${storagePath}`)
}
console.log(`\nDone: ${matched} matched, ${uploaded} uploaded, ${sieFiles.length - matched} unmatched`)
}
main().catch(err => {
console.error(err)
process.exit(1)
})