diff --git a/.drone.yml b/.drone.yml index 2f7e8f0..c2130ee 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,6 +1,5 @@ kind: pipeline name: default - steps: - name: build-node image: node:latest @@ -11,16 +10,14 @@ steps: from_secret: git_password commands: - npm install - - node -v - - + - export VERSION=$(node -e "console.log(require('./package.json').version)") - name: build-docker-image image: plugins/docker settings: repo: swiftlogiclabs/cdn-app-insider - tags: + tags: - latest - - v1.0.0-beta.17 + - v${VERSION} dockerfile: Dockerfile username: from_secret: docker_username diff --git a/package-lock.json b/package-lock.json index 642729d..e15a6bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "bcrypt": "^5.1.1", "chalk": "^4.1.2", "chokidar": "^3.6.0", + "compression": "^1.7.5", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.6", "debug": "^4.3.4", @@ -35,6 +36,7 @@ "jsonwebtoken": "^9.0.2", "mime-types": "^2.1.35", "multer": "^1.4.5-lts.1", + "multiparty": "^4.2.3", "mysql2": "^3.6.3", "ncp": "^2.0.0", "node-cron": "^3.0.3", @@ -1299,6 +1301,60 @@ "node": ">= 6" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3349,6 +3405,54 @@ "node": ">= 6.0.0" } }, + "node_modules/multiparty": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.3.tgz", + "integrity": "sha512-Ak6EUJZuhGS8hJ3c2fY6UW5MbkGUPMBEGd13djUzoY/BHqV/gTuFWtC6IuVA7A2+v3yjBS6c4or50xhzTQZImQ==", + "license": "MIT", + "dependencies": { + "http-errors": "~1.8.1", + "safe-buffer": "5.2.1", + "uid-safe": "2.1.5" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/multiparty/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multiparty/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multiparty/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", diff --git a/package.json b/package.json index 2be1a0a..f2c801e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "bcrypt": "^5.1.1", "chalk": "^4.1.2", "chokidar": "^3.6.0", + "compression": "^1.7.5", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.6", "debug": "^4.3.4", @@ -36,6 +37,7 @@ "jsonwebtoken": "^9.0.2", "mime-types": "^2.1.35", "multer": "^1.4.5-lts.1", + "multiparty": "^4.2.3", "mysql2": "^3.6.3", "ncp": "^2.0.0", "node-cron": "^3.0.3", diff --git a/public/assets/Thumbs.db b/public/assets/Thumbs.db new file mode 100644 index 0000000..475994a Binary files /dev/null and b/public/assets/Thumbs.db differ diff --git a/public/assets/background/Thumbs.db b/public/assets/background/Thumbs.db new file mode 100644 index 0000000..e1df98b Binary files /dev/null and b/public/assets/background/Thumbs.db differ diff --git a/public/assets/icon.ico b/public/assets/icon.ico new file mode 100644 index 0000000..03960f6 Binary files /dev/null and b/public/assets/icon.ico differ diff --git a/public/css/dashboard.styles.css b/public/css/dashboard.styles.css index ec00f1f..631df81 100644 --- a/public/css/dashboard.styles.css +++ b/public/css/dashboard.styles.css @@ -321,18 +321,32 @@ position: relative !important; .initial-loading { position: fixed; inset: 0; - background: linear-gradient(135deg, - hsla(var(--background), 0.85), - hsla(var(--background), 0.9) - ); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background-color: #333; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 9999; -} + transition: background-color 0.3s ease; + } + .initial-loading > .loader { + border: 4px solid #fff; + border-top-color: transparent; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + } + @keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + .initial-loading > .message { + margin-top: 16px; + font-size: 16px; + font-weight: 600; + color: #fff; + } .success-animation { position: relative; @@ -458,4 +472,170 @@ position: relative !important; to { opacity: 0; } +} + +/* Notifications container */ +.notification-container { + position: fixed; + top: 1rem; + right: 1rem; + z-index: 1100; + display: flex; + flex-direction: column; + gap: 0.5rem; + pointer-events: none; + max-width: 100%; + padding: 1rem; +} + +/* Individual notification */ +.notification { + display: flex; + align-items: center; + background-color: hsl(var(--card)); + border: 1px solid hsl(var(--border)); + border-radius: var(--radius); + padding: 1rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateX(120%); + opacity: 0; + transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); + pointer-events: auto; + max-width: 380px; + margin-bottom: 0.5rem; +} + +.notification.show { + transform: translateX(0); + opacity: 1; +} + +/* Icon styles */ +.notification-icon { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: 50%; + margin-right: 1rem; + flex-shrink: 0; +} + +/* Notification types */ +.notification.success .notification-icon { + background: rgba(16, 185, 129, 0.1); + color: #10B981; +} + +.notification.error .notification-icon { + background: rgba(239, 68, 68, 0.1); + color: #EF4444; +} + +.notification.warning .notification-icon { + background: rgba(245, 158, 11, 0.1); + color: #F59E0B; +} + +.notification.info .notification-icon { + background: rgba(59, 130, 246, 0.1); + color: #3B82F6; +} + +/* Content styling */ +.notification-content { + flex: 1; +} + +.notification-title { + font-weight: 600; + font-size: 0.925rem; + margin-bottom: 0.25rem; + color: hsl(var(--foreground)); +} + +.notification-message { + font-size: 0.875rem; + color: hsl(var(--muted-foreground)); + line-height: 1.4; +} + +/* Dark mode adjustments */ +.dark .notification { + background-color: hsl(var(--card)); + border-color: hsl(var(--border)); +} + +.dark .notification-title { + color: hsl(var(--foreground)); +} + +.dark .notification-message { + color: hsl(var(--muted-foreground)); +} + +/* Animations */ +@keyframes slideIn { + from { + transform: translateX(120%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(120%); + opacity: 0; + } +} + +/* File info styles */ +.file-info { + padding: 0.5rem; + background: rgba(var(--card), 0.5); + border-radius: var(--radius); +} + +.file-info p { + margin-bottom: 0.5rem; +} + +.file-info strong { + color: hsl(var(--foreground)); +} + +/* Metadata info styles */ +.metadata-info { + padding: 0.5rem; + background: rgba(var(--card), 0.5); + border-radius: var(--radius); +} + +.metadata-info p { + margin-bottom: 0.5rem; +} + +.metadata-info strong { + color: hsl(var(--foreground)); +} + +/* Responsive adjustments */ +@media (max-width: 640px) { + .notification-container { + left: 1rem; + right: 1rem; + } + + .notification { + max-width: 100%; + } } \ No newline at end of file diff --git a/public/js/dashboard.js b/public/js/dashboard.js index b300818..75a65b4 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -715,12 +715,6 @@ function initializeLoadingScreen() { }); } -// Nettoyer le sessionStorage lors de la déconnexion -function handleLogout() { - sessionStorage.removeItem('hasSeenLoadingAnimation'); - // Votre code de déconnexion existant... -} - document.addEventListener('DOMContentLoaded', async function() { try { await initializeLoadingScreen(); diff --git a/routes/Dpanel/API/Upload.js b/routes/Dpanel/API/Upload.js index b8f8841..ca29ef9 100644 --- a/routes/Dpanel/API/Upload.js +++ b/routes/Dpanel/API/Upload.js @@ -1,206 +1,87 @@ const express = require('express'); const fs = require('fs'); const path = require('path'); +const multiparty = require('multiparty'); const router = express.Router(); -const fileUpload = require('express-fileupload'); -const { loggers } = require('winston'); -const ncp = require('ncp'); -const util = require('util'); -const configFile = fs.readFileSync(path.join(__dirname, '../../../data', 'setup.json'), 'utf-8') -const config = JSON.parse(configFile); -const bodyParser = require('body-parser'); -const jwt = require('jsonwebtoken'); -const authMiddleware = require('../../../Middlewares/authMiddleware'); -const { getUserData, getSetupData } = require('../../../Middlewares/watcherMiddleware'); -const { logger, logRequestInfo, ErrorLogger, authLogger } = require('../../../config/logs'); -let setupData = getSetupData(); -let userData = getUserData(); -router.use(bodyParser.json()); +// Limite de taille de fichier à 10 Go +const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024; // 10 Go -/** - * @swagger - * /dpanel/upload?token={token}: - * post: - * security: - * - bearerAuth: [] - * tags: - * - File - * summary: Upload a file - * description: This route allows you to upload a file. It requires a valid JWT token in the Authorization header. - * requestBody: - * required: true - * content: - * multipart/form-data: - * schema: - * type: object - * properties: - * file: - * type: string - * format: binary - * description: The file to upload - * expiryDate: - * type: string - * format: date-time - * description: The expiry date of the file - * password: - * type: string - * description: The password to protect the file - * parameters: - * - in: header - * name: Authorization - * required: true - * schema: - * type: string - * description: The JWT token of your account to have access - * responses: - * 200: - * description: Success - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * 400: - * description: Bad Request - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * 401: - * description: Unauthorized - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * 500: - * description: Error uploading the file - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * error: - * type: string - */ - -function authenticateToken(req, res, next) { - let token = null; - const authHeader = req.headers['authorization']; - - if (authHeader) { - token = authHeader.split(' ')[1]; - } else if (req.query.token) { - token = req.query.token; - } - - if (token == null) { - if (req.user) { - return next(); - } else { - return res.status(401).json({ message: 'Unauthorized' }); - } - } - - fs.readFile(path.join(__dirname, '../../../data', 'user.jso,'), 'utf8', (err, data) => { - if (err) { - console.error('Error reading user.jso,:', err); - return res.status(401).json({ message: 'Unauthorized' }); - } - - const users = JSON.parse(data); - - const user = users.find(u => u.token === token); - - if (user) { - req.user = user; - next(); - } else { - return res.status(401).json({ message: 'Unauthorized' }); - } - }); +// Crée le dossier temporaire à la racine s'il n'existe pas +const tempDir = path.join(process.cwd(), 'temp'); +if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); } -router.get('/', (req, res) => { - res.status(400).json({ error: 'Bad Request. The request cannot be fulfilled due to bad syntax or missing parameters.' }); -}); - -router.use(fileUpload({ - limits: { fileSize: 15 * 1024 * 1024 * 1024 }, -})); - -router.post('/', authenticateToken, async (req, res) => { - try { - if (!req.files || Object.keys(req.files).length === 0) { - return res.status(400).send('5410 - Download error, please try again later.'); +router.post('/', (req, res) => { + const form = new multiparty.Form({ + uploadDir: tempDir, + maxFilesSize: MAX_FILE_SIZE, + }); + + form.parse(req, (err, fields, files) => { + if (err) { + console.error('Error parsing the file:', err); + return res.status(400).send('Error during the file upload'); + } + + if (!files.file || files.file.length === 0) { + return res.status(400).send('No file uploaded'); } - const file = req.files.file; - const userId = req.user.name; - const Id = req.user.id; - const uploadDir = path.join('cdn-files', userId); - const originalFileName = file.name; - const domain = config.domain || 'mydomain.com'; - let expiryDate = req.body.expiryDate; - let password = req.body.password; + const file = files.file[0]; + // Modifier le chemin pour être relatif à la racine + const userDir = path.join(process.cwd(), 'cdn-files', req.user.name); + + // Utiliser le nom sécurisé fourni par le client + const filename = fields.filename ? fields.filename[0] : file.originalFilename; + const filePath = path.join(userDir, filename); - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }); + // Crée le répertoire s'il n'existe pas + if (!fs.existsSync(userDir)) { + fs.mkdirSync(userDir, { recursive: true }); } - file.mv(path.join(uploadDir, originalFileName), async (err) => { - if (err) { - console.error(err); - return res.status(500).send({ message: 'Error downloading file.' }); + // Lecture en chunks pour plus de performances + const readStream = fs.createReadStream(file.path, { highWaterMark: 1024 * 1024 }); + const writeStream = fs.createWriteStream(filePath, { flags: 'a' }); + + readStream.pipe(writeStream); + + readStream.on('end', () => { + // Supprimer le fichier temporaire + fs.unlinkSync(file.path); + + // Vérifier que le nom du fichier suit bien le format attendu + const fileNamePattern = /^\d{8}_[A-Z0-9]{6}_.*$/; + if (!fileNamePattern.test(filename)) { + console.warn('Le fichier uploadé ne suit pas le format de nom sécurisé attendu:', filename); } + + res.status(200).send({ + message: 'File uploaded successfully.', + filename: filename + }); + }); - const fileExtension = path.extname(originalFileName).toLowerCase(); - - const bcrypt = require('bcrypt'); - const saltRounds = 10; - - let hashedPassword = ''; - if (password) { - hashedPassword = bcrypt.hashSync(password, saltRounds); + readStream.on('error', (err) => { + console.error('Error reading the file:', err); + // Nettoyer le fichier temporaire en cas d'erreur + if (fs.existsSync(file.path)) { + fs.unlinkSync(file.path); } + res.status(500).send({ message: 'Error uploading file.' }); + }); - const fileInfo = { - fileName: originalFileName, - expiryDate: expiryDate || '', - password: hashedPassword, - Id: Id, - path: path.join(uploadDir, originalFileName) - }; - - if (expiryDate || password) { - let data = []; - if (fs.existsSync(path.join(__dirname, '../../../data', 'file_info.json'))) { - const existingData = await fs.promises.readFile(path.join(__dirname, '../../../data', 'file_info.json'), 'utf8'); - data = JSON.parse(existingData); - if (!Array.isArray(data)) { - data = []; - } - } - data.push(fileInfo); - await fs.promises.writeFile(path.join(__dirname, '../../../data', 'file_info.json'), JSON.stringify(data, null, 2)); + writeStream.on('error', (err) => { + console.error('Error writing the file:', err); + // Nettoyer le fichier temporaire en cas d'erreur + if (fs.existsSync(file.path)) { + fs.unlinkSync(file.path); } - - res.status(200).send({ message: 'Your file has been successfully uploaded.' }); - }); - } catch (error) { - console.error(error); - return res.status(500).send({ message: 'Error downloading file.' }); - } + res.status(500).send({ message: 'Error uploading file.' }); + }); + }); }); module.exports = router; \ No newline at end of file diff --git a/routes/attachments.js b/routes/attachments.js index 221ecfc..26d7657 100644 --- a/routes/attachments.js +++ b/routes/attachments.js @@ -6,10 +6,13 @@ const fsStandard = require('fs'); const mime = require('mime-types'); const { logger, ErrorLogger } = require('../config/logs'); const bcrypt = require('bcrypt'); -const saltRounds = 10; - +const compression = require('compression'); +const { pipeline } = require('stream/promises'); // Utilisation du pipeline moderne const baseDir = 'cdn-files'; +// Middleware de compression gzip +router.use(compression()); + async function getSamAccountNameFromUserId(userId) { const data = await fs.readFile(path.join(__dirname, '../data', 'user.json'), 'utf8'); const users = JSON.parse(data); @@ -60,27 +63,14 @@ router.get('/:userId/:filename', async (req, res) => { try { const filePath = await findFileInUserDir(userId, filename); - if (!filePath) { return res.render('file-not-found'); } const data = await fs.readFile(path.join(__dirname, '../data', 'file_info.json'), 'utf8'); - let fileInfoArray; - try { - fileInfoArray = JSON.parse(data); - } catch (error) { - console.error('Error parsing file_info.json:', error); - return res.status(500).send('Error reading file info.'); - } - - if (!Array.isArray(fileInfoArray)) { - console.error('fileInfoArray is not an array'); - return res.status(500).send('Invalid file info format.'); - } + const fileInfoArray = JSON.parse(data); const fileInfo = fileInfoArray.find(info => info.fileName === filename && info.Id === userId); - if (fileInfo) { const expiryDate = new Date(fileInfo.expiryDate); const now = new Date(); @@ -95,18 +85,41 @@ router.get('/:userId/:filename', async (req, res) => { } } - const readStream = fsStandard.createReadStream(filePath); - let mimeType = mime.lookup(filePath) || 'application/octet-stream'; + const mimeType = mime.lookup(filePath) || 'application/octet-stream'; + const range = req.headers.range; + const stats = await fs.stat(filePath); + const fileSize = stats.size; - res.setHeader('Content-Type', mimeType); - readStream.pipe(res); + if (range) { + const [start, end] = range.replace(/bytes=/, '').split('-'); + const chunkStart = parseInt(start, 10); + const chunkEnd = end ? parseInt(end, 10) : fileSize - 1; - if (fileInfo) { - req.session.passwordVerified = false; + if (chunkStart >= fileSize || chunkEnd >= fileSize) { + res.setHeader('Content-Range', `bytes */${fileSize}`); + return res.status(416).send('Requested Range Not Satisfiable'); + } + + res.status(206); + res.setHeader('Content-Range', `bytes ${chunkStart}-${chunkEnd}/${fileSize}`); + res.setHeader('Accept-Ranges', 'bytes'); + res.setHeader('Content-Length', chunkEnd - chunkStart + 1); + res.setHeader('Content-Type', mimeType); + + const readStream = fsStandard.createReadStream(filePath, { start: chunkStart, end: chunkEnd }); + await pipeline(readStream, res); // Utilisation de pipeline avec await pour éviter les erreurs + } else { + res.setHeader('Content-Length', fileSize); + res.setHeader('Content-Type', mimeType); + + const readStream = fsStandard.createReadStream(filePath); + await pipeline(readStream, res); } } catch (err) { - ErrorLogger.error('Error reading file:', err); - return res.status(500).send('Error reading file.'); + ErrorLogger.error('Error handling request:', err); + if (!res.headersSent) { + res.status(500).send('Error reading file.'); + } } }); @@ -116,18 +129,7 @@ router.post('/:userId/:filename', async (req, res) => { try { const data = await fs.readFile(path.join(__dirname, '../data', 'file_info.json'), 'utf8'); - let fileInfoArray; - try { - fileInfoArray = JSON.parse(data); - } catch (error) { - console.error('Error parsing file_info.json:', error); - return res.status(500).send('Error reading file info.'); - } - - if (!Array.isArray(fileInfoArray)) { - console.error('fileInfoArray is not an array'); - return res.status(500).send('Invalid file info format.'); - } + const fileInfoArray = JSON.parse(data); const fileInfo = fileInfoArray.find(info => info.fileName === filename && info.Id === userId); @@ -138,81 +140,26 @@ router.post('/:userId/:filename', async (req, res) => { const passwordMatch = await bcrypt.compare(enteredPassword, fileInfo.password); if (passwordMatch) { req.session.passwordVerified = true; + const filePath = await findFileInUserDir(userId, filename); + const mimeType = mime.lookup(filePath) || 'application/octet-stream'; const readStream = fsStandard.createReadStream(filePath); - let mimeType = mime.lookup(filePath) || 'application/octet-stream'; let fileContent = ''; - readStream.on('data', chunk => { + for await (const chunk of readStream) { fileContent += chunk.toString('base64'); - }); + } - readStream.on('end', () => { - res.json({ success: true, fileContent, mimeType }); - }); + res.json({ success: true, fileContent, mimeType }); } else { res.json({ success: false, message: 'Incorrect password' }); } } catch (err) { ErrorLogger.error('Error reading file:', err); - return res.status(500).send('Error reading file.'); + if (!res.headersSent) { + res.status(500).send('Error reading file.'); + } } }); -async function deleteExpiredFiles() { - let data; - try { - data = await fs.readFile(path.join(__dirname, '../data', 'file_info.json'), 'utf8'); - } catch (error) { - console.error('Error reading file_info.json:', error); - return; - } - - let fileInfoArray; - try { - fileInfoArray = JSON.parse(data); - } catch (error) { - console.error('Error parsing file_info.json:', error); - return; - } - - if (!Array.isArray(fileInfoArray)) { - console.error('fileInfoArray is not an array'); - return; - } - - const now = new Date(); - let newFileInfoArray = []; - - for (const fileInfo of fileInfoArray) { - let expiryDate; - if (fileInfo.expiryDate && fileInfo.expiryDate.trim() !== '') { - expiryDate = new Date(fileInfo.expiryDate); - } else { - continue; - } - - if (expiryDate < now) { - try { - const samaccountname = await getSamAccountNameFromUserId(fileInfo.userId); - const userDir = path.join(baseDir, samaccountname); - const filePath = path.join(userDir, fileInfo.fileName); - await fs.unlink(filePath); - } catch (err) { - ErrorLogger.error('Error deleting file:', err); - } - } else { - newFileInfoArray.push(fileInfo); - } - } - - try { - await fs.writeFile(path.join(__dirname, '../data', 'file_info.json'), JSON.stringify(newFileInfoArray, null, 2), 'utf8'); - } catch (err) { - ErrorLogger.error('Error writing to file_info.json:', err); - } -} - -setInterval(deleteExpiredFiles, 24 * 60 * 60 * 1000); - module.exports = router; diff --git a/server.js b/server.js index 1830086..dd9ec57 100644 --- a/server.js +++ b/server.js @@ -17,6 +17,8 @@ const chalk = require('chalk'); require('dotenv').config(); const app = express(); +app.set('trust proxy', 1); + require('./models/fileCreated.js'); let setup; @@ -95,7 +97,6 @@ cron.schedule('0 * * * *', async () => { } } - await fs.promises.writeFile(path.join(__dirname, 'file_info.json'), JSON.stringify(fileInfo, null, 2), 'utf8'); logger.info('Successfully checked file expirations and updated file_info.json'); } catch (err) { ErrorLogger.error(`Failed to check file expirations: ${err}`); diff --git a/views/doc_cdn-app_api.ejs b/views/doc_cdn-app_api.ejs new file mode 100644 index 0000000..5e60ea2 --- /dev/null +++ b/views/doc_cdn-app_api.ejs @@ -0,0 +1,266 @@ + + +
+ + +URL de base : /api/dpanel/
+Cette documentation décrit l'API CDN-APP, qui permet aux utilisateurs de gérer les fichiers et les dossiers au sein de l'application CDN. Elle comprend des points de terminaison pour la création, la suppression, le déplacement et la récupération de fichiers et de dossiers. Toutes les requêtes nécessitent un jeton JWT valide dans l'en-tête d'autorisation.
+ +Point de terminaison : POST /dashboard/getfilefolder/{folderName}?token={token}
+Description : Cette route vous permet d'obtenir les fichiers et les dossiers d'un dossier spécifique. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Point de terminaison : POST /dashboard/deletefolder/{folderName}?token={token}
+Description : Cette route vous permet de supprimer un dossier spécifique. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Point de terminaison : POST /dashboard/newfolder?token={token}
+Description : Cette route vous permet de créer un nouveau dossier. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Point de terminaison : POST /dashboard/deletefile?token={token}
+Description : Cette route vous permet de supprimer un fichier spécifique. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Point de terminaison : POST /dashboard/getfile?token={token}
+Description : Cette route vous permet d'obtenir les informations sur un fichier spécifique. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Point de terminaison : POST /dashboard/movefile?token={token}
+Description : Cette route vous permet de déplacer un fichier vers un dossier différent. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Point de terminaison : POST /dashboard/rename?token={token}
+Description : Cette route vous permet de renommer un fichier. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Point de terminaison : POST /dpanel/upload?token={token}
+Description : Cette route vous permet de télécharger un fichier. Elle nécessite un jeton JWT valide dans l'en-tête d'autorisation.
+Paramètres :
+Réponses :
+Le fichier <%= filename %> est protégé. Veuillez entrer le mot de passe pour y accéder.
- +Le fichier <%= filename %> est protégé. Veuillez entrer le mot de passe pour y accéder.
+ +