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
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.floor((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}`);
|
|
});
|
|
|