|
|
|
@@ -14,6 +14,7 @@ import { ResponseStats } from '../models/responses/ResponseStats';
|
|
|
|
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization';
|
|
|
|
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization';
|
|
|
|
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
|
|
|
|
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
|
|
|
|
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
|
|
|
|
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
|
|
|
|
|
|
|
|
import { getStatsCache, setStatsCache } from '../nats/StatsKV';
|
|
|
|
|
|
|
|
|
|
|
|
@JsonController('/stats')
|
|
|
|
@JsonController('/stats')
|
|
|
|
export class StatsController {
|
|
|
|
export class StatsController {
|
|
|
|
@@ -22,6 +23,13 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStats)
|
|
|
|
@ResponseSchema(ResponseStats)
|
|
|
|
@OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" })
|
|
|
|
@OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" })
|
|
|
|
async get() {
|
|
|
|
async get() {
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStats>('overview');
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
const connection = getConnection();
|
|
|
|
const connection = getConnection();
|
|
|
|
const runnersViaSelfservice = await connection.getRepository(Runner).count({ where: { created_via: "selfservice" } });
|
|
|
|
const runnersViaSelfservice = await connection.getRepository(Runner).count({ where: { created_via: "selfservice" } });
|
|
|
|
const runnersViaKiosk = await connection.getRepository(Runner).count({ where: { created_via: "kiosk" } });
|
|
|
|
const runnersViaKiosk = await connection.getRepository(Runner).count({ where: { created_via: "kiosk" } });
|
|
|
|
@@ -43,7 +51,12 @@ export class StatsController {
|
|
|
|
let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
|
|
|
|
let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
|
|
|
|
const donors = await connection.getRepository(Donor).count();
|
|
|
|
const donors = await connection.getRepository(Donor).count();
|
|
|
|
|
|
|
|
|
|
|
|
return new ResponseStats(runnersViaSelfservice, runners, teams, orgs, users, scans, donations, distace, donors, runnersViaKiosk)
|
|
|
|
const result = new ResponseStats(runnersViaSelfservice, runners, teams, orgs, users, scans, donations, distace, donors, runnersViaKiosk);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache('overview', result);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Get("/runners/distance")
|
|
|
|
@Get("/runners/distance")
|
|
|
|
@@ -51,6 +64,13 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStatsRunner, { isArray: true })
|
|
|
|
@ResponseSchema(ResponseStatsRunner, { isArray: true })
|
|
|
|
@OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
@OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
async getTopRunnersByDistance() {
|
|
|
|
async getTopRunnersByDistance() {
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStatsRunner[]>('runners.distance');
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] });
|
|
|
|
let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] });
|
|
|
|
if (!runners || runners.length == 0) {
|
|
|
|
if (!runners || runners.length == 0) {
|
|
|
|
return [];
|
|
|
|
return [];
|
|
|
|
@@ -60,6 +80,10 @@ export class StatsController {
|
|
|
|
topRunners.forEach(runner => {
|
|
|
|
topRunners.forEach(runner => {
|
|
|
|
responseRunners.push(new ResponseStatsRunner(runner));
|
|
|
|
responseRunners.push(new ResponseStatsRunner(runner));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache('runners.distance', responseRunners);
|
|
|
|
|
|
|
|
|
|
|
|
return responseRunners;
|
|
|
|
return responseRunners;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -68,6 +92,13 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStatsRunner, { isArray: true })
|
|
|
|
@ResponseSchema(ResponseStatsRunner, { isArray: true })
|
|
|
|
@OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
@OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
async getTopRunnersByDonations() {
|
|
|
|
async getTopRunnersByDonations() {
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStatsRunner[]>('runners.donations');
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
let runners = await getConnection().getRepository(Runner).find({ relations: ['group', 'distanceDonations', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] });
|
|
|
|
let runners = await getConnection().getRepository(Runner).find({ relations: ['group', 'distanceDonations', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] });
|
|
|
|
if (!runners || runners.length == 0) {
|
|
|
|
if (!runners || runners.length == 0) {
|
|
|
|
return [];
|
|
|
|
return [];
|
|
|
|
@@ -77,6 +108,10 @@ export class StatsController {
|
|
|
|
topRunners.forEach(runner => {
|
|
|
|
topRunners.forEach(runner => {
|
|
|
|
responseRunners.push(new ResponseStatsRunner(runner));
|
|
|
|
responseRunners.push(new ResponseStatsRunner(runner));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache('runners.donations', responseRunners);
|
|
|
|
|
|
|
|
|
|
|
|
return responseRunners;
|
|
|
|
return responseRunners;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -85,6 +120,14 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStatsRunner, { isArray: true })
|
|
|
|
@ResponseSchema(ResponseStatsRunner, { isArray: true })
|
|
|
|
@OpenAPI({ description: "Returns the top ten runners by fastest laptime on your selected track (track by id).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
@OpenAPI({ description: "Returns the top ten runners by fastest laptime on your selected track (track by id).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
async getTopRunnersByLaptime(@QueryParam("track") track: number) {
|
|
|
|
async getTopRunnersByLaptime(@QueryParam("track") track: number) {
|
|
|
|
|
|
|
|
// Try cache first (cache key includes track id, using dots for NATS KV compatibility)
|
|
|
|
|
|
|
|
const cacheKey = `runners.laptime.${track}`;
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStatsRunner[]>(cacheKey);
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
let scans = await getConnection().getRepository(TrackScan).find({ relations: ['track', 'runner', 'runner.group', 'runner.scans', 'runner.scans.track', 'runner.distanceDonations'] });
|
|
|
|
let scans = await getConnection().getRepository(TrackScan).find({ relations: ['track', 'runner', 'runner.group', 'runner.scans', 'runner.scans.track', 'runner.distanceDonations'] });
|
|
|
|
if (!scans || scans.length == 0) {
|
|
|
|
if (!scans || scans.length == 0) {
|
|
|
|
return [];
|
|
|
|
return [];
|
|
|
|
@@ -105,6 +148,10 @@ export class StatsController {
|
|
|
|
topScans.forEach(scan => {
|
|
|
|
topScans.forEach(scan => {
|
|
|
|
responseRunners.push(new ResponseStatsRunner(scan.runner, scan.lapTime));
|
|
|
|
responseRunners.push(new ResponseStatsRunner(scan.runner, scan.lapTime));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache(cacheKey, responseRunners);
|
|
|
|
|
|
|
|
|
|
|
|
return responseRunners;
|
|
|
|
return responseRunners;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -121,6 +168,13 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStatsTeam, { isArray: true })
|
|
|
|
@ResponseSchema(ResponseStatsTeam, { isArray: true })
|
|
|
|
@OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
@OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
async getTopTeamsByDistance() {
|
|
|
|
async getTopTeamsByDistance() {
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStatsTeam[]>('teams.distance');
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.scans.track'] });
|
|
|
|
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.scans.track'] });
|
|
|
|
if (!teams || teams.length == 0) {
|
|
|
|
if (!teams || teams.length == 0) {
|
|
|
|
return [];
|
|
|
|
return [];
|
|
|
|
@@ -130,6 +184,10 @@ export class StatsController {
|
|
|
|
topTeams.forEach(team => {
|
|
|
|
topTeams.forEach(team => {
|
|
|
|
responseTeams.push(new ResponseStatsTeam(team));
|
|
|
|
responseTeams.push(new ResponseStatsTeam(team));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache('teams.distance', responseTeams);
|
|
|
|
|
|
|
|
|
|
|
|
return responseTeams;
|
|
|
|
return responseTeams;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -138,6 +196,13 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStatsTeam, { isArray: true })
|
|
|
|
@ResponseSchema(ResponseStatsTeam, { isArray: true })
|
|
|
|
@OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
@OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
async getTopTeamsByDonations() {
|
|
|
|
async getTopTeamsByDonations() {
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStatsTeam[]>('teams.donations');
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] });
|
|
|
|
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] });
|
|
|
|
if (!teams || teams.length == 0) {
|
|
|
|
if (!teams || teams.length == 0) {
|
|
|
|
return [];
|
|
|
|
return [];
|
|
|
|
@@ -147,6 +212,10 @@ export class StatsController {
|
|
|
|
topTeams.forEach(team => {
|
|
|
|
topTeams.forEach(team => {
|
|
|
|
responseTeams.push(new ResponseStatsTeam(team));
|
|
|
|
responseTeams.push(new ResponseStatsTeam(team));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache('teams.donations', responseTeams);
|
|
|
|
|
|
|
|
|
|
|
|
return responseTeams;
|
|
|
|
return responseTeams;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -155,6 +224,13 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
|
|
|
|
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
|
|
|
|
@OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
@OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
async getTopOrgsByDistance() {
|
|
|
|
async getTopOrgsByDistance() {
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStatsOrgnisation[]>('organizations.distance');
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
|
|
|
|
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
|
|
|
|
if (!orgs || orgs.length == 0) {
|
|
|
|
if (!orgs || orgs.length == 0) {
|
|
|
|
return [];
|
|
|
|
return [];
|
|
|
|
@@ -164,6 +240,10 @@ export class StatsController {
|
|
|
|
topOrgs.forEach(org => {
|
|
|
|
topOrgs.forEach(org => {
|
|
|
|
responseOrgs.push(new ResponseStatsOrgnisation(org));
|
|
|
|
responseOrgs.push(new ResponseStatsOrgnisation(org));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache('organizations.distance', responseOrgs);
|
|
|
|
|
|
|
|
|
|
|
|
return responseOrgs;
|
|
|
|
return responseOrgs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -172,6 +252,13 @@ export class StatsController {
|
|
|
|
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
|
|
|
|
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
|
|
|
|
@OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
@OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
|
|
async getTopOrgsByDonations() {
|
|
|
|
async getTopOrgsByDonations() {
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
|
|
|
const cached = await getStatsCache<ResponseStatsOrgnisation[]>('organizations.donations');
|
|
|
|
|
|
|
|
if (cached) {
|
|
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache miss - compute fresh stats
|
|
|
|
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.distanceDonations', 'runners.distanceDonations.runner', 'runners.distanceDonations.runner.scans', 'runners.distanceDonations.runner.scans.track', 'teams', 'teams.runners', 'teams.runners.distanceDonations', 'teams.runners.distanceDonations.runner', 'teams.runners.distanceDonations.runner.scans', 'teams.runners.distanceDonations.runner.scans.track'] });
|
|
|
|
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.distanceDonations', 'runners.distanceDonations.runner', 'runners.distanceDonations.runner.scans', 'runners.distanceDonations.runner.scans.track', 'teams', 'teams.runners', 'teams.runners.distanceDonations', 'teams.runners.distanceDonations.runner', 'teams.runners.distanceDonations.runner.scans', 'teams.runners.distanceDonations.runner.scans.track'] });
|
|
|
|
if (!orgs || orgs.length == 0) {
|
|
|
|
if (!orgs || orgs.length == 0) {
|
|
|
|
return [];
|
|
|
|
return [];
|
|
|
|
@@ -181,6 +268,10 @@ export class StatsController {
|
|
|
|
topOrgs.forEach(org => {
|
|
|
|
topOrgs.forEach(org => {
|
|
|
|
responseOrgs.push(new ResponseStatsOrgnisation(org));
|
|
|
|
responseOrgs.push(new ResponseStatsOrgnisation(org));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store in cache for 60 seconds
|
|
|
|
|
|
|
|
await setStatsCache('organizations.donations', responseOrgs);
|
|
|
|
|
|
|
|
|
|
|
|
return responseOrgs;
|
|
|
|
return responseOrgs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|