-
Notifications
You must be signed in to change notification settings - Fork 1
UVa problems and submissions #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
root = true | ||
|
||
[*] | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = space | ||
indent_size = 2 | ||
charset = utf-8 | ||
trim_trailing_whitespace = true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
//================= | ||
//==== UVA API ==== | ||
//================= | ||
|
||
/** | ||
* This file includes all the logic behind the UVa/uHunt part of the API. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const request = require('request'); | ||
const sprintf = require('sprintf-js').sprintf; | ||
const _ = require('lodash'); | ||
|
||
const UVA_USERNAME_TO_ID = 'https://uhunt.onlinejudge.org/api/uname2uid/%s'; | ||
const UVA_USER_SUBMISSIONS = 'http://uhunt.onlinejudge.org/api/subs-user/%s'; | ||
const UVA_PROBLEMS = 'http://uhunt.onlinejudge.org/api/p'; | ||
|
||
/** | ||
* Make sure the user <user> has the correct user id in the database. | ||
*/ | ||
function update_user_id(db, uva_username, callback) { | ||
// uHunt query | ||
request(sprintf(UVA_USERNAME_TO_ID, uva_username), (err, res, body) => { | ||
if(err) throw err; | ||
|
||
// Check for user in the db | ||
db.query('SELECT * FROM uva_users WHERE uva_username = $1 LIMIT 1;', [uva_username], (err, result) => { | ||
if(err) throw err; | ||
var user = result.rows[0]; | ||
// If the user is not in db, add it | ||
if(!user) { | ||
// Don't add non existing users | ||
if(body == 0) | ||
return; | ||
|
||
db.query('INSERT INTO uva_users (uva_id, uva_username) VALUES ($1::integer, $2) RETURNING *;', [body, uva_username], (err, result) => { | ||
if(err) throw err; | ||
callback(db, result.rows); | ||
}); | ||
} | ||
// If the user changed name, remove the submissions and update the user in the db | ||
else if(body != user.uva_id) { | ||
db.query("BEGIN;"); | ||
db.query("DELETE FROM submissions WHERE platform = 'UVa' AND username = $1;", [user.uva_username], (err) => { | ||
if(err) throw err; | ||
}); | ||
// Update if the user still has a valid ID | ||
if(body != 0) | ||
db.query("UPDATE uva_users SET uva_id = $1::integer WHERE uva_username = $2;", [body, user.uva_username], (err) => { | ||
if(err) throw err; | ||
}); | ||
// Remove if the user doesn't exist anymore | ||
else | ||
db.query("DELETE FROM uva_users WHERE uva_username = $1;", [user.uva_username], (err) => { | ||
if(err) throw err; | ||
}); | ||
|
||
db.query("COMMIT;", (err) => { | ||
if(err) throw err; | ||
|
||
// Only fetch the submissions if the user actually exists | ||
if(body != 0) | ||
callback(db, body); | ||
}); | ||
} | ||
else | ||
callback(db, user); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Update the submissions for user <user> | ||
*/ | ||
function update_user_submissions(db, user) { | ||
request(sprintf(UVA_USER_SUBMISSIONS, user.uva_id), (err, res, body) => { | ||
if(err) throw err; | ||
|
||
/* | ||
* Possible improvement: only ask for new submissions (with id > than the last one in the db) | ||
* Drawbacks: more complicated and may cause missing data issues | ||
* | ||
* Also, doing all the updates in one query might actually be better. | ||
*/ | ||
|
||
var data = JSON.parse(body).subs; | ||
_.forEach(data, (sub) => { | ||
db.query( | ||
// SQL Query | ||
'INSERT INTO submissions VALUES\n' + | ||
'(\'UVa\', $1::integer, $2, $3, $4, $5, $6::integer, $7::bigint)\n' + | ||
'ON CONFLICT(platform, platform_submission_id) DO UPDATE SET\n' + | ||
'verdict = EXCLUDED.verdict,\n' + | ||
'runtime = EXCLUDED.runtime;\n', | ||
// Parameters | ||
[sub[0], sub[1], user.uva_username, get_verdict_text(sub[2]), get_language_text(sub[5]),sub[3], sub[4]], | ||
// Callback | ||
(err) => { | ||
if(err) throw err; | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Get the submissions for user with username <uva_username> | ||
*/ | ||
function get_user_submissions(db, uva_username) { | ||
return new Promise((resolve, reject) => { | ||
db.query('SELECT * FROM submissions WHERE platform = \'UVa\' AND username = $1;', [uva_username], (err, result) => { | ||
if(err) throw err; // Should this reject ? | ||
|
||
resolve(result.rows); | ||
update_user_id(db, uva_username, update_user_submissions); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Update the stored problems | ||
*/ | ||
function update_problems(db) { | ||
request(UVA_PROBLEMS, (err, res, body) => { | ||
if(err) throw err; | ||
|
||
var data = JSON.parse(body); | ||
_.forEach(data, (p) => { | ||
db.query('INSERT INTO problems VALUES (\'UVa\', $1, $2, $3);', [p[0], p[1], p[2]], (err) => { | ||
if(err) throw err; | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
module.exports = { | ||
get_user_submissions: get_user_submissions, | ||
update_problems: update_problems, | ||
}; | ||
|
||
//============================= | ||
//==== UTILITARY FUNCTIONS ==== | ||
//============================= | ||
var verdicts = { | ||
10: 'Submission error', | ||
15: 'Can\'t be judged', | ||
20: 'In judge queue', | ||
30: 'Compile error', | ||
35: 'Restricted function', | ||
40: 'Runtime error', | ||
45: 'Output limit exceeded', | ||
50: 'Time limit exceeded', | ||
60: 'Memory limit exceeded', | ||
70: 'Wrong answer', | ||
80: 'Presentation error', | ||
90: 'Accepted', | ||
}; | ||
|
||
function get_verdict_text(verdict) { | ||
return verdicts[verdict] || 'In judge queue'; | ||
} | ||
|
||
var languages = { | ||
1: 'C', | ||
2: 'Java', | ||
3: 'C++', | ||
4: 'Pascal', | ||
5: 'C++', // C++11 | ||
6: 'Python', // Python 3 | ||
}; | ||
|
||
function get_language_text(language) { | ||
return languages[language]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
var _ = require('lodash'); | ||
|
||
function all(db, callback) { | ||
db.query('SELECT * FROM problems;', (err, result) => { | ||
if(err) throw err; | ||
|
||
const res = { | ||
statusCode: 200, | ||
body: { | ||
problems: _.map(result.rows, p => [p.platform, p.platform_problem_id, p.platform_extra_data, p.title]), | ||
}, | ||
}; | ||
callback(null, res); | ||
}); | ||
} | ||
|
||
|
||
module.exports = { | ||
all, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
'use strict'; | ||
|
||
var _ = require('lodash'); | ||
var uva_submissions = require('./platforms/uva').get_user_submissions; | ||
|
||
function user(db, event, callback) { | ||
var ret = []; | ||
|
||
var user = {}; | ||
|
||
// Submission requests for all supported platforms | ||
var arr = []; | ||
|
||
// Allow custom id for better user identification | ||
if(event.id) { | ||
user.id = event.id; | ||
} | ||
// UVa | ||
if(event.uva) { | ||
user.uva = event.uva; | ||
arr.push(uva_submissions(db, event.uva)); | ||
} | ||
|
||
// Return them back to the client | ||
Promise.all(arr).then(subs => { | ||
const res = { | ||
statusCode: 200, | ||
body: { | ||
user: user, | ||
submissions: _.flatten(subs).map(s => [s.platform, s.platform_submission_id, s.platform_problem_id, s.username, s.verdict, s.language, s.runtime, s.time]), | ||
}, | ||
}; | ||
callback(null, res); | ||
}); | ||
} | ||
|
||
|
||
module.exports = { | ||
user, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
'use strict'; | ||
|
||
var dbm; | ||
var type; | ||
var seed; | ||
|
||
/** | ||
* We receive the dbmigrate dependency from dbmigrate initially. | ||
* This enables us to not have to rely on NODE_PATH. | ||
*/ | ||
exports.setup = function(options, seedLink) { | ||
dbm = options.dbmigrate; | ||
type = dbm.dataType; | ||
seed = seedLink; | ||
}; | ||
|
||
exports.up = function(db) { | ||
|
||
return db.createTable('uva_users', { | ||
uva_user_id: { type: 'serial', primaryKey: true }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Le premier attribut est un identifiant propre à nous ? Alors il faudrait plutôt utiliser comme clé la clé étrangère de There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pour moi, la table users contient les utilisateurs "privilégiés" pour lesquels on veut calculer les scores. Les différents services ont des besoins différents, et essayer de tout caser dans une seule table m'a l'air très foireux. Que doit-il se passer en cas de requête sans username Codeforces d'un utilisateur qui n'avait pas encore fait de requête, par exemple ? Il faut aller chercher les soumissions sur UVa et ensuite, il faut les stocker où ? Et on fait quoi des champs Codeforces ? Je ne sais pas si cet attribut a une grande utilité, je vais regarder si je dois le virer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cela dit, je n'ai pas encore viré les champs inutiles des autres tables. Et je n'ai pas encore rajouté les indexes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Je ne suis pas à 100% certains d'avoir compris ta question mais l'idée était de parser de temps en temps les updates UVa et Codeforces pour chaque utilisateur, d'insérer les avancements relevants pour nous (c'est-à-dire liés à une tâche définie dans There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Je pensais stocker les infos de tous les utilisateurs qui ont sollicité la plateforme, et d'utiliser ces infos pour les quelques utilisateurs privilégiés (à savoir ceux qui participent à beCP) afin de mettre à jour leurs scores à intervalles réguliers. Je me dis que la plateforme peut servir comme uniformisation des services uHunt, Codeforces, ... et qu'elle peut en plus nous permettre de calculer les scores relatifs aux différents contestants belges. |
||
uva_id: { type: 'int' }, | ||
uva_username: { type: 'string', length: 50, notNull: true, unique: true }, | ||
}).then( | ||
function(result) { | ||
db.runSql( | ||
'CREATE TABLE submissions (\n' + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pq ne pas avoir utilisé There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parce que je ne pense pas qu'il supporte une primary key à plusieurs colonnes, en tout cas je n'ai pas trouvé. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Si si, il faut juste le définir séparément la clé comme tu fais en SQL. Cfr http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html#label-primary_key There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pas sûr qu'ils y aient pensé pour db-migrate: https://github.com/db-migrate/node-db-migrate/blob/master/lib/interface/migratorInterface.js. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah oui, je ne sais pas pq je t'ai envoyé sur le migrateur de Sequel ruby ;-) |
||
'platform varchar(50),\n' + | ||
'platform_submission_id integer,\n' + | ||
'platform_problem_id varchar(50) NOT NULL,\n' + | ||
'username text NOT NULL,\n' + | ||
'verdict varchar(50) NOT NULL,\n' + | ||
'language varchar(50) NOT NULL,\n' + | ||
'runtime integer,\n' + | ||
'time bigint,\n' + | ||
'primary key (platform, platform_submission_id)\n' + | ||
');\n' | ||
); | ||
} | ||
).then( | ||
function(result) { | ||
db.runSql( | ||
'CREATE TABLE problems (\n' + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ce contenu était déjà prévu dans There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Je pensais que tasks contiendrait les exercices assignés par les coaches. Et il n'y a pas de titres en français ni en néerlandais. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmh oui le but est stocker tous les exercices qui ont de la valeur pour l'évaluation, donc pas tous les exercices mais bcp, triés par topic etc. Mais donc l'idée serait d'avoir des traductions fr/nl qui arrivent à un moment, mais pas nécessaire. |
||
'platform varchar(50),\n' + | ||
'platform_problem_id varchar(50),\n' + | ||
'platform_extra_data varchar,\n' + | ||
'title varchar NOT NULL,\n' + | ||
'primary key (platform, platform_problem_id)\n' + | ||
');\n' | ||
); | ||
} | ||
); | ||
}; | ||
|
||
exports.down = function(db) { | ||
return db.dropTable('uva_users'). | ||
then( function(result) { | ||
db.dropTable('submissions'); | ||
}).then( function(result) { | ||
db.dropTable('problems'); | ||
}); | ||
}; | ||
|
||
exports._meta = { | ||
"version": 1 | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Est-ce que ça nous sert de stocker les détails des soumissions UVa? L'idée de base était d'uniformiser toutes les soumissions dans un format à nous et ainsi d'éviter de stocker des "détails inutiles" et de devoir faire des tables pour tous les formats supportés. Tout de façon il faut bien les interpréter à un moment, pourquoi ne pas les interpréter dès que possible et jeter l'info inutile ? Je ne dis pas que j'ai pensé à tout, c'est plutôt une réflexion...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Je trouve le truc actuel plutôt minimaliste, surtout par rapport aux soumissions Codeforces (exemple: http://codeforces.com/api/user.status?handle=Fefer_Ivan&from=1&count=10). Les soumissions et les problèmes sont déjà uniformisés mais pour la table des utilisateurs UVa il n'y a pas le choix puisqu'il faut gérer la correspondance entre le username et l'id.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mais à un moment, tu devras bien "réduire" ça à qqchose de commun à notre plateforme. Stocker tout chez ne me semble pas nécessaire, mais tu veux essayer, vas-y.