first commit
This commit is contained in:
9
.env
Normal file
9
.env
Normal file
@@ -0,0 +1,9 @@
|
||||
CDN_UPLOAD_URL=http://localhost:4000/upload
|
||||
CDN_SERVICE_TOKEN=5075761248974997bf50fdf2a136e2d27320c15358d59ab09e37438b3ee7f08e1d15770a114a8774bf90e14189560832
|
||||
CDN_PUBLIC_BASE=http://localhost:4000
|
||||
CDN_WEBHOOK_SECRET=webhook-secret
|
||||
MAX_PROFILE_SIZE=2097152 # 2MB
|
||||
MAX_DOCUMENT_SIZE=10485760 # 10MB
|
||||
ALLOWED_PROFILE_TYPES=["image/jpeg","image/png","image/webp"]
|
||||
ALLOWED_DOCUMENT_TYPES=["application/pdf","application/zip","application/dicom","application/octet-stream"]
|
||||
PORT=4000
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
exports/*
|
||||
logs/*
|
||||
node_modules
|
||||
uploads/*
|
||||
23
config/init.js
Normal file
23
config/init.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const {default: rateLimit} = require("express-rate-limit");
|
||||
const path = require("path");
|
||||
require("dotenv").config();
|
||||
module.exports = {
|
||||
PORT: process.env.PORT || 4000,
|
||||
JWT_SECRET:
|
||||
process.env.JWT_SECRET || "dasdG23qewqe1234441fFGfdhdghnnbCCZXQSDQWEweqwe",
|
||||
STORAGE_PATH: path.resolve(__dirname, "../uploads"),
|
||||
MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
|
||||
ALLOWED_FILE_TYPES: ["image/jpeg", "image/png", "image/webp"], // نوعهای مجاز
|
||||
limiter: rateLimit({
|
||||
windowMs: 5 * 60 * 1000, // 5 دقیقه
|
||||
max: 30,
|
||||
standardHeaders: true, // Return rate limit info in `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
handler: (req, res) => {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
message: "تعداد درخواست ها بیش تر از حد مجاز، در فرصتی دیگر تلاش کنید",
|
||||
});
|
||||
},
|
||||
}),
|
||||
};
|
||||
17
config/logger.js
Normal file
17
config/logger.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const winston = require("winston");
|
||||
const logDirectory = path.join(__dirname, "..", "logs");
|
||||
if (!fs.existsSync(logDirectory)) {
|
||||
fs.mkdirSync(logDirectory);
|
||||
}
|
||||
const logger = winston.createLogger({
|
||||
level: "info",
|
||||
format: winston.format?.json(),
|
||||
|
||||
transports: [
|
||||
new winston.transports.File({filename: path.join(logDirectory, "cdn.log")}),
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = logger;
|
||||
2
generate-token.js
Normal file
2
generate-token.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const crypto = require('crypto')
|
||||
console.log(crypto.randomBytes(48).toString("hex"))
|
||||
1914
package-lock.json
generated
Normal file
1914
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "cdn-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"busboy": "^1.6.0",
|
||||
"compression": "^1.8.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
"file-type": "^21.1.1",
|
||||
"multer": "^2.0.2",
|
||||
"winston": "^3.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.11"
|
||||
}
|
||||
}
|
||||
195
server.js
Normal file
195
server.js
Normal file
@@ -0,0 +1,195 @@
|
||||
const express = require("express");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const process = require("process");
|
||||
const logger = require("./config/logger");
|
||||
const compression = require("compression");
|
||||
const multer = require("multer");
|
||||
const dotenv = require("dotenv");
|
||||
const { limiter } = require("./config/init");
|
||||
const { performance } = require("perf_hooks");
|
||||
performance.eventLoopUtilization();
|
||||
dotenv.config();
|
||||
const app = express();
|
||||
|
||||
const UPLOAD_DIR = path.join(__dirname, "uploads");
|
||||
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
||||
|
||||
const EXPORTS_DIR = path.join(__dirname, "exports");
|
||||
if (!fs.existsSync(EXPORTS_DIR)) fs.mkdirSync(EXPORTS_DIR, { recursive: true });
|
||||
|
||||
const upload_files = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (_, __, cb) => cb(null, UPLOAD_DIR),
|
||||
filename: (_, file, cb) => cb(null, `${Date.now()}-${file.originalname}`),
|
||||
}),
|
||||
});
|
||||
const upload_exports = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (_, __, cb) => cb(null, EXPORTS_DIR),
|
||||
filename: (_, file, cb) => cb(null, `${Date.now()}-${file.originalname}`),
|
||||
}),
|
||||
});
|
||||
const serviceStartTime = Date.now();
|
||||
|
||||
app.use(compression());
|
||||
// app.use((req, res, next) => {
|
||||
// logger.info({
|
||||
// method: req.method,
|
||||
// url: req.url,
|
||||
// timestamp: new Date().toISOString(),
|
||||
// });
|
||||
// next();
|
||||
// });
|
||||
app.post("/upload", upload_files.single("file"), (req, res) => {
|
||||
// Authorization
|
||||
|
||||
logger.info({
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
const auth = req.headers["authorization"]?.split(" ")[1];
|
||||
if (auth !== process.env.CDN_SERVICE_TOKEN) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
||||
|
||||
const publicUrl = `${process.env.CDN_PUBLIC_BASE}/uploads/${req.file.filename}`;
|
||||
|
||||
return res.json({
|
||||
url: publicUrl,
|
||||
size: req.file.size,
|
||||
mime: req.file.mimetype,
|
||||
});
|
||||
});
|
||||
app.post("/upload-exports", upload_exports.single("exports"), (req, res) => {
|
||||
// Authorization
|
||||
// console.log(req);
|
||||
logger.info({
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
const auth = req.headers["authorization"]?.split(" ")[1];
|
||||
if (auth !== process.env.CDN_SERVICE_TOKEN) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
if (!req.file) return res.status(400).json({ error: "No file uploaded" });
|
||||
|
||||
const publicUrl = `${process.env.CDN_PUBLIC_BASE}/exports/${req.file.filename}`;
|
||||
|
||||
return res.json({
|
||||
url: publicUrl,
|
||||
size: req.file.size,
|
||||
mime: req.file.mimetype,
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/upload/delete", (req, res) => {
|
||||
logger.info({
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
const auth = req.headers["authorization"];
|
||||
const bearer = auth?.split(" ")[1];
|
||||
if (bearer !== process.env.CDN_SERVICE_TOKEN) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
console.log(req);
|
||||
const { fileKey, fileUrl } = req.body;
|
||||
const fileName = fileKey ?? fileUrl?.split("/").pop();
|
||||
if (!fileName) return res.status(400).json({ error: "file key/url missing" });
|
||||
const savePath = path.join(UPLOAD_DIR, fileName);
|
||||
if (!fs.existsSync(savePath))
|
||||
return res.status(404).json({ error: "Not found" });
|
||||
fs.unlinkSync(savePath);
|
||||
return res.json({ success: true, file: fileName });
|
||||
});
|
||||
|
||||
app.use("/uploads", express.static(UPLOAD_DIR, { maxAge: 3600 }));
|
||||
app.use("/exports", express.static(EXPORTS_DIR, { maxAge: 3600 }));
|
||||
|
||||
app.get("/status", (req, res) => {
|
||||
try {
|
||||
const mem = process.memoryUsage();
|
||||
const cpu = process.cpuUsage();
|
||||
const elu = performance.eventLoopUtilization();
|
||||
res.status(200).json({
|
||||
status: "OK",
|
||||
system: {
|
||||
service: {
|
||||
uptime: Math.floor(process.uptime()),
|
||||
pid: process.pid,
|
||||
nodeVersion: process.version,
|
||||
},
|
||||
memory: {
|
||||
rss: mem.rss,
|
||||
heapUsed: mem.heapUsed,
|
||||
heapTotal: mem.heapTotal,
|
||||
external: mem.external,
|
||||
},
|
||||
cpu: {
|
||||
user: cpu.user,
|
||||
system: cpu.system,
|
||||
},
|
||||
eventLoop: {
|
||||
utilization: +elu.utilization.toFixed(4),
|
||||
active: elu.active,
|
||||
idle: elu.idle,
|
||||
},
|
||||
system: {
|
||||
freeMemory: os.freemem(),
|
||||
totalMemory: os.totalmem(),
|
||||
loadAvg: os.loadavg(),
|
||||
cpuCores: os.cpus().length,
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
status: "ERROR",
|
||||
message: "خطای داخلی سرویس",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
app.get("/logs", (req, res) => {
|
||||
|
||||
const logFilePath = path.join(__dirname, "logs", "cdn.log");
|
||||
|
||||
fs.readFile(logFilePath, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
status: "ERROR",
|
||||
message: "لاگ خوانده نشد",
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
status: "OK",
|
||||
logs: data.split("\n").filter((line) => line),
|
||||
});
|
||||
});
|
||||
});
|
||||
app.delete("/logs/clear", (req, res) => {
|
||||
const logFilePath = path.join(__dirname, "logs", "cdn.log");
|
||||
|
||||
fs.truncate(logFilePath, 0, (err) => {
|
||||
if (err) {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ status: "ERROR", message: "خطا در پاکسازی لاگ ها" });
|
||||
}
|
||||
return res
|
||||
.status(200)
|
||||
.json({ status: "OK", message: "لاگ ها با موفقیت پاک شدند" });
|
||||
});
|
||||
});
|
||||
app.listen(4000, () => console.log("CDN server listening on 4000"));
|
||||
Reference in New Issue
Block a user