diff --git a/.gitea/assets/image-2.png b/.gitea/assets/image-2.png new file mode 100644 index 0000000..eaf1c58 Binary files /dev/null and b/.gitea/assets/image-2.png differ diff --git a/package.json b/package.json index 57be037..040407f 100644 --- a/package.json +++ b/package.json @@ -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 Wilczyński", + "license": "AGPL-3.0-only", "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^22.0.0", diff --git a/readme.md b/readme.md index c07e919..79f86d9 100644 --- a/readme.md +++ b/readme.md @@ -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) diff --git a/src/http/api.ts b/src/http/api.ts deleted file mode 100644 index 494c6f0..0000000 --- a/src/http/api.ts +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/src/http/routes/stations.ts b/src/http/routes/stations.ts new file mode 100644 index 0000000..6c98ae1 --- /dev/null +++ b/src/http/routes/stations.ts @@ -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; + } +} \ No newline at end of file diff --git a/src/http/server.ts b/src/http/server.ts new file mode 100644 index 0000000..33ace50 --- /dev/null +++ b/src/http/server.ts @@ -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); + } +} \ No newline at end of file diff --git a/src/http/views/_modules/footer.ejs b/src/http/views/_modules/footer.ejs new file mode 100644 index 0000000..bd506b7 --- /dev/null +++ b/src/http/views/_modules/footer.ejs @@ -0,0 +1,7 @@ +
+

Made with ❤️ by alekswilc for SimRail Community

+

Open Source

+ <% if (thanks) { %> +

Podziękowania dla AQUALYTH, osób lajkujących posta, osób użytkujących strone i całego community Simrail ❤️

+ <% } %> +
\ No newline at end of file diff --git a/src/http/views/_modules/header.ejs b/src/http/views/_modules/header.ejs new file mode 100644 index 0000000..78ed9f9 --- /dev/null +++ b/src/http/views/_modules/header.ejs @@ -0,0 +1,21 @@ +
+

SimRail Logs

+ + <% if (section==='stations' ) { %> + + + <% } else {%> + Logi posterunków / + Logi pociągów + <% } %> +
+

Hej! Podoba Ci się projekt? Polajkuj post na forum, a będzie + mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu.

+
\ No newline at end of file diff --git a/src/http/views/home.ejs b/src/http/views/home.ejs new file mode 100644 index 0000000..b9511f6 --- /dev/null +++ b/src/http/views/home.ejs @@ -0,0 +1,113 @@ + + + + + + + simrail.alekswilc.dev + + + + + + + + + + + + + <%- include('_modules/header.ejs', { section:'home' }) %> + +

Prosty projekt, logujący wyjścia z posterunków.

+

Cele projektu

+

Główne założenia

+ + +

Rozwój aplikacji

+

Planowane funkcjonalności

+ +

Informacje dodatkowe

+
+ + Zgłaszanie błędów i propozycji + + +
Zgłaszanie błedów
+ + + + + +
Zgłaszanie propozycji
+ + + +
+ + + +

Licencja

+
+ + Copyright (C) 2024 Aleksander Wilczyński + + + + 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/. +
+ +
+ <%- include('_modules/footer.ejs', { thanks: true }) %> + + + + + \ No newline at end of file diff --git a/src/http/views/details.ejs b/src/http/views/stations/details.ejs similarity index 83% rename from src/http/views/details.ejs rename to src/http/views/stations/details.ejs index 383b973..727fe67 100644 --- a/src/http/views/details.ejs +++ b/src/http/views/stations/details.ejs @@ -1,89 +1,86 @@ - - - - - - - simrail.alekswilc.dev - - - - - - - - - - - - - - - - -
-

SimRail Logs

-

Dokumentacja

-
-

Hej! Podoba Ci się projekt? Polajkuj post na forum, a będzie mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu

-
- -
- -

Użytkownik: - <%= record.userUsername %> -

-

Stacja: <%= record.stationName %> -

-

Serwer: <%= record.server.toUpperCase() %> -

-

Data wejścia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' - %> (<%= record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)

-

Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.leftDate).fromNow() - %>)

-

Spędzony czas: <%= record.joinedDate ? msToTime(record.leftDate - record.joinedDate) : '--' %> -

- -
- ;station: <%= record.stationName %> - ;steam: <%= record.userSteamId %> - ;server: <%= record.server %> - ;name: <%= record.userUsername %> - ;joined: <%=record.joinedDate ? dayjs(record.joinedDate).format() : 'no-data'%> - ;left: <%=dayjs(record.leftDate).format()%> - ;url: https://simrail.alekswilc.dev/details/<%= record.id %>/ - -
-

- - - -
- - - + + + + + + + simrail.alekswilc.dev + + + + + + + + + + + + + + + + + <%- include('../_modules/header.ejs', { section: 'stations' }) %> + +
+ +

Użytkownik: + <%= record.userUsername %> +

+

Stacja: <%= record.stationName %> +

+

Serwer: <%= record.server.toUpperCase() %> +

+

Data wejścia: <%= record.joinedDate ? dayjs(record.joinedDate).format('HH:mm DD/MM/YYYY') : '--:-- --/--/--' + %> (<%= record.joinedDate ? dayjs(record.joinedDate).fromNow() : '--' %>)

