186 lines
7.0 KiB
JavaScript
186 lines
7.0 KiB
JavaScript
const express = require('express');
|
||
const { Client } = require('pg');
|
||
const session = require('express-session');
|
||
const dotenv = require('dotenv');
|
||
const MemoryStore = require('memorystore')(session);
|
||
const fs = require('fs');
|
||
const bcrypt = require('bcryptjs');
|
||
// const fileupload = require('express-fileupload');
|
||
|
||
const app = express();
|
||
|
||
dotenv.config({ path: './web.env' });
|
||
const default_board_settings = require('../default_board_settings.json');
|
||
|
||
const db = new Client({
|
||
user: process.env.DB_USER,
|
||
host: process.env.DB_HOST,
|
||
database: process.env.DB_NAME,
|
||
password: process.env.DB_PASS,
|
||
port: 5432
|
||
});
|
||
|
||
const errorHandler = (err) => {
|
||
if (err) console.log(err);
|
||
}
|
||
|
||
db.connect((error) => {
|
||
if (error) console.log(error);
|
||
else console.log("Database connected");
|
||
db.query('SELECT to_regclass(\'admins\');', (err, res) => {
|
||
if(res.rows[0].to_regclass != "admins") init();
|
||
})
|
||
})
|
||
|
||
const init = async () => {
|
||
let initSQL = fs.readFileSync("./database_schematic.pgsql").toString();
|
||
|
||
console.log("No tables found, assuming first run, creating database scheme");
|
||
|
||
db.query(initSQL, errorHandler);
|
||
|
||
let adminPassword = Math.random().toString(36).slice(-8);
|
||
let passwordHash = await bcrypt.hash(adminPassword, 8);
|
||
console.log(`Creating admin account with credentials: admin:${adminPassword}`);
|
||
db.query("INSERT INTO priveleges (privelege_name, access_level) VALUES ('admin', 100)");
|
||
db.query("INSERT INTO admins (login, password_hash, privelege_name) VALUES ('admin', $1, 'admin')", [passwordHash])
|
||
}
|
||
|
||
let tokens = {};
|
||
|
||
app.use(express.urlencoded({ extended: true }))
|
||
app.use(express.json())
|
||
|
||
|
||
app.use(session({
|
||
secret: process.env.SESSION_SECRET,
|
||
store: new MemoryStore({
|
||
checkPeriod: 86400000
|
||
}),
|
||
resave: false,
|
||
saveUninitialized: false,
|
||
cookie: { maxAge: 1000*60*60*24 }
|
||
}));
|
||
|
||
|
||
app.post('/api/uploadMedia', async (req, res) => {
|
||
|
||
});
|
||
|
||
app.post('/api/post', async (req, res) => {
|
||
const { content, } = req.body;
|
||
});
|
||
|
||
app.post('/api/createThread', async (req, res) => {
|
||
let login, token, isLocked, isPinned
|
||
const { boardId, threadTitle, content, options} = req.body;
|
||
|
||
isLocked = isLocked? isLocked : false;
|
||
isPinned = isPinned? isPinned : false;
|
||
|
||
console.log(`Board id: ${boardId}\nThread name: ${threadTitle}\nIs locked: ${isLocked}\nIs pinned: ${isPinned}\nContent: ${content}\nOptions: ${options}`);
|
||
|
||
try {
|
||
let currentSession = req.session;
|
||
token = currentSession.token;
|
||
login = currentSession.login;
|
||
} catch (err) {
|
||
console.log(err);
|
||
}
|
||
|
||
|
||
if (login && token && token != tokens[login]) return res.status(403).send("Невалидный токен");
|
||
let isAdmin = token? true : false;
|
||
|
||
const boardOptions = (await db.query('SELECT * FROM boards WHERE board_id = $1', [boardId])).rows[0].options
|
||
let postId = (await db.query('SELECT nextval(pg_get_serial_sequence(\'posts\', \'post_id\'))')).rows[0].nextval;
|
||
let threadId = (await db.query('SELECT nextval(pg_get_serial_sequence(\'threads\', \'thread_id\'))')).rows[0].nextval;
|
||
|
||
let validateResults = validateThread(threadTitle, isLocked,
|
||
isPinned, content, options,
|
||
boardOptions, isAdmin);
|
||
if (validateResults != "ok") return res.status(400).send(validateResults);
|
||
|
||
await db.query('INSERT INTO posts (post_id, content, is_root, timestamp, user_ip) VALUES($1, $2, $3, NOW(), $4)', [postId, content, true, req.socket.remoteAddress]);
|
||
await db.query('INSERT INTO threads (thread_id, thread_name, posts_ids, is_locked, is_pinned, options) VALUES ($1, $2, $3, $4, $5, $6)', [threadId, threadTitle, [postId], isLocked, isPinned, options]);
|
||
await db.query('UPDATE boards SET threads_ids = ARRAY_APPEND(threads_ids, $1) WHERE board_id = $2', [threadId, boardId]);
|
||
res.redirect(`/${boardId}/${postId}`);
|
||
});
|
||
|
||
app.get('/api/getThreads/:boardId', async (req, res) => {
|
||
let queryRes = (await db.query('SELECT * FROM boards WHERE board_id = $1', [req.params.boardId])).rows[0];
|
||
|
||
res.setHeader('Content-Type', 'application/json');
|
||
res.end(JSON.stringify(queryRes.threads_ids));
|
||
});
|
||
|
||
app.get('/api/getBoards', async (req, res) => {
|
||
let queryRes = await db.query('SELECT * FROM boards');
|
||
|
||
res.setHeader('Content-Type', 'application/json');
|
||
res.end(JSON.stringify(queryRes.rows));
|
||
});
|
||
|
||
app.post('/api/login', async (req, res) => {
|
||
const { login, password } = req.body;
|
||
|
||
if (!(login && password)) return res.status(401).send("Не указан логин или пароль");
|
||
|
||
queryRes = await db.query('SELECT * FROM admins WHERE login = $1', [login])
|
||
if (queryRes.rowCount == 0) return res.status(401).send("Такого лоигна нет");
|
||
|
||
let hashedPassword = queryRes.rows[0].password_hash;
|
||
bcrypt.compare(password, hashedPassword, (err, result) => {
|
||
if (!result) return res.status(403).send("Пароли не совпадают");
|
||
let currentSession = req.session;
|
||
currentSession.login = login;
|
||
if (!tokens[login]) {
|
||
let token = Math.random().toString(26).slice(2); // ONLY IN DEV MODE
|
||
tokens[login] = token;
|
||
currentSession.token = token;
|
||
console.log(token);
|
||
}
|
||
res.redirect("/");
|
||
});
|
||
});
|
||
|
||
app.post('/api/createBoard', async (req, res) => {
|
||
let login, token;
|
||
let { boardId, boardTitle, options} = req.body;
|
||
|
||
try {
|
||
let currentSession = req.session;
|
||
token = currentSession.token;
|
||
login = currentSession.login;
|
||
} catch (err) {
|
||
console.log(err);
|
||
}
|
||
|
||
if (token != tokens[login] || !token) return res.status(403).send("Невалидный токен");
|
||
if (!boardId || !boardTitle) return res.status(400).send("Неверно сформирован запрос");
|
||
|
||
console.log(`Admin ${login} is creating new board: ${boardId}, ${boardTitle}`);
|
||
|
||
let queryRes = await db.query('SELECT * FROM boards WHERE board_id = $1::text', [boardId]);
|
||
if (boardId.length == 0 || boardId.length > 5) return res.status(401).send("Неверный размер URI борды");
|
||
if (boardTitle.length == 0 || boardTitle.length > 32) return res.status(401).send("Неверный размер имени борды");
|
||
if (queryRes.rowCount > 0) return res.status(401).send("Такая борда уже существует.");
|
||
|
||
if (!options) options = default_board_settings;
|
||
|
||
await db.query('INSERT INTO boards (board_id, board_name, options) VALUES ($1, $2, $3)', [boardId, boardTitle, options]);
|
||
return res.status(200).send("Борда успешно создана");
|
||
});
|
||
|
||
app.listen(process.env.APP_PORT, () => {
|
||
console.log("App started");
|
||
});
|
||
|
||
const validateThread = (threadName, isLocked, isPinned, content, options, boardOptions, isAdmin) => {
|
||
if ((isPinned || isLocked) && !isAdmin) return "Нет прав на выставление админских флагов";
|
||
if (!content) return "Нельзя создать тред без текста";
|
||
|
||
//TODO: check if image is required
|
||
return 'ok'
|
||
}
|