Merge pull request 'v1.1.0' (#10) from dev into main

Reviewed-on: alekswilc/simrail-logs#10
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
This commit is contained in:
Aleksander Wilczyński 2024-08-07 19:54:54 +02:00
commit 0f46e29f0e
Signed by: gitea
GPG Key ID: CECFC30736A3D1C8
15 changed files with 618 additions and 425 deletions

BIN
.gitea/assets/image-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1,16 +1,13 @@
{
"name": "play-history",
"version": "1.0.0",
"main": "index.js",
"name": "simrail-logs",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"postbuild": "copyfiles --error --up 1 src/http/views/*.* dist",
"postbuild": "copyfiles --error --up 1 src/http/views/**/* dist",
"dev": "npm run build && doppler run node dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"author": "Aleksander <alekswilc> Wilczyński",
"license": "AGPL-3.0-only",
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.0.0",

View File

@ -3,26 +3,17 @@ Prosty projekt, logujący wyjścia z posterunków.
### Hej! Podoba Ci się projekt? Polajkuj post na [forum](https://forum.simrail.eu/topic/9142-logowanie-wyj%C5%9B%C4%87-z-posterunk%C3%B3w/), a będzie mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu
## Aplikacja
https://simrail.alekswilc.dev/
## Cele
- Ułatwienie zgłaszania graczy, którzy robią "Hit and Run" (psuje i wychodze z posterunku)
- Statystyki
## Dalszy rozwój
- Obsługa pociagów, a nie tylko posterunków
# Jak korzystać?
- Otwórz https://simrail.alekswilc.dev/
- Znajdź interesujący Cie rekord.
- Naciśnij przycisk więcej
- Naciśnij na okienko z angielskim zapisem informacji (lub przycisk kopiuj link)
- Wklej na kanał #multiplayer-help-request z opisem sytuacji.
- Gotowe, kolejny troll jest zgłoszony.
![alt text](.gitea/assets/image.png)
- Obsługa pociagów, a nie tylko posterunków (#8)
# Zgłaszanie błedów
Aplikacja jest w wersji testowej, i obsługuje tylko serwer PL2
Jak zgłosić błąd?
- Otwórz https://git.alekswilc.dev/alekswilc/simrail-logs/issues
- Załóż konto (to tylko kilka kliknięć, a twoje zgłoszenie bardzo mi pomoże)

View File

@ -1,103 +0,0 @@
import express from 'express';
import { MLog } from '../mongo/logs.js';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import dayjs from 'dayjs';
import { PlayerUtil } from '../util/PlayerUtil.js';
import { msToTime } from '../util/time.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export class ApiModule {
public static load() {
const app = express();
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views')
app.get('/', async (req, res) => {
const records = await MLog.find()
.sort({ leftDate: -1 })
.limit(30)
res.render('index.ejs', {
records,
dayjs,
msToTime
});
})
const generateSearch = (regex: RegExp) => [
{
stationName: { $regex: regex },
},
{
userUsername: { $regex: regex },
},
{
stationShort: { $regex: regex },
},
{
userSteamId: { $regex: regex },
},
{
server: { $regex: regex },
}
]
app.get('/search', async (req, res) => {
if (!req.query.q) return res.redirect('/');
const s = req.query.q.toString().split(',').map(x => new RegExp(x, "i"));
const records = await MLog.aggregate([
{
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
}
])
.sort({ leftDate: -1 })
.limit(30)
res.render('search.ejs', {
records,
dayjs,
q: req.query.q,
msToTime
});
})
app.get('/details/:id', async (req, res) => {
if (!req.params.id) return res.redirect('/');
const record = await MLog.findOne({ id: req.params.id });
const player = await PlayerUtil.getPlayer(record?.userSteamId!);
res.render('details.ejs', {
record,
dayjs,
player,
msToTime
});
})
app.get('/api/last', async (req, res) => {
const records = await MLog.find()
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})
app.get('/api/search', async (req, res) => {
if (!req.query.q) return res.send('invalid');
const records = await MLog.find({ $text: { $search: req.query.q as string } })
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})
app.listen(2005);
}
}

129
src/http/routes/stations.ts Normal file
View File

@ -0,0 +1,129 @@
import { Router } from 'express';
import { MLog, raw_schema } from '../../mongo/logs.js';
import dayjs from 'dayjs';
import { PlayerUtil } from '../../util/PlayerUtil.js';
import { msToTime } from '../../util/time.js';
import { PipelineStage } from 'mongoose';
const generateSearch = (regex: RegExp) => [
{
stationName: { $regex: regex },
},
{
userUsername: { $regex: regex },
},
{
stationShort: { $regex: regex },
},
{
userSteamId: { $regex: regex },
},
{
server: { $regex: regex },
}
]
export class StationsRoute {
static load() {
const app = Router();
app.get('/', async (req, res) => {
const s = req.query.q?.toString().split(',').map(x => new RegExp(x, "i"));
const filter: PipelineStage[] = [];
s && filter.push({
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
})
const records = await MLog.aggregate(filter)
.sort({ leftDate: -1 })
.limit(30)
res.render('stations/index.ejs', {
records,
dayjs,
q: req.query.q,
msToTime
});
})
app.get('/details/:id', async (req, res) => {
if (!req.params.id) return res.redirect('/stations/');
const record = await MLog.findOne({ id: req.params.id });
const player = await PlayerUtil.getPlayer(record?.userSteamId!);
res.render('stations/details.ejs', {
record,
dayjs,
player,
msToTime
});
})
app.get('/leaderboard/', async (req, res) => {
const s = req.query.q?.toString().split(',').map(x => new RegExp(x, "i"));
const data = Object.keys(raw_schema)
.reduce((o, key) => ({ ...o, [key]: `$${key}` }), {});
const filter: PipelineStage[] = [
{
$project: {
// record.leftDate - record.joinedDate
result: { $subtract: ['$leftDate', '$joinedDate'] },
...data
}
},
];
s && filter.unshift(
{
$match: {
$and: [
...s.map(x => ({ $or: generateSearch(x) }))
]
}
}
)
const records = await MLog.aggregate(filter)
.sort({ result: -1 })
.limit(30)
res.render('stations/leaderboard.ejs', {
records,
dayjs,
q: req.query.q,
msToTime
});
})
// API ENDPOINTS
// CREATE AN ISSUE IF YOU NEED API ACCESS: https://git.alekswilc.dev/alekswilc/simrail-logs/issues
/*
app.get('/api/last', async (req, res) => {
const records = await MLog.find()
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})
app.get('/api/search', async (req, res) => {
if (!req.query.q) return res.send('invalid');
const records = await MLog.find({ $text: { $search: req.query.q as string } })
.sort({ leftDate: -1 })
.limit(30)
res.json({ code: 200, records });
})*/
return app;
}
}

26
src/http/server.ts Normal file
View File

@ -0,0 +1,26 @@
import express from 'express';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { StationsRoute } from './routes/stations.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export class ApiModule {
public static load() {
const app = express();
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views')
app.get('/', (_, res) => res.render('home'));
// backward compatible
app.get('/details/:id', (req, res) => res.redirect('/stations/details/'+req.params.id));
app.use('/stations/', StationsRoute.load());
app.listen(2005);
}
}

View File

@ -0,0 +1,7 @@
<div style="font-size: 13.5px;">
<p>Made with <span style="color: red;">❤️</span> by <a href="https://www.alekswilc.dev/">alekswilc</a> for SimRail Community</p>
<p><a href="https://git.alekswilc.dev/alekswilc/">Open Source</a></p>
<% if (thanks) { %>
<p>Podziękowania dla <a href="https://discord.com/users/1079005960653254656">AQUALYTH</a>, osób lajkujących <a href="https://forum.simrail.eu/topic/9142-logowanie-wyj%C5%9B%C4%87-z-posterunk%C3%B3w/">posta</a>, osób użytkujących strone i całego community Simrail <span style="color: red;">❤️</span></p>
<% } %>
</div>

View File

@ -0,0 +1,21 @@
<header>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs</a></h1>
<% if (section==='stations' ) { %>
<nav style="margin-bottom: 0;">
<a href="/">Strona Główna</a> /
<a href="https://git.alekswilc.dev/alekswilc/simrail-logs/issues/8">Logi pociągów</a>
</nav>
<nav>
<a href="/stations/">Logi posterunków</a> /
<a href="/stations/leaderboard">Tablica godzin</a>
</nav>
<% } else {%>
<a href="/stations/">Logi posterunków</a> /
<a href="https://git.alekswilc.dev/alekswilc/simrail-logs/issues/8">Logi pociągów</a>
<% } %>
<br />
<p style="color: darkorange; font-size: 14px;">Hej! Podoba Ci się projekt? Polajkuj post na <a
href="https://forum.simrail.eu/topic/9142-logowanie-wyj%C5%9B%C4%87-z-posterunk%C3%B3w/">forum</a>, a będzie
mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu.</p>
</header>

113
src/http/views/home.ejs Normal file
View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>simrail.alekswilc.dev</title>
<meta name="description" content="Simrail Utils">
<meta property="og:title" content="simrail.alekswilc.dev">
<meta property="og:url" content="https://simrail.alekswilc.dev">
<meta property="og:description" content="Simrail Utils">
<meta property="og:type" content="website">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/open-fonts@1.1.1/fonts/inter.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css">
<style>
p {
margin: 1%;
}
</style>
</head>
<body>
<%- include('_modules/header.ejs', { section:'home' }) %>
<p>Prosty projekt, logujący wyjścia z posterunków.</p>
<h3>Cele projektu</h3>
<p>Główne założenia</p>
<ul>
<li>
<p>Ułatwienie zgłaszania graczy, którzy robią "Hit and Run" (psuje i wychodze z posterunku)</p>
</li>
<li>
<p>Statystyki</p>
</li>
</ul>
<h3>Rozwój aplikacji</h3>
<p>Planowane funkcjonalności</p>
<ul>
<li>
<p>Obsługa pociągów (<a href="https://git.alekswilc.dev/alekswilc/simrail-logs/issues/8">#8</a>)</p>
</li>
</ul>
<h3>Informacje dodatkowe</h3>
<details>
<summary>
Zgłaszanie błędów i propozycji
</summary>
<h6>Zgłaszanie błedów</h6>
<ul>
<li>Otwórz <a
href="https://git.alekswilc.dev/alekswilc/simrail-logs/issues">https://git.alekswilc.dev/alekswilc/simrail-logs/issues</a>
</li>
<li>Zaloguj się (np poprzez Discorda)</li>
<li>Naciśnij przycisk "New issue" ("Nowe zgłoszenie")</li>
<li>W tytule napisz krótki opis (np. Problem z wyświetleniem strony)</li>
<li>Opisz problem (kiedy występuje, podaj linki, postaraj sie napisać jak moge odtworzyć ten błąd, jak
powinna zachowywać sie aplikacja bez tego błędu)</li>
<li>Poczekaj na informacje z mojej strony</li>
<li>Dziekuje :)</li>
</ul>
<img src="https://git.alekswilc.dev/alekswilc/simrail-logs/raw/branch/main/.gitea/assets/image-1.png">
<h6>Zgłaszanie propozycji</h6>
<ul>
<li>Otwórz <a
href="https://git.alekswilc.dev/alekswilc/simrail-logs/issues">https://git.alekswilc.dev/alekswilc/simrail-logs/issues</a>
</li>
<li>Zaloguj się (np poprzez Discorda)</li>
<li>Naciśnij przycisk "New issue" ("Nowe zgłoszenie")</li>
<li>W tytule napisz krótki opis (np. Logi wejścia i wyjścia z pociągów)</li>
<li>Opisz propozycje (dokładnie co z dyżurkami ale dla pociągów)</li>
<li>Poczekaj na informacje z mojej strony</li>
<li>Dziekuje :)</li>
</ul>
<img src="https://git.alekswilc.dev/alekswilc/simrail-logs/raw/branch/main/.gitea/assets/image-2.png">
</details>
<h2>Licencja</h2>
<details>
<summary>
Copyright (C) 2024 Aleksander <alekswilc> Wilczyński
</summary>
<code style="white-space: pre-line">This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see https://www.gnu.org/licenses/.</code>
</details>
<hr>
<%- include('_modules/footer.ejs', { thanks: true }) %>
</body>
</html>

