Skip to content

Commit 0cae3c0

Browse files
committed
submit endpoint and testing
Signed-off-by: Tathagat Thapliyal <[email protected]>
1 parent 438c508 commit 0cae3c0

File tree

14 files changed

+142
-185
lines changed

14 files changed

+142
-185
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"apidoc": "apidoc -i src -o docs",
4545
"prestart": "npm run build && npm run apidoc",
4646
"start": "scripts/wait-for-it.sh ${AMQP_HOST}:${AMQP_PORT} -- node dist/run.js",
47-
"test": "node_modules/.bin/mocha --timeout 12000 --exit --require ts-node/register test/utils/setup*.ts test/*.ts",
47+
"test": "NODE_PATH=src node_modules/.bin/mocha --timeout 12000 --exit --require ts-node/register test/utils/setup*.ts test/*.ts",
4848
"cover": "nyc npm test",
4949
"seedlangs": "node dist/scripts/seed-defaultlangs.js",
5050
"precodecov": "npm run cover",
@@ -71,4 +71,4 @@
7171
"npm": ">=5",
7272
"yarn": "1"
7373
}
74-
}
74+
}

src/models/ApiKeys.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Sequelize from "sequelize";
22

3-
export type ApiKeyAttrs = {
3+
export type ApiKeyAttrs = {
44
id: number,
55
key: string,
66
whitelist_domains: string[] | undefined
@@ -32,9 +32,4 @@ export const define = (
3232
})
3333
}
3434

35-
export const associate = ({ langs, submissions }) => {
36-
submissions.belongsTo(langs, {
37-
foreignKey: 'lang',
38-
otherKey: 'lang_slug'
39-
})
40-
};
35+
export const associate = () => { }

src/models/Submission.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ export type Testcase = {
88
}
99

1010
export type SubmissionAttributes = {
11-
id?: number
11+
id: number
1212
lang: string
1313
start_time: Date
14-
end_time?: Date
14+
end_time: Date
1515
mode: string
16-
results?: Array<Testcase>
17-
outputs?: Array<string>
18-
callback?: string
16+
results: Array<Testcase>
17+
outputs: Array<string>
18+
callback: string
1919
}
2020

2121
export type SubmissionInstance = Sequelize.Instance<SubmissionAttributes> & SubmissionAttributes
@@ -30,7 +30,8 @@ export const define = (
3030
primaryKey: true
3131
},
3232
lang: {
33-
type: Sequelize.STRING(10)
33+
type: Sequelize.STRING(10),
34+
allowNull: false
3435
},
3536
start_time: Sequelize.DATE,
3637
end_time: Sequelize.DATE,
@@ -42,14 +43,9 @@ export const define = (
4243
outputs: Sequelize.ARRAY(Sequelize.STRING),
4344
callback: Sequelize.STRING
4445
}, {
45-
paranoid: true, // We do not want to lose any submission data
46-
timestamps: false // Start and end times are already logged
47-
})
46+
paranoid: true, // We do not want to lose any submission data
47+
timestamps: false // Start and end times are already logged
48+
})
4849
}
4950

50-
export const associate = ({ langs, submissions }) => {
51-
submissions.belongsTo(langs, {
52-
foreignKey: 'lang',
53-
otherKey: 'lang_slug'
54-
})
55-
};
51+
export const associate = () => { }

src/rabbitmq/jobqueue.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import * as amqp from 'amqplib/callback_api'
2-
import {Channel, Connection} from 'amqplib/callback_api'
3-
import {EventEmitter} from 'events'
4-
import {RunResponse} from '../routes/api/run'
2+
import { Channel, Connection } from 'amqplib/callback_api'
3+
import { EventEmitter } from 'events'
54
const debug = require('debug')('judge:api:jobqueue')
65
import config = require('../../config')
76

87
export interface SubmissionJob {
98
id: number
109
source: string,
1110
lang: string,
12-
testcases: [{input: string, output: string}],
13-
getstdout: boolean
11+
timelimit: number,
12+
testcases: [{
13+
id: number,
14+
stdin: string,
15+
stodut: string
16+
}],
1417
}
1518

