Compare commits

...

2 Commits

Author SHA1 Message Date
agomizi1cm c1f2cea77a add programs 2 weeks ago
agomizi1cm a750545367 add programs 2 weeks ago
  1. 13
      .gitignore
  2. 1026
      package-lock.json
  3. 21
      package.json
  4. 146
      server.js

13
.gitignore vendored

@ -0,0 +1,13 @@
node_modules/
uploads/
!uploads/.gitkeep
.env
.env.local
npm-debug.log*
.DS_Store
Thumbs.db

1026
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
{
"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"
}
}

@ -0,0 +1,146 @@
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…
Cancel
Save