View File

@ -45,12 +45,7 @@
</script>
<body>
<header>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs</a></h1>
<p><a href="https://git.alekswilc.dev/alekswilc/simrail-logs">Dokumentacja</a></p>
<br />
<p style="color: darkorange;">Hej! Podoba Ci się projekt? Polajkuj post na <a href="https://forum.simrail.eu/topic/9142-logowanie-wyj%C5%9B%C4%87-z-posterunk%C3%B3w/">forum</a>, a będzie mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu</p>
</header>
<%- include('../_modules/header.ejs', { section: 'stations' }) %>
<div class="details">
@ -83,6 +78,8 @@
</div>
<hr>
<%- include('../_modules/footer.ejs', { thanks: false }) %>
</body>

View File

@ -21,23 +21,18 @@
</head>
<body>
<header>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs</a></h1>
<p><a href="https://git.alekswilc.dev/alekswilc/simrail-logs">Dokumentacja</a></p>
<br />
<p style="color: darkorange;">Hej! Podoba Ci się projekt? Polajkuj post na <a href="https://forum.simrail.eu/topic/9142-logowanie-wyj%C5%9B%C4%87-z-posterunk%C3%B3w/">forum</a>, a będzie mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu</p>
</header>
<%- include('../_modules/header.ejs', { section: 'stations' }) %>
<h2>Wyszukaj posterunek, osobe lub serwer</h2>
<div class="container">
<input type="text" id="search">
<input type="text" id="search" value="<%=q%>">
<button onclick="search()">Szukaj</button>
<button onclick="clearSearch()">Wyczyść</button>
<p>Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy</p>
</div>
<h2>Ostatnie opuszczenia posterunków</h2>
<ul>
<% records.forEach(record=> { %>
<li>
@ -69,9 +64,20 @@
<% }) %>
</ul>
<% if (!records.length) { %>
<h4>Nie znaleziono wyników dla twojego zapytania.</h4>
<% } %>
<hr>
<%- include('../_modules/footer.ejs', { thanks: false }) %>
<script>
function search() {
location.href = '/search?q=' + document.getElementById('search').value
location.href = '/stations/?q=' + document.getElementById('search').value
}
function clearSearch() {
console.log('test')
location.href = '/stations/';
}
document.getElementById('search').addEventListener("keyup", (event) => {
if (event.key === "Enter")

View File

@ -22,24 +22,16 @@
</head>
<body>
<header>
<h1><a style="color: white; text-decoration: none;" href="/">SimRail Logs</a></h1>
<p><a href="https://git.alekswilc.dev/alekswilc/simrail-logs">Dokumentacja</a></p>
<br />
<p style="color: darkorange;">Hej! Podoba Ci się projekt? Polajkuj post na <a href="https://forum.simrail.eu/topic/9142-logowanie-wyj%C5%9B%C4%87-z-posterunk%C3%B3w/">forum</a>, a będzie mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu</p>
</header>
<h2>Wyszukaj posterunek, osobe lub serwer</h2>
<%- include('../_modules/header.ejs', { section: 'stations' }) %>
<h2>Tablica godzin</h2>
<div class="container">
<input type="text" id="search" value="<%=q%>">
<button onclick="search()">Szukaj</button>
<button onclick="search()">Filtruj</button>
<button onclick="clearSearch()">Wyczyść</button>
<p>Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy</p>
</div>
<h2>Wyniki wyszukiwania</h2>
<ul>
<% records.forEach(record=> { %>
<li>
@ -50,6 +42,8 @@
<%= record.stationName %>
</span> - <span style="color:hotpink">
<%= record.userUsername %>
</span>- <span style="color:lightseagreen">
<%= record.joinedDate ? msToTime(record.leftDate - record.joinedDate) : '--' %>
</span>
<p style="margin-bottom: 0; opacity: 0.5;">
<%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %>
@ -71,17 +65,28 @@
<% }) %>
</ul>
<% if (!records.length) { %>
<h4>Nie znaleziono wyników dla twojego zapytania.</h4>
<% } %>
<script>
function search() {
location.href = '/search?q=' + document.getElementById('search').value
location.href = '/stations/leaderboard?q=' + document.getElementById('search').value
}
function clearSearch() {
location.href = '/stations/leaderboard'
}
document.getElementById('search').addEventListener("keyup", (event) => {
if (event.key === "Enter")
search();
});
</script>
<hr>
<%- include('../_modules/footer.ejs', { thanks: false }) %>
</body>
</html>

View File

@ -3,7 +3,7 @@ import './util/time.js';
import { SimrailClient, SimrailClientEvents } from './util/SimrailClient.js';
import dayjs from 'dayjs';
import { StationsModule } from './modules/stations.js';
import { ApiModule } from './http/api.js';
import { ApiModule } from './http/server.js';
import mongoose from 'mongoose';
import { IPlayer } from './types/player.js';
import { Station, Server } from '@simrail/types';

View File

@ -1,8 +1,7 @@
import { Model, model, Schema } from 'mongoose';
const schema = new Schema<ILog>(
{
export const raw_schema = {
id: {
type: String,
required: true
@ -41,7 +40,8 @@ const schema = new Schema<ILog>(
required: true
},
}
);
const schema = new Schema<ILog>(raw_schema);
schema.index({ stationName: 'text', userUsername: 'text', stationShort: 'text', userSteamId: 'text', server: 'text' })
export type TMLog = Model<ILog>

View File

@ -10,5 +10,9 @@ export const msToTime = (duration: number) => {
const minutes = Math.floor((duration / (1000 * 60)) % 60);
const hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
if (minutes === 0 && hours === 0 && duration > 0)
return "1m";
return `${hours ? `${hours}h ` : ''}${minutes ? `${minutes}m` : ''}`;
}