Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2cc5462
Add hamburger menu, settings accordion, and success confirmation
12dinitrobenzene Apr 4, 2026
cce3e91
added hamburger menu to adjust settings
12dinitrobenzene Apr 4, 2026
399110c
updated message
Jax0312 Apr 8, 2026
fec13cb
admin refresh button fix
Jax0312 Apr 8, 2026
617cce8
moved queueConfig into a queueHandler class
Jax0312 Mar 30, 2026
e00bc0a
implemented SSE for queueConfig
Jax0312 Mar 30, 2026
8f41f97
migrated to SSE for queueConfig
Jax0312 Mar 30, 2026
6a248cf
migrated to SSE for queue
Jax0312 Mar 31, 2026
9b5097b
implemented SSE endpoints for queue
Jax0312 Mar 31, 2026
9dc2acc
containerised backend
Jax0312 Apr 2, 2026
ca53e0c
updated backend url
Jax0312 Apr 2, 2026
1a62529
fixed invalid path
Jax0312 Apr 2, 2026
c1a59cc
removed print statement
Jax0312 Apr 2, 2026
200de35
changed backend url to DO
Jax0312 Apr 8, 2026
f61ca90
bug fix
Jax0312 Apr 12, 2026
0045a02
fix Digital Ocean NGINX issue
Jax0312 Apr 12, 2026
6387ac6
changed sse endpoints to POST to bypass Digital Ocean CDN cache
Jax0312 Apr 12, 2026
92969a3
enable telegram notification in backend
Jax0312 Apr 13, 2026
b910db3
disable dummy endpoint
Jax0312 Apr 13, 2026
0adf232
update user page when is no longer in queue
Jax0312 Apr 13, 2026
86a2cb0
updated location info
Jax0312 Apr 15, 2026
c27f8b3
refactor
Jax0312 Apr 30, 2026
a598c54
updated db schema
Jax0312 Apr 30, 2026
01e3325
refactor
Jax0312 Apr 30, 2026
4a642eb
added endpoints to change queue configs
Jax0312 Apr 30, 2026
0723551
fixed UI for updating queue config
Jax0312 Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
.env
18 changes: 18 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:24-alpine

WORKDIR /app

# Copy package.json and package-lock.json (or yarn.lock) to leverage Docker's layer caching
COPY package*.json ./

# Copy the rest of the application code
COPY . .

# Install dependencies
RUN npm install && npx prisma generate && npm run build

# Expose the port the app runs on
EXPOSE 3000

