Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file added README.pdf
Binary file not shown.
32 changes: 32 additions & 0 deletions src/controllers/matchmaking/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Elysia, {t} from "elysia";
import {
createContestTree,
getAllContestTrees,
getContestTreeByContestId,
getContestTreeByRankId,
updateContestTreeByContestId,
updateContestTreeByRankId } from "./handlers";
import { ContestTreeSchema } from "../../utils/contest-tree/contest-tree-schema";

export const matchmaking = new Elysia({prefix: "/matchmaking"})
.state("user", {})
.post("", async (context) => { return createContestTree(context)}, {
body: ContestTreeSchema
})
.get("", async (context) => { return getAllContestTrees(context)})
.get("rank/:rank_id", async (context) => {
return getContestTreeByRankId(context.params.rank_id);
})
.get("contest/:contest_id", async (context) => {
return getContestTreeByContestId(parseInt(context.params.contest_id));
})
.put("/contest/:contest_id", async (context) => {
return updateContestTreeByContestId(parseInt(context.params.contest_id), context.body);
}, {
body: ContestTreeSchema
})
.put("/rank/:rank_id", async (context) => {
return updateContestTreeByRankId(context.params.rank_id, context.body);
}, {
body: ContestTreeSchema
});
81 changes: 81 additions & 0 deletions src/controllers/matchmaking/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Context } from "elysia";
import { IDatabase } from "../../db/database.interface";
import { MongoAdapter } from "../../db/mongo/mongo.adapter";
import { ContestTree } from "../../utils/contest-tree/contest-tree-schema";
import ContestTreesCache from "../../utils/contest-tree/contest-trees-cache";

const COLLECTION: string = "matchmaking";
const db: IDatabase = new MongoAdapter();
const cache = ContestTreesCache.getInstance();

// Crud - Create

export const createContestTree = async (context: Context) => {
const contestTree = context.body as ContestTree;
const existing = await db.getBy(COLLECTION, { contest_id: contestTree.contest_id });
if (existing.data){
console.log("Existing contest tree found for contest", contestTree.contest_id, "with rank", existing.data.rank_id);
return null;
}

if (existing.error) {
const result = await db.insert(COLLECTION, contestTree);
if (result.error) return null;
return result.data;
} else {
const result = await db.insert(COLLECTION, contestTree);
if (result.error) return null;
return result.data;
}
};

// Crud - Read all

export const getAllContestTrees = async (context: Context) => {
const result = await db.getAll(COLLECTION);
const data = Array.isArray(result) ? result : result.data ?? [];

return data;
};


// Crud - Read by contest id

export const getContestTreeByContestId = async (contest_id: number): Promise<ContestTree | null> => {
let cacheTree = cache.getByContest(contest_id);
if (cacheTree) {
return cacheTree;
}
const result = await db.getBy(COLLECTION, { contest_id });
if (result.error) return null;
cache.count_call(result.data);
return result.data;
}

// Crud - read by rank id

export const getContestTreeByRankId = async (rank_id: string) => {
const result = await db.getBy(COLLECTION, { rank_id });
if (result.error) return null;
cache.count_call(result.data);
return result.data;
}

// Crud - update by contest id

export const updateContestTreeByContestId = async (contest_id: number, updatedData: ContestTree) => {
const result = await db.update(COLLECTION, { contest_id }, updatedData);
if (result.error) return null;
cache.updateStoredTree(updatedData);
return result.data;
}

// Crud - update by rank id

export const updateContestTreeByRankId = async (rank_id: string, updatedData: ContestTree) => {
const result = await db.update(COLLECTION, { rank_id }, updatedData);
if (result.error) return null;
cache.updateStoredTree(updatedData);
return result.data;
};

12 changes: 11 additions & 1 deletion src/controllers/results/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { IDatabase } from "../../db/database.interface";
import { SupabaseAdapter } from "../../db/supabase/supabase.adapter";
import { CreateResultSchema, UpdateResultSchema } from "../../utils/entities";
import { ENTITY_FILTER_SCHEMAS, getEntityFilters } from "../../utils/filters";
import ContestTreesCache from "../../utils/contest-tree/contest-trees-cache";
import { getContestTreeByContestId } from "../matchmaking/handlers";
import { ContestTreeNode } from "../../utils/contest-tree/contest-tree-node-class";

