2020-10-31 13:33:59 -07:00
import { Router } from 'express' ;
import SQL from 'sql-template-strings' ;
2020-11-02 12:12:15 -08:00
import avatar from '../avatar' ;
2022-01-03 10:22:10 -08:00
import { buildDict , now , shuffle , handleErrorAsync } from "../../src/helpers" ;
2021-01-12 11:06:59 -08:00
import locales from '../../src/locales' ;
2021-06-09 23:45:13 -07:00
import { calculateStats , statsFile } from '../../src/stats' ;
import fs from 'fs' ;
2021-07-02 16:15:44 -07:00
import { caches } from "../../src/cache" ;
2021-12-03 15:40:08 -08:00
import mailer from "../../src/mailer" ;
2021-12-18 13:37:26 -08:00
import { profilesSnapshot } from "./profile" ;
2022-01-03 10:22:10 -08:00
import buildLocaleList from "../../src/buildLocaleList" ;
2022-02-12 09:28:56 -08:00
import { archiveBan , liftBan } from "../ban" ;
2022-04-06 04:52:14 -07:00
import marked from 'marked' ;
2020-10-31 13:33:59 -07:00
const router = Router ( ) ;
2021-06-09 05:47:08 -07:00
router . get ( '/admin/list' , handleErrorAsync ( async ( req , res ) => {
2021-07-02 16:15:44 -07:00
return res . json ( await caches . admins . fetch ( async ( ) => {
2021-06-17 14:29:47 -07:00
const admins = await req . db . all ( SQL `
2021-11-23 06:05:14 -08:00
SELECT u . username , p . teamName , p . locale , u . id , u . email , u . avatarSource , p . credentials , p . credentialsLevel , p . credentialsName , a . payload
2021-06-17 14:29:47 -07:00
FROM users u
2021-11-23 06:05:14 -08:00
LEFT JOIN profiles p ON p . userId = u . id
LEFT JOIN authenticators a ON u . id = a . userId AND a . type = u . avatarSource AND ( a . validUntil IS NULL OR a . validUntil > $ { now ( ) } )
2021-06-17 14:29:47 -07:00
WHERE p . teamName IS NOT NULL
AND p . teamName != ''
ORDER BY RANDOM ( )
` );
2021-01-12 11:06:59 -08:00
2021-06-17 14:29:47 -07:00
const adminsGroupped = buildDict ( function * ( ) {
2021-06-17 16:10:59 -07:00
yield [ global . config . locale , [ ] ] ;
2021-06-17 14:29:47 -07:00
for ( let [ locale , , , published ] of locales ) {
2021-06-17 16:10:59 -07:00
if ( locale !== global . config . locale && published ) {
2021-06-17 14:29:47 -07:00
yield [ locale , [ ] ] ;
}
2021-01-12 11:06:59 -08:00
}
2021-06-17 14:29:47 -07:00
yield [ '' , [ ] ] ;
} ) ;
for ( let admin of admins ) {
admin . avatar = await avatar ( req . db , admin ) ;
delete admin . id ;
delete admin . email ;
2021-11-23 06:05:14 -08:00
delete admin . payload ;
2021-08-26 05:25:59 -07:00
if ( admin . credentials ) {
admin . credentials = admin . credentials . split ( '|' ) ;
}
2021-01-12 11:06:59 -08:00
2021-06-17 14:29:47 -07:00
if ( adminsGroupped [ admin . locale ] !== undefined ) {
adminsGroupped [ admin . locale ] . push ( admin ) ;
} else {
adminsGroupped [ '' ] . push ( admin ) ;
}
2021-01-12 11:06:59 -08:00
}
2021-06-17 14:29:47 -07:00
return adminsGroupped ;
} ) ) ;
2021-06-09 05:47:08 -07:00
} ) ) ;
2021-01-12 11:06:59 -08:00
2021-06-09 05:47:08 -07:00
router . get ( '/admin/list/footer' , handleErrorAsync ( async ( req , res ) => {
2021-07-02 16:15:44 -07:00
return res . json ( shuffle ( await caches . adminsFooter . fetch ( async ( ) => {
2021-06-17 14:29:47 -07:00
const fromDb = await req . db . all ( SQL `
SELECT u . username , p . footerName , p . footerAreas , p . locale
FROM users u
LEFT JOIN profiles p ON p . userId = u . id
2021-06-17 16:10:59 -07:00
WHERE p . locale = $ { global . config . locale }
2021-06-17 14:29:47 -07:00
AND p . footerName IS NOT NULL AND p . footerName != ''
AND p . footerAreas IS NOT NULL AND p . footerAreas != ''
` );
2021-04-06 11:48:44 -07:00
2021-06-17 16:10:59 -07:00
const fromConfig = global . config . contact . authors || [ ] ;
2021-04-06 11:48:44 -07:00
2021-06-17 14:29:47 -07:00
return [ ... fromDb , ... fromConfig ] ;
} ) ) ) ;
2021-06-09 05:47:08 -07:00
} ) ) ;
2021-01-12 11:06:59 -08:00
2021-06-09 05:47:08 -07:00
router . get ( '/admin/users' , handleErrorAsync ( async ( req , res ) => {
2020-12-30 15:03:30 -08:00
if ( ! req . isGranted ( 'users' ) ) {
2020-10-31 13:33:59 -07:00
return res . status ( 401 ) . json ( { error : 'Unauthorised' } ) ;
}
2021-11-26 10:37:35 -08:00
const conditions = [ ] ;
let sql = SQL `
2021-11-26 08:08:10 -08:00
SELECT u . id , u . username , u . email , u . roles , u . avatarSource , group _concat ( p . locale ) AS profiles
2020-10-31 13:33:59 -07:00
FROM users u
LEFT JOIN profiles p ON p . userId = u . id
2021-11-26 10:37:35 -08:00
`
if ( req . query . filter ) {
conditions . push ( SQL ` u.username LIKE ${ '%' + req . query . filter + '%' } ` ) ;
}
if ( req . query . localeFilter ) {
conditions . push ( SQL ` p.locale= ${ global . config . locale } ` ) ;
}
if ( req . query . adminsFilter ) {
conditions . push ( SQL ` u.roles != '' ` ) ;
}
let conditionsSql = SQL ` ` ;
if ( conditions . length ) {
let i = 0 ;
for ( let condition of conditions ) {
conditionsSql = conditionsSql . append ( i ++ ? SQL ` AND ` : SQL ` WHERE ` ) . append ( condition ) ;
}
}
sql = sql . append ( conditionsSql ) . append ( SQL `
2021-11-26 08:08:10 -08:00
GROUP BY u . id
2020-12-30 15:03:30 -08:00
ORDER BY u . id DESC
2021-11-26 10:37:35 -08:00
LIMIT $ { parseInt ( req . query . limit || 100 ) }
OFFSET $ { parseInt ( req . query . offset || 0 ) }
2020-10-31 13:33:59 -07:00
` );
2021-11-26 10:37:35 -08:00
const countSql = SQL ` SELECT COUNT(*) AS c FROM (SELECT u.id FROM users u LEFT JOIN profiles p ON p.userId = u.id ` . append ( conditionsSql ) . append ( ` GROUP BY u.id) ` ) ;
return res . json ( {
count : ( await req . db . get ( countSql ) ) . c ,
data : ( await req . db . all ( sql ) ) . map ( u => {
u . profiles = u . profiles ? u . profiles . split ( ',' ) : [ ] ;
return u ;
} ) ,
} ) ;
2021-06-09 05:47:08 -07:00
} ) ) ;
2020-10-31 13:33:59 -07:00
2021-06-09 05:47:08 -07:00
router . get ( '/admin/stats' , handleErrorAsync ( async ( req , res ) => {
2020-12-30 15:03:30 -08:00
if ( ! req . isGranted ( 'panel' ) ) {
2020-11-24 15:54:02 -08:00
return res . status ( 401 ) . json ( { error : 'Unauthorised' } ) ;
}
2021-06-09 23:45:13 -07:00
const stats = fs . existsSync ( statsFile )
? JSON . parse ( fs . readFileSync ( statsFile ) )
2021-06-17 16:10:59 -07:00
: await calculateStats ( req . db , buildLocaleList ( global . config . locale ) ) ;
2020-11-27 11:30:21 -08:00
2021-06-09 23:45:13 -07:00
for ( let locale in stats . locales ) {
if ( stats . locales . hasOwnProperty ( locale ) && ! req . isGranted ( 'panel' , locale ) ) {
delete stats . locales [ locale ] ;
2020-11-24 15:54:02 -08:00
}
}
2021-06-09 23:45:13 -07:00
return res . json ( stats ) ;
2021-06-09 05:47:08 -07:00
} ) ) ;
2020-11-24 15:54:02 -08:00
2021-06-16 07:08:38 -07:00
const normalise = s => s . trim ( ) . toLowerCase ( ) ;
router . post ( '/admin/ban/:username' , handleErrorAsync ( async ( req , res ) => {
if ( ! req . isGranted ( 'users' ) ) {
return res . status ( 401 ) . json ( { error : 'Unauthorised' } ) ;
}
2021-12-03 15:40:08 -08:00
const user = await req . db . get ( SQL ` SELECT id, email FROM users WHERE usernameNorm = ${ normalise ( req . params . username ) } ` ) ;
2021-07-24 10:43:17 -07:00
if ( ! user ) {
return res . status ( 400 ) . json ( { error : 'No such user' } ) ;
}
2021-08-12 03:14:34 -07:00
if ( req . body . reason ) {
if ( ! req . body . terms . length ) {
return res . status ( 400 ) . json ( { error : 'Terms are required' } ) ;
}
await req . db . get ( SQL `
UPDATE users
SET bannedReason = $ { req . body . reason } ,
bannedTerms = $ { req . body . terms . join ( ',' ) } ,
2021-12-18 13:37:26 -08:00
bannedBy = $ { req . user . id } ,
banSnapshot = $ { await profilesSnapshot ( req . db , normalise ( req . params . username ) ) }
2021-08-12 03:14:34 -07:00
WHERE id = $ { user . id }
` );
2022-02-12 09:28:56 -08:00
await archiveBan ( req . db , user ) ;
2022-02-27 00:10:13 -08:00
mailer ( user . email , 'ban' , { reason : req . body . reason , username : normalise ( req . params . username ) } ) ;
2021-08-12 03:14:34 -07:00
} else {
await req . db . get ( SQL `
UPDATE users
SET bannedReason = null
WHERE id = $ { user . id }
` );
2022-02-12 09:28:56 -08:00
await liftBan ( req . db , user ) ;
2021-08-12 03:14:34 -07:00
}
2021-07-24 10:43:17 -07:00
await req . db . get ( SQL `
UPDATE reports
SET isHandled = 1
WHERE userId = $ { user . id }
2021-06-16 07:08:38 -07:00
` );
return res . json ( true ) ;
} ) ) ;
2021-07-24 10:18:39 -07:00
router . get ( '/admin/reports' , handleErrorAsync ( async ( req , res ) => {
2021-06-16 07:48:24 -07:00
if ( ! req . isGranted ( 'users' ) ) {
return res . status ( 401 ) . json ( { error : 'Unauthorised' } ) ;
}
return res . json ( await req . db . all ( SQL `
2022-04-06 09:10:57 -07:00
SELECT reports . id , group _concat ( p . locale ) as profiles , sus . username AS susUsername , reporter . username AS reporterUsername , reports . comment , reports . isAutomatic , reports . isHandled
2021-07-24 10:18:39 -07:00
FROM reports
LEFT JOIN users sus ON reports . userId = sus . id
LEFT JOIN users reporter ON reports . reporterId = reporter . id
2022-04-06 09:10:57 -07:00
LEFT JOIN profiles p on sus . id = p . userId
GROUP BY reports . id
ORDER BY min ( reports . isHandled ) ASC , reports . id DESC
2021-06-16 07:48:24 -07:00
` ));
} ) ) ;
2022-04-06 04:52:14 -07:00
router . get ( '/admin/reports/:id' , handleErrorAsync ( async ( req , res ) => {
if ( ! req . isGranted ( 'users' ) ) {
return res . status ( 401 ) . json ( { error : 'Unauthorised' } ) ;
}
return res . json ( await req . db . all ( SQL `
SELECT reports . id , sus . username AS susUsername , reporter . username AS reporterUsername , reports . comment , reports . isAutomatic , reports . isHandled
FROM reports
LEFT JOIN users sus ON reports . userId = sus . id
LEFT JOIN users reporter ON reports . reporterId = reporter . id
WHERE reports . userId = $ { req . params . id }
ORDER BY reports . isHandled ASC , reports . id DESC
` ));
} ) ) ;
2021-07-24 10:18:39 -07:00
router . post ( '/admin/reports/handle/:id' , handleErrorAsync ( async ( req , res ) => {
2021-06-16 07:48:24 -07:00
if ( ! req . isGranted ( 'users' ) ) {
return res . status ( 401 ) . json ( { error : 'Unauthorised' } ) ;
}
await req . db . get ( SQL `
2021-07-24 10:18:39 -07:00
UPDATE reports
SET isHandled = 1
2021-06-16 07:48:24 -07:00
WHERE id = $ { req . params . id }
` );
return res . json ( true ) ;
} ) ) ;
2022-04-06 04:52:14 -07:00
router . get ( '/admin/moderation' , handleErrorAsync ( async ( req , res ) => {
if ( ! req . isGranted ( 'users' ) ) {
return res . status ( 401 ) . json ( { error : 'Unauthorised' } ) ;
}
const dir = _ _dirname + '/../../moderation' ;
const susRegexes = fs . readFileSync ( dir + '/sus.txt' ) . toString ( 'utf-8' ) . split ( '\n' ) . filter ( x => ! ! x ) ;
const rulesUsers = marked ( fs . readFileSync ( dir + '/rules-users.md' ) . toString ( 'utf-8' ) ) ;
const rulesTerminology = marked ( fs . readFileSync ( dir + '/rules-terminology.md' ) . toString ( 'utf-8' ) ) ;
const rulesSources = marked ( fs . readFileSync ( dir + '/rules-sources.md' ) . toString ( 'utf-8' ) ) ;
return res . json ( {
susRegexes ,
rulesUsers ,
rulesTerminology ,
rulesSources ,
} )
} ) ) ;
2020-10-31 13:33:59 -07:00
export default router ;