Skip to content
Merged
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
5 changes: 3 additions & 2 deletions .docker/init-mongo.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
db.getSiblingDB('abacus')

db.createUser({
user: "username",
pwd: "password",
user: "MUad",
pwd: "ep16y11BPqP",
roles: [{
role: "readWrite",
db: "abacus"
Expand Down Expand Up @@ -695,6 +695,7 @@ db.setting.insert({
points_per_minute: "1"
})

db.createCollection('standing')

db.createCollection('problem')

Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/ci-test-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '16'
- uses: egordm/gha-yarn-node-cache@v1
node-version: '18'
- uses: actions/cache@v4
with:
path: backend/package.json
key: npm-${{ hashFiles('package-lock.json') }}
- run: yarn install
working-directory: backend
- run: yarn build
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/ci-test-frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '16'
- uses: egordm/gha-yarn-node-cache@v1
node-version: '18'
- uses: actions/cache@v4
with:
path: backend/package.json
key: npm-${{ hashFiles('package-lock.json') }}
- run: yarn install
working-directory: frontend
- run: yarn build
Expand Down
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pull official base image
FROM node:16
FROM node:18

ENV CHOKIDAR_USEPOLLING true

Expand Down
2 changes: 1 addition & 1 deletion backend/Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pull official base image
FROM node:16 as build
FROM node:18 as build

# set working directory
WORKDIR /app
Expand Down
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3",
"@types/uuid": "^8.3.0",
"@actions/cache": "^4.0.0",
"nodemon": "^2.0.22",
"openapi-types": "^12.1.3",
"ts-node": "10.9.1",
Expand Down
7 changes: 7 additions & 0 deletions backend/src/@types/abacus/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,13 @@ declare module 'abacus' {
type?: 'success' | 'warning' | 'error'
}

export interface Standing extends Record<string, unknown> {
division: string
problems: Problem[]
standings: any
time_updated: number
}

export type Item = Record<string, unknown>
export type Args = Record<string, unknown>
}
16 changes: 15 additions & 1 deletion backend/src/abacus/contest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Args, Clarification, Item, Problem, ResolvedSubmission, Settings, Submission, User } from 'abacus'
import { Args, Clarification, Item, Problem, ResolvedSubmission, Settings, Submission, User, Standing } from 'abacus'
import { Lambda } from 'aws-sdk'
import { Database } from '../services'
import { MongoDB } from '../services/db'
Expand Down Expand Up @@ -142,6 +142,20 @@ class ContestService {
save_settings(settings: Record<string, number | string>): Promise<Settings> {
return this.db.update('setting', {}, settings) as Promise<Settings>
}

/* Standings */

async create_standing(item: Item): Promise<Standing> {
return this.db.put('standing', item) as Promise<Standing>
}

async get_standing(division: string): Promise<Standing> {
return this.db.get('standing', {division}) as Promise<Standing>
}

async update_standing(division: string, item: Item): Promise<Standing> {
return this.db.update('standing', {division}, item) as Promise<Standing>
}
}

export default new ContestService()
81 changes: 79 additions & 2 deletions backend/src/api/standings/getStandings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ interface BlueTeam {
>
}

const getBlueStandings = async (isPractice: boolean): Promise<Standings<BlueTeam>> => {
// Function that calculates blue standings and stores the standings in the database
const calculateBlueStandings = async (isPractice: boolean): Promise<Standings<BlueTeam>> => {
let teams = await contest.get_users({ role: 'team', division: 'blue' })
teams = teams.filter((user) => !user.disabled)

Expand Down Expand Up @@ -191,12 +192,54 @@ const getBlueStandings = async (isPractice: boolean): Promise<Standings<BlueTeam
return s2.solved - s1.solved
})

const division = 'blue'
const time_updated = Date.now()

const standing = {
division,
problems,
standings,
time_updated
}

await contest.update_standing('blue', standing)

return {
problems: Object.values(problems),
standings
}
}

// Function that checks whether it is time to recalculate the standings
const isTimeToUpdateStandings = async (time_updated: number): Promise<boolean> => {
const current_time = Date.now()

if (current_time < (time_updated + (5 * 60 * 1000))) {
return false
}
else
return true
}

// Function that returns the blue standings
const getBlueStandings = async (isPractice: boolean): Promise<Standings<BlueTeam>> => {
const standing = await contest.get_standing('blue')

if (await isTimeToUpdateStandings(standing.time_updated) === false) {
return {
problems: Object.values(standing.problems),
standings: standing.standings
}
}
else {
const standing = calculateBlueStandings(isPractice)
return {
problems: Object.values((await standing).problems),
standings: (await standing).standings
}
}
}

interface GoldTeam {
display_name: string
uid: string
Expand All @@ -205,7 +248,8 @@ interface GoldTeam {
problems: Record<string, { score: number; status: string }>
}

const getGoldStandings = async (isPractice: boolean): Promise<Standings<GoldTeam>> => {
// Function that calculates gold standings and stores the standings in the database
const calculateGoldStandings = async (isPractice: boolean): Promise<Standings<GoldTeam>> => {
let teams = Object.values(await contest.get_users({ role: 'team', division: 'gold' }))
teams = teams.filter((user) => !user.disabled)

Expand Down Expand Up @@ -286,12 +330,45 @@ const getGoldStandings = async (isPractice: boolean): Promise<Standings<GoldTeam
return s2.score - s1.score
})

const division = 'gold'
const time_updated = Date.now()

const standing = {
division,
problems,
standings,
time_updated
}

await contest.update_standing('gold', standing)

return {
problems: Object.values(problems),
standings
}
}

// Function that returns the gold standings
const getGoldStandings = async (isPractice: boolean): Promise<Standings<GoldTeam>> => {
const standing = await contest.get_standing('gold')

if (await isTimeToUpdateStandings(standing.time_updated) === false) {
return {
problems: Object.values(standing.problems),
standings: standing.standings
}
}
else {
const standing = calculateGoldStandings(isPractice)

return {
problems: Object.values((await standing).problems),
standings: (await standing).standings
}
}
}

// Function that returns either blue or gold standings
export const getStandings = async (req: Request, res: Response): Promise<void> => {
const errors = validationResult(req).array()
if (errors.length > 0) {
Expand Down
6 changes: 6 additions & 0 deletions backend/src/api/submissions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { postSubmissions, schema as postSchema } from './postSubmissions'
import { putSubmissions, schema as putSchema } from './putSubmissions'
import { rerunSubmission, schema as rerunSchema } from './rerunSubmission'
import { submissionsQueue } from './submissionsQueue'
import { doublyLinkedList } from './submissionsDoublyLinkedList'

/**
* @swagger
Expand Down Expand Up @@ -49,5 +50,10 @@ submissions.post('/submissions/submissionsEnqueue', isAuthenticated, (req: Reque
submissionsQueue.enqueue(submission)
})

// Route to get the current state of the doubly linked list
submissions.get('/submissions/submissionsDoublyLinkedList', isAuthenticated, (_req: Request, res: Response) => {
res.json(doublyLinkedList.get())
})

// Export the 'submissions' router to be used in the main app
export default submissions
4 changes: 4 additions & 0 deletions backend/src/api/submissions/rerunSubmission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import axios from 'axios'
import { Request, Response } from 'express'
import { matchedData, ParamSchema, validationResult } from 'express-validator'
import { contest } from '../../abacus'
import { io } from '../../server'

// Define the validation schema for the request body
export const schema: Record<string, ParamSchema> = {
Expand Down Expand Up @@ -152,6 +153,9 @@ export const rerunSubmission = async (req: Request, res: Response): Promise<void
// Save the updated submission to the database
await contest.update_submission(submission.sid as string, {...submission, sid: submission.sid})

// Emit a socket event to notify other services or clients about the updated submission
io.emit('update_submission', { sid: submission.sid })

// Send the updated submission as a response
res.send(submission)
}
Expand Down
18 changes: 18 additions & 0 deletions backend/src/api/submissions/submissionsDoublyLinkedList.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RawSubmission, Submission } from "abacus"
import { io } from '../../server'

// Define a Node clas to store a Submission and links to the next and previous nodes
class Node_<Submission extends RawSubmission>
Expand Down Expand Up @@ -35,6 +36,8 @@ class DoublyLinkedList<Submission extends RawSubmission>
this.head = this.tail = newNode
}
this.size++

io.emit('update_doubly_linked_list', { sid: data.sid })
}

// Function to prepend a new node with submission data to the beginning of the list
Expand Down Expand Up @@ -77,6 +80,8 @@ class DoublyLinkedList<Submission extends RawSubmission>
{
if (this.head)
{
io.emit('update_doubly_linked_list', { sid: this.head.data.sid })

if (this.head.next)
{
this.head = this.head.next
Expand Down Expand Up @@ -125,6 +130,19 @@ class DoublyLinkedList<Submission extends RawSubmission>
}
console.log(result.slice(0, -4))
}

// Returns the current list of submission in the doubly linked list
get(): Submission[] {
let currentNode = this.head
const doublyLinkedList: Submission[] = []

while (currentNode) {
doublyLinkedList.push(currentNode.data)
currentNode = currentNode.next
}

return doublyLinkedList
}
}

// Create an instance of DoublyLinkedList
Expand Down
6 changes: 4 additions & 2 deletions backend/src/api/submissions/submissionsQueue.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RawSubmission, Submission, User } from "abacus"
import { doublyLinkedList } from "./submissionsDoublyLinkedList"
import { sendNotification } from '../../server'
import { io, sendNotification } from '../../server'

// Class that represents the submissions queue
export class SubmissionsQueue<Submission extends RawSubmission>
Expand Down Expand Up @@ -29,6 +29,7 @@ export class SubmissionsQueue<Submission extends RawSubmission>
return
}
this.submissions.push(item)
io.emit('update_queue', { sid: item.sid })
}

/* Removes a submission for the queue by its submission ID (sid).
Expand All @@ -40,13 +41,14 @@ export class SubmissionsQueue<Submission extends RawSubmission>

if (index !== -1)
{
io.emit('update_queue', { sid: this.submissions[index].sid })
this.submissions.splice(index, 1)
}

if (!doublyLinkedList.isEmpty())
{
const submission = doublyLinkedList.getNodeAt(0)?.data as Submission
this.submissions.push(submission)
this.enqueue(submission)
doublyLinkedList.removeFirst()
const judgeInfo = submission.claimed as User
sendNotification({
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/context/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface ClientToServerEvents {
new_clarification: () => void;
update_submission: (submission: Submission) => void
delete_submission: (submission: Submission) => void
update_queue: (submission: Submission) => void
update_doubly_linked_list: (submission: Submission) => void
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down
Loading