const COLLECTION: string = "results";

Expand Down Expand Up @@ -53,18 +56,25 @@ export const createResult = async (
body: typeof CreateResultSchema;
},
) => {
const { local_id, visitant_id, winner_id } = context.body;
const { contest_id, local_id, visitant_id, winner_id } = context.body;

if (local_id == visitant_id)
return BadRequest(context, "Visitant and Local are the same.");
if (winner_id != local_id && winner_id != visitant_id)
return BadRequest(context, "Neither visitant or local is the winner");

const result = await db.insert(COLLECTION, context.body);
getContestTreeByContestId(contest_id).then((contestTree) => {
if (contestTree) {
let tree = new ContestTreeNode();
tree.rebuildTree(contestTree.tree);
tree.set_winner(winner_id);
}

if (result.error) return BadRequest(context, result.error);

return Created(context, result.data);
});
};

export const updateResult = async (
Expand Down
122 changes: 61 additions & 61 deletions src/db/mongo/mongo.adapter.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,71 @@
import { ObjectId } from "mongodb";
import { IDatabase } from "../database.interface";
import MongoDB from "./mongo";
import { ObjectId } from "mongodb";
import { IDatabase } from "../database.interface";
import MongoDB from "./mongo";

export class MongoAdapter implements IDatabase {
private db = MongoDB.getInstance();
export class MongoAdapter implements IDatabase {
private db = MongoDB.getInstance();

async insert<T>(collection: string, data: T) {
return this.db.insertDocument(collection, data);
}
async insert<T>(collection: string, data: T) {
return this.db.insertDocument(collection, data);
}

async insertMany<T>(collection: string, data: T[]) {
return this.db.insertManyDocuments(collection, data);
}
async insertMany<T>(collection: string, data: T[]) {
return this.db.insertManyDocuments(collection, data);
}

async getAll(
collection: string,
order?: {
column: string;
asc?: boolean;
},
suborder?: {
column: string;
asc?: boolean;
},
limit?: number,
) {
return this.db.getAllDocuments(collection, order, suborder, limit);
}
async getAll(
collection: string,
order?: {
column: string;
asc?: boolean;
},
suborder?: {
column: string;
asc?: boolean;
},
limit?: number,
) {
return this.db.getAllDocuments(collection, order, suborder, limit);
}

// Por alguna razón este getBy siempre esta limitado a solo 1 documento.
// No se implementa order y limit por la misma razón.
// Por alguna razón este getBy siempre esta limitado a solo 1 documento.
// No se implementa order y limit por la misma razón.

async getBy<T>(
collection: string,
query: Partial<T>,
order?: {
column: string;
asc?: boolean;
},
suborder?: {
column: string;
asc?: boolean;
},
limit?: number,
) {
//@ts-expect-error
if (collection == "members" && query._id)
query._id = parseInt(query._id as string);
//@ts-expect-error
else if (query._id) query._id = new ObjectId(query._id as string);
return await this.db.getOneDocument(collection, query);
}
async getBy<T>(
collection: string,
query: Partial<T>,
order?: {
column: string;
asc?: boolean;
},
suborder?: {
column: string;
asc?: boolean;
},
limit?: number,
) {
//@ts-expect-error
if (collection == "members" && query._id)
query._id = parseInt(query._id as string);
//@ts-expect-error
else if (query._id) query._id = new ObjectId(query._id as string);
return await this.db.getOneDocument(collection, query);
}

async update<T>(collection: string, query: Partial<T>, data: Partial<T>) {
return this.db.updateOneDocument(collection, query, data);
}
async update<T>(collection: string, query: Partial<T>, data: Partial<T>) {
return this.db.updateOneDocument(collection, query, data);
}

async delete<T>(collection: string, query: Partial<T>) {
return this.db.deleteOneDocument(collection, query);
}
async delete<T>(collection: string, query: Partial<T>) {
return this.db.deleteOneDocument(collection, query);
}

async getMultiple(
table: string,
column: string,
options: any[],
): Promise<{ error: string | null; data: any }> {
// TODO: Hay que implementarlo cuando se necesite, creo que por ahora no hay necesidad.
throw new Error("Method not implemented.");
async getMultiple(
table: string,
column: string,
options: any[],
): Promise<{ error: string | null; data: any }> {
// TODO: Hay que implementarlo cuando se necesite, creo que por ahora no hay necesidad.
throw new Error("Method not implemented.");
}
}
}
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { pictures } from "./controllers/picture/controller";
import { results } from "./controllers/results/controller";
import { students } from "./controllers/student/controller";
import { participation } from "./controllers/participation/controller";
import { matchmaking } from "./controllers/matchmaking/controller";
import contestTreesCache from "./utils/contest-tree/contest-trees-cache";

export const app = new Elysia()
.use(
Expand All @@ -31,8 +33,10 @@ export const app = new Elysia()
.use(students)
.use(pictures)
.use(participation)
.use(matchmaking)
.get("/ping", () => "Pong! From Xaverian ACM Chapter")
.listen(Number(process.env.PORT));
.listen(Number(process.env.PORT))
.state("contest-trees-cache", contestTreesCache);

console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
Expand Down
87 changes: 87 additions & 0 deletions src/utils/contest-tree/contest-tree-node-class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// contest-tree-node-class.ts
import type { Static } from "elysia";
import { ContestTreeNodeSchema } from "./contest-tree-node-schema";

export type ContestTreeNodeType = Static<typeof ContestTreeNodeSchema>;

export class ContestTreeNode implements ContestTreeNodeType {
id_participant?: number;
parent?: ContestTreeNode;
left?: ContestTreeNode;
right?: ContestTreeNode;

constructor(id_participant?: number) {
this.id_participant = id_participant;
}

public createTree(ids_participants: number[]): void {
// Create a complete binary tree and assign participant IDs to the leaves
const depth = Math.ceil(Math.log2(ids_participants.length + 1)) - 1;
this.createTreeRecursive(depth);
// Assign participant IDs to the leaves in left-to-right order
const leaves: ContestTreeNode[] = [];
this.collectLeaves(this, leaves);
for (let i = 0; i < ids_participants.length; i++) {
if (i < leaves.length) {
leaves[i].id_participant = ids_participants[i];
}
}
}

public rebuildTree(nodeData: any): ContestTreeNode | undefined {
if (!nodeData) return undefined;
this.left = this.rebuildTree(nodeData.left);
this.right = this.rebuildTree(nodeData.right);

if (this.left) this.left.parent = this;
if (this.right) this.right.parent = this;

return this;
}


collectLeaves(node: ContestTreeNode | undefined, leaves: ContestTreeNode[]): void {
if (!node) return;
if (!node.left && !node.right) {
leaves.push(node);
} else {
this.collectLeaves(node.left, leaves);
this.collectLeaves(node.right, leaves);
}
}


createTreeRecursive(depth: number): void {
if (depth <= 0) return;
this.left = new ContestTreeNode();
this.right = new ContestTreeNode();
this.left.parent = this;
this.right.parent = this;
this.left.createTreeRecursive(depth - 1);
this.right.createTreeRecursive(depth - 1);
}

printTree(prefix: string = "", isLeft: boolean = true): void {
console.log(prefix + (isLeft ? "├── " : "└── ") + (this.id_participant !== undefined ? this.id_participant : "null"));
if (this.left) this.left.printTree(prefix + (isLeft ? "│ " : " "), true);
if (this.right) this.right.printTree(prefix + (isLeft ? "│ " : " "), false);
}

findNode(id_participant: number): ContestTreeNode | null {
if (this.id_participant === id_participant) return this;
let foundNode: ContestTreeNode | null = null;
if (this.left) foundNode = this.left.findNode(id_participant);
if (foundNode) return foundNode;
if (this.right) foundNode = this.right.findNode(id_participant);
return foundNode;
}

public set_winner(id_participant: number): void {
// Find the node with the given participant ID
const node = this.findNode(id_participant);
if (!node || !node.parent) return; // Node not found or is root

// Set the parent's participant ID to the winner's ID
node.parent.id_participant = id_participant;
}
}
Loading