Add import feature
This commit is contained in:
parent
dae17e0f7e
commit
9b34e3d339
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -128,3 +128,5 @@ dist
|
|||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
uploads/
|
159
package-lock.json
generated
159
package-lock.json
generated
|
@ -11,12 +11,14 @@
|
|||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"colors": "^1.4.0",
|
||||
"csv-parser": "^3.0.0",
|
||||
"discord.js": "^14.16.3",
|
||||
"dotenv": "^16.4.7",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"mariadb": "^3.4.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"noblox.js": "^6.0.2",
|
||||
"totp-generator": "^1.0.0",
|
||||
"uuid": "^11.0.3"
|
||||
|
@ -441,6 +443,12 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
|
@ -616,6 +624,23 @@
|
|||
"base64-js": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
|
@ -767,6 +792,51 @@
|
|||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
||||
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
||||
"engines": [
|
||||
"node >= 0.8"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^2.2.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-stream/node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
|
@ -843,6 +913,21 @@
|
|||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/csv-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"csv-parser": "bin/csv-parser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
|
@ -1741,6 +1826,12 @@
|
|||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
|
@ -1977,6 +2068,15 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
|
@ -2029,6 +2129,36 @@
|
|||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "1.4.5-lts.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
|
||||
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^1.0.0",
|
||||
"concat-stream": "^1.5.2",
|
||||
"mkdirp": "^0.5.4",
|
||||
"object-assign": "^4.1.1",
|
||||
"type-is": "^1.6.4",
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
|
@ -2312,6 +2442,12 @@
|
|||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
@ -2630,6 +2766,14 @@
|
|||
"bluebird": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
|
@ -2764,6 +2908,12 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
|
@ -2964,6 +3114,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"colors": "^1.4.0",
|
||||
"csv-parser": "^3.0.0",
|
||||
"discord.js": "^14.16.3",
|
||||
"dotenv": "^16.4.7",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^4.21.1",
|
||||
"express-session": "^1.18.1",
|
||||
"mariadb": "^3.4.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"noblox.js": "^6.0.2",
|
||||
"totp-generator": "^1.0.0",
|
||||
"uuid": "^11.0.3"
|
||||
|
|
62
public/assets/js/admin/import.js
Normal file
62
public/assets/js/admin/import.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
var messageElement = null;
|
||||
var pollInterval = null;
|
||||
var uploadId = null;
|
||||
const poll = async function () {
|
||||
try {
|
||||
const pollResponse = await fetch(`/admin/api/uploads/${uploadId}`);
|
||||
const pollResult = await pollResponse.json();
|
||||
|
||||
if (!pollResult.completed) {
|
||||
messageElement.className = 'alert alert-info';
|
||||
const percentage = ((pollResult.processed / pollResult.total) * 100).toFixed(2);
|
||||
messageElement.textContent = `${pollResult.processed}/${pollResult.total} Imported. ${percentage}% done. You can leave this page.`;
|
||||
} else {
|
||||
clearInterval(pollInterval);
|
||||
messageElement.className = 'alert alert-success';
|
||||
messageElement.textContent = 'Import completed successfully. Redirecting...';
|
||||
setTimeout(() => {
|
||||
window.location.href = '/admin';
|
||||
}, 1000);
|
||||
}
|
||||
} catch (pollError) {
|
||||
clearInterval(pollInterval);
|
||||
messageElement.className = 'alert alert-danger';
|
||||
messageElement.textContent = 'An error occurred while polling the upload status.';
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('banForm').addEventListener('submit', async function (event) {
|
||||
event.preventDefault();
|
||||
if (pollInterval !== null) {
|
||||
return; // Upload already in progress
|
||||
}
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
messageElement = document.getElementById('message');
|
||||
messageElement.className = 'alert alert-info';
|
||||
messageElement.textContent = 'Uploading... Please Wait';
|
||||
messageElement.style.display = 'block';
|
||||
try {
|
||||
const response = await fetch(event.target.action, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
Array.from(form.elements).forEach(element => element.disabled = true);
|
||||
uploadId = result.id;
|
||||
if (uploadId) {
|
||||
poll(); // Start polling
|
||||
pollInterval = setInterval(poll, 250); // Poll every 5 seconds
|
||||
}
|
||||
} else {
|
||||
messageElement.className = 'alert alert-danger';
|
||||
messageElement.textContent = result.message || 'Failed to import file.';
|
||||
}
|
||||
} catch (error) {
|
||||
messageElement.className = 'alert alert-danger';
|
||||
messageElement.textContent = 'An error occurred while importing the file.';
|
||||
}
|
||||
});
|
23
public/assets/js/admin/uploadStatus.js
Normal file
23
public/assets/js/admin/uploadStatus.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Once the doc loads, find #import-status div, and start polling /admin/api/uploads to get full list
|
||||
/* example response
|
||||
{"51e4dd908c4d6dcffad46bc4b939712d":{"completed":true,"total":1,"processed":0},"b35467b9295d51c38d6cc4681cd18d02":{"completed":false,"total":18933,"processed":3383}}
|
||||
*/
|
||||
|
||||
const poll = async function () {
|
||||
try {
|
||||
const pollResponse = await fetch(`/admin/api/uploads`);
|
||||
const pollResult = await pollResponse.json();
|
||||
const importStatus = document.getElementById('import-status');
|
||||
importStatus.innerHTML = '';
|
||||
for (const [uploadId, uploadData] of Object.entries(pollResult)) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = `${uploadId}: ${uploadData.processed}/${uploadData.total} Imported. ${((uploadData.processed / uploadData.total) * 100).toFixed(2)}% done.`;
|
||||
importStatus.appendChild(div);
|
||||
}
|
||||
} catch (pollError) {
|
||||
console.error('An error occurred while polling the upload status.');
|
||||
}
|
||||
setTimeout(poll, 250);
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', poll);
|
|
@ -11,6 +11,9 @@ const { execSync } = require('child_process');
|
|||
const { env } = require('process');
|
||||
const session = require('express-session');
|
||||
const totp = require('totp-generator').TOTP;
|
||||
const multer = require('multer');
|
||||
const csv = require('csv-parser');
|
||||
const fs = require('fs');
|
||||
|
||||
// Create a MariaDB connection pool
|
||||
const pool = mariadb.createPool({
|
||||
|
@ -94,7 +97,7 @@ router.post('/create', authenticate, async (req, res) => {
|
|||
const discordUsername = data.discordUsername || null;
|
||||
const robloxUsername = data.robloxUsername || null;
|
||||
|
||||
await conn.query('INSERT INTO bans (reasonShort, reasonLong, reasonsFlag, moderator, expiresTimestamp, robloxId, discordId, robloxUsername, discordUsername) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
await conn.query('INSERT INTO bans (reasonShort, reasonLong, reasonsFlag, moderator, expiresTimestamp, robloxId, discordId, robloxUsername, discordUsername) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[reasonShort, reasonLong, reasonsFlag, moderator, expiresTimestamp, robloxId, discordId, robloxUsername, discordUsername]);
|
||||
conn.end();
|
||||
auditLog('ban_create', { robloxId, discordId, moderator, reasonShort, reasonLong, reasonsFlag, expiresTimestamp }, req.session.user.username);
|
||||
|
@ -106,7 +109,7 @@ router.post('/edit/:id', authenticate, async (req, res) => {
|
|||
const conn = await pool.getConnection();
|
||||
const id = req.params.id;
|
||||
const data = req.body;
|
||||
|
||||
|
||||
const originalData = await conn.query('SELECT * FROM bans WHERE id = ?', [id]);
|
||||
|
||||
if (!data.robloxId && !data.discordId) {
|
||||
|
@ -122,13 +125,76 @@ router.post('/edit/:id', authenticate, async (req, res) => {
|
|||
const discordUsername = data.discordUsername || null;
|
||||
const robloxUsername = data.robloxUsername || null;
|
||||
|
||||
await conn.query('UPDATE bans SET reasonShort = ?, reasonLong = ?, reasonsFlag = ?, expiresTimestamp = ?, robloxId = ?, discordId = ?, robloxUsername = ?, discordUsername = ? WHERE id = ?',
|
||||
await conn.query('UPDATE bans SET reasonShort = ?, reasonLong = ?, reasonsFlag = ?, expiresTimestamp = ?, robloxId = ?, discordId = ?, robloxUsername = ?, discordUsername = ? WHERE id = ?',
|
||||
[reasonShort, reasonLong, reasonsFlag, expiresTimestamp, robloxId, discordId, robloxUsername, discordUsername, id]);
|
||||
conn.end();
|
||||
auditLog('ban_edit', {old: originalData, new: { robloxId, discordId, reasonShort, reasonLong, reasonsFlag, expiresTimestamp }}, req.session.user.username);
|
||||
auditLog('ban_edit', { old: originalData, new: { robloxId, discordId, reasonShort, reasonLong, reasonsFlag, expiresTimestamp } }, req.session.user.username);
|
||||
res.json({ success: true, message: 'User updated successfully', redirect: '/admin' });
|
||||
});
|
||||
|
||||
router.get("/import", authenticate, (req, res) => {
|
||||
res.render('admin/import', { env: process.env, session: req.session });
|
||||
});
|
||||
|
||||
const upload = multer({ dest: 'uploads/' });
|
||||
|
||||
uploads = {}; // Storing upload progress
|
||||
|
||||
router.post('/import', authenticate, upload.single('fileInput'), async (req, res) => {
|
||||
const fileType = req.body.fileType;
|
||||
const filePath = req.file.path;
|
||||
const uploadId = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
if (fileType === 'csv') {
|
||||
const results = [];
|
||||
fs.createReadStream(filePath)
|
||||
.pipe(csv())
|
||||
.on('data', (data) => results.push(data))
|
||||
.on('end', async () => {
|
||||
const conn = await pool.getConnection();
|
||||
uploads[uploadId] = { completed: false, total: results.length, processed: 0 };
|
||||
res.end(JSON.stringify({ success: true, message: 'Started upload, please wait!', id: uploadId }));
|
||||
results.shift(); // Remove the first line if it is headers
|
||||
for (const row of results) {
|
||||
const { robloxId, discordId, robloxUsername, discordUsername, reasonShort, reasonLong } = row;
|
||||
await conn.query('INSERT INTO bans (robloxId, discordId, reasonShort, reasonLong, reasonsFlag, expiresTimestamp, robloxUsername, discordUsername) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[robloxId, discordId, reasonShort, reasonLong, robloxUsername, discordUsername]);
|
||||
uploads[uploadId].processed++;
|
||||
}
|
||||
conn.end();
|
||||
fs.unlinkSync(filePath); // Remove the file after processing
|
||||
uploads[uploadId].completed = true;
|
||||
});
|
||||
} else if (fileType === 'mfd') {
|
||||
const data = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = data.split('\n');
|
||||
const conn = await pool.getConnection();
|
||||
uploads[uploadId] = { completed: false, total: lines.length, processed: 0 };
|
||||
res.end(JSON.stringify({ success: true, message: 'Started upload, please wait!', id: uploadId }));
|
||||
for (const line of lines) {
|
||||
// This is a text file, split by "x/"", get [0], then split by / and get the last element, that will be an ID. All other values are static.
|
||||
const robloxId = line.split('x/')[0].split('/').pop();
|
||||
const reasonLong = line.split('/profile ')[1];
|
||||
const reasonShort = "Listed by MFD";
|
||||
const moderator = "MFD";
|
||||
const reasonsFlag = flags.addFlag(0, reasonFlags.CHILD_SAFETY)
|
||||
await conn.query('INSERT INTO bans (robloxId, reasonShort, reasonLong, reasonsFlag, moderator) VALUES (?, ?, ?, ?, ?)',
|
||||
[robloxId, reasonShort, reasonLong, reasonsFlag, moderator]);
|
||||
uploads[uploadId].processed++;
|
||||
}
|
||||
conn.end();
|
||||
uploads[uploadId].completed = true;
|
||||
fs.unlinkSync(filePath); // Remove the file after processing
|
||||
} else {
|
||||
fs.unlinkSync(filePath); // Remove the file if the type is not supported
|
||||
res.json({ success: false, message: 'Unsupported file type.' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/uploadStatus', authenticate, (req, res) => {
|
||||
res.render('admin/uploadStatus', { env: process.env, session: req.session, uploads });
|
||||
});
|
||||
|
||||
// API STUFF //
|
||||
|
||||
router.get("/api/bans", authenticate, async (req, res) => {
|
||||
|
@ -138,6 +204,19 @@ router.get("/api/bans", authenticate, async (req, res) => {
|
|||
res.json(rows);
|
||||
});
|
||||
|
||||
router.get('/api/uploads/:id', authenticate, (req, res) => {
|
||||
const id = req.params.id;
|
||||
if (!uploads[id]) {
|
||||
res.json({ success: false, message: 'Upload not found' });
|
||||
return;
|
||||
}
|
||||
res.json(uploads[id]);
|
||||
});
|
||||
|
||||
router.get('/api/uploads', authenticate, (req, res) => {
|
||||
res.json(uploads);
|
||||
});
|
||||
|
||||
// AUTH STUFF //
|
||||
|
||||
router.get('/login', (req, res) => {
|
||||
|
|
18933
uploads/0117f3a7fe6e13bc12e844d6bea58f5c
Normal file
18933
uploads/0117f3a7fe6e13bc12e844d6bea58f5c
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/0d9c56cc5e388a17327f6ea146eef619
Normal file
18933
uploads/0d9c56cc5e388a17327f6ea146eef619
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/1d24a179f2feabdd693ad14741b6ea03
Normal file
18933
uploads/1d24a179f2feabdd693ad14741b6ea03
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/31db3ad4ab9aec745ac3db67a8941ee3
Normal file
18933
uploads/31db3ad4ab9aec745ac3db67a8941ee3
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/3840082c4cb14f5595e7be505660a01b
Normal file
18933
uploads/3840082c4cb14f5595e7be505660a01b
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/3c5933c7028a08912eb2ddb5213ba4cb
Normal file
18933
uploads/3c5933c7028a08912eb2ddb5213ba4cb
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/40f216c9f1c4ec51d70f2b7a616ac122
Normal file
18933
uploads/40f216c9f1c4ec51d70f2b7a616ac122
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/48420ccf9f8b0633e004d80a4123adbe
Normal file
18933
uploads/48420ccf9f8b0633e004d80a4123adbe
Normal file
File diff suppressed because it is too large
Load diff
6
uploads/63271bde5359f803ba15fe2352520e27
Normal file
6
uploads/63271bde5359f803ba15fe2352520e27
Normal file
|
@ -0,0 +1,6 @@
|
|||
"id","robloxId","discordId","robloxUsername","discordUsername","reasonShort","reasonLong","reasonsFlag","moderator","banTimestamp","expiresTimestamp"
|
||||
"1b5f4d5c-bfc6-11ef-8db2-e0d55eac39c4","1234",,,,asdf,asdf,3,admin,2024-12-21 18:05:10.000,2024-12-21 11:05:00.000
|
||||
"216d4c19-bf76-11ef-8db2-e0d55eac39c4","4321","43211234",,,aasdf,asdfasdfasdf,9,admin,2024-12-21 08:32:39.000,
|
||||
"54fdbfd6-bfc5-11ef-8db2-e0d55eac39c4","121234","21312341234",,,"42424","32423424",31,admin,2024-12-21 17:59:37.000,
|
||||
"83bd09b5-bf78-11ef-8db2-e0d55eac39c4","25226480",,,,Edited,Guh,31,admin,2024-12-21 08:49:43.000,
|
||||
e3ae0728-bfc5-11ef-8db2-e0d55eac39c4,"12344321","4123",,,asdfadsf,asdfasdf,0,admin,2024-12-21 18:03:37.000,
|
18933
uploads/7d15b5bfa97282298502f467c8842307
Normal file
18933
uploads/7d15b5bfa97282298502f467c8842307
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/83d833c1cebfc1f4cd1d288b7df870cd
Normal file
18933
uploads/83d833c1cebfc1f4cd1d288b7df870cd
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/8aba52925d0da52c8aaa94ef078e6c24
Normal file
18933
uploads/8aba52925d0da52c8aaa94ef078e6c24
Normal file
File diff suppressed because it is too large
Load diff
2
uploads/90c9ab4e93fa395f980e047131c4584d
Normal file
2
uploads/90c9ab4e93fa395f980e047131c4584d
Normal file
|
@ -0,0 +1,2 @@
|
|||
robloxId,discordId,robloxUsername,discordUsername,reasonShort,reasonLong
|
||||
123654,,test,,Test Reason,test2
|
18933
uploads/a1902891738daf7cf68ab65a55c038a0
Normal file
18933
uploads/a1902891738daf7cf68ab65a55c038a0
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/c223cc1db429864e2114bb1e8be5a9c9
Normal file
18933
uploads/c223cc1db429864e2114bb1e8be5a9c9
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/c4a96f29807dd3cc483232363362154c
Normal file
18933
uploads/c4a96f29807dd3cc483232363362154c
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/dc35c28b4f0aab1321fd66dcd26ffac0
Normal file
18933
uploads/dc35c28b4f0aab1321fd66dcd26ffac0
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/e12e511dd2fa91945ca50d7bbd5bb2e1
Normal file
18933
uploads/e12e511dd2fa91945ca50d7bbd5bb2e1
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/e23538202322996152c9cc6e1b0dec5f
Normal file
18933
uploads/e23538202322996152c9cc6e1b0dec5f
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/eb5d25b5cc826c4ae5c256a65a7207f5
Normal file
18933
uploads/eb5d25b5cc826c4ae5c256a65a7207f5
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/f0f8bcf31add8d5bb751eabc00ea814b
Normal file
18933
uploads/f0f8bcf31add8d5bb751eabc00ea814b
Normal file
File diff suppressed because it is too large
Load diff
18933
uploads/f105dd38e173a268eb8cce3a96d19376
Normal file
18933
uploads/f105dd38e173a268eb8cce3a96d19376
Normal file
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/gh/hung1001/font-awesome-pro@4cac1a6/css/all.css" rel="stylesheet" type="text/css" />
|
||||
<title>UBS Admin Dashboard</title>
|
||||
</head>
|
||||
|
||||
|
@ -18,6 +19,14 @@
|
|||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<div class="d-flex">
|
||||
<a href="/admin/import" class="btn btn-secondary me-2">
|
||||
<i class="fas fa-file-import"></i> Import Bans
|
||||
</a>
|
||||
<a href="/admin/create" class="btn btn-primary">
|
||||
<i class="fas fa-plus-circle"></i> New Ban
|
||||
</a>
|
||||
</div>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Welcome, <%= session.user.username %></a>
|
||||
</li>
|
||||
|
@ -30,7 +39,6 @@
|
|||
</nav>
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4 d-inline">Bans</h2>
|
||||
<a href="/admin/create" class="btn btn-primary float-end">New Ban</a>
|
||||
</div>
|
||||
<table class="table table-dark table-striped">
|
||||
<thead>
|
||||
|
|
41
views/admin/import.ejs
Normal file
41
views/admin/import.ejs
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>UBS Admin - New ban</title>
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card bg-dark text-light shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-center mb-4">Mass Ban (Import)</h2>
|
||||
<div class="alert" id="message" style="display: none;"></div>
|
||||
<div class="alert alert-warning">
|
||||
The import system does <strong>NOT</strong> have deduplication. Please be sure to remove duplicate entries from your list <strong>BEFORE</strong> uploading!
|
||||
</div>
|
||||
<form id="banForm">
|
||||
<div class="mb-3">
|
||||
<label for="fileType" class="form-label">Select File Type</label>
|
||||
<select class="form-select" id="fileType" name="fileType" required>
|
||||
<option value="csv">CSV</option>
|
||||
<option value="mfd">ModForDummies</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="fileInput" class="form-label">Upload File</label>
|
||||
<input type="file" class="form-control" id="fileInput" name="fileInput" accept=".csv, .txt" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/admin/import.js"></script>
|
||||
</body>
|
||||
</html>
|
24
views/admin/uploadStatus.ejs
Normal file
24
views/admin/uploadStatus.ejs
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>UBS Admin - New ban</title>
|
||||
</head>
|
||||
<body class="bg-dark text-light">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card bg-dark text-light shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-center mb-4">Import Status</h2>
|
||||
<div class="alert" id="message" style="display: none;"></div>
|
||||
<div id="import-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/admin/uploadStatus.js"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue