2023-02-12 01:53:13 -05:00
const ROW _COUNT = 5 ;
2023-02-05 09:50:49 -05:00
2023-02-07 18:06:18 -05:00
const AUTH _ACTIONS = [
'postCreate' ,
2023-02-07 19:36:32 -05:00
'fileCreate' ,
2023-02-11 02:11:45 -05:00
'vote' ,
2023-03-06 02:26:57 -05:00
'postDelete' ,
2023-03-06 16:15:27 -05:00
'pfp' ,
2023-03-06 02:26:57 -05:00
'follow'
2023-02-07 18:06:18 -05:00
] ;
2023-03-08 18:56:28 -05:00
const LEGAL _SORTS = {
'time' : 'time' ,
'rating' : 'rating' ,
'hot' : ` rating / (%d - time + 24000) `
}
2023-02-12 12:06:19 -05:00
2023-03-08 19:49:39 -05:00
const roles = [
'Owner' ,
'Admin' ,
'Veteran'
]
2023-02-12 01:53:13 -05:00
const FILE _SIZE _LIMIT = 1024 * 1024 * 16 ;
2023-02-12 16:18:59 -05:00
const VALID _EXTENSIONS = [ 'png' , 'jpg' , 'jpeg' , 'gif' , 'svg' , 'mp4' ] ;
2023-02-10 23:44:25 -05:00
var ridArray = { } ;
2023-02-10 19:12:57 -05:00
2023-02-05 09:50:49 -05:00
import sqlite3 from 'sqlite3'
import { open } from 'sqlite'
import { hash , compare } from 'bcrypt'
2023-02-07 19:36:32 -05:00
import { randomBytes , createHash } from 'node:crypto' ;
2023-02-12 01:53:13 -05:00
import { writeFile } from 'node:fs/promises' ;
2023-02-12 04:50:15 -05:00
import { calcVote , checkLength , checkRegex , safePath , formatPost } from '../util.js' ;
2023-02-05 09:50:49 -05:00
var db ;
async function initDb ( ) {
db = await open ( {
filename : ` ${ process . cwd ( ) } /db/main.db ` ,
driver : sqlite3 . Database
} ) ;
await db . run ( 'CREATE TABLE IF NOT EXISTS auth (username CHAR(64), password CHAR(1024))' ) ;
await db . run ( 'CREATE TABLE IF NOT EXISTS token (username CHAR(64), token CHAR(1024))' ) ;
2023-02-12 12:06:19 -05:00
await db . run ( 'CREATE TABLE IF NOT EXISTS post (username CHAR(64), id CHAR(64), content CHAR(10240), upvotes INTEGER, downvotes INTEGER, rating REAL, reply CHAR(64), time INTEGER)' ) ;
2023-02-05 09:50:49 -05:00
await db . run ( 'CREATE TABLE IF NOT EXISTS vote (id CHAR(64), username CHAR(64), type INTEGER)' ) ;
2023-02-11 19:42:10 -05:00
await db . run ( 'CREATE TABLE IF NOT EXISTS user (username CHAR(64), followers INTEGER, following INTEGER, upvotes INTEGER, downvotes INTEGER, reputation REAL)' ) ;
2023-03-06 02:26:57 -05:00
await db . run ( 'CREATE TABLE IF NOT EXISTS bio (username CHAR(64), content CHAR(10240), roles INTEGER)' ) ;
await db . run ( 'CREATE TABLE IF NOT EXISTS follow (username CHAR(64), following CHAR(64))' ) ;
2023-02-05 09:50:49 -05:00
}
2023-02-07 18:06:18 -05:00
let backendProxy = async ( { route , backendParams } ) => {
if ( ! db ) await initDb ( ) ;
2023-03-08 19:49:39 -05:00
var user = ( await backend . token ( { cookies : backendParams . cookies } ) ) . data ;
if ( ( ! user || user == '' ) && AUTH _ACTIONS . indexOf ( route ) != - 1 ) return { 'success' : 'Not authorized.' } ;
2023-02-07 18:06:18 -05:00
2023-03-08 19:49:39 -05:00
var isAdmin = false ;
if ( user && user != '' ) isAdmin = ( await userRoles ( user ) ) . indexOf ( 'Admin' ) != - 1 ;
backendParams [ 'admin' ] = isAdmin ;
if ( AUTH _ACTIONS . indexOf ( route ) != - 1 ) backendParams [ 'user' ] = user ;
2023-02-07 18:06:18 -05:00
return backend [ route ] ( backendParams ) ;
}
2023-02-05 09:50:49 -05:00
var backend = { } ;
2023-02-07 18:06:18 -05:00
let updateUser = async ( { user } ) => {
2023-02-05 09:50:49 -05:00
let allPosts = await db . all ( 'SELECT * from post WHERE username = ?' , [
user
] ) ;
let upvotes = 0 ;
let downvotes = 0 ;
allPosts . forEach ( post => {
upvotes += post . upvotes || 0 ;
downvotes += post . downvotes || 0 ;
} ) ;
await db . run ( 'DELETE FROM user WHERE username = ?' , [
user
] ) ;
await db . run ( 'INSERT INTO user (username,followers,following,upvotes,downvotes,reputation) VALUES (?,?,?,?,?,?)' , [
user ,
0 ,
0 ,
upvotes ,
downvotes ,
2023-02-12 01:53:13 -05:00
calcVote ( upvotes , downvotes , 'user' )
2023-02-05 09:50:49 -05:00
] ) ;
}
backend . register = async ( { user , pass , pass2 } ) => {
var lengthCheck = false ;
lengthCheck =
checkLength ( pass , 'Password' , 4 , 1024 ) ||
checkLength ( user , 'Username' , 1 , 64 ) ||
checkRegex ( user , 'Username' , /[^A-Za-z0-9\-\_]/g ) ;
if ( lengthCheck ) return lengthCheck ;
if ( pass != pass2 ) return { 'success' : 'Passwords don\'t match.' } ;
var existingAccounts = await db . all ( 'SELECT username FROM auth WHERE username = ?' , [
user
] ) ;
if ( existingAccounts && existingAccounts . length > 0 )
return { success : 'Account already exists.' } ;
var passHash = await hash ( pass , 10 ) ;
await db . run ( 'INSERT INTO auth (username, password) VALUES (?, ?)' , [
user ,
passHash
] )
2023-03-06 02:26:57 -05:00
await updateUser ( { user : user } ) ;
2023-02-11 02:11:45 -05:00
2023-02-05 09:50:49 -05:00
return { success : 'Successfully created account.' , location : '/' } ;
}
backend . login = async ( { user , pass , cookies } ) => {
var existingAccounts = await db . all ( 'SELECT username, password FROM auth WHERE username = ?' , [
user
] ) ;
if ( ! existingAccounts || existingAccounts . length < 1 )
return { success : 'Account does not exist.' } ;
var passHash = await compare ( pass , existingAccounts [ 0 ] . password ) ;
if ( ! passHash )
return { success : 'Incorrect password.' } ;
var token = randomBytes ( 256 ) . toString ( 'hex' ) ;
await db . run ( 'INSERT INTO token (username, token) VALUES (?, ?)' , [
user ,
token
] )
if ( token ) {
cookies . set ( 'token' , token , {
maxAge : 60 * 60 * 24 * 7 ,
path : '/'
} ) ;
} ;
return { success : 'Successfully logged into account.' , data : token , location : '/' } ;
}
2023-02-07 19:36:32 -05:00
backend . postCreate = async ( { content , user } ) => {
2023-02-10 23:44:25 -05:00
if ( ! content ) return { 'success' : 'No post provided.' }
2023-02-05 09:50:49 -05:00
var lengthCheck = checkLength ( content , 'Post content' , 1 , 10240 ) ;
if ( lengthCheck )
return lengthCheck ;
2023-02-07 18:06:18 -05:00
if ( ! content ) return { 'success' : 'There is no post!' } ;
2023-02-05 09:50:49 -05:00
var id = randomBytes ( 10 ) . toString ( 'hex' ) ;
2023-02-12 04:50:15 -05:00
var postFlatten = formatPost ( content ) . flat ( ) ;
var reply = postFlatten [ postFlatten . findIndex ( x => x . subtype == 'post' ) ] ;
if ( reply )
reply = reply . url . split ( '/' ) . pop ( ) ;
2023-02-12 12:06:19 -05:00
await db . run ( 'INSERT INTO post (username, id, content, rating, reply, time) VALUES (?, ?, ?, ?, ?, ?)' , [
2023-02-05 09:50:49 -05:00
user ,
id ,
content ,
2023-02-12 04:50:15 -05:00
calcVote ( 0 , 0 ) ,
2023-02-12 12:06:19 -05:00
reply || '' ,
Math . floor ( new Date ( ) * 1000 )
2023-02-05 09:50:49 -05:00
] )
2023-02-07 19:36:32 -05:00
return { 'success' : 'Your post has been broadcasted!' , 'href' : ` /post/ ${ id } ` } ;
2023-02-05 09:50:49 -05:00
}
2023-03-08 19:49:39 -05:00
backend . postDelete = async ( { id , user , admin } ) => {
if ( admin ) {
await db . run ( 'DELETE FROM post WHERE id = ?' , [
id
] )
} else {
await db . run ( 'DELETE FROM post WHERE username = ? AND id = ?' , [
user ,
id
] )
}
2023-02-11 02:11:45 -05:00
return { 'success' : 'Your post has been deleted!' , 'href' : ` /post/ ${ id } ` } ;
}
2023-03-08 19:49:39 -05:00
let userRoles = async ( { user } ) => {
var rolesLocal = await db . all ( 'SELECT roles from bio WHERE username = ?' , [
user
] ) ;
return roles . filter ( ( elem , i ) => ( ( rolesLocal % 1 << i ) == 0 ) ) ;
} ;
2023-02-05 09:50:49 -05:00
backend . userGet = async ( { user } ) => {
var posts = await db . all ( 'SELECT * from user WHERE username = ?' , [
user
] )
if ( ! posts || posts . length < 1 ) {
2023-02-11 19:42:10 -05:00
return { 'success' : 'User does not exist.' }
}
2023-03-06 02:26:57 -05:00
var following = await db . all ( 'SELECT * from follow WHERE username = ?' , [
user
] ) ;
var followers = await db . all ( 'SELECT * from follow WHERE following = ?' , [
user
] ) ;
if ( ! following ) following = [ ] ;
if ( ! followers ) followers = [ ] ;
return { data : posts [ 0 ] , following , followers } ;
2023-02-11 19:42:10 -05:00
}
backend . userBio = async ( { user } ) => {
var posts = await db . all ( 'SELECT * from bio WHERE username = ?' , [
user
] )
if ( ! posts || posts . length < 1 ) {
return { 'success' : 'Bio does not exist.' }
2023-02-05 09:50:49 -05:00
}
2023-03-08 19:49:39 -05:00
posts [ 0 ] . rolesArr = ( await userRoles ( user ) ) || [ ] ;
2023-02-05 09:50:49 -05:00
return { data : posts [ 0 ] } ;
}
2023-03-08 19:49:39 -05:00
backend . postBulk = async ( { page , id , user , cookies , sort , type , admin } ) => {
2023-02-05 09:50:49 -05:00
var posts ;
2023-03-07 18:53:56 -05:00
var userAuth = ( await backend . token ( { cookies } ) ) . data || '' ;
2023-02-12 01:53:13 -05:00
2023-03-08 18:56:28 -05:00
sort = ( LEGAL _SORTS [ sort ] ) || 'rating' ;
if ( sort + '' != sort ) sort = 'rating' ;
sort = sort . replaceAll ( '%d' , Math . floor ( new Date ( ) * 1000 ) ) ;
2023-02-12 12:06:19 -05:00
2023-03-07 18:53:56 -05:00
if ( type == 'all' ) {
2023-02-12 12:06:19 -05:00
posts = await db . all ( 'SELECT * from post ORDER BY ' + sort + ' DESC LIMIT ?, ?' , [
2023-02-12 01:53:13 -05:00
page * ROW _COUNT ,
ROW _COUNT
] )
2023-03-07 18:53:56 -05:00
} else if ( type == 'post' ) {
2023-02-12 01:53:13 -05:00
posts = await db . all ( 'SELECT * from post WHERE id = ?' , [
id
2023-02-12 04:50:15 -05:00
] ) ;
if ( posts . length == 0 ) posts . push ( { } ) ;
2023-02-12 12:06:19 -05:00
posts . push ( ... ( await db . all ( 'SELECT * from post WHERE reply = ? ORDER BY ' + sort + ' DESC LIMIT ?, ?' , [
2023-02-12 04:50:15 -05:00
id ,
page * ROW _COUNT ,
ROW _COUNT
] ) ) )
2023-03-07 18:53:56 -05:00
} else if ( type == 'user' ) {
2023-02-12 12:06:19 -05:00
posts = await db . all ( 'SELECT * from post WHERE username = ? ORDER BY ' + sort + ' DESC LIMIT ?, ?' , [
2023-02-05 09:50:49 -05:00
user ,
2023-02-12 01:53:13 -05:00
page * ROW _COUNT ,
ROW _COUNT
2023-02-05 09:50:49 -05:00
] )
2023-03-07 18:53:56 -05:00
} else if ( type == 'follow' ) {
2023-03-07 18:56:18 -05:00
posts = await db . all ( 'SELECT * from post WHERE username IN (SELECT following from follow WHERE username = ?) ORDER BY ' + sort + ' DESC LIMIT ?, ?' , [
2023-03-07 18:53:56 -05:00
userAuth ,
page * ROW _COUNT ,
ROW _COUNT
] )
2023-02-05 09:50:49 -05:00
}
2023-02-12 01:53:13 -05:00
posts = posts . map ( post => {
2023-03-08 19:49:39 -05:00
return { ... post , isAuthor : userAuth == post . username || admin } ;
2023-02-12 01:53:13 -05:00
} )
2023-02-05 09:50:49 -05:00
return { data : posts } ;
}
2023-02-12 01:53:13 -05:00
backend . vote = async ( { id , vote , user } ) => {
2023-02-07 18:06:18 -05:00
if ( ! id || ( vote != 'down' && vote != 'up' ) ) return { success : 'fail' } ;
2023-02-16 17:03:40 -05:00
var isCreator = ( await db . all ( 'SELECT * from post WHERE id = ?' , [
id
] ) ) [ 0 ] . username == user ;
if ( isCreator )
return { success : 'fail' } ;
2023-02-05 09:50:49 -05:00
await db . run ( 'DELETE FROM vote WHERE username = ? AND id = ?' , [
user ,
id
] ) ;
await db . run ( 'INSERT INTO vote (id, username, type) VALUES (?,?,?)' , [
id ,
user ,
vote == 'up' ? 1 : 2
] ) ;
var votes = await db . all ( 'SELECT type from vote WHERE id = ?' , [
id
] ) || [ ] ;
var up = votes . filter ( x => x . type == 1 ) . length ;
var down = votes . filter ( x => x . type == 2 ) . length ;
await db . run ( 'UPDATE post SET upvotes = ?, downvotes = ?, rating = ? WHERE id = ?' , [
up ,
down ,
calcVote ( up , down ) ,
id
] ) ;
var user = await db . all ( 'SELECT * from post WHERE id = ?' , [
id
] ) || [ ] ;
if ( ! user [ 0 ] )
return { success : 'fail' } ;
await updateUser ( { user : user [ 0 ] . username } ) ;
return { data : { up , down } } ;
}
backend . token = async ( { cookies } ) => {
var tokenIn = cookies . get ( 'token' ) ;
var existingAccounts = await db . all ( 'SELECT username from token WHERE token = ?' , [
tokenIn
] ) ;
if ( ! existingAccounts || existingAccounts . length < 1 )
return false ;
return { data : existingAccounts [ 0 ] . username } ;
}
2023-03-06 02:26:57 -05:00
backend . follow = async ( { target , user } ) => {
var isFollowing = await db . all ( 'SELECT * FROM follow WHERE username = ? AND following = ?' , [
user ,
target
] ) ;
if ( isFollowing && isFollowing . length > 0 ) {
await db . run ( 'DELETE FROM follow WHERE username = ? AND following = ?' , [
user ,
target
] ) ;
} else {
await db . run ( 'INSERT INTO follow (username, following) VALUES (?, ?)' , [
user ,
target
] ) ;
}
var following = await db . all ( 'SELECT * from follow WHERE username = ?' , [
target
] ) ;
var followers = await db . all ( 'SELECT * from follow WHERE following = ?' , [
target
] ) ;
return { 'success' : 'User followed/unfollowed.' , 'data' : { following , followers } } ;
} ;
2023-03-06 16:15:27 -05:00
let fileCreate = ( type ) => {
return async ( { img , extension , id , last , user } ) => {
let validExtensions = VALID _EXTENSIONS ;
2023-02-10 23:44:25 -05:00
2023-03-06 16:15:27 -05:00
if ( type == 'pfp' ) validExtensions = [ 'png' ] ;
2023-02-07 19:36:32 -05:00
2023-03-06 16:15:27 -05:00
if ( ridArray [ id ] !== '' && ! ( ridArray [ id ] ) ) {
ridArray [ id ] = img ;
} else {
ridArray [ id ] += img ;
}
const imgData = ridArray [ id ] ;
if ( last != 'true' ) {
return { 'success' : 'Image still proccessing...' }
} else {
ridArray [ id ] = false ;
}
const imgHash = createHash ( 'md5' ) . update ( imgData ) . digest ( 'hex' ) ;
if ( ! imgHash )
return { 'success' : 'Image not provided.' }
if ( imgHash . length > FILE _SIZE _LIMIT )
return { 'success' : 'Image too big.' }
const extensionSafe = safePath ( extension ) ;
if ( validExtensions . indexOf ( extensionSafe ) == - 1 )
return { success : 'Illegal file extension. Permitted file extensions are: ' + validExtensions . join ( ', ' ) } ;
if ( type == 'post' ) {
writeFile ( ` ${ process . cwd ( ) } /db/post- ${ imgHash } . ${ extensionSafe } ` , imgData , { encoding : 'base64' } ) ;
} else {
writeFile ( ` ${ process . cwd ( ) } /db/pfp- ${ user } .png ` , imgData , { encoding : 'base64' } ) ;
}
return { success : 'Successfully uploaded file.' , 'href' : ` /img/ ${ imgHash } . ${ extensionSafe } ` } ;
}
}
backend . fileCreate = fileCreate ( 'post' ) ;
backend . pfp = fileCreate ( 'pfp' ) ;
2023-02-07 19:36:32 -05:00
2023-02-05 09:50:49 -05:00
export {
2023-02-07 18:06:18 -05:00
backendProxy ,
2023-03-06 16:15:27 -05:00
backend ,
VALID _EXTENSIONS
2023-02-05 09:50:49 -05:00
}