1619
export interface RunJob {
@@ -36,18 +39,18 @@ amqp.connect(`amqp://${config.AMQP.USER}:${config.AMQP.PASS}@${config.AMQP.HOST}
3639
(err, connection) => {
3740
if (err) throw err
3841

39-
connection.createChannel((err, channel) =>{
42+
connection.createChannel((err, channel) => {
4043
if (err) throw err
4144

42-
channel.assertQueue(jobQ, {durable: true})
43-
channel.assertQueue(successQ, {durable: true})
45+
channel.assertQueue(jobQ, { durable: true })
46+
channel.assertQueue(successQ, { durable: true })
4447
jobChannel = channel
4548
jobChannel.consume(successQ, (msg) => {
4649
debug(`SUCCESS:CONSUME: msg.content = ${msg.content.toString()}`)
4750

4851
const payload = JSON.parse(msg.content.toString())
4952
const eventName = payload.testcases ? 'submit_result' : 'run_result'
50-
53+
5154
successListener.emit(eventName, payload)
5255
jobChannel.ack(msg)
5356
})
@@ -60,7 +63,7 @@ amqp.connect(`amqp://${config.AMQP.USER}:${config.AMQP.PASS}@${config.AMQP.HOST}
6063
* @returns {boolean} true if job was put on queue successfully
6164
*/
6265
function queueJob(job: JudgeJob) {
63-
return jobChannel.sendToQueue(jobQ, Buffer.from(JSON.stringify(job)), {persistent: true})
66+
return jobChannel.sendToQueue(jobQ, Buffer.from(JSON.stringify(job)), { persistent: true })
6467
}
6568
export {
6669
queueJob,

src/routes/api/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {NextFunction, Request, Response, Router} from 'express'
1+
import { NextFunction, Request, Response, Router } from 'express'
22
import run from './run'
3-
import {route as submissions} from './submissions'
4-
import {route as langs} from './langs'
5-
import {checkValidApiKey} from '../../validators/ApiKeyValidators'
3+
import submit from './submit'
4+
import { route as langs } from './langs'
5+
import { checkValidApiKey } from '../../validators/ApiKeyValidators'
66
import * as debug from 'debug'
77

88
const log = debug('judge:api')
@@ -26,7 +26,7 @@ route.use((req: Request, res: Response, next: NextFunction) => {
2626
})
2727

2828
route.use('/runs', run)
29-
route.use('/submissions', submissions)
29+
route.use('/submissions', submit)
3030
route.use('/langs', langs)
3131

3232
export default route

src/routes/api/langs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Router} from 'express'
1+
import { Router } from 'express'
22
import DB from 'models'
33

44
const route: Router = Router()
@@ -13,10 +13,10 @@ const route: Router = Router()
1313
*
1414
*/
1515
route.get('/', (req, res, next) => {
16-
Langs.findAll()
16+
DB.langs.findAll()
1717
.then((langs) => {
1818
res.status(200).json(langs)
1919
})
2020
})
2121

22-
export {route}
22+
export { route }

src/routes/api/run/controller.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type RunPoolElement = {
1616
res: Response
1717
}
1818

19-
const RunPool: {[x: number]: RunPoolElement} = {}
19+
const RunPool: { [x: number]: RunPoolElement } = {}
2020

2121
class RunController {
2222
async RunPOST(req: Request, res: Response) {
@@ -50,14 +50,14 @@ class RunController {
5050
}
5151
}
5252

53-
async onSuccess(result: RunResponse) {
53+
async onSuccess(result: RunResponse) {
5454
const { url } = await upload(result)
5555

5656
const job = await DB.submissions.findById(result.id)
5757
job.outputs = [url]
5858
await job.save()
5959

60-
switch(job.mode) {
60+
switch (job.mode) {
6161
case 'callback':
6262
await axios.post(job.callback, result)
6363
break

src/routes/api/submissions.ts

Lines changed: 0 additions & 115 deletions
This file was deleted.

src/routes/api/submit/controller.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Request, Response } from 'express'
2+
import { SubmissionJob, queueJob } from 'rabbitmq/jobqueue'
3+
import DB from 'models'
4+
import axios from 'axios'
5+
6+
type Testcase = {
7+
id: number,
8+
score: number,
9+
time: string,
10+
result: string
11+
}
12+
13+
type SubmitResponse = {
14+
id: number,
15+
stderr: string,
16+
testcases: Array<Testcase>
17+
}
18+
19+
type RunPoolElement = {
20+
res: Response
21+
}
22+
23+
const RunPool: { [x: number]: RunPoolElement } = {}
24+
25+
class SubmitController {
26+
async SubmitPOST(req: Request, res: Response) {
27+
const mode = req.body.mode || 'sync'
28+
const job = await DB.submissions.create({
29+
lang: req.body.lang,
30+
start_time: new Date(),
31+
mode,
32+
callback: req.body.callback
33+
})
34+
35+
await queueJob(<SubmissionJob>{
36+
source: req.body.source,
37+
lang: req.body.lang,
38+
timelimit: req.body.timelimit,
39+
testcases: req.body.testcases
40+
})
41+
42+
if (['callback', 'poll'].includes(mode)) {
43+
return res.json({
44+
id: job.id
45+
})
46+
}
47+
48+
// if mode === 'sync'
49+
RunPool[job.id] = {
50+
res
51+
}
52+
}
53+
54+
async onSuccess(result: SubmitResponse) {
55+
const job = await DB.submissions.findById(result.id)
56+
job.results = result.testcases
57+
await job.save()
58+
59+
switch (job.mode) {
60+
case 'callback':
61+
await axios.post(job.callback, result)
62+
break
63+
case 'sync':
64+
RunPool[job.id].res.json(result)
65+
break
66+
}
67+
}
68+
}
69+
70+
export default new SubmitController()

0 commit comments

Comments
 (0)