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 cors = require('cors'); const { authorize, authenticate, tokens } = require('./auth.js'); 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 privileges (privilege_name, access_level) VALUES ('admin', 100)"); db.query("INSERT INTO admins (login, password_hash, privilege_name) VALUES ('admin', $1, 'admin')", [passwordHash]) } app.use(express.urlencoded({ extended: true })) app.use(express.json()) app.use(cors()); 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.get('/api/getThreadIdByPostId/:postId', async (req, res) => { if (!req.params.postId) { return res.status(400).send("Didn't provide post id."); } threadId = (await db.query(`SELECT thread_id FROM threads WHERE $1 = ANY(posts_ids)`, [req.params.postId])); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(threadId.rows[0].thread_id)); }) app.get('/api/getPosts/:boardId/:threadId', async (req, res) => { if (!req.params.boardId) return res.status(400).send("Didn't provide board id."); if (req.params.threadId == undefined) return res.status(400).send("Didn't provide thread id."); posts = []; (await db.query('SELECT post_id, content, timestamp, options FROM posts WHERE board_id = $1 AND thread_id = $2', [req.params.boardId, req.params.threadId])).rows .forEach((post) => posts.push(post)) res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(posts)); }); app.post('/api/post', authorize, async (req, res) => { const {options, content, threadId, boardId} = req.body; console.log(`${options} ${content} ${threadId} ${boardId}`) if (!threadId || !boardId) return res.status(400).send("Thread ID or board ID is not specified"); let postId = Number((await db.query('SELECT post_id FROM posts WHERE board_id = $1 ORDER BY post_id DESC LIMIT 1', [boardId])).rows[0].post_id) + 1 await db.query('INSERT INTO posts(board_id, thread_id, post_id, options, content, media_ids, is_root, timestamp, user_ip) VALUES ($1, $2, $3, $4, $5, \'{}\', false, NOW(), $6)', [boardId, threadId, postId, options, content, req.socket.remoteAddress]); await db.query('UPDATE threads SET posts_ids = ARRAY_APPEND(posts_ids, $1) WHERE thread_id = $2 AND board_id = $3', [postId, threadId, boardId]); res.status(200).send("Post sent"); }); app.post('/api/createThread', authorize, async (req, res) => { let isLocked, isPinned const { boardId, threadTitle, content, options} = req.body; if (!boardId) return res.status(400).send("Board name is not specified"); isLocked = isLocked || false; // if undefined then false isPinned = isPinned || false; console.log(`Board id: ${boardId}\nThread name: ${threadTitle}\nIs locked: ${isLocked}\nIs pinned: ${isPinned}\nContent: ${content}\nOptions: ${options}`); const boardOptions = (await db.query('SELECT options FROM boards WHERE board_id = $1', [boardId])).rows[0].options; let threadId = (await db.query('SELECT EXISTS(SELECT FROM threads WHERE board_id = $1)', [boardId])).rows[0].exists? Number((await db.query('SELECT thread_id FROM threads WHERE board_id = $1 ORDER BY thread_id DESC LIMIT 1', [boardId])).rows[0].thread_id) + 1 : 0 let postId = (await db.query('SELECT EXISTS(SELECT FROM posts WHERE board_id = $1)', [boardId])).rows[0].exists? Number((await db.query('SELECT post_id FROM posts WHERE board_id = $1 ORDER BY post_id DESC LIMIT 1', [boardId])).rows[0].post_id) + 1 : 0 console.log(`ThreadId: ${threadId} postId: ${postId}`); let validateResults = validateThread(threadTitle, isLocked, isPinned, content, options, boardOptions, req.isAdmin); if (validateResults != "ok") return res.status(400).send(validateResults); await db.query('INSERT INTO posts (board_id, thread_id, post_id, content, is_root, timestamp, user_ip) VALUES($1, $2, $3, $4, $5, NOW(), $6)', [boardId, threadId, postId, content, true, req.socket.remoteAddress]); await db.query('INSERT INTO threads (board_id, thread_id, thread_name, posts_ids, is_locked, is_pinned, options) VALUES ($1, $2, $3, $4, $5, $6, $7)', [boardId, threadId, threadTitle, [postId], isLocked, isPinned, options]); res.redirect(`/${boardId}/${postId}`); }); app.get('/api/getThreads/:boardId', async (req, res) => { threads = []; (await db.query('SELECT * FROM threads WHERE board_id = $1', [req.params.boardId])).rows .forEach((thread) => threads.push(thread)) res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(threads)); }); 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("Login or password is not specified"); queryRes = await db.query('SELECT * FROM admins WHERE login = $1', [login]) if (queryRes.rowCount == 0) return res.status(401).send("No such login"); let hashedPassword = queryRes.rows[0].password_hash; bcrypt.compare(password, hashedPassword, (err, result) => { if (!result) return res.status(403).send("Incorrect password"); 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; } res.redirect("/"); }); }); app.post('/api/createBoard', authenticate, async (req, res) => { let login = req.session.login; let { boardId, boardTitle, options} = req.body; if (!boardId || !boardTitle) return res.status(400).send("Board ID or board title is not specified"); 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("Invalid size of the URI of board"); if (boardTitle.length == 0 || boardTitle.length > 32) return res.status(401).send("Invalid size of the name of board"); if (queryRes.rowCount > 0) return res.status(401).send("Such board already exists"); 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("The board was created succsessfully"); }); app.listen(process.env.APP_PORT, () => { console.log("App started"); }); const validateThread = (threadName, isLocked, isPinned, content, options, boardOptions, isAdmin) => { if ((isPinned || isLocked) && !isAdmin) return "Insuffucuent permissions for flags"; if (!content && boardOptions.requireContentForThreadCreation) return "You cannot create thread without text"; //TODO: check if image is required return 'ok' }; app.post('/api/test', authorize, (req, res) => { console.log("test") console.log(authorize) return res.status(200).send("Здаров заебал") });