You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

146 lines
5.5 KiB

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(`
<!DOCTYPE html>
<html>
<head><title>File Chest</title><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
<body>
<h1>File Chest (空)</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="chestFile" required>
<button type="submit">アップロード (有効期限3分)</button>
</form>
</body>
</html>
`);
} else {
// チェストにファイルがある場合:ダウンロード用リンクを返す
const remainingTime = Math.max(0, Math.規劃((currentChestFile.expiresAt - Date.now()) / 1000));
res.send(`
<!DOCTYPE html>
<html>
<head><title>File Chest</title><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
<body>
<h1>File Chest (保管中)</h1>
<p>ファイル名: <strong>${currentChestFile.originalName}</strong></p>
<p>サイズ: ${(currentChestFile.size / 1024 / 1024).toFixed(2)} MB</p>
<p>残り時間: 約 ${remainingTime} 秒</p>
<a href="/download"><button style="font-size: 1.2em; padding: 10px 20px;">ダウンロード</button></a>
<hr>
<p>※新しいファイルをアップロードすると、現在のファイルは即座に上書き削除されます。</p>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="chestFile" required>
<button type="submit">新しく上書きアップロード</button>
</form>
</body>
</html>
`);
}
});
// ファイルアップロード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}`);
});