updating the scores works now yayyy
but also the database is shit so imma rewrite it lol
This commit is contained in:
@@ -72,7 +72,7 @@ export const ledger = sqliteTable('ledger', {
|
|||||||
.default(sql`(unixepoch())`),
|
.default(sql`(unixepoch())`),
|
||||||
type: text('type').notNull().default('event'),
|
type: text('type').notNull().default('event'),
|
||||||
event: integer('event').references(() => events.id),
|
event: integer('event').references(() => events.id),
|
||||||
scorer: text('scorer').references(() => scorers.id)
|
scorer: integer('scorer').references(() => scorers.id)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ledgerScores = sqliteTable('ledgerScores', {
|
export const ledgerScores = sqliteTable('ledgerScores', {
|
||||||
@@ -99,3 +99,22 @@ export const teamScoresView = sqliteView('teamScoresView').as((qb) => {
|
|||||||
.leftJoin(ledgerScores, eq(teams.id, ledgerScores.player))
|
.leftJoin(ledgerScores, eq(teams.id, ledgerScores.player))
|
||||||
.groupBy(teams.id);
|
.groupBy(teams.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const playerDetailsView = sqliteView('playerDetailsView').as((qb) => {
|
||||||
|
return qb
|
||||||
|
.select({
|
||||||
|
playerId: players.id,
|
||||||
|
firstName: players.firstName,
|
||||||
|
lastName: players.lastName,
|
||||||
|
teamID: teams.id,
|
||||||
|
teamName: teams.name,
|
||||||
|
teamColor: teams.color,
|
||||||
|
divisionId: divisions.id,
|
||||||
|
eventId: events.id
|
||||||
|
})
|
||||||
|
.from(players)
|
||||||
|
.innerJoin(teams, eq(players.team, teams.id))
|
||||||
|
.innerJoin(divisions, eq(players.division, divisions.id))
|
||||||
|
.leftJoin(eventAttributions, eq(players.id, eventAttributions.playerID))
|
||||||
|
.leftJoin(events, eq(eventAttributions.eventID, events.id));
|
||||||
|
});
|
||||||
|
|||||||
64
src/lib/server/eventManager.ts
Normal file
64
src/lib/server/eventManager.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { EventEmitter } from 'node:events';
|
||||||
|
import { db } from '$lib/server/db';
|
||||||
|
import * as schema from '$lib/server/db/schema';
|
||||||
|
|
||||||
|
let testScore = 0;
|
||||||
|
|
||||||
|
// Emitter that emits
|
||||||
|
export const globalEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
// Increment score for testing (remove ts)
|
||||||
|
const increment = () => {
|
||||||
|
testScore++;
|
||||||
|
console.log('score incremented', testScore);
|
||||||
|
globalEmitter.emit('scoreUpdate');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increment scores when there is an emit
|
||||||
|
globalEmitter.on('incrementScores', increment);
|
||||||
|
|
||||||
|
// Get teams object from database
|
||||||
|
export async function getTeams() {
|
||||||
|
const allTeams = await db.select().from(schema.teamScoresView);
|
||||||
|
return {
|
||||||
|
teams: allTeams.map((team) => ({
|
||||||
|
name: team.teamName,
|
||||||
|
color: team.teamColor,
|
||||||
|
points: team.totalPoints || testScore
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPlayers() {
|
||||||
|
// 1. Fetch the view records, divisions, and events all at once
|
||||||
|
const [allPlayers, allDivisions, allEvents] = await Promise.all([
|
||||||
|
db.select().from(schema.playerDetailsView),
|
||||||
|
db.select().from(schema.divisions),
|
||||||
|
db.select().from(schema.events)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 2. Build quick key/value lookup maps for IDs to names
|
||||||
|
const divisionMap = new Map(allDivisions.map((d) => [d.id, d.name]));
|
||||||
|
const eventMap = new Map(allEvents.map((e) => [e.id, e.name]));
|
||||||
|
|
||||||
|
// 3. Return your original cleanly mapped data structure
|
||||||
|
return {
|
||||||
|
teams: allPlayers.map((player) => ({
|
||||||
|
ID: player.playerId,
|
||||||
|
firstName: player.firstName,
|
||||||
|
lastName: player.lastName,
|
||||||
|
teamID: player.teamID,
|
||||||
|
teamName: player.teamName,
|
||||||
|
teamColor: player.teamColor,
|
||||||
|
|
||||||
|
// Map division name safely
|
||||||
|
divisionName: player.divisionId
|
||||||
|
? divisionMap.get(player.divisionId) || 'Unknown Division'
|
||||||
|
: 'No Division',
|
||||||
|
|
||||||
|
// Map event name safely (fixing the team name leak!)
|
||||||
|
eventName: player.eventId ? eventMap.get(player.eventId) || 'No Event' : 'No Event',
|
||||||
|
eventID: player.eventId
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,28 +1,6 @@
|
|||||||
import { db } from '$lib/server/db';
|
import * as eventManager from '$lib/server/eventManager';
|
||||||
import * as schema from '$lib/server/db/schema';
|
|
||||||
import type { Actions } from './$types';
|
|
||||||
|
|
||||||
|
// Literally only here so that the frontend has the right structure
|
||||||
export const load = async () => {
|
export const load = async () => {
|
||||||
return await getTeams();
|
return await eventManager.getTeams();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
|
||||||
addEntry: async (event) => {
|
|
||||||
console.log('something');
|
|
||||||
}
|
|
||||||
} satisfies Actions;
|
|
||||||
|
|
||||||
let testScore = 0;
|
|
||||||
|
|
||||||
export async function getTeams() {
|
|
||||||
const allTeams = await db.select().from(schema.teamScoresView);
|
|
||||||
console.log(allTeams);
|
|
||||||
return {
|
|
||||||
teams: allTeams.map((team) => ({
|
|
||||||
...team,
|
|
||||||
name: team.teamName,
|
|
||||||
color: team.teamColor,
|
|
||||||
points: team.totalPoints || testScore
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,17 +1,62 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
|
|
||||||
|
// Get initial data from the load thing (innacurate lol)
|
||||||
let { data }: { data: import('./$types').PageData } = $props();
|
let { data }: { data: import('./$types').PageData } = $props();
|
||||||
|
|
||||||
let leaderboard = $derived([...data.teams].sort((a, b) => b.points - a.points));
|
// Derived unordered
|
||||||
|
let teams = $derived(data.teams);
|
||||||
|
|
||||||
|
//// Layout logic
|
||||||
|
function getColCount(width: number): number {
|
||||||
|
if (width < 640) return 1; // sm
|
||||||
|
if (width < 1024) return 2; // md
|
||||||
|
// lg: 1 col per item, max 5
|
||||||
|
return Math.min(teams.length, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
//// Leaderboard Database logic
|
||||||
|
// new event source for websocket
|
||||||
|
let scoreEndpoint: EventSource;
|
||||||
|
let playerEndpoint: EventSource;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// get endpoint
|
||||||
|
scoreEndpoint = new EventSource('/api/teams');
|
||||||
|
|
||||||
|
// when you get a message do something
|
||||||
|
scoreEndpoint.onmessage = (e) => {
|
||||||
|
const parsed = JSON.parse(e.data);
|
||||||
|
// If the message has a teams object update the score thing
|
||||||
|
if (parsed['teams']) {
|
||||||
|
teams = parsed['teams'];
|
||||||
|
console.log('teams updated');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Player endpoint
|
||||||
|
playerEndpoint = new EventSource('/api/players');
|
||||||
|
|
||||||
|
playerEndpoint.onmessage = (e) => {
|
||||||
|
let playerData = JSON.parse(e.data);
|
||||||
|
console.log(playerData);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// When window destroyed close the websocket connection
|
||||||
|
onDestroy(() => scoreEndpoint?.close());
|
||||||
|
|
||||||
|
// Order leaderboard so that its displayed correctly
|
||||||
|
let leaderboard = $derived([...teams].sort((a, b) => b.points - a.points));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<svelte:window onbeforeunload={() => scoreEndpoint?.close()} />
|
||||||
class="grid-cols-1 justify-center p-10 sm:grid sm:grid-cols-2 md:grid-cols-2 lg:flex lg:flex-row"
|
|
||||||
>
|
<div class="p-[2vw]">
|
||||||
{#each leaderboard as house (house.name)}
|
{#each leaderboard as house (house.name)}
|
||||||
<div
|
<div
|
||||||
style="--theme-color: {house.color};"
|
style="--theme-color: {house.color};"
|
||||||
class="--theme-color: m-5 border-solid {house.color} score-box aspect-1/1 rounded-2xl border-3 first:col-span-2 sm:first:aspect-2/1 lg:w-70"
|
class="score-box mb-2 aspect-3/1 rounded-2xl border-5 first:aspect-2/1"
|
||||||
>
|
>
|
||||||
<div class="text-center">{house.name}</div>
|
<div class="text-center">{house.name}</div>
|
||||||
<div class="items-center justify-center text-center"><p>{house.points}</p></div>
|
<div class="items-center justify-center text-center"><p>{house.points}</p></div>
|
||||||
@@ -19,7 +64,17 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="POST" action="?/addEntry" use:enhance><button>click</button></form>
|
<button
|
||||||
|
onclick={() =>
|
||||||
|
// Onclick send a request to the post endpoint
|
||||||
|
fetch('/api/teams', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ message: 'hello' })
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Send update
|
||||||
|
</button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import url('https://cdn.jsdelivr.net/npm/@catppuccin/palette/css/catppuccin.css');
|
@import url('https://cdn.jsdelivr.net/npm/@catppuccin/palette/css/catppuccin.css');
|
||||||
|
|||||||
46
src/routes/api/players/+server.ts
Normal file
46
src/routes/api/players/+server.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { globalEmitter, getPlayers } from '$lib/server/eventManager';
|
||||||
|
|
||||||
|
// Expose endpoint
|
||||||
|
export async function GET() {
|
||||||
|
// expose function type to both start and cancel
|
||||||
|
let playerList: () => Promise<void>;
|
||||||
|
|
||||||
|
// When request recieved open a new websocket
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
// Make a new controller so I can send messages
|
||||||
|
async start(controller) {
|
||||||
|
// Function for sending messages
|
||||||
|
const enqueue = (data: any) => {
|
||||||
|
let transferdata = JSON.stringify(data);
|
||||||
|
// stringify data and add to controller queue
|
||||||
|
controller.enqueue(`data: ${transferdata}\n\n`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to grab score from database and add it to message queue
|
||||||
|
playerList = async () => {
|
||||||
|
let players = await getPlayers();
|
||||||
|
enqueue(players);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update score on connection so everything is synced
|
||||||
|
enqueue(await getPlayers());
|
||||||
|
// Update score every time the emitter emits that the score has been changed
|
||||||
|
globalEmitter.on('playerUpdate', playerList);
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
// Delete the emitter listener so that the server doesnt crash
|
||||||
|
globalEmitter.off('playerUpdate', playerList);
|
||||||
|
console.log('closed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return a response with an eventstream mime type
|
||||||
|
return new Response(stream, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/event-stream',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
Connection: 'keep-alive',
|
||||||
|
'X-Accel-Buffering': 'no'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
54
src/routes/api/teams/+server.ts
Normal file
54
src/routes/api/teams/+server.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { globalEmitter, getTeams } from '$lib/server/eventManager';
|
||||||
|
|
||||||
|
// Expose endpoint
|
||||||
|
export async function GET() {
|
||||||
|
// expose function type to both start and cancel
|
||||||
|
let newScore: () => Promise<void>;
|
||||||
|
|
||||||
|
// When request recieved open a new websocket
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
// Make a new controller so I can send messages
|
||||||
|
start(controller) {
|
||||||
|
// Function for sending messages
|
||||||
|
const enqueue = (data: any) => {
|
||||||
|
let transferdata = JSON.stringify(data);
|
||||||
|
// stringify data and add to controller queue
|
||||||
|
controller.enqueue(`data: ${transferdata}\n\n`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to grab score from database and add it to message queue
|
||||||
|
newScore = async () => {
|
||||||
|
let newScores = await getTeams();
|
||||||
|
enqueue(newScores);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update score on connection so everything is synced
|
||||||
|
enqueue(getTeams());
|
||||||
|
// Update score every time the emitter emits that the score has been changed
|
||||||
|
globalEmitter.on('scoreUpdate', newScore);
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
// Delete the emitter listener so that the server doesnt crash
|
||||||
|
globalEmitter.off('scoreUpdate', newScore);
|
||||||
|
console.log('closed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return a response with an eventstream mime type
|
||||||
|
return new Response(stream, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/event-stream',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
Connection: 'keep-alive',
|
||||||
|
'X-Accel-Buffering': 'no'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose post request
|
||||||
|
export async function POST({ request }: any) {
|
||||||
|
// When post request recieved increment testscores by 1
|
||||||
|
globalEmitter.emit('incrementScores');
|
||||||
|
// Return ok so the frontend is happy
|
||||||
|
return new Response('ok');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user