+

Data wyjścia: <%= dayjs(record.leftDate).format('HH:mm DD/MM/YYYY') %> (<%= dayjs(record.leftDate).fromNow() + %>)

+

Spędzony czas: <%= record.joinedDate ? msToTime(record.leftDate - record.joinedDate) : '--' %> +

+ +
+ ;station: <%= record.stationName %> + ;steam: <%= record.userSteamId %> + ;server: <%= record.server %> + ;name: <%= record.userUsername %> + ;joined: <%=record.joinedDate ? dayjs(record.joinedDate).format() : 'no-data'%> + ;left: <%=dayjs(record.leftDate).format()%> + ;url: https://simrail.alekswilc.dev/details/<%= record.id %>/ + +
+

+ + + +
+
+ <%- include('../_modules/footer.ejs', { thanks: false }) %> + + + \ No newline at end of file diff --git a/src/http/views/index.ejs b/src/http/views/stations/index.ejs similarity index 77% rename from src/http/views/index.ejs rename to src/http/views/stations/index.ejs index 46d35b2..cae0e10 100644 --- a/src/http/views/index.ejs +++ b/src/http/views/stations/index.ejs @@ -1,85 +1,91 @@ - - - - - - - simrail.alekswilc.dev - - - - - - - - - - - - -
-

SimRail Logs

-

Dokumentacja

-
-

Hej! Podoba Ci się projekt? Polajkuj post na forum, a będzie mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu

-
- -

Wyszukaj posterunek, osobe lub serwer

- -
- - -

Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy

-
- -

Ostatnie opuszczenia posterunków

- - - - - - - + + + + + + + simrail.alekswilc.dev + + + + + + + + + + + + + <%- include('../_modules/header.ejs', { section: 'stations' }) %> + +

Wyszukaj posterunek, osobe lub serwer

+ +
+ + + + +

Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy

+
+ + + + <% if (!records.length) { %> +

Nie znaleziono wyników dla twojego zapytania.

+ <% } %> + +
+ <%- include('../_modules/footer.ejs', { thanks: false }) %> + + + + + \ No newline at end of file diff --git a/src/http/views/search.ejs b/src/http/views/stations/leaderboard.ejs similarity index 76% rename from src/http/views/search.ejs rename to src/http/views/stations/leaderboard.ejs index 614273b..0487012 100644 --- a/src/http/views/search.ejs +++ b/src/http/views/stations/leaderboard.ejs @@ -1,87 +1,92 @@ - - - - - - - simrail.alekswilc.dev - - - - - - - - - - - - - -
-

SimRail Logs

-

Dokumentacja

-
-

Hej! Podoba Ci się projekt? Polajkuj post na forum, a będzie mi miło! Dla Ciebie to jedno kliknięcie, a dla mnie motywacja do rozwijania projektu

-
- -

Wyszukaj posterunek, osobe lub serwer

- - -
- - -

Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy

-
- -

Wyniki wyszukiwania

- - - - - - - + + + + + + + simrail.alekswilc.dev + + + + + + + + + + + + + + <%- include('../_modules/header.ejs', { section: 'stations' }) %> + +

Tablica godzin

+
+ + + + +

Użyj przecinka, aby wyszukać wiele wartości: pl2,Łazy

+
+ + + <% if (!records.length) { %> +

Nie znaleziono wyników dla twojego zapytania.

+ <% } %> + + + +
+ <%- include('../_modules/footer.ejs', { thanks: false }) %> + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index af4fc7e..ac8593a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/mongo/logs.ts b/src/mongo/logs.ts index 9555162..9467cee 100644 --- a/src/mongo/logs.ts +++ b/src/mongo/logs.ts @@ -1,47 +1,47 @@ import { Model, model, Schema } from 'mongoose'; -const schema = new Schema( - { - id: { - type: String, - required: true - }, - userSteamId: { - type: String, - required: true - }, - userUsername: { - type: String, - required: true - }, - userAvatar: { - type: String, - required: true - }, - joinedDate: { - type: Number, - required: false, - default: undefined - }, - leftDate: { - type: Number, - required: true - }, - stationName: { - type: String, - required: true - }, - stationShort: { - type: String, - required: true - }, - server: { - type: String, - required: true - }, - } -); +export const raw_schema = { + id: { + type: String, + required: true + }, + userSteamId: { + type: String, + required: true + }, + userUsername: { + type: String, + required: true + }, + userAvatar: { + type: String, + required: true + }, + joinedDate: { + type: Number, + required: false, + default: undefined + }, + leftDate: { + type: Number, + required: true + }, + stationName: { + type: String, + required: true + }, + stationShort: { + type: String, + required: true + }, + server: { + type: String, + required: true + }, +} + +const schema = new Schema(raw_schema); schema.index({ stationName: 'text', userUsername: 'text', stationShort: 'text', userSteamId: 'text', server: 'text' }) export type TMLog = Model diff --git a/src/util/time.ts b/src/util/time.ts index da32243..2d77399 100644 --- a/src/util/time.ts +++ b/src/util/time.ts @@ -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` : ''}`; } \ No newline at end of file