Springcleaning

This commit is contained in:
BurnyLlama 2021-11-03 13:49:24 +01:00
parent c5427e0ee3
commit ca617d50d3
23 changed files with 0 additions and 967 deletions

View File

@ -1,24 +0,0 @@
import { Router } from 'express'
import finishApi from './finishes.js'
import mapApi from './maps.js'
import playerApi from './players.js'
const api = Router()
api.get(
'/',
(req, res) => res.json({
success: true,
response: "You connected to DDStats API! :D"
})
)
api.use('/finishes', finishApi)
api.use('/maps', mapApi)
api.use('/players', playerApi)
/**
* This module is the entrypoint for the API.
* @module api/api
*/
export default api

View File

@ -1,57 +0,0 @@
import { Router } from 'express'
import Finish from '../schemas/Finish.js'
const finishApi = Router()
finishApi.get(
'/count',
(req, res) => {
Finish.find({}).count().then(
finishAmount => {
res.json({
success: true,
response: finishAmount
})
}
)
}
)
finishApi.get(
'/find',
async (req, res) => {
if (!req.query.player && !req.query.map)
return res.json({
success: false,
response: "Please provide either a player or map!"
})
const player = req.query.player
const map = req.query.map
const sort = req.query.sort ?? 'date'
const order = req.query.order === "desc" ? -1 : 1
Finish.find({ player: player ?? /\w/, map: map ?? /\w/ }).sort([[sort, order]]).then(
finishes => {
if (!finishes[0])
return res.json({
success: false,
response: "No maps found!"
})
res.json({
success: true,
response: {
finishes
}
})
}
)
}
)
/**
* This module handles all API actions related to maps.
* @module api/finishes
*/
export default finishApi

View File

@ -1,115 +0,0 @@
import { Router } from 'express'
import Level from '../schemas/Level.js'
const mapApi = Router()
mapApi.get(
'/count',
(req, res) => {
Level.find({}).count().then(
mapAmount => {
res.json({
success: true,
response: mapAmount
})
}
)
}
)
mapApi.get(
'/get/:map',
(req, res) => {
Level.findOne({ name: req.params.map }).then(
map => {
if (!map)
return res.json({
success: false,
response: "No map found!"
})
res.json({
success: true,
response: map
})
}
)
}
)
mapApi.get(
'/getAll',
(req, res) => {
Level.find({}).then(
maps => {
res.json({
success: true,
response: maps
})
}
)
}
)
mapApi.get(
'/category/:category',
(req, res) => {
Level.find({ category: req.params.category }).then(
maps => {
if (!maps[0])
return res.json({
success: false,
response: "Invalid category name!"
})
res.json({
success: true,
response: maps
})
}
)
}
)
mapApi.get(
'/search',
async (req, res) => {
if (!req.query.q)
return res.json({
success: false,
response: "No query ('host/path?q=query') provided!"
})
const name = req.query.q
const sort = req.query.sort ?? 'name'
const order = req.query.order === "desc" ? -1 : 1
const page = req.query.page ?? 1
const filter = req.query.byMapper ? { mapper: { $regex: name, $options: 'i' }} : { name: { $regex: name, $options: 'i' }}
const pageCount = Math.ceil((await Level.find({ name: { $regex: name, $options: 'i' }}).count()) / 20)
Level.find(filter).sort([[sort, order]]).limit(20).skip((page - 1) * 20).then(
maps => {
if (!maps[0])
return res.json({
success: false,
response: "No maps found!"
})
res.json({
success: true,
response: {
pageCount,
maps
}
})
}
)
}
)
/**
* This module handles all API actions related to maps.
* @module api/maps
*/
export default mapApi

View File

