const express = require('express'); const multer = require('multer'); const fs = require('fs'); const path = require('path'); const app = express(); const PORT = 5000; // ファイルを一時保存するディレクトリ(存在しない場合は自動作成) const UPLOAD_DIR = path.join(__dirname, 'uploads'); if (!fs.existsSync(UPLOAD_DIR)) { fs.mkdirSync(UPLOAD_DIR); } // 単一スロットのチェスト状態を管理するオブジェクト let currentChestFile = null; let activeDeleteTimer = null; // 3分(180,000ミリ秒)の有効期限 const FILE_LIFETIME = 3 * 60 * 1000; // 物理ファイルとメタデータを完全に削除する関数 const clearChest = () => { if (currentChestFile) { const filePath = path.join(UPLOAD_DIR, currentChestFile.savedName); fs.unlink(filePath, (err) => { if (err && err.code !== 'ENOENT') { console.error('ファイル削除エラー:', err); } }); currentChestFile = null; } if (activeDeleteTimer) { clearTimeout(activeDeleteTimer); activeDeleteTimer = null; } console.log('チェストが空になりました(有効期限切れ、または手動上書き)。'); }; // Multerのストレージ設定(ファイル名サニタイズのため、ランダムな名前に変更して保存) const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, UPLOAD_DIR); }, filename: (req, file, cb) => { // ディレクトリトラバーサル防止のため、拡張子だけ維持してファイル名はタイムスタンプに変更 const ext = path.extname(file.originalname); cb(null, `${Date.now()}${ext}`); } }); const upload = multer({ storage: storage, limits: { fileSize: 100 * 1024 * 1024 } // Nginx側と合わせて100MB制限 }); // メイン画面(状態に応じてアップロードかダウンロードのUIを出し分ける) app.get('/', (req, res) => { if (!currentChestFile) { // チェストが空の場合:アップロード用フォームを返す res.send(`
ファイル名: ${currentChestFile.originalName}
サイズ: ${(currentChestFile.size / 1024 / 1024).toFixed(2)} MB
残り時間: 約 ${remainingTime} 秒
※新しいファイルをアップロードすると、現在のファイルは即座に上書き削除されます。
`); } }); // ファイルアップロードAPI app.post('/upload', upload.single('chestFile'), (req, res) => { if (!req.file) { return res.status(400).send('ファイルがありません。'); } // 既存のファイルがあれば、タイマーをクリアして物理ファイルを即時削除(1スロット制限の維持) clearChest(); // 新しいファイル情報をチェストに格納 currentChestFile = { savedName: req.file.filename, originalName: req.file.originalname, mimetype: req.file.mimetype, size: req.file.size, expiresAt: Date.now() + FILE_LIFETIME }; // 3分後の自動削除タイマーを起動 activeDeleteTimer = setTimeout(() => { clearChest(); }, FILE_LIFETIME); res.redirect('/'); }); // ファイルダウンロードAPI app.get('/download', (req, res) => { if (!currentChestFile) { return res.status(404).send('チェストは空か、有効期限が切れています。'); } const filePath = path.join(UPLOAD_DIR, currentChestFile.savedName); // 安全な強制ダウンロードの実行 res.download(filePath, currentChestFile.originalName, (err) => { if (err) { console.error('ダウンロードエラー:', err); if (!res.headersSent) { res.status(500).send('ダウンロード中にエラーが発生しました。'); } } }); }); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });