diff --git a/backend/src/app.ts b/backend/src/app.ts index 5f50c87..13ebca8 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -6,6 +6,7 @@ import { OrderByParser, FilterParser } from './tokenizer' import { addGameApis } from './gameservice' import { addCharacterApis } from './characterservice' import { addRushStatsApis } from './rushstatsservice' +import { addDmApis } from './dmservice' const app = express() const jsonParser = json() @@ -14,7 +15,7 @@ const port = 3001 addGameApis(app, jsonParser) addCharacterApis(app, jsonParser) addRushStatsApis(app, jsonParser) - +addDmApis(app, jsonParser) app.use(express.static(__dirname + '/frontend')) app.use('/', (req, res) => { diff --git a/backend/src/characterservice.ts b/backend/src/characterservice.ts index 7cce42c..1750d46 100644 --- a/backend/src/characterservice.ts +++ b/backend/src/characterservice.ts @@ -5,7 +5,7 @@ export function addCharacterApis(app, jsonParser) { app.get('/api/character/:characterId', async (req, res) => { try { const character = await Character.findOne({ - attributes: ["id", "characterName", "playerName", "role", "creationDate", "status"], + attributes: ['id', 'characterName', 'playerName', 'role', 'creationDate', 'status'], where: { id: req.params['characterId'] } }) if (character) { @@ -35,11 +35,9 @@ export function addCharacterApis(app, jsonParser) { where: filter }) const totalCount = await Character.count({ - where: filter - }) - const pageCount = Math.ceil( - totalCount / count - ) + where: filter + }) + const pageCount = Math.ceil(totalCount / count) res.setHeader('Content-Type', 'application/json') res.send({ characterData, pageCount, totalCount }) diff --git a/backend/src/dmservice.ts b/backend/src/dmservice.ts new file mode 100644 index 0000000..3bb3dad --- /dev/null +++ b/backend/src/dmservice.ts @@ -0,0 +1,58 @@ +import { database, Character, Game, Pick, App } from './db' +import { OrderByParser, FilterParser, ParsingError } from './tokenizer' +import { fn, col } from 'sequelize' + +export function addDmApis(app, jsonParser) { + app.get('/api/dm/:dmName', async (req, res) => { + const dmName = req.params.dmName + + let games = await Game.findAll({ + where: { gamemaster: dmName } + }) + + res.send(games) + }) + + app.post('/api/dm', jsonParser, async (req, res) => { + try { + const fp = new FilterParser() + const obp = new OrderByParser() + + const page = req.body.page || 0 + const orderBy = req.body.orderBy ? obp.parse(req.body.orderBy) : ['id'] + const count = req.body.count || 10 + const filter = req.body.filter ? fp.parse(req.body.filter) : {} + + const dms = await Game.findAll({ + offset: page * count, + limit: count, + attributes: ['gamemaster', [fn('count', col('id')), 'gamecount']], + group: ['gamemaster'], + order: orderBy, + where: filter + }) + + const totalCount = ( + await Game.count({ + group: ['gamemaster'], + where: filter + }) + ).length + + const pageCount = Math.ceil(totalCount / count) + + res.setHeader('Content-Type', 'application/json') + res.send({ + dms, + pageCount, + totalCount + }) + } catch (e) { + if (e instanceof ParsingError) { + res.status(400).send('Could not parse filter.') + } else { + res.status(500).send(e) + } + } + }) +} diff --git a/backend/src/gameservice.ts b/backend/src/gameservice.ts index 96140f7..24d6738 100644 --- a/backend/src/gameservice.ts +++ b/backend/src/gameservice.ts @@ -33,11 +33,9 @@ export function addGameApis(app, jsonParser) { where: filter }) const totalCount = await Game.count({ - where: filter - }) - const pageCount = Math.ceil( - totalCount / count - ) + where: filter + }) + const pageCount = Math.ceil(totalCount / count) res.setHeader('Content-Type', 'application/json') res.send({ gameData, pageCount, totalCount }) diff --git a/backend/src/tokenizer.ts b/backend/src/tokenizer.ts index 5a4fe83..fde9ee2 100644 --- a/backend/src/tokenizer.ts +++ b/backend/src/tokenizer.ts @@ -98,7 +98,6 @@ export class FilterParser { ctx.accept('value', m[3]) }) - this.lexer.rule(spacerRegex, (ctx, m) => { ctx.ignore() }) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1daed69..8a9be1c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,6 +5,7 @@ Server Stats Games Characters + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index f5d159d..a41d190 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -5,6 +5,7 @@ import GameDetails from '../vues/GameDetails.vue' import CharacterList from '../vues/CharacterList.vue' import CharacterDetails from '../vues/CharacterDetails.vue' import ServerStats from '../vues/ServerStats.vue' +import DmList from '../vues/DmList.vue' const root = { path: '/', @@ -36,13 +37,19 @@ const serverStatsRoute = { component: ServerStats } +const gameMasterListRoute = { + path: '/gamemasters', + component: DmList +} + const routes = [ root, gameListRoute, gameDetailsRoute, characterListRoute, characterDetailsRoute, - serverStatsRoute + serverStatsRoute, + gameMasterListRoute ] export default createRouter({ diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 814c40f..c13d050 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -21,6 +21,13 @@ export interface Character { creationDate: number } +export interface DmStats { + name: string + gameCount: string + lastGame: number + games: Game +} + export interface GameStats { Complete: number Postponed: number diff --git a/frontend/src/vues/DmList.vue b/frontend/src/vues/DmList.vue new file mode 100644 index 0000000..6a92ea0 --- /dev/null +++ b/frontend/src/vues/DmList.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/frontend/src/vues/ServerStats.vue b/frontend/src/vues/ServerStats.vue index 3d46b68..cc4d007 100644 --- a/frontend/src/vues/ServerStats.vue +++ b/frontend/src/vues/ServerStats.vue @@ -33,9 +33,7 @@ Role - - Characters - + Characters App Count Games Played Pick Rate % diff --git a/install.sh b/install.sh index 39013bb..c7d21ed 100755 --- a/install.sh +++ b/install.sh @@ -2,11 +2,30 @@ parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +echo "" +echo "---" +echo "Creating user rushstats" +echo "---" +if [id rushstats >/dev/null 2>&1]; then + echo "rushstats user already exists, skipping." +else + useradd -s /sbin/nologin rushstats + echo "User rushstats created with /sbin/nologin" +fi + echo "" echo "---" echo "Moving the distributable folder into /srv/gamestats" echo "---" -cp -rf "$parent_path"/dist /srv/gamestats +if [ -d /srv/gamestats ]; then + echo "/srv/gamestats already exists; removing" + rm -r /srv/rushstats +fi +mkdir /srv/gamestats +cp -r "$parent_path"/dist/* /srv/gamestats +cp -r "$parent_path"/loader/* /srv/gamestats +chown -R rushstats:rushstats /srv/gamestats + echo "" echo "---" @@ -32,14 +51,18 @@ echo "[Install]" >> $SYSTEMD_SERVICE_FILE echo "WantedBy=multi-user.target" >> $SYSTEMD_SERVICE_FILE echo "" >> $SYSTEMD_SERVICE_FILE -echo "" -echo "---" -echo "Creating the cron job to refresh the database." -echo "---" +echo "Created new service." +echo cat $SYSTEMD_SERVICE_FILE echo "" echo "---" echo "Reloading daemons and starting service" echo "---" -systemctl daeomon-reload -systemctl start rushstats.service \ No newline at end of file +systemctl daemon-reload +systemctl enable rushstats.service +systemctl start rushstats.service + +echo "" +echo "---" +echo "Creating the cron job to refresh the database." +echo "---" diff --git a/loader/chron-script.sh b/loader/chron-script.sh new file mode 100644 index 0000000..f0c7e2a --- /dev/null +++ b/loader/chron-script.sh @@ -0,0 +1,15 @@ +#!/bin/bash +parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +PYTHON_VEN="$parent_path"/venv/bin/python +if [ ! -f $PYTHON_VEN ]; then + echo "Setting up new VENV" + python3 -m venv venv + echo "Installing requirements." + source ./venv/bin/activate + pip install -r requirements.txt + deactivate +fi + +echo "Start DB Creation" +$parent_path/venv/bin/python createrushdatabase.py diff --git a/loader/databasesync.py b/loader/databasesync.py index 41cb9a6..f5f2f61 100644 --- a/loader/databasesync.py +++ b/loader/databasesync.py @@ -12,6 +12,10 @@ Game = namedtuple('Game', Link = namedtuple('Link', ['gameId', 'gameTitle', 'characterId', 'characterName']) +SET_WAL_PRAGMA = """ +PRAGMA journal_mode=WAL; +""" + APPS_TABLE_CREATE = """ CREATE TABLE IF NOT EXISTS "Apps" ( "gameId" INTEGER, @@ -170,6 +174,7 @@ def loadAppsAndPicks(characterNameToId, gameTitleToId, gameFileName): def createTables(dbName): with sqlite3.connect(dbName) as connection: cursor = connection.cursor() + cursor.execute(SET_WAL_PRAGMA) cursor.execute(CHARACTER_TABLE_CREATE) cursor.execute(GAMES_TABLE_CREATE) cursor.execute(APPS_TABLE_CREATE) diff --git a/loader/requirements.txt b/loader/requirements.txt new file mode 100644 index 0000000..fd8654b --- /dev/null +++ b/loader/requirements.txt @@ -0,0 +1,5 @@ +certifi==2024.6.2 +charset-normalizer==3.3.2 +idna==3.7 +requests==2.32.3 +urllib3==2.2.1 diff --git a/setup.sh b/setup.sh index 1eaf546..c8ca604 100755 --- a/setup.sh +++ b/setup.sh @@ -22,7 +22,7 @@ cp -f ./backend/dist/* ./dist cd "$parent_path"/loader python3 createrushdatabase.py cd ../ -cp -r ./loader/testdb.db ./dist/testdb.db +cp -r ./loader/* ./dist # Move the package into the dist folder and install the needed modules cd "$parent_path"