@ -1,80 +0,0 @@
import { Router } from 'express'
import Player from '../schemas/Player.js'
const playerApi = Router()
playerApi.get(
'/count',
(req, res) => {
Player.count({}).then(
playerAmount => {
res.json({
success: true,
response: playerAmount
})
}
)
}
)
playerApi.get(
'/get/:player',
(req, res) => {
Player.findOne({ name: req.params.player }).then(
player => {
if (!player)
return res.json({
success: false,
response: "No player found!"
})
res.json({
success: true,
response: player
})
}
)
}
)
playerApi.get(
'/search',
async (req, res) => {
if (!req.query.q)
return res.json({
success: false,
response: "No query ('host/path?q=query') provided!"
})
const name = req.query.q
const sort = req.query.sort ?? 'name'
const order = req.query.order === "desc" ? -1 : 1
const page = req.query.page ?? 1
const pageCount = Math.ceil((await Player.find({ name: { $regex: name, $options: 'i' }}).count()) / 20)
Player.find({ name: { $regex: name, $options: 'i' }}).sort([[sort, order]]).limit(20).skip((page - 1) * 20).then(
players => {
if (!players[0])
return res.json({
success: false,
response: "No players found!"
})
res.json({
success: true,
response: {
pageCount,
players
}
})
}
)
}
)
/**
* This module handles all API actions related to players.
* @module api/players
*/
export default playerApi

View File

