185 lines
5.0 KiB
V
185 lines
5.0 KiB
V
module main
|
|
|
|
// import profanity
|
|
import vweb
|
|
import json
|
|
import time
|
|
import crypto.sha256
|
|
|
|
[get; head]
|
|
pub fn (mut app App) index() vweb.Result {
|
|
if app.config.redirect {
|
|
return app.redirect(app.config.redirect_url)
|
|
}
|
|
|
|
return app.not_found()
|
|
}
|
|
|
|
['/api/v2/score/list/:game'; get; head]
|
|
pub fn (mut app App) score_list(game string) vweb.Result {
|
|
app.add_cors_headers()
|
|
|
|
if !app.auth() {
|
|
return app.status(Status{401, 'Access token is wrong or missing'})
|
|
}
|
|
|
|
key := app.query['key'] or { default_score_key }
|
|
offset := (app.query['offset'] or { '0' }).int()
|
|
limit := (app.query['limit'] or { '-1' }).int()
|
|
|
|
scores := app.get_scores_limit(game, key, offset, limit) or {
|
|
app.logger.error('A database error occurred: ${err.str()}')
|
|
return app.status(Status{500, 'An error occurred while retrieving score list'})
|
|
}
|
|
|
|
return app.json(scores)
|
|
}
|
|
|
|
['/api/v2/score/submit/:game'; post]
|
|
pub fn (mut app App) score_submit(game string) vweb.Result {
|
|
app.add_cors_headers()
|
|
|
|
if !app.auth() {
|
|
return app.status(Status{401, 'Access token is wrong or missing'})
|
|
}
|
|
|
|
body := json.decode(Score, app.req.data) or {
|
|
app.logger.error('Bad JSON object was retrieved: ${err.str()}')
|
|
return app.status(Status{400, 'Bad JSON object'})
|
|
}
|
|
|
|
if (body.score != none && body.scores != none) || (body.score == none && body.scores == none) {
|
|
return app.status(Status{400, 'Either score field or scores field should be used'})
|
|
}
|
|
|
|
if !app.valid_key(game, body) {
|
|
app.logger.error('${body.player} tried to use invalid verification key for game ${game}')
|
|
return app.status(Status{401, 'Verification key is wrong or missing'})
|
|
}
|
|
|
|
if game !in app.config.games {
|
|
app.logger.error('${body.player} tried to submit score(s) for invalid game ${game}')
|
|
return app.status(Status{400, 'Unknown game'})
|
|
}
|
|
|
|
if body.player.trim_space().len == 0 {
|
|
app.logger.error('Somebody (${body.player}) tried to submit an empty name for game ${game}')
|
|
return app.status(Status{400, 'Name cannot be empty'})
|
|
}
|
|
|
|
// TODO: Bad word filter
|
|
|
|
mut scores := []ScoreTable{}
|
|
|
|
if score_num := body.score {
|
|
if score_num <= 0 {
|
|
app.logger.error('${body.player} tried to submit an illegal score for game ${game}')
|
|
return app.status(Status{400, 'Illegal score'})
|
|
}
|
|
scores << ScoreTable{
|
|
player: body.player
|
|
game: game
|
|
key: default_score_key
|
|
score: score_num
|
|
time: time.now().unix_time()
|
|
}
|
|
} else if scores_option := body.scores {
|
|
score_list := scores_option.clone()
|
|
for key, score in score_list {
|
|
valid_key := app.config.valid_keys[game] or { [key] }
|
|
if key !in valid_key {
|
|
app.logger.error('${body.player} tried to use an invalid key for game ${game}')
|
|
return app.status(Status{400, 'Unknown key'})
|
|
}
|
|
if score <= 0 {
|
|
app.logger.error('${body.player} tried to submit illegal score for game ${game}')
|
|
return app.status(Status{400, 'Illegal score for "${key}"'})
|
|
}
|
|
scores << ScoreTable{
|
|
player: body.player
|
|
game: game
|
|
key: key
|
|
score: score
|
|
time: time.now().unix_time()
|
|
}
|
|
}
|
|
} else {
|
|
app.logger.error('Bad JSON object was retrieved (Neither score and scores is an Option)')
|
|
return app.status(Status{400, 'Bad JSON object'})
|
|
}
|
|
|
|
score_res := app.insert_score_res(scores) or {
|
|
app.logger.error('A database error occurred: ${err.str()}')
|
|
return app.status(Status{500, 'An error occurred while inserting score'})
|
|
}
|
|
|
|
app.logger.info('New score(s) submitted by ${body.player} for game ${game}')
|
|
|
|
return app.json(score_res)
|
|
}
|
|
|
|
['/api/v2/score/list'; options]
|
|
pub fn (mut app App) handle_score_list_options() vweb.Result {
|
|
return app.handle_options()
|
|
}
|
|
|
|
['/api/v2/score/submit'; options]
|
|
pub fn (mut app App) handle_score_submit_options() vweb.Result {
|
|
return app.handle_options()
|
|
}
|
|
|
|
fn (mut app App) handle_options() vweb.Result {
|
|
app.set_status(204, '')
|
|
app.add_cors_headers()
|
|
return app.ok('')
|
|
}
|
|
|
|
fn (mut app App) add_cors_headers() {
|
|
origin := app.get_header('origin')
|
|
|
|
// Only return headers if actual cross-origin request
|
|
if origin.len == 0 {
|
|
return
|
|
}
|
|
|
|
origins := app.config.origins
|
|
|
|
default_origin := origins[0] or { '*' }
|
|
allowed_origin := if origins.any(it == origin) { origin } else { default_origin }
|
|
|
|
app.add_header('Access-Control-Allow-Origin', allowed_origin)
|
|
app.add_header('Access-Control-Allow-Methods', 'OPTIONS, HEAD, GET, POST')
|
|
app.add_header('Access-Control-Allow-Headers', 'Authorization, Content-Type')
|
|
app.add_header('Access-Control-Max-Age', '86400')
|
|
}
|
|
|
|
fn (mut app App) auth() bool {
|
|
auth_header := app.get_header('Authorization')
|
|
token := auth_header.after('Bearer ')
|
|
|
|
return token == app.config.token
|
|
}
|
|
|
|
fn (mut app App) valid_key(game string, score &Score) bool {
|
|
mut scorestr := ''
|
|
|
|
if score_num := score.score {
|
|
scorestr += score_num.str()
|
|
} else if scores_option := score.scores {
|
|
score_list := scores_option.clone()
|
|
for k, v in score_list {
|
|
scorestr += k + v.str()
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
valid_key := sha256.hexhash(game + score.player + scorestr + app.config.valkey)
|
|
return score.valkey == valid_key
|
|
}
|
|
|
|
fn (mut app App) status(status &Status) vweb.Result {
|
|
app.set_status(status.status, '')
|
|
return app.json(status)
|
|
}
|