Merge branch 'master' of https://gitlab.badgerbox.co/rush/rush-statistics
This commit is contained in:
@@ -6,6 +6,7 @@ import { OrderByParser, FilterParser } from './tokenizer'
|
|||||||
import { addGameApis } from './gameservice'
|
import { addGameApis } from './gameservice'
|
||||||
import { addCharacterApis } from './characterservice'
|
import { addCharacterApis } from './characterservice'
|
||||||
import { addRushStatsApis } from './rushstatsservice'
|
import { addRushStatsApis } from './rushstatsservice'
|
||||||
|
import { addDmApis } from './dmservice'
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const jsonParser = json()
|
const jsonParser = json()
|
||||||
@@ -14,7 +15,7 @@ const port = 3001
|
|||||||
addGameApis(app, jsonParser)
|
addGameApis(app, jsonParser)
|
||||||
addCharacterApis(app, jsonParser)
|
addCharacterApis(app, jsonParser)
|
||||||
addRushStatsApis(app, jsonParser)
|
addRushStatsApis(app, jsonParser)
|
||||||
|
addDmApis(app, jsonParser)
|
||||||
|
|
||||||
app.use(express.static(__dirname + '/frontend'))
|
app.use(express.static(__dirname + '/frontend'))
|
||||||
app.use('/', (req, res) => {
|
app.use('/', (req, res) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export function addCharacterApis(app, jsonParser) {
|
|||||||
app.get('/api/character/:characterId', async (req, res) => {
|
app.get('/api/character/:characterId', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const character = await Character.findOne({
|
const character = await Character.findOne({
|
||||||
attributes: ["id", "characterName", "playerName", "role", "creationDate", "status"],
|
attributes: ['id', 'characterName', 'playerName', 'role', 'creationDate', 'status'],
|
||||||
where: { id: req.params['characterId'] }
|
where: { id: req.params['characterId'] }
|
||||||
})
|
})
|
||||||
if (character) {
|
if (character) {
|
||||||
@@ -35,11 +35,9 @@ export function addCharacterApis(app, jsonParser) {
|
|||||||
where: filter
|
where: filter
|
||||||
})
|
})
|
||||||
const totalCount = await Character.count({
|
const totalCount = await Character.count({
|
||||||
where: filter
|
where: filter
|
||||||
})
|
})
|
||||||
const pageCount = Math.ceil(
|
const pageCount = Math.ceil(totalCount / count)
|
||||||
totalCount / count
|
|
||||||
)
|
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'application/json')
|
res.setHeader('Content-Type', 'application/json')
|
||||||
res.send({ characterData, pageCount, totalCount })
|
res.send({ characterData, pageCount, totalCount })
|
||||||
|
|||||||
58
backend/src/dmservice.ts
Normal file
58
backend/src/dmservice.ts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -33,11 +33,9 @@ export function addGameApis(app, jsonParser) {
|
|||||||
where: filter
|
where: filter
|
||||||
})
|
})
|
||||||
const totalCount = await Game.count({
|
const totalCount = await Game.count({
|
||||||
where: filter
|
where: filter
|
||||||
})
|
})
|
||||||
const pageCount = Math.ceil(
|
const pageCount = Math.ceil(totalCount / count)
|
||||||
totalCount / count
|
|
||||||
)
|
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'application/json')
|
res.setHeader('Content-Type', 'application/json')
|
||||||
res.send({ gameData, pageCount, totalCount })
|
res.send({ gameData, pageCount, totalCount })
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ export class FilterParser {
|
|||||||
ctx.accept('value', m[3])
|
ctx.accept('value', m[3])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
this.lexer.rule(spacerRegex, (ctx, m) => {
|
this.lexer.rule(spacerRegex, (ctx, m) => {
|
||||||
ctx.ignore()
|
ctx.ignore()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<v-btn to="/serverstats">Server Stats</v-btn>
|
<v-btn to="/serverstats">Server Stats</v-btn>
|
||||||
<v-btn to="/games">Games</v-btn>
|
<v-btn to="/games">Games</v-btn>
|
||||||
<v-btn to="/characters">Characters</v-btn>
|
<v-btn to="/characters">Characters</v-btn>
|
||||||
|
<!-- <v-btn to="/gamemasters">Gamemasters</v-btn> -->
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<router-view />
|
<router-view />
|
||||||
</v-main>
|
</v-main>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import GameDetails from '../vues/GameDetails.vue'
|
|||||||
import CharacterList from '../vues/CharacterList.vue'
|
import CharacterList from '../vues/CharacterList.vue'
|
||||||
import CharacterDetails from '../vues/CharacterDetails.vue'
|
import CharacterDetails from '../vues/CharacterDetails.vue'
|
||||||
import ServerStats from '../vues/ServerStats.vue'
|
import ServerStats from '../vues/ServerStats.vue'
|
||||||
|
import DmList from '../vues/DmList.vue'
|
||||||
|
|
||||||
const root = {
|
const root = {
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -36,13 +37,19 @@ const serverStatsRoute = {
|
|||||||
component: ServerStats
|
component: ServerStats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gameMasterListRoute = {
|
||||||
|
path: '/gamemasters',
|
||||||
|
component: DmList
|
||||||
|
}
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
root,
|
root,
|
||||||
gameListRoute,
|
gameListRoute,
|
||||||
gameDetailsRoute,
|
gameDetailsRoute,
|
||||||
characterListRoute,
|
characterListRoute,
|
||||||
characterDetailsRoute,
|
characterDetailsRoute,
|
||||||
serverStatsRoute
|
serverStatsRoute,
|
||||||
|
gameMasterListRoute
|
||||||
]
|
]
|
||||||
|
|
||||||
export default createRouter({
|
export default createRouter({
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ export interface Character {
|
|||||||
creationDate: number
|
creationDate: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DmStats {
|
||||||
|
name: string
|
||||||
|
gameCount: string
|
||||||
|
lastGame: number
|
||||||
|
games: Game
|
||||||
|
}
|
||||||
|
|
||||||
export interface GameStats {
|
export interface GameStats {
|
||||||
Complete: number
|
Complete: number
|
||||||
Postponed: number
|
Postponed: number
|
||||||
|
|||||||
17
frontend/src/vues/DmList.vue
Normal file
17
frontend/src/vues/DmList.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>Hello World</template>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { DmStats } from '../types'
|
||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import { VDataTable } from 'vuetify/components'
|
||||||
|
|
||||||
|
type ReadonlyHeaders = VDataTable['$props']['headers']
|
||||||
|
|
||||||
|
const headers: ReadonlyHeaders = [
|
||||||
|
{ title: 'Game Master', align: 'start', sortable: true, key: 'id' },
|
||||||
|
{ title: 'Game Count', align: 'start', sortable: true, key: 'characterName' },
|
||||||
|
{ title: 'Last Game', align: 'start', sortable: true, key: 'role' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
@@ -33,9 +33,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-left">Role</th>
|
<th class="text-left">Role</th>
|
||||||
<th class="text-left">
|
<th class="text-left">Characters</th>
|
||||||
Characters
|
|
||||||
</th>
|
|
||||||
<th class="text-left">App Count</th>
|
<th class="text-left">App Count</th>
|
||||||
<th class="text-left">Games Played</th>
|
<th class="text-left">Games Played</th>
|
||||||
<th class="text-left">Pick Rate %</th>
|
<th class="text-left">Pick Rate %</th>
|
||||||
|
|||||||
37
install.sh
37
install.sh
@@ -2,11 +2,30 @@
|
|||||||
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
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 "---"
|
echo "---"
|
||||||
echo "Moving the distributable folder into /srv/gamestats"
|
echo "Moving the distributable folder into /srv/gamestats"
|
||||||
echo "---"
|
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 ""
|
||||||
echo "---"
|
echo "---"
|
||||||
@@ -32,14 +51,18 @@ echo "[Install]" >> $SYSTEMD_SERVICE_FILE
|
|||||||
echo "WantedBy=multi-user.target" >> $SYSTEMD_SERVICE_FILE
|
echo "WantedBy=multi-user.target" >> $SYSTEMD_SERVICE_FILE
|
||||||
echo "" >> $SYSTEMD_SERVICE_FILE
|
echo "" >> $SYSTEMD_SERVICE_FILE
|
||||||
|
|
||||||
echo ""
|
echo "Created new service."
|
||||||
echo "---"
|
echo cat $SYSTEMD_SERVICE_FILE
|
||||||
echo "Creating the cron job to refresh the database."
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "---"
|
echo "---"
|
||||||
echo "Reloading daemons and starting service"
|
echo "Reloading daemons and starting service"
|
||||||
echo "---"
|
echo "---"
|
||||||
systemctl daeomon-reload
|
systemctl daemon-reload
|
||||||
systemctl start rushstats.service
|
systemctl enable rushstats.service
|
||||||
|
systemctl start rushstats.service
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "---"
|
||||||
|
echo "Creating the cron job to refresh the database."
|
||||||
|
echo "---"
|
||||||
|
|||||||
15
loader/chron-script.sh
Normal file
15
loader/chron-script.sh
Normal file
@@ -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
|
||||||
@@ -12,6 +12,10 @@ Game = namedtuple('Game',
|
|||||||
|
|
||||||
Link = namedtuple('Link', ['gameId', 'gameTitle', 'characterId', 'characterName'])
|
Link = namedtuple('Link', ['gameId', 'gameTitle', 'characterId', 'characterName'])
|
||||||
|
|
||||||
|
SET_WAL_PRAGMA = """
|
||||||
|
PRAGMA journal_mode=WAL;
|
||||||
|
"""
|
||||||
|
|
||||||
APPS_TABLE_CREATE = """
|
APPS_TABLE_CREATE = """
|
||||||
CREATE TABLE IF NOT EXISTS "Apps" (
|
CREATE TABLE IF NOT EXISTS "Apps" (
|
||||||
"gameId" INTEGER,
|
"gameId" INTEGER,
|
||||||
@@ -170,6 +174,7 @@ def loadAppsAndPicks(characterNameToId, gameTitleToId, gameFileName):
|
|||||||
def createTables(dbName):
|
def createTables(dbName):
|
||||||
with sqlite3.connect(dbName) as connection:
|
with sqlite3.connect(dbName) as connection:
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
cursor.execute(SET_WAL_PRAGMA)
|
||||||
cursor.execute(CHARACTER_TABLE_CREATE)
|
cursor.execute(CHARACTER_TABLE_CREATE)
|
||||||
cursor.execute(GAMES_TABLE_CREATE)
|
cursor.execute(GAMES_TABLE_CREATE)
|
||||||
cursor.execute(APPS_TABLE_CREATE)
|
cursor.execute(APPS_TABLE_CREATE)
|
||||||
|
|||||||
5
loader/requirements.txt
Normal file
5
loader/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
certifi==2024.6.2
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
idna==3.7
|
||||||
|
requests==2.32.3
|
||||||
|
urllib3==2.2.1
|
||||||
2
setup.sh
2
setup.sh
@@ -22,7 +22,7 @@ cp -f ./backend/dist/* ./dist
|
|||||||
cd "$parent_path"/loader
|
cd "$parent_path"/loader
|
||||||
python3 createrushdatabase.py
|
python3 createrushdatabase.py
|
||||||
cd ../
|
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
|
# Move the package into the dist folder and install the needed modules
|
||||||
cd "$parent_path"
|
cd "$parent_path"
|
||||||
|
|||||||
Reference in New Issue
Block a user