はじめに
この記事では、クラウドストレージサービスである Amazon S3 (Simple Storage Service)の操作と、Webアプリケーションにおけるデータ削除のビジネスロジックを組み合わせた活用事例を紹介します。
【今回活用したS3および関連操作】
- ファイルパスの収集: 削除対象のデータベースレコードから、関連するS3上のファイルパスを抽出
- 複数ファイルの削除(S3一括削除): 収集したファイルパスの配列を使用し、AWS SDKで実装した関数を利用して、S3上の関連画像を一括削除
- エラー耐性の確保: S3からのファイル削除が失敗しても、データベースレコードの削除は継続し、データ不整合を防ぐ
- 権限確認とログ記録: 処理実行前に管理者権限を確認し、削除操作の追跡可能なログを記録
1. S3ファイル削除の実際の活用場面
// 削除関数をインポート
import { deleteFilesFromS3 } from "~/utils/s3.server"
2. 削除処理の発生条件
// 削除処理の判定
if (intent === "delete") {
// 管理者がスーパーユーザーであることを確認
if (!isSuperAdmin) {
throw new Response("削除権限がありません。", { status: 403 })
}
処理内容:
- フォームで
intent="delete"が送信された時 - スーパーユーザー権限を持つ管理者のみが削除可能
3. 削除前の画像ファイル情報取得
// 削除前に関連する画像ファイルを取得
const userRecordWithImages = await db.userRecord.findUnique({
where: { kyufuUniqueCode: params.recordId },
include: {
bankAccountInfo: {
select: {
copyImageUrl: true, // 口座コピー画像1
copyImageUrl2: true, // 口座コピー画像2
},
},
},
})
処理内容:
- データベースから削除対象レコードを取得
-
includeで関連する銀行口座情報も取得 - 削除前に画像URLを収集するため
4. 削除対象ファイルの収集
// S3から削除する画像ファイルのパスを収集
const s3FilesToDelete: string[] = []
// 本人確認画像のURL
if (userRecordWithImages.personalIdentificationImageUrl) {
s3FilesToDelete.push(userRecordWithImages.personalIdentificationImageUrl)
}
if (userRecordWithImages.personalIdentificationImageUrl2) {
s3FilesToDelete.push(userRecordWithImages.personalIdentificationImageUrl2)
}
// 口座関連の画像URL
userRecordWithImages.bankAccountInfo.forEach(account => {
if (account.copyImageUrl) {
s3FilesToDelete.push(account.copyImageUrl)
}
if (account.copyImageUrl2) {
s3FilesToDelete.push(account.copyImageUrl2)
}
})
処理内容:
-
本人確認画像:
personalIdentificationImageUrl、personalIdentificationImageUrl2 -
口座関連画像: 各口座の
copyImageUrl、copyImageUrl2 - 存在する画像URLのみを配列に追加
- 最大で住民1人につき:本人確認画像2枚
5. 実際のS3削除処理の実行
// S3から関連する画像ファイルを削除
if (s3FilesToDelete.length > 0) {
try {
await deleteFilesFromS3(s3FilesToDelete) // ← ここでS3削除関数を呼び出し
} catch (s3Error) {
console.error("S3からの画像削除中にエラーが発生しました:", s3Error)
// S3の削除に失敗してもデータベースの削除は続行
}
}
処理内容:
- 削除対象ファイルがある場合のみ実行
-
deleteFilesFromS3関数に配列を渡して一括削除 - 重要: S3削除でエラーが発生してもデータベース削除は継続
6. データベース削除とログ記録
// 削除前にログデータを作成
const logData = JSON.stringify({
adminUserId: await getAdminUserName(adminUserId),
action: "deleteUserRecord",
parameters: {
recordId: await getHouseholdNumberFromKyufuUniqueCode(params.recordId || ""),
},
})
// レコードの削除
await db.userRecord.delete({
where: { kyufuUniqueCode: params.recordId },
})
// 削除操作ログの記録
await createLog(adminUserId, logData)
処理内容:
- データベースからレコードを削除
- 操作ログを記録(誰がいつ何を削除したか)
7. 削除処理の全体的な流れ
// 実際の処理順序
1. 権限確認(スーパーユーザーのみ)
2. 削除対象レコードの取得
3. 関連画像ファイルのパス収集
4. ログデータの準備
5. S3から画像ファイルの削除(エラーが発生しても継続)
6. データベースからレコードの削除
7. 操作ログの記録
8. 管理画面一覧にリダイレクト
8. エラーハンドリング
// 全体のエラーハンドリング
} catch (error) {
console.error("レコード削除時のエラー:", error)
return json({ error: "レコード削除中にエラーが発生しました。" }, { status: 500 })
}
処理内容:
- 削除処理全体でエラーが発生した場合
- エラーログを出力し、ユーザーにエラーメッセージを表示
9. 実際の利用例
// 削除対象のファイル
s3FilesToDelete = [
"uuid1-identification1.jpg", // 本人確認画像1
"uuid2-identification2.jpg", // 本人確認画像2
"uuid3-bank-copy1.jpg", // 口座コピー画像1
"uuid4-bank-copy2.jpg", // 口座コピー画像2
]
// 一括削除実行
await deleteFilesFromS3(s3FilesToDelete)
重要なポイント
- データ整合性: データベース削除前にS3ファイルを削除
- エラー耐性: S3削除失敗でもデータベース削除は継続
- 権限管理: スーパーユーザーのみが削除可能
- ログ管理: 削除操作の記録を残す
- 一括処理: 関連する全ての画像を一度に削除