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' ) ;
2024-01-14 18:48:59 +03:00
const { authorize , authenticate , tokens } = require ( './auth.js' ) ;
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 } ` ) ;
2024-01-14 18:48:59 +03:00
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 ] )
2023-10-27 18:42:05 +03:00
}
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 ) => {
} ) ;
2024-01-14 18:48:59 +03:00
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 ) ) ;
} )
2023-11-17 01:17:39 +03:00
app . get ( '/api/getPosts/:boardId/:threadId' , async ( req , res ) => {
2024-01-14 18:48:59 +03:00
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." ) ;
2023-11-17 01:17:39 +03:00
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 ) ) ;
} ) ;
2024-01-14 18:48:59 +03:00
app . post ( '/api/post' , authorize , async ( req , res ) => {
2023-11-17 01:17:39 +03:00
const { options , content , threadId , boardId } = req . body ;
2024-01-14 18:48:59 +03:00
console . log ( ` ${ options } ${ content } ${ threadId } ${ boardId } ` )
2023-11-17 01:17:39 +03:00
2024-01-14 18:48:59 +03:00
if ( ! threadId || ! boardId ) return res . status ( 400 ) . send ( "Thread ID or board ID is not specified" ) ;
2023-11-17 01:17:39 +03:00
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 ] ) ;
2024-01-14 18:48:59 +03:00
res . status ( 200 ) . send ( "Post sent" ) ;
2023-11-03 21:11:45 +03:00
} ) ;
2024-01-14 18:48:59 +03:00
app . post ( '/api/createThread' , authorize , async ( req , res ) => {
let isLocked ,
isPinned
2023-11-04 14:55:10 +03:00
const { boardId , threadTitle , content , options } = req . body ;
2023-11-04 11:37:10 +03:00
2024-01-14 18:48:59 +03:00
if ( ! boardId ) return res . status ( 400 ) . send ( "Board name is not specified" ) ;
2023-11-17 01:17:39 +03:00
2024-01-14 18:48:59 +03:00
isLocked = isLocked || false ; // if undefined then false
isPinned = isPinned || false ;
2023-11-04 14:55:10 +03:00
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-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 ,
2024-01-14 18:48:59 +03:00
boardOptions , req . isAdmin ) ;
2023-11-04 14:55:10 +03:00
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 = [ ] ;
2024-01-14 18:48:59 +03:00
( await db . query ( 'SELECT * FROM threads WHERE board_id = $1' , [ req . params . boardId ] ) ) . rows
. forEach ( ( thread ) => threads . push ( thread ) )
2023-11-17 01:17:39 +03:00
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 ) ) ;
} ) ;
app . post ( '/api/login' , async ( req , res ) => {
const { login , password } = req . body ;
2024-01-14 18:48:59 +03:00
if ( ! ( login && password ) ) return res . status ( 401 ) . send ( "Login or password is not specified" ) ;
2023-10-27 18:42:05 +03:00
queryRes = await db . query ( 'SELECT * FROM admins WHERE login = $1' , [ login ] )
2024-01-14 18:48:59 +03:00
if ( queryRes . rowCount == 0 ) return res . status ( 401 ) . send ( "No such login" ) ;
2023-10-27 18:42:05 +03:00
let hashedPassword = queryRes . rows [ 0 ] . password _hash ;
bcrypt . compare ( password , hashedPassword , ( err , result ) => {
2024-01-14 18:48:59 +03:00
if ( ! result ) return res . status ( 403 ) . send ( "Incorrect password" ) ;
2023-10-27 18:42:05 +03:00
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 ;
}
2023-11-03 21:11:45 +03:00
res . redirect ( "/" ) ;
2023-10-27 18:42:05 +03:00
} ) ;
} ) ;
2024-01-14 18:48:59 +03:00
app . post ( '/api/createBoard' , authenticate , async ( req , res ) => {
let login = req . session . login ;
2023-11-04 11:37:10 +03:00
let { boardId , boardTitle , options } = req . body ;
2024-01-14 18:48:59 +03:00
if ( ! boardId || ! boardTitle ) return res . status ( 400 ) . send ( "Board ID or board title is not specified" ) ;
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 ] ) ;
2024-01-14 18:48:59 +03:00
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" ) ;
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 ] ) ;
2024-01-14 18:48:59 +03:00
return res . status ( 200 ) . send ( "The board was created succsessfully" ) ;
2023-10-27 18:42:05 +03:00
} ) ;
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 ) => {
2024-01-14 18:48:59 +03:00
if ( ( isPinned || isLocked ) && ! isAdmin ) return "Insuffucuent permissions for flags" ;
if ( ! content && boardOptions . requireContentForThreadCreation ) return "You cannot create thread without text" ;
2023-11-04 14:55:10 +03:00
//TODO: check if image is required
return 'ok'
2023-11-17 01:17:39 +03:00
} ;
2024-01-14 18:48:59 +03:00
app . post ( '/api/test' , authorize , ( req , res ) => {
console . log ( "test" )
console . log ( authorize )
return res . status ( 200 ) . send ( "Здаров заебал" )
} ) ;