# Command to run the application when the container starts
CMD ["npm", "start"]
28 changes: 22 additions & 6 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "tsx watch --env-file=.env src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"start": "node dist/src/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
Expand All @@ -16,9 +16,10 @@
"dependencies": {
"@fastify/autoload": "^6.3.1",
"@fastify/cors": "^11.2.0",
"@fastify/sse": "^0.4.0",
"@prisma/adapter-neon": "^7.2.0",
"@prisma/client": "^7.2.0",
"fastify": "^5.6.2",
"fastify": "5.8.1",
"fastify-type-provider-zod": "^6.1.0",
"jose": "^6.1.3",
"unique-names-generator": "^4.7.1",
Expand Down
13 changes: 5 additions & 8 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,21 @@ datasource db {
}

model Admin {
id Int @id @default(autoincrement())
telegram_id String @unique
id Int @id @default(autoincrement())
telegram_id String @unique
expiryDate DateTime
}

model QueueConfig {
id Int @id @default(autoincrement())
positionBeforePing Int
isOpen Boolean
eventName String
venue String
}

model Queue {
name String
telegram_id String @unique
timeCreated DateTime
}

model AdminRequester {
id Int @id @default(autoincrement())
telegram_id String @unique
telegram_username String
}
10 changes: 7 additions & 3 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import {
serializerCompiler,
type ZodTypeProvider
} from 'fastify-type-provider-zod';
import queueConfigPlugin from "./queueConfigPlugin.js";
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import fastifySSE from "@fastify/sse";
import {queueHandlerPlugin} from "./queueHandlerPlugin.js";


const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand All @@ -28,10 +30,12 @@ fastify.setSerializerCompiler(serializerCompiler);
await fastify.register(cors, {
origin: true, // set this to frontend url for production
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization', 'User-Id'],
allowedHeaders: ['Content-Type', 'Authorization', 'User-Id', 'last-event-id'],
credentials: true,
});

await fastify.register(fastifySSE.default);

const authHook = async (request: FastifyRequest, reply: FastifyReply) => {

const secret = new TextEncoder().encode(
Expand All @@ -54,7 +58,7 @@ const authHook = async (request: FastifyRequest, reply: FastifyReply) => {
// Init sql db connection
fastify.register(prismaPlugin);
// Register customs plugins
fastify.register(queueConfigPlugin);
fastify.register(queueHandlerPlugin);

fastify.register((fastify) => {

Expand Down
30 changes: 0 additions & 30 deletions backend/src/queueConfigPlugin.ts

This file was deleted.

115 changes: 115 additions & 0 deletions backend/src/queueHandlerPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import fp from 'fastify-plugin'
import {type FastifyInstance, type FastifyPluginAsync} from 'fastify'
import type {SSEReplyInterface} from "@fastify/sse";
import type {QueueConfigModel} from "./generated/prisma/models/QueueConfig.js";
import type {QueueModel} from "./generated/prisma/models/Queue.js";

enum PayloadType {
CONFIG = "CFG",
LIST = "LST",
}

export class QueueHandler {

private users: SSEReplyInterface[]
private admins: SSEReplyInterface[]
private cacheConfig: QueueConfigModel | undefined;
private cacheEntries: QueueModel[] | undefined;
private fastify: FastifyInstance;

constructor(fastify: FastifyInstance) {
this.users = [];
this.admins = [];
this.fastify = fastify;
this.getQueueConfig();
}

public async updateQueue(transform: Promise<any>): Promise<QueueModel[]> {
return transform.then(async (_) => {
this.cacheEntries = await this.fastify.prisma.queue.findMany({
orderBy: {timeCreated: "asc"},
});
this.notify({type: PayloadType.LIST, data: null}, true);
this.notify({type: PayloadType.LIST, data: null}, false);
return this.cacheEntries;
});
}

public async getQueueEntries(): Promise<QueueModel[]> {
if (this.cacheEntries != null) {
return this.cacheEntries!;
}
try {
this.cacheEntries = await this.fastify.prisma.queue.findMany({
orderBy: {timeCreated: "asc"},
});
} catch (_) {
throw new Error("Failed to fetch queue entries");
}
return this.cacheEntries;
}

// QueueConfig should only be modified through this function to ensure synchronization
public async updateQueueConfig(config: QueueConfigModel): Promise<QueueConfigModel> {
return this.fastify.prisma.queueConfig.update({
where: {
id: config.id
},
data: config
}).then((result) => {
this.notify({type: PayloadType.CONFIG, data: result}, true);
this.notify({type: PayloadType.CONFIG, data: result}, false);
this.cacheConfig = result;
return result;
});
}

// QueueConfig should only be accessed through this function to reduce calls to DB
// force if true forces a db fetch
public async getQueueConfig(force?: boolean): Promise<QueueConfigModel> {
if (this.cacheConfig != null && !force) {
return this.cacheConfig!;
}
await this.fastify.prisma.queueConfig.findFirst().then((queueConfig) => {
if (queueConfig == null) {
throw new Error("No queue configured");
} else {
this.cacheConfig = queueConfig;
}
});
return this.cacheConfig!;
}

public addConnection(conn: SSEReplyInterface, isAdmin: boolean): void {
if (isAdmin) {
this.admins.push(conn);
} else {
this.users.push(conn);
}
}

private notify(payload: any, admin: boolean) : void {

this.users = this.users.filter((conn: SSEReplyInterface) => conn.isConnected);
this.admins = this.admins.filter((conn: SSEReplyInterface) => conn.isConnected);

(admin ? this.admins : this.users).forEach((conn: SSEReplyInterface) => {
conn.send({
id: "1",
event: 'update',
data: payload,
});
})

}

}

export const queueHandlerPlugin: FastifyPluginAsync = fp(async (fastify, _) => {

let queueHandler = new QueueHandler(fastify);

fastify.decorate('queueHandler', queueHandler);

})

Loading