GitBucket
4.23.1
Toggle navigation
Sign in
Files
Branches
1
Releases
Issues
Pull requests
Labels
Priorities
Milestones
Wiki
Forks
hincoco
/
supabase-sample
Browse code
テーブル情報の更新
main
1 parent
818f76d
commit
fe9492b692639a999c20704bf9a275d11a6cf06f
Shinya Tomozumi
authored
on 31 May
Patch
Showing
2 changed files
src/app/api/export-csv/route.ts
src/app/api/ingest-csv/route.ts
Ignore Space
Show notes
View
src/app/api/export-csv/route.ts
// src/app/api/export-csv/route.ts import { NextResponse } from 'next/server'; import { createClient } from '@supabase/supabase-js'; import { stringify } from 'csv-stringify'; import fs from 'fs/promises'; // Node.jsのファイルシステムモジュール import path from 'path'; // Node.jsのパス操作モジュール // Supabaseクライアントの初期化 // データのエクスポートには、通常、読み取り権限のある anon key で十分ですが、 // Row Level Security (RLS) のポリシーに注意してください。 // RLSが有効な場合、ポリシーによってはデータが取得できません。 // 全データをエクスポートしたい場合は、SUPABASE_SERVICE_ROLE_KEY を使うのが確実です。 const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL; const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; // Service Role Keyを優先 if (!supabaseUrl || !supabaseKey) { throw new Error('Supabase URL or API Key is missing in environment variables.'); } const supabase = createClient(supabaseUrl, supabaseKey as string, { auth: { persistSession: false, }, }); // エクスポート先のフォルダ const exportDirPath = path.join(process.cwd(), 'export'); export async function GET(request: Request) { const { searchParams } = new URL(request.url); const tableNamesParam = searchParams.get('tables'); // URLパラメータからテーブル名を取得 (例: ?tables=users,products) // エクスポートしたいテーブル名の配列 // URLパラメータで指定されたテーブル名、またはデフォルトのリスト let tablesToExport: string[]; if (tableNamesParam) { tablesToExport = tableNamesParam.split(',').map(name => name.trim().toLowerCase()); } else { // デフォルトでエクスポートしたいテーブルのリスト // ここにあなたのテーブル名を追加してください。 // 例: ['users', 'serif', 'tests'] tablesToExport = ['users', 'serif', 'tests']; } const results: { tableName: string; status: string; insertedCount?: number; error?: string; details?: any; hint?: any; code?: any; rowCount?: Number; filePath?: string; }[] = []; try { // export フォルダが存在しない場合は作成 await fs.mkdir(exportDirPath, { recursive: true }); for (const tableName of tablesToExport) { console.log(`Exporting data from table: ${tableName}...`); const csvFilePath = path.join(exportDirPath, `${tableName}.csv`); try { // Supabaseからデータを取得 // RLSが有効な場合、適切なSELECTポリシーがないと空のデータが返るか、権限エラーになります。 const { data, error } = await supabase .from(tableName) .select('*'); // すべてのカラムを選択 if (error) { console.error(`Supabase fetch error for ${tableName}:`, error); results.push({ tableName, status: 'failed', error: error.message, details: (error as any).details || null, hint: (error as any).hint || null, code: (error as any).code || null, }); continue; // 次のテーブルへ } if (!data || data.length === 0) { console.log(`No data found in table: ${tableName}. Skipping CSV creation.`); results.push({ tableName, status: 'skipped', error: 'No data found' }); continue; } // CSV文字列に変換 // `stringify` は、データ配列とオプションを受け取る // ヘッダー行を自動で含めるために `header: true` を指定 const csvString = await new Promise<string>((resolve, reject) => { stringify(data, { header: true }, (err, output) => { if (err) { return reject(err); } resolve(output as string); }); }); // CSVファイルを保存 await fs.writeFile(csvFilePath, csvString, { encoding: 'utf8' }); console.log(`Successfully exported ${data.length} rows to ${csvFilePath}`); results.push({ tableName, status: 'success', rowCount: data.length, filePath: csvFilePath, }); } catch (tableExportError: any) { console.error(`Error exporting table ${tableName}:`, tableExportError); results.push({ tableName, status: 'failed', error: `Error processing table ${tableName}: ${tableExportError.message}`, }); } } return NextResponse.json({ message: 'CSV export process completed.', status: 'completed', results: results, }); } catch (error) { console.error('Unexpected error during batch CSV export:', error); return NextResponse.json( { message: 'An unexpected error occurred during batch CSV export', status: 'error', error: (error as Error).message }, { status: 500 } ); } }
// src/app/api/export-csv/route.ts import { NextResponse } from 'next/server'; import { createClient } from '@supabase/supabase-js'; import { stringify } from 'csv-stringify'; import fs from 'fs/promises'; // Node.jsのファイルシステムモジュール import path from 'path'; // Node.jsのパス操作モジュール // Supabaseクライアントの初期化 // データのエクスポートには、通常、読み取り権限のある anon key で十分ですが、 // Row Level Security (RLS) のポリシーに注意してください。 // RLSが有効な場合、ポリシーによってはデータが取得できません。 // 全データをエクスポートしたい場合は、SUPABASE_SERVICE_ROLE_KEY を使うのが確実です。 const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL; const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; // Service Role Keyを優先 if (!supabaseUrl || !supabaseKey) { throw new Error('Supabase URL or API Key is missing in environment variables.'); } const supabase = createClient(supabaseUrl, supabaseKey as string, { auth: { persistSession: false, }, }); // エクスポート先のフォルダ const exportDirPath = path.join(process.cwd(), 'export'); export async function GET(request: Request) { const { searchParams } = new URL(request.url); const tableNamesParam = searchParams.get('tables'); // URLパラメータからテーブル名を取得 (例: ?tables=users,products) // エクスポートしたいテーブル名の配列 // URLパラメータで指定されたテーブル名、またはデフォルトのリスト let tablesToExport: string[]; if (tableNamesParam) { tablesToExport = tableNamesParam.split(',').map(name => name.trim().toLowerCase()); } else { // デフォルトでエクスポートしたいテーブルのリスト // ここにあなたのテーブル名を追加してください。 // 例: ['users', 'serif', 'tests'] tablesToExport = ['users', 'serif', 'tests']; } const results: { tableName: string; status: string; rowCount?: number; filePath?: string; error?: string }[] = []; try { // export フォルダが存在しない場合は作成 await fs.mkdir(exportDirPath, { recursive: true }); for (const tableName of tablesToExport) { console.log(`Exporting data from table: ${tableName}...`); const csvFilePath = path.join(exportDirPath, `${tableName}.csv`); try { // Supabaseからデータを取得 // RLSが有効な場合、適切なSELECTポリシーがないと空のデータが返るか、権限エラーになります。 const { data, error } = await supabase .from(tableName) .select('*'); // すべてのカラムを選択 if (error) { console.error(`Supabase fetch error for ${tableName}:`, error); results.push({ tableName, status: 'failed', error: error.message, details: (error as any).details || null, hint: (error as any).hint || null, code: (error as any).code || null, }); continue; // 次のテーブルへ } if (!data || data.length === 0) { console.log(`No data found in table: ${tableName}. Skipping CSV creation.`); results.push({ tableName, status: 'skipped', error: 'No data found' }); continue; } // CSV文字列に変換 // `stringify` は、データ配列とオプションを受け取る // ヘッダー行を自動で含めるために `header: true` を指定 const csvString = await new Promise<string>((resolve, reject) => { stringify(data, { header: true }, (err, output) => { if (err) { return reject(err); } resolve(output as string); }); }); // CSVファイルを保存 await fs.writeFile(csvFilePath, csvString, { encoding: 'utf8' }); console.log(`Successfully exported ${data.length} rows to ${csvFilePath}`); results.push({ tableName, status: 'success', rowCount: data.length, filePath: csvFilePath, }); } catch (tableExportError: any) { console.error(`Error exporting table ${tableName}:`, tableExportError); results.push({ tableName, status: 'failed', error: `Error processing table ${tableName}: ${tableExportError.message}`, }); } } return NextResponse.json({ message: 'CSV export process completed.', status: 'completed', results: results, }); } catch (error) { console.error('Unexpected error during batch CSV export:', error); return NextResponse.json( { message: 'An unexpected error occurred during batch CSV export', status: 'error', error: (error as Error).message }, { status: 500 } ); } }
Ignore Space
Show notes
View
src/app/api/ingest-csv/route.ts
import { NextResponse } from 'next/server'; import { createClient } from '@supabase/supabase-js'; import { parse } from 'csv-parse'; import fs from 'fs/promises'; import path from 'path'; // Supabaseクライアントの初期化 (前回のものと同じ) const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY; const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; // 必要に応じてService Role Keyを使用 if (!supabaseUrl || (!supabaseAnonKey && !supabaseServiceRoleKey)) { throw new Error('Supabase URL or Anon/Service Role Key is missing in environment variables.'); } // サーバーサイド用のSupabaseクライアントを作成 // 本番環境では anonKey の代わりに serviceRoleKey を使うことを強く推奨します。 // 例: const supabase = createClient(supabaseUrl, supabaseServiceRoleKey || supabaseAnonKey, { ... }); const supabase = createClient(supabaseUrl, supabaseAnonKey as string, { // デバッグ用に anonKey を使用 auth: { persistSession: false, }, }); // CSVファイルをパースしてデータを取得するヘルパー関数 (前回のものと同じ) async function parseCsvFile(filePath: string): Promise<any[]> { const fileContent = await fs.readFile(filePath, { encoding: 'utf8' }); return new Promise((resolve, reject) => { parse( fileContent, { columns: true, // ヘッダー行をカラム名として使用 skip_empty_lines: true, }, (err, records) => { if (err) { return reject(err); } resolve(records); } ); }); } export async function GET(request: Request) { const results: { tableName: string; status: string; insertedCount?: number; error?: string; details?: any; hint?: any; code?: any }[] = []; try { const dataDirPath = path.join(process.cwd(), 'data'); let files: string[]; try { files = await fs.readdir(dataDirPath); } catch (readDirError: any) { if (readDirError.code === 'ENOENT') { return NextResponse.json( { message: 'Data directory not found. Please create a "data" folder at the project root.', status: 'error' }, { status: 404 } ); } console.error('Error reading data directory:', readDirError); return NextResponse.json( { message: `Failed to read data directory: ${readDirError.message}`, status: 'error' }, { status: 500 } ); } const csvFiles = files.filter(file => file.endsWith('.csv')); if (csvFiles.length === 0) { return NextResponse.json( { message: 'No CSV files found in the "data" directory.', status: 'success', results: [] }, { status: 200 } ); } for (const csvFileName of csvFiles) { const tableName = path.parse(csvFileName).name.toLowerCase(); // ファイル名からテーブル名を決定 (小文字に変換) const csvFilePath = path.join(dataDirPath, csvFileName); console.log(`Processing ${csvFileName} for table ${tableName}...`); try { let records = await parseCsvFile(csvFilePath); if (records.length === 0) { console.log(`CSV file ${csvFileName} is empty. Skipping insertion.`); results.push({ tableName, status: 'skipped', error: 'CSV file is empty' }); continue; // 次のファイルへ } // ここでレコードを変換する必要がある場合があります。 // 例: USER_IDが自動採番の場合、CSVからUSER_IDを取り除く if (tableName === 'users' && records[0].USER_ID !== undefined) { // CSVにUSER_IDがあるが、Supabaseで自動採番されている場合、 // そのカラムを除外して挿入するための変換を行う records = records.map(({ USER_ID, ...rest }) => rest); console.log(`Removed USER_ID column for insertion into 'users' table.`); } // 他のテーブルで同様の変換が必要ならここに追加 console.log(`Parsed ${records.length} records from ${csvFileName}.`); const { data: insertedData, error: insertError } = await supabase .from(tableName) .insert(records); if (insertError) { console.error(`Supabase insertion error for ${tableName}:`, insertError); // ここを修正: insertError の詳細を results に含める results.push({ tableName, status: 'failed', error: insertError.message || 'Unknown error', // エラーメッセージ details: (insertError as any).details || null, // Supabaseが返す詳細情報 hint: (insertError as any).hint || null, // Supabaseが返すヒント code: (insertError as any).code || null, // Supabaseが返すエラーコード (PostgreSQLのコードなど) }); } else { console.log(`Successfully inserted ${records.length} records into ${tableName} table.`); results.push({ tableName, status: 'success', insertedCount: records.length }); } } catch (fileProcessError: any) { console.error(`Error processing ${csvFileName}:`, fileProcessError); results.push({ tableName, status: 'failed', error: `Error processing file: ${fileProcessError.message}`, }); } } return NextResponse.json({ message: 'CSV ingestion process completed.', status: 'completed', results: results, }); } catch (error) { console.error('Unexpected error during batch CSV ingestion:', error); return NextResponse.json( { message: 'An unexpected error occurred during batch CSV ingestion', status: 'error', error: (error as Error).message }, { status: 500 } ); } }
import { NextResponse } from 'next/server'; import { createClient } from '@supabase/supabase-js'; import { parse } from 'csv-parse'; import fs from 'fs/promises'; import path from 'path'; // Supabaseクライアントの初期化 (前回のものと同じ) const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY; const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; // 必要に応じてService Role Keyを使用 if (!supabaseUrl || (!supabaseAnonKey && !supabaseServiceRoleKey)) { throw new Error('Supabase URL or Anon/Service Role Key is missing in environment variables.'); } // サーバーサイド用のSupabaseクライアントを作成 // 本番環境では anonKey の代わりに serviceRoleKey を使うことを強く推奨します。 // 例: const supabase = createClient(supabaseUrl, supabaseServiceRoleKey || supabaseAnonKey, { ... }); const supabase = createClient(supabaseUrl, supabaseAnonKey as string, { // デバッグ用に anonKey を使用 auth: { persistSession: false, }, }); // CSVファイルをパースしてデータを取得するヘルパー関数 (前回のものと同じ) async function parseCsvFile(filePath: string): Promise<any[]> { const fileContent = await fs.readFile(filePath, { encoding: 'utf8' }); return new Promise((resolve, reject) => { parse( fileContent, { columns: true, // ヘッダー行をカラム名として使用 skip_empty_lines: true, }, (err, records) => { if (err) { return reject(err); } resolve(records); } ); }); } export async function GET(request: Request) { const results: { tableName: string; status: string; insertedCount?: number; error?: string }[] = []; try { const dataDirPath = path.join(process.cwd(), 'data'); let files: string[]; try { files = await fs.readdir(dataDirPath); } catch (readDirError: any) { if (readDirError.code === 'ENOENT') { return NextResponse.json( { message: 'Data directory not found. Please create a "data" folder at the project root.', status: 'error' }, { status: 404 } ); } console.error('Error reading data directory:', readDirError); return NextResponse.json( { message: `Failed to read data directory: ${readDirError.message}`, status: 'error' }, { status: 500 } ); } const csvFiles = files.filter(file => file.endsWith('.csv')); if (csvFiles.length === 0) { return NextResponse.json( { message: 'No CSV files found in the "data" directory.', status: 'success', results: [] }, { status: 200 } ); } for (const csvFileName of csvFiles) { const tableName = path.parse(csvFileName).name.toLowerCase(); // ファイル名からテーブル名を決定 (小文字に変換) const csvFilePath = path.join(dataDirPath, csvFileName); console.log(`Processing ${csvFileName} for table ${tableName}...`); try { let records = await parseCsvFile(csvFilePath); if (records.length === 0) { console.log(`CSV file ${csvFileName} is empty. Skipping insertion.`); results.push({ tableName, status: 'skipped', error: 'CSV file is empty' }); continue; // 次のファイルへ } // ここでレコードを変換する必要がある場合があります。 // 例: USER_IDが自動採番の場合、CSVからUSER_IDを取り除く if (tableName === 'users' && records[0].USER_ID !== undefined) { // CSVにUSER_IDがあるが、Supabaseで自動採番されている場合、 // そのカラムを除外して挿入するための変換を行う records = records.map(({ USER_ID, ...rest }) => rest); console.log(`Removed USER_ID column for insertion into 'users' table.`); } // 他のテーブルで同様の変換が必要ならここに追加 console.log(`Parsed ${records.length} records from ${csvFileName}.`); const { data: insertedData, error: insertError } = await supabase .from(tableName) .insert(records); if (insertError) { console.error(`Supabase insertion error for ${tableName}:`, insertError); // ここを修正: insertError の詳細を results に含める results.push({ tableName, status: 'failed', error: insertError.message || 'Unknown error', // エラーメッセージ details: (insertError as any).details || null, // Supabaseが返す詳細情報 hint: (insertError as any).hint || null, // Supabaseが返すヒント code: (insertError as any).code || null, // Supabaseが返すエラーコード (PostgreSQLのコードなど) }); } else { console.log(`Successfully inserted ${records.length} records into ${tableName} table.`); results.push({ tableName, status: 'success', insertedCount: records.length }); } } catch (fileProcessError: any) { console.error(`Error processing ${csvFileName}:`, fileProcessError); results.push({ tableName, status: 'failed', error: `Error processing file: ${fileProcessError.message}`, }); } } return NextResponse.json({ message: 'CSV ingestion process completed.', status: 'completed', results: results, }); } catch (error) { console.error('Unexpected error during batch CSV ingestion:', error); return NextResponse.json( { message: 'An unexpected error occurred during batch CSV ingestion', status: 'error', error: (error as Error).message }, { status: 500 } ); } }
Show line notes below