Skip to content

Commit a1be2fa

Browse files
authored
Dn 338 + 336 + 339 + 340 (#341)
* - Changes the view of the judge home page based on whether the judge is in the blue or gold division - Fixes bug where gold submissions were being added to the queue and linked list - Fixes the functionality of the unclaim button * - Fixed bug where unclaiming a submission from the submissions page would not remove the submission from either the queue or doubly linked list - Added a button to the admin home page where the button clears the queue and doubly linked list * - Fixed bug where gold teams could resubmit to problems that already has a submission with the status 'accepted' - Added comments and removed console logs
1 parent 5294074 commit a1be2fa

11 files changed

Lines changed: 467 additions & 162 deletions

File tree

backend/src/api/submissions/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,17 @@ submissions.get('/submissions/submissionsDoublyLinkedList', isAuthenticated, (_r
5555
res.json(doublyLinkedList.get())
5656
})
5757

58+
// Route to remove submission from doubly linked list
59+
submissions.post('/submissions/removeAtDoublyLinkedList', isAuthenticated, (req: Request, _res: Response) => {
60+
const { sid } = req.body
61+
doublyLinkedList.removeAt(sid)
62+
})
63+
64+
// Route to clear both the queue and the doubly linked list
65+
submissions.post('/submissions/clearQueueAndDoublyLinkedList', isAuthenticated, (_req: Request, _res: Response) => {
66+
submissionsQueue.clear()
67+
doublyLinkedList.clear()
68+
})
69+
5870
// Export the 'submissions' router to be used in the main app
5971
export default submissions

backend/src/api/submissions/putSubmissions.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,12 @@ export const putSubmissions = async (req: Request, res: Response): Promise<void>
180180
// Get the existing submission using the submission ID (sid)
181181
const submission = await contest.get_submission(item.sid)
182182

183-
// Check if the 'claimed' field is being modified
184-
if (item.claimed !== undefined && submission.claimed !== undefined) {
185-
// Trying to change a claimed submission
186-
if (req.user?.role !== 'admin' && item.claimed !== null) {
187-
res.status(403).send({ message: 'This submission is already claimed!' })
188-
return
189-
}
183+
// Set claimed and claimed_date to null to remove those attributes from the submission data entry in the database
184+
if (item.claimed === '' && submission.claimed !== undefined) {
185+
item.claimed = null
186+
item.claimed_date = null
190187
}
191-
188+
192189
// Update the submission with the new data provided in the request
193190
await contest.update_submission(item.sid, item)
194191

backend/src/api/submissions/submissionsDoublyLinkedList.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class DoublyLinkedList<Submission extends RawSubmission>
6262
{
6363
if (this.tail)
6464
{
65+
io.emit('update_doubly_linked_list', { sid: this.tail.data.sid })
66+
6567
if (this.tail.prev)
6668
{
6769
this.tail = this.tail.prev
@@ -95,6 +97,43 @@ class DoublyLinkedList<Submission extends RawSubmission>
9597
}
9698
}
9799

100+
// Function to remove node with specified sid
101+
removeAt(sid: string): void
102+
{
103+
let currentNode = this.head
104+
105+
while (currentNode)
106+
{
107+
if (currentNode.data.sid === sid)
108+
{
109+
if (currentNode === this.head)
110+
{
111+
this.removeFirst()
112+
}
113+
else if (currentNode === this.tail)
114+
{
115+
this.remove()
116+
}
117+
else
118+
{
119+
if (currentNode.prev)
120+
{
121+
currentNode.prev.next = currentNode.next
122+
}
123+
if (currentNode.next)
124+
{
125+
currentNode.next.prev = currentNode.prev
126+
}
127+
this.size--
128+
129+
io.emit('update_doubly_linked_list', { sid: sid })
130+
}
131+
return
132+
}
133+
currentNode = currentNode.next
134+
}
135+
}
136+
98137
// Function to get a node at a specific index in the list
99138
getNodeAt(index: number): Node_<Submission> | null
100139
{
@@ -112,6 +151,7 @@ class DoublyLinkedList<Submission extends RawSubmission>
112151
{
113152
this.head = this.tail = null
114153
this.size = 0
154+
io.emit('update_doubly_linked_list')
115155
}
116156

117157
// Function to check if the list is empty

backend/src/api/submissions/submissionsQueue.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export class SubmissionsQueue<Submission extends RawSubmission>
8585
clear(): void
8686
{
8787
this.submissions = []
88+
io.emit('update_queue')
8889
}
8990

9091
// Checks if the specified submission is already in the queue

backend/src/services/db/mongo.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,25 +87,32 @@ export default class MongoDB extends Database {
8787

8888
update(TableName: string, Key: Key, Item: Item): Promise<Item> {
8989
return new Promise((resolve, reject) => {
90-
const unsetFields = Object.assign(
91-
{},
92-
...Object.entries(Item)
93-
.filter((obj) => obj[1] === undefined || obj[1] === null)
94-
.map((obj) => ({ [`${obj[0]}`]: 1 }))
95-
)
90+
const setFields: Record<string, any> = {}
91+
const unsetFields: Record<string, any> = {}
92+
93+
// Separate the fields into $set and $unset
94+
for (const key in Item) {
95+
if (Item[key] === null) {
96+
unsetFields[key] = "" // Remove the field if it's null
97+
} else {
98+
setFields[key] = Item[key] // Update the field with the new value
99+
}
100+
}
101+
102+
const updateOps: Record<string, any> = {}
103+
if (Object.keys(setFields).length) updateOps['$set'] = setFields
104+
if (Object.keys(unsetFields).length) updateOps['$unset'] = unsetFields
96105

106+
// Perform the update
97107
this.db.collection(TableName).updateOne(
98108
Key,
99-
{
100-
$set: Item,
101-
$unset: unsetFields
102-
},
109+
updateOps, // Apply both $set and $unset as needed
103110
(err, data) => {
104111
if (err) {
105112
reject(err)
106113
return
107114
}
108-
if (data) resolve(data as unknown as Item)
115+
if (data) resolve(Item) // Return the original item as confirmation
109116
}
110117
)
111118
})

docker-compose.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ services:
1717
context: backend
1818
network: host
1919
environment:
20-
- MONGO_HOST=mongo
21-
- MONGO_USER=MUad
22-
- MONGO_PASS=ep16y11BPqP
23-
- MONGO_DBNAME=admin
20+
- MONGO_HOST=mongodb
21+
- MONGO_USER=username
22+
- MONGO_PASS=password
23+
- MONGO_DBNAME=abacus
2424
volumes:
2525
- "./backend/src:/app/src"
2626
ports:

frontend/src/pages/admin/Home.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,28 @@ import { AppContext, SocketContext } from 'context'
44
import { Block, PageLoading } from 'components'
55
import config from 'environment'
66
import moment from 'moment'
7-
import { Table } from 'semantic-ui-react'
7+
import { Table, Button } from 'semantic-ui-react'
88
import { Link } from 'react-router-dom'
99
import { usePageTitle } from 'hooks'
1010

11+
// Main Admin Home Component
1112
const Home = (): React.JSX.Element => {
13+
// Set the page title
1214
usePageTitle("Abacus | Admin")
1315

16+
// Using socket context for real-time updates
1417
const socket = useContext(SocketContext)
18+
// Track loading state
1519
const [isLoading, setLoading] = useState(true)
20+
// Store the submissions list
1621
const [submissions, setSubmissions] = useState<Submission[]>()
22+
// Track component mounted status
1723
const [isMounted, setMounted] = useState(true)
1824

25+
// Access the user and settings context to retrieve current user data and setting data
1926
const { user, settings } = useContext(AppContext)
2027

28+
// Function to load submissions from API and filter the submissions
2129
const loadSubmissions = async () => {
2230
const getSubmissions = await fetch(`${config.API_URL}/submissions`, {
2331
headers: { Authorization: `Bearer ${localStorage.accessToken}` }
@@ -38,6 +46,22 @@ const Home = (): React.JSX.Element => {
3846
setLoading(false)
3947
}
4048

49+
// Function that clears the queue and doubly linked list
50+
const clearQueue = async () => {
51+
const response = await fetch(`${config.API_URL}/submissions/clearQueueAndDoublyLinkedList`, {
52+
method: 'POST',
53+
headers: {
54+
'Content-Type': 'application/json',
55+
Authorization: `Bearer ${localStorage.accessToken}`
56+
},
57+
})
58+
59+
if (response.ok) {
60+
console.log("Queue has been cleared")
61+
}
62+
}
63+
64+
// Effect hook to load submissions on component mount and set up socket listeners for real-time updates
4165
useEffect(() => {
4266
loadSubmissions().then(() => setLoading(false))
4367
socket?.on('new_submission', loadSubmissions)
@@ -46,8 +70,10 @@ const Home = (): React.JSX.Element => {
4670
return () => setMounted(false)
4771
}, [])
4872

73+
// Filter only flagged submissions
4974
const flaggedSubmissions = useMemo(() => submissions?.filter(({ flagged }) => flagged !== undefined), [submissions])
5075

76+
// Generate time slot categories from start to end time
5177
const categories: string[] = []
5278
if (settings?.start_date && settings?.end_date) {
5379
for (let time = Number(settings?.start_date); time <= Number(settings?.end_date); time += 1800000) {
@@ -98,12 +124,27 @@ const Home = (): React.JSX.Element => {
98124
};
99125
*/
100126

127+
// Show loading page until data is ready
101128
if (isLoading) return <PageLoading />
102129

130+
// Main UI rendering
103131
return (
104132
<>
105133
<Block size="xs-12">
106-
<h1>Admin Dashboard</h1>
134+
<div style={{ position: 'relative' }}>
135+
<h1 style={{ marginBottom: 0 }}>Admin Dashboard</h1>
136+
<Button
137+
color="red"
138+
content="Clear Queue"
139+
onClick={ clearQueue }
140+
style={{
141+
position: 'absolute',
142+
top: 0,
143+
right: 0
144+
}}
145+
>
146+
</Button>
147+
</div>
107148
</Block>
108149

109150
<Block size="xs-6">

frontend/src/pages/gold/Problem.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@ import { AppContext } from 'context'
99
import { userHome } from 'utils'
1010
import { usePageTitle } from 'hooks'
1111

12+
// Gold Problems Page Component
1213
const Problem = (): React.JSX.Element => {
14+
// Access the user and settings context to retrieve current user data and setting data
1315
const { user, settings } = useContext(AppContext)
16+
// Track loading state
1417
const [isLoading, setLoading] = useState(true)
18+
// Store the problem
1519
const [problem, setProblem] = useState<ProblemType>()
20+
// Route param (problem ID from URL)
1621
const { pid } = useParams<{ pid: string }>()
1722

23+
// Store the submissions list
1824
const [submissions, setSubmissions] = useState<Submission[]>()
25+
// Get latest submission (if any) using useMemo to avoid unnecessary recalculations
1926
const latestSubmission = useMemo(() => {
2027
if (!submissions?.length || !user) return <></>
2128
const { sid } = submissions[submissions.length - 1]
@@ -26,10 +33,13 @@ const Problem = (): React.JSX.Element => {
2633
)
2734
}, [submissions])
2835

36+
// Track component mounted status
2937
const [isMounted, setMounted] = useState(true)
3038

39+
// Set the page title dynamically
3140
usePageTitle(`Abacus | ${problem?.name ?? ""}`)
3241

42+
// Effect hook to load problem on component mount
3343
useEffect(() => {
3444
loadProblem().then(() => {
3545
setLoading(false)
@@ -39,6 +49,7 @@ const Problem = (): React.JSX.Element => {
3949
}
4050
}, [])
4151

52+
// Function to load problem data and user's submissions for this specific problem
4253
const loadProblem = async () => {
4354
let response = await fetch(
4455
`${config.API_URL}/problems?division=gold&id=${pid}&columns=description,project_id,design_document`,
@@ -69,12 +80,16 @@ const Problem = (): React.JSX.Element => {
6980
}
7081
}
7182

83+
// If competition hasn't started and user isn't in the right division or role, block access
7284
if (!settings || new Date() < settings.start_date)
7385
if (user?.division != 'gold' && user?.role != 'admin') return <Unauthorized />
7486

87+
// Show loading spinner while data is being fetched
7588
if (isLoading) return <PageLoading />
89+
// If problem data doesn't exist, show not found page
7690
if (!problem) return <NotFound />
7791

92+
// Main component rendering
7893
return (
7994
<>
8095
<Countdown />
@@ -108,7 +123,7 @@ const Problem = (): React.JSX.Element => {
108123
{settings && new Date() < settings?.end_date ? (
109124
<>
110125
<Button
111-
disabled={submissions?.filter(({ status, released }) => status == 'pending' || !released).length !== 0}
126+
disabled={submissions?.filter(({ status, released }) => status == 'pending' || status === 'accepted' || !released).length !== 0}
112127
as={Link}
113128
to={`/gold/problems/${problem?.id}/submit`}
114129
content="Submit"

0 commit comments

Comments
 (0)