imported flash card

This commit is contained in:
2026-04-16 15:08:05 +07:00
parent 088c555515
commit 3e0b3f6a6d
87 changed files with 209283 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
/**
* Import flashcard data from JSON files into Supabase.
*
* Usage:
* SUPABASE_URL=https://xxx.supabase.co SUPABASE_SERVICE_ROLE_KEY=ey... npx tsx supabase/scripts/import-flashcards.ts
*
* Requires: @supabase/supabase-js (already in project deps)
*/
import { createClient } from "@supabase/supabase-js"
import { readFileSync, existsSync } from "fs"
import { resolve, dirname } from "path"
import { fileURLToPath } from "url"
const __dirname = dirname(fileURLToPath(import.meta.url))
const DATA_DIR = resolve(__dirname, "../data")
const supabaseUrl = process.env.SUPABASE_URL
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!supabaseUrl || !serviceRoleKey) {
console.error("Missing env vars: SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY")
process.exit(1)
}
const supabase = createClient(supabaseUrl, serviceRoleKey)
interface RawList {
id: string
title: string
is_public: boolean
type: "parent" | "term"
parent_id: string | null
child_ids: string[]
}
interface RawTerm {
word: string
part_of_speech: string | null
phonetic: string | null
definition: string | null
example: string | null
image_url: string | null
audio_tts_text: string | null
audio_lang: string | null
display_order: number
}
function readJson<T>(path: string): T {
return JSON.parse(readFileSync(path, "utf-8"))
}
async function importLists(lists: RawList[]) {
// Insert parent lists first (parent_id = null), then children
const parents = lists.filter((l) => l.parent_id === null)
const children = lists.filter((l) => l.parent_id !== null)
for (const batch of [parents, children]) {
const rows = batch.map((l) => ({
id: Number(l.id),
title: l.title,
is_public: l.is_public,
type: l.type,
parent_id: l.parent_id ? Number(l.parent_id) : null,
}))
if (rows.length === 0) continue
const { error } = await supabase.from("flashcard_list").upsert(rows, {
onConflict: "id",
})
if (error) {
console.error(`Error inserting lists batch:`, error.message)
} else {
console.log(`Imported ${rows.length} lists`)
}
}
}
async function importTerms(listId: number) {
const filePath = resolve(DATA_DIR, `terms/flashcard_terms_${listId}.json`)
if (!existsSync(filePath)) {
console.log(` No terms file for list ${listId}, skipping`)
return 0
}
const terms = readJson<RawTerm[]>(filePath)
if (terms.length === 0) {
console.log(` List ${listId}: empty, skipping`)
return 0
}
const rows = terms.map((t) => ({
list_id: listId,
word: t.word,
part_of_speech: t.part_of_speech,
phonetic: t.phonetic,
definition: t.definition,
example: t.example,
image_url: t.image_url,
audio_tts_text: t.audio_tts_text,
audio_lang: t.audio_lang || "en-US",
display_order: t.display_order,
}))
// Supabase has a row limit per request, batch in chunks of 500
const BATCH_SIZE = 500
let imported = 0
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
const chunk = rows.slice(i, i + BATCH_SIZE)
const { error } = await supabase.from("flashcard_term").insert(chunk)
if (error) {
console.error(` List ${listId} batch ${i}: ${error.message}`)
} else {
imported += chunk.length
}
}
console.log(` List ${listId}: ${imported}/${terms.length} terms`)
return imported
}
async function updateTotalWords(listId: number) {
const { count } = await supabase
.from("flashcard_term")
.select("*", { count: "exact", head: true })
.eq("list_id", listId)
if (count !== null) {
await supabase
.from("flashcard_list")
.update({ total_words: count })
.eq("id", listId)
}
}
async function main() {
console.log("=== Flashcard Import ===\n")
// 1. Import lists
const listsPath = resolve(DATA_DIR, "flashcard_lists.json")
const lists = readJson<RawList[]>(listsPath)
console.log(`Found ${lists.length} lists`)
await importLists(lists)
// 2. Import terms for each list
console.log("\nImporting terms...")
let totalTerms = 0
for (const list of lists) {
const count = await importTerms(Number(list.id))
totalTerms += count
}
// 3. Update total_words counts
console.log("\nUpdating word counts...")
for (const list of lists) {
await updateTotalWords(Number(list.id))
}
console.log(`\nDone! ${lists.length} lists, ${totalTerms} terms imported.`)
}
main().catch((err) => {
console.error("Fatal:", err)
process.exit(1)
})