2023-10-22 22:39:45 +03:00
const express = require ( 'express' ) ;
2023-10-27 18:42:05 +03:00
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' ) ;
2023-11-04 11:37:10 +03:00
// const fileupload = require('express-fileupload');
2023-11-18 14:12:46 +03:00
const cors = require ( 'cors' ) ;
2023-10-22 22:39:45 +03:00
const app = express ( ) ;
2023-10-27 18:42:05 +03:00
dotenv . config ( { path : './web.env' } ) ;
2023-11-04 11:37:10 +03:00
const default _board _settings = require ( '../default_board_settings.json' ) ;
2023-10-27 18:42:05 +03:00
const db = new Client ( {
user : process . env . DB _USER ,
2023-11-04 11:37:10 +03:00
host : process . env . DB _HOST ,
2023-10-27 18:42:05 +03:00
database : process . env . DB _NAME ,
password : process . env . DB _PASS ,
port : 5432
} ) ;
2023-11-04 14:55:10 +03:00
const errorHandler = ( err ) => {
if ( err ) console . log ( err ) ;
}
2023-10-27 18:42:05 +03:00
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" ) ;
2023-11-04 14:55:10 +03:00
db . query ( initSQL , errorHandler ) ;
2023-10-27 18:42:05 +03:00
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 ( ) )
2023-11-18 14:12:46 +03:00
app . use ( cors ( ) ) ;
2023-10-27 18:42:05 +03:00
app . use ( session ( {
secret : process . env . SESSION _SECRET ,
store : new MemoryStore ( {
checkPeriod : 86400000
} ) ,
resave : false ,
saveUninitialized : false ,
cookie : { maxAge : 1000 * 60 * 60 * 24 }
} ) ) ;
2023-10-22 22:39:45 +03:00
2023-10-27 18:42:05 +03:00
2023-11-03 21:11:45 +03:00
app . post ( '/api/uploadMedia' , async ( req , res ) => {
} ) ;
2023-11-17 01:17:39 +03:00
app . get ( '/api/getPosts/:boardId/:threadId' , async ( req , res ) => {
posts = [ ] ;
2023-11-17 09:16:05 +03:00
( 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 ) )
2023-11-17 01:17:39 +03:00
res . setHeader ( 'Content-Type' , 'application/json' ) ;
res . end ( JSON . stringify ( posts ) ) ;
} ) ;
2023-11-03 21:11:45 +03:00
app . post ( '/api/post' , async ( req , res ) => {
2023-11-17 01:17:39 +03:00
let login = req . session . login ,
token = req . session . token ,
isAdmin = false ;
const { options , content , threadId , boardId } = req . body ;
if ( ! threadId || ! boardId ) return res . status ( 400 ) . send ( "Н е указано ID треда или доски" ) ;
if ( login && token ) {
if ( authorize ( login , token ) ) isAdmin = true ;
else res . status ( 403 ) . send ( "Невалидный токен" ) ;
}
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 ( "Пост отправлен" ) ;
2023-11-03 21:11:45 +03:00
} ) ;
app . post ( '/api/createThread' , async ( req , res ) => {
2023-11-17 01:17:39 +03:00
let login = req . session . login ,
token = req . session . token ,
isLocked ,
isPinned ,
isAdmin = false ;
2023-11-04 14:55:10 +03:00
const { boardId , threadTitle , content , options } = req . body ;
2023-11-04 11:37:10 +03:00
2023-11-17 01:17:39 +03:00
if ( ! boardId ) return res . status ( 400 ) . send ( "Н е указано имя доски" ) ;
isLocked = isLocked ? isLocked : false ; // if undefined then false
2023-11-04 14:55:10 +03:00
isPinned = isPinned ? isPinned : false ;
console . log ( ` Board id: ${ boardId } \n Thread name: ${ threadTitle } \n Is locked: ${ isLocked } \n Is pinned: ${ isPinned } \n Content: ${ content } \n Options: ${ options } ` ) ;
2023-11-17 01:17:39 +03:00
if ( login && token ) {
if ( authorize ( login , token ) ) isAdmin = true ;
else res . status ( 403 ) . send ( "Невалидный токен" ) ;
2023-11-04 11:37:10 +03:00
}
2023-11-17 01:17:39 +03:00
const boardOptions = ( await db . query ( 'SELECT options FROM boards WHERE board_id = $1' , [ boardId ] ) ) . rows [ 0 ] . options ;
2023-11-04 11:37:10 +03:00
2023-11-17 01:17:39 +03:00
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 } ` ) ;
2023-11-04 14:55:10 +03:00
let validateResults = validateThread ( threadTitle , isLocked ,
isPinned , content , options ,
boardOptions , isAdmin ) ;
if ( validateResults != "ok" ) return res . status ( 400 ) . send ( validateResults ) ;
2023-11-17 01:17:39 +03:00
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 ] ) ;
2023-11-04 14:55:10 +03:00
res . redirect ( ` / ${ boardId } / ${ postId } ` ) ;
2023-11-03 21:11:45 +03:00
} ) ;
2023-11-11 21:25:47 +03:00
app . get ( '/api/getThreads/:boardId' , async ( req , res ) => {
2023-11-17 01:17:39 +03:00
threads = [ ] ;
( await db . query ( 'SELECT thread_id FROM threads WHERE board_id = $1' , [ req . params . boardId ] ) ) . rows
. forEach ( ( thread ) => threads . push ( thread . thread _id ) )
2023-11-11 21:25:47 +03:00
res . setHeader ( 'Content-Type' , 'application/json' ) ;
2023-11-17 01:17:39 +03:00
res . end ( JSON . stringify ( threads ) ) ;
2023-11-11 21:25:47 +03:00
} ) ;
2023-10-27 18:42:05 +03:00
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 ) ) ;
2023-11-18 14:12:46 +03:00
// res.json(JSON.stringify(queryRes.rows));
2023-10-27 18:42:05 +03:00
} ) ;
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 ) ;
}
2023-11-03 21:11:45 +03:00
res . redirect ( "/" ) ;
2023-10-27 18:42:05 +03:00
} ) ;
} ) ;
app . post ( '/api/createBoard' , async ( req , res ) => {
let login , token ;
2023-11-04 11:37:10 +03:00
let { boardId , boardTitle , options } = req . body ;
2023-10-27 18:42:05 +03:00
try {
let currentSession = req . session ;
token = currentSession . token ;
login = currentSession . login ;
} catch ( err ) {
console . log ( err ) ;
}
2023-11-04 11:37:10 +03:00
if ( token != tokens [ login ] || ! token ) return res . status ( 403 ) . send ( "Невалидный токен" ) ;
if ( ! boardId || ! boardTitle ) return res . status ( 400 ) . send ( "Неверно сформирован запрос" ) ;
2023-10-27 18:42:05 +03:00
2023-10-28 01:11:17 +03:00
console . log ( ` Admin ${ login } is creating new board: ${ boardId } , ${ boardTitle } ` ) ;
2023-10-27 18:42:05 +03:00
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 борды" ) ;
2023-10-28 01:01:39 +03:00
if ( boardTitle . length == 0 || boardTitle . length > 32 ) return res . status ( 401 ) . send ( "Неверный размер имени борды" ) ;
2023-10-27 18:42:05 +03:00
if ( queryRes . rowCount > 0 ) return res . status ( 401 ) . send ( "Такая борда уже существует." ) ;
2023-11-04 11:37:10 +03:00
if ( ! options ) options = default _board _settings ;
2023-10-27 18:42:05 +03:00
2023-11-04 11:37:10 +03:00
await db . query ( 'INSERT INTO boards (board_id, board_name, options) VALUES ($1, $2, $3)' , [ boardId , boardTitle , options ] ) ;
2023-10-27 18:42:05 +03:00
return res . status ( 200 ) . send ( "Борда успешно создана" ) ;
} ) ;
2023-10-22 22:39:45 +03:00
2023-10-27 22:22:25 +03:00
app . listen ( process . env . APP _PORT , ( ) => {
2023-10-27 18:42:05 +03:00
console . log ( "App started" ) ;
2023-11-04 14:55:10 +03:00
} ) ;
const validateThread = ( threadName , isLocked , isPinned , content , options , boardOptions , isAdmin ) => {
if ( ( isPinned || isLocked ) && ! isAdmin ) return "Нет прав на выставление админских флагов" ;
2023-11-18 14:12:46 +03:00
if ( ! content && boardOptions . requireContentForThreadCreation ) return "Нельзя создать тред без текста" ;
2023-11-04 14:55:10 +03:00
//TODO: check if image is required
return 'ok'
2023-11-17 01:17:39 +03:00
} ;
const authorize = ( login , token ) => {
return tokens [ login ] == token ? true : false ;
} ;