@ -8,11 +8,7 @@ import express from 'express'
import dotenv from 'dotenv' import dotenv from 'dotenv'
import njk from 'nunjucks' import njk from 'nunjucks'
import sqlite2mongo from './libs/database/sqlite2mongo.js'
import databaseInit from './libs/database/init.js'
import initLog from './libs/utils/log.js' import initLog from './libs/utils/log.js'
import api from './api/api.js'
import routes from './routes/routes.js' import routes from './routes/routes.js'
import { initWorkers } from './libs/utils/multithread.js' import { initWorkers } from './libs/utils/multithread.js'
@ -22,15 +18,6 @@ const log = initLog("[ MAIN ]")
// Read the .env file // Read the .env file
dotenv.config() dotenv.config()
// await databaseInit()
initWorkers(process.env.THREADS ?? 4)
if (process.env.LOAD_DB === "true")
await sqlite2mongo()
const Server = express() const Server = express()
njk.configure( njk.configure(
@ -45,6 +32,5 @@ njk.configure(
Server.use('/', routes) Server.use('/', routes)
Server.use('/assets', express.static('static')) Server.use('/assets', express.static('static'))
Server.use('/api', api)
Server.listen(process.env.PORT ?? 12345, () => log(`Server started and listening on port ${process.env.PORT}.`)) Server.listen(process.env.PORT ?? 12345, () => log(`Server started and listening on port ${process.env.PORT}.`))

View File

@ -1,22 +0,0 @@
import initLog from '../utils/log.js'
import { checkForFinishes, checkForMaps, checkForPlayers } from './sqlite2mongo/checks.js'
import postTasks from './sqlite2mongo/postTasks.js'
const log = initLog("sqlite2mongo")
/**
* This function handles the sqlite database provided by DDNet and
* migrates the data to mongodb.
* @module libs/database/sqlite2mongo
*/
async function sqlite2mongo() {
log("Checking for additions to 'ddnet.sqlite'...")
await checkForMaps()
await checkForPlayers()
await checkForFinishes()
await postTasks()
}
export default sqlite2mongo

View File

@ -1,126 +0,0 @@
import { sqlite } from "../init.js"
import Level from '../../../schemas/Level.js'
import Player from '../../../schemas/Player.js'
import Finish from '../../../schemas/Finish.js'
import initLog from '../../utils/log.js'
const log = initLog("sqlite2mongo")
/**
* This for new maps from the sqlite db. If any are found it adds them to mongodb.
* @module libs/database/sqlite2mongo/checks
*/
export async function checkForMaps() {
log("Checking for new maps...")
const latestMap = await Level.findOne({}).sort({ "release": "desc" })
const date = latestMap ? latestMap.release.toISOString().replace(/[TZ]/g, " ").replace(/\.[0-9\s]+/, "") : undefined
const newMapsAmount = (await sqlite.get(`SELECT count(Map) FROM maps ${date ? `WHERE Timestamp > datetime('${date}')` : ''} ORDER BY Timestamp`))['count(Map)']
log(`Found ${newMapsAmount} new maps!`)
let addedMaps = 0
await sqlite.each(
`SELECT Map, Server, Points, Stars, Mapper, Timestamp FROM maps ${date ? `WHERE Timestamp > datetime('${date}')` : ''} ORDER BY Timestamp`,
[],
(err, map) => {
if (err) return log(err)
Level.create({
name: map.Map,
mapper: map.Mapper,
release: map.Timestamp === '0000-00-00 00:00:00' ? new Date('January 1, 1970 00:00:00 UTC') : new Date(`${map.Timestamp}+00:00`),
category: map.Server,
rating: map.Stars,
awardPoints: map.Points
}).then(() => {
++addedMaps
log(`Added map ${addedMaps}/${newMapsAmount} -> ${map.Map}`)
})
}
)
}
/**
* This checks for new players in the sqlite db. If any are found it adds them to mongodb.
* @module libs/database/sqlite2mongo/checks
*/
export async function checkForPlayers() {
log("Checking for new players...")
const latestPlayer = await Player.findOne({}).sort({ "firstFinish": "desc" })
const date = latestPlayer ? latestPlayer.firstFinish.toISOString().replace(/[TZ]/g, " ").replace(/\.[0-9\s]+/, "") : undefined
const newPlayerAmount = (await sqlite.get(`select count(Name) FROM (SELECT Name, Timestamp FROM (SELECT Name, Timestamp FROM (SELECT * FROM race ORDER BY Timestamp ASC, Name ASC) GROUP BY Name) ${date ? `WHERE Timestamp > datetime('${date}')` : ''} ORDER BY Timestamp ASC)`))['count(Name)']
if (newPlayerAmount === 0) return log("No new players found...")
// Create a temporary table to prevent re-running CPU-intensive queries.
log(`Found ${newPlayerAmount} new players!`)
log("Importing new players...")
await sqlite.exec("DROP TABLE IF EXISTS temp")
await sqlite.exec("CREATE TABLE temp(Name varchar(128) NOT NULL, Timestamp timestamp NOT NULL)")
await sqlite.exec(`INSERT INTO TEMP (Name, Timestamp) SELECT Name, Timestamp FROM (SELECT Name, Timestamp FROM (SELECT Name, Timestamp FROM (SELECT * FROM race ORDER BY Timestamp ASC, Name ASC) GROUP BY Name) ${date ? `WHERE Timestamp > datetime('${date}')` : ''} ORDER BY Timestamp ASC)`)
log("Imported new players into 'temp'!")
let addedPlayers = 0
let offset = -1
while (offset < newPlayerAmount) {
await sqlite.each(
`SELECT Name, Timestamp FROM temp LIMIT 10000 OFFSET ${offset + 1}`,
[],
async (err, player) => {
if (err) return log(err)
if (addedPlayers >= newPlayerAmount) return {}
Player.create({
name: player.Name,
firstFinish: player.Timestamp === '0000-00-00 00:00:00' ? new Date('January 1, 1970 00:00:00 UTC') : new Date(`${player.Timestamp}+00:00`)
}).then(() => {
++addedPlayers
log(`Added player ${addedPlayers}/${newPlayerAmount} -> ${player.Name}`)
})
}
)
offset += 10000
}
}
/**
* This checks for new finishes in the sqlite db. If any are found it adds them to mongodb.
* @module libs/database/sqlite2mongo/checks
*/
export async function checkForFinishes() {
log("Checking for new finishes...")
const latestFinish = await Finish.findOne({}).sort({ "date": "desc" })
const date = latestFinish ? latestFinish.date.toISOString().replace(/[TZ]/g, " ").replace(/\.[0-9\s]+/, "") : undefined
const newFinishAmount = (await sqlite.get(`SELECT count(Name) FROM race ${date ? `WHERE Timestamp > datetime('${date}')` : ''}`))['count(Name)']
if (newFinishAmount === 0) return log("No new finishes found...")
log(`Found ${newFinishAmount} new finishes!`)
let addedFinishes = 0
let offset = -1
while (offset < newFinishAmount) {
await sqlite.each(
`SELECT * FROM race ${date ? `WHERE Timestamp > datetime('${date}')` : ''} ORDER BY Timestamp LIMIT 5000 OFFSET ${offset + 1}`,
[],
async (err, finish) => {
if (err) return log(err)
if (addedFinishes >= newFinishAmount) return {}
Finish.create({
map: finish.Map,
time: finish.Time,
date: finish.Timestamp === '0000-00-00 00:00:00' ? new Date('January 1, 1970 00:00:00 UTC') : new Date(`${finish.Timestamp}+00:00`),
serverLocation: finish.Server ?? '',
player: finish.Name
}).then(() => {
++addedFinishes
log(`Added finish ${addedFinishes}/${newFinishAmount} -> At ${finish.Timestamp} «${finish.Name}» completed «${finish.Map}» in ${finish.Time} s`)
})
}
)
offset += 5000
}
}

View File

@ -1,22 +0,0 @@
import fs from 'fs'
import msgpack from '@msgpack/msgpack'
/**
* This module parses the msgpack provided by DDNet...
* @module libs/database/sqlite2mongo/decodeMsgpack
*/
export default function decodeMsgpack() {
const data = fs.readFileSync('players.msgpack')
const decoded = msgpack.decodeMulti(data, {wrap: true})
const order = ['categories', 'maps', 'totalPoints', 'pointsRanks', 'pointsThisWeek', 'pointsThisMonth', 'teamRankPoints', 'rankPoints', 'serverRanks']
let final = {}
let i = 0
for (const part of decoded) {
final[order[i]] = part
++i
}
return final
}

View File

@ -1,125 +0,0 @@
import Level from '../../../schemas/Level.js'
import Player from '../../../schemas/Player.js'
import initLog from '../../utils/log.js'
import decodeMsgpack from './decodeMsgpack.js'
import { spread } from '../../utils/multithread.js'
const log = initLog("sqlite2mongo")
/**
* This handles some post-tasks that run after the initial checks.
* @module libs/database/sqlite2mongo/postTasks
*/
export default async function postTasks() {
log("Post tasks...")
log("Loading cached info from DDNet...")
const cache = decodeMsgpack()
log("Adding total amounts of finishes to maps...")
const totalMaps = await Level.find({}).count()
let processedMaps = 0
for (const category in cache.maps) {
for (const map of cache.maps[category]) {
Level.findOneAndUpdate(
{ name: map[0] },
{ totalFinishes: map[2] }
).then(
() => {
++processedMaps
log(`Processed map ${processedMaps}/${totalMaps} -> «${map[0]}» [${category}] with ${map[2]} finishes!`)
}
)
}
}
log("Reseting weekly and monthly points...")
await Player.updateMany({}, { pointsThisWeek: 0, pointsThisMonth: 0 })
log("Done!")
log("Processing points for players...")
const totalPlayers = await Player.find({}).count()
let processedPlayerPoints = 0
for (const entry of cache.pointsRanks) {
spread(
'./playerPoints.js',
{
name: entry[0],
points: entry[1]
}
).then(
() => {
++processedPlayerPoints
log(`Processed player ${processedPlayerPoints}/${totalPlayers} -> «${entry[0]}» has ${entry[1]} points!`)
}
)
}
log("Processing rank points for players...")
let processedPlayerRankPoints = 0
for (const entry of cache.rankPoints) {
spread(
'./playerRankPoints.js',
{
name: entry[0],
rankPoints: entry[1]
}
).then(
() => {
++processedPlayerRankPoints
log(`Processed player ${processedPlayerRankPoints}/${cache.rankPoints.length} -> «${entry[0]}» has ${entry[1]} rank points!`)
}
)
}
log("Processing team points for players...")
let processedTeamPoints = 0
for (const entry in cache.teamRankPoints) {
spread(
'./playerTeamPoints.js',
{
name: entry[0],
teamPoints: entry[1]
}
).then(
() => {
++processedTeamPoints
log(`Processed player ${processedTeamPoints}/${cache.teamRankPoints.length} -> «${entry[0]}» has ${entry[1]} team points!`)
}
)
}
log("Processing players' points for the last week...")
let processedPlayerPointsWeek = 0
for (const entry of cache.pointsThisWeek) {
spread(
'./playerWeekPoints.js',
{
name: entry[0],
pointsThisWeek: entry[1]
}
).then(
() => {
++processedPlayerPointsWeek
log(`Processed player ${processedPlayerPointsWeek}/${cache.pointsThisWeek.length} -> «${entry[0]}» got ${entry[1]} points this week!`)
}
)
}
log("Processing players' points for the last month...")
let processedPlayerPointsMonth = 0
for (const entry of cache.pointsThisMonth) {
spread(
'./playerMonthPoints.js',
{
name: entry[0],
pointsThisMonth: entry[1]
}
).then(
() => {
++processedPlayerPointsMonth
log(`Processed player ${processedPlayerPointsMonth}/${cache.pointsThisMonth.length} -> «${entry[0]}» got ${entry[1]} points this month!`)
}
)
}
}

View File

@ -1,84 +0,0 @@
import { randomUUID } from 'crypto'
import { SHARE_ENV, Worker } from 'worker_threads'
import initLog from './log.js'
const log = initLog("Worker Setup")
let workerFarm = []
let workerReady = []
let scheduledJobs = []
function scheduler() {
if (scheduledJobs.length === 0) return
const readyIndex = workerReady.indexOf(true)
if (readyIndex === -1) return
workerReady[readyIndex] = false
const worker = workerFarm[readyIndex]
const job = scheduledJobs.shift()
worker.postMessage({
type: 'runScript',
script: job.script,
data: job.data,
id: job.jobID
})
const messageHandler = message => {
if (message.id === job.jobID) {
workerReady[readyIndex] = true
worker.removeListener(
'message',
messageHandler
)
return job.callback(message.result)
}
}
worker.on(
'message',
messageHandler
)
}
export function initWorkers(threads) {
for (let i = 0; i < threads; ++i) {
workerFarm.push(new Worker('./libs/utils/multithread/genericWorker.js', { env: SHARE_ENV }))
const worker = workerFarm[i]
worker.postMessage({
type: 'initWorker',
name: `Worker ${parseInt(i) + 1}`
})
workerReady[i] = true
}
log(`Initialised ${threads} workers!`)
setInterval(
scheduler,
process.env.SCHEDULE_TIME ?? 50
)
log(`Initialised the scheduler!`)
}
export function spread(script, data) {
return new Promise(
async (resolve, reject) => {
const jobID = randomUUID()
scheduledJobs.push({
script,
data,
jobID,
callback: result => {
resolve(result)
}
})
}
)
}

View File

@ -1,29 +0,0 @@
import initLog from '../log.js'
const log = initLog('Fibonacci')
export function main(data) {
return new Promise(
(resolve, reject) => {
let nums = [1, 1]
for (let i = 0; i < 44; i++) {
nums.push(nums[nums.length - 2] + nums[nums.length - 1])
}
let primes = []
for (const num of nums) {
let isPrime = true
for (let i = 2; i < num / 2; ++i) {
if (num % i === 0)
isPrime = false
}
if (isPrime)
primes.push(num)
}
resolve(primes)
}
)
}

View File

@ -1,38 +0,0 @@
import { workerData, parentPort } from 'worker_threads'
import initLog from '../log.js'
let runScript = ''
let myName = ''
let log = initLog(myName)
parentPort.on(
'message',
async message => {
switch (message.type) {
case 'initWorker':
myName = message.name
log = initLog(myName)
process.env.DEBUG && log(`Started new thread -> «${myName}»`)
break
case 'runScript':
runScript = message.script
process.env.DEBUG && log(`Running script -> «${runScript}»`)
import(runScript).then(
script => script.main(message.data).then(
result => {
parentPort.postMessage({ result, id: message.id })
process.env.DEBUG && log(`Script done -> «${runScript}»`)
}
)
)
break
default:
log(`Invalid message -> ${message.type ?? 'No message type provided!'}`)
break
}
}
)

View File

@ -1,11 +0,0 @@
import mongoose from 'mongoose'
export async function connectMongoose() {
await mongoose.connect(
process.env.MONGO_URI,
{
useNewUrlParser: true,
useUnifiedTopology: true
}
)
}

View File

@ -1,17 +0,0 @@
import Player from '../../../schemas/Player.js'
import { connectMongoose } from './mongoForWorkes.js'
connectMongoose()
export function main(data) {
return new Promise(
(resolve, reject) => {
Player.findOneAndUpdate(
{ name: data.name },
{ rankPoints: data.pointsThisMonth }
).then(
() => resolve()
)
}
)
}

View File

@ -1,17 +0,0 @@
import Player from '../../../schemas/Player.js'
import { connectMongoose } from './mongoForWorkes.js'
connectMongoose()
export function main(data) {
return new Promise(
(resolve, reject) => {
Player.findOneAndUpdate(
{ name: data.name },
{ points: data.points }
).then(
() => resolve()
)
}
)
}

View File

@ -1,17 +0,0 @@
import Player from '../../../schemas/Player.js'
import { connectMongoose } from './mongoForWorkes.js'
connectMongoose()
export function main(data) {
return new Promise(
(resolve, reject) => {
Player.findOneAndUpdate(
{ name: data.name },
{ rankPoints: data.rankPoints }
).then(
() => resolve()
)
}
)
}

View File

@ -1,17 +0,0 @@
import Player from '../../../schemas/Player.js'
import { connectMongoose } from './mongoForWorkes.js'
connectMongoose()
export function main(data) {
return new Promise(
(resolve, reject) => {
Player.findOneAndUpdate(
{ name: data.name },
{ teamPoints: data.teamPoints }
).then(
() => resolve()
)
}
)
}

View File

@ -1,17 +0,0 @@
import Player from '../../../schemas/Player.js'
import { connectMongoose } from './mongoForWorkes.js'
connectMongoose()
export function main(data) {
return new Promise(
(resolve, reject) => {
Player.findOneAndUpdate(
{ name: data.name },
{ pointsThisWeek: data.pointsThisWeek }
).then(
() => resolve()
)
}
)
}

View File

@ -1,17 +0,0 @@
import mongoose from 'mongoose'
const Finish = new mongoose.Schema({
map: String,
time: Number,
date: Date,
serverLocation: String,
player: String
})
Finish.index({ player: 1, map: 1 })
/**
* This cotains the mongoose 'Finish' model.
* @module schemas/Finish
*/
export default mongoose.model("Finish", Finish)

View File

@ -1,17 +0,0 @@
import mongoose from 'mongoose'
const Level = new mongoose.Schema({
name: String,
mapper: String,
release: Date,
category: String,
rating: Number,
awardPoints: Number,
totalFinishes: Number
})
/**
* This cotains the mongoose 'Level' (maps) model.
* @module schemas/Level
*/
export default mongoose.model("Level", Level)

View File

@ -1,19 +0,0 @@
import mongoose from 'mongoose'
const Player = new mongoose.Schema({
name: String,
points: Number,
rankPoints: Number,
teamPoints: Number,
pointsThisWeek: Number,
pointsThisMonth: Number,
firstFinish: Date
})
Player.index({ name: 1 })
/**
* This cotains the mongoose 'Player' model.
* @module schemas/Player
*/
export default mongoose.model("Player", Player)

View File

@ -1,15 +0,0 @@
import mongoose from 'mongoose'
const TeamFinish = new mongoose.Schema({
map: String,
time: Number,
date: Date,
serverLocation: String,
players: [ String ]
})
/**
* This cotains the mongoose 'TeamFinish' model.
* @module schemas/TeamFinish
*/
export default mongoose.model("TeamFinish", TeamFinish)

View File

@ -1,67 +0,0 @@
import { initWorkers, spread } from '../libs/utils/multithread.js'
import Player from '../schemas/Player.js'
import decodeMsgpack from '../libs/database/sqlite2mongo/decodeMsgpack.js'
import initLog from '../libs/utils/log.js'
const log = initLog("Fib. Test")
initWorkers(3)
const jobs = 10
let completed = 0
for (let i = 0; i < jobs; ++i) {
spread(
'./fibonacci.js',
{}
).then(
result => {
++completed
log(`Completed job ${completed}/${jobs} -> ${result}`)
if (completed === jobs) process.exit(0)
}
)
}
export default async function() {
const cache = decodeMsgpack()
await Player.updateMany(
{},
{
pointsThisWeek: 0,
pointsThisMonth: 0
}
)
let processedPlayers = 0
const totalPlayers = cache.pointsRanks.length
const rawDbFunc = (name, points) => new Promise((resolve, reject) => Player.findOneAndUpdate({ name: name }, { points: points }).then(() => resolve()))
for (const entry of cache.pointsRanks) {
let data = new SharedArrayBuffer(1024 * 10)
// let data = new Array(buffer)
data[0] = 0
data[1] = entry[0]
data[2] = entry[1]
spread(
'./db.test.js',
data
).then(
result => {
console.log(data[0])
++processedPlayers
log(`Process player ${processedPlayers}/${totalPlayers} -> «${entry[0]}» with ${entry[1]} points!`)
if (processedPlayers === totalPlayers)
process.exit(0)
}
)
}
}