Compare commits
No commits in common. 'c1f2cea77a821eac76b186a3a9c2542efd4a20fd' and 'a05bcb8a8debe9938da60c0d9740ede16dd48236' have entirely different histories.
c1f2cea77a
...
a05bcb8a8d
4 changed files with 0 additions and 1206 deletions
@ -1,13 +0,0 @@ |
|||||||
node_modules/ |
|
||||||
|
|
||||||
uploads/ |
|
||||||
!uploads/.gitkeep |
|
||||||
|
|
||||||
.env |
|
||||||
.env.local |
|
||||||
|
|
||||||
npm-debug.log* |
|
||||||
|
|
||||||
.DS_Store |
|
||||||
Thumbs.db |
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,21 +0,0 @@ |
|||||||
{ |
|
||||||
"name": "drop_site", |
|
||||||
"version": "1.0.0", |
|
||||||
"description": "", |
|
||||||
"main": "index.js", |
|
||||||
"scripts": { |
|
||||||
"test": "echo \"Error: no test specified\" && exit 1" |
|
||||||
}, |
|
||||||
"repository": { |
|
||||||
"type": "git", |
|
||||||
"url": "https://gitea.kanawo.net/agomizi1cm/drop_site.git" |
|
||||||
}, |
|
||||||
"keywords": [], |
|
||||||
"author": "", |
|
||||||
"license": "ISC", |
|
||||||
"type": "commonjs", |
|
||||||
"dependencies": { |
|
||||||
"express": "^5.2.1", |
|
||||||
"multer": "^2.2.0" |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,146 +0,0 @@ |
|||||||
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}`); |
|
||||||
}); |
|
||||||
Loading…
Reference in new issue