Handle CORS, Additional error cases, Simplified database

This commit is contained in:
Manuel 2023-01-12 19:34:28 +01:00
parent 859ac51f1a
commit d38cb1f9db
Signed by: Manuel
GPG key ID: 4085037435E1F07A
5 changed files with 92 additions and 26 deletions

View file

@ -4,3 +4,6 @@ token = "asd"
redirect = false redirect = false
redirect_url = "" redirect_url = ""
db_path = "./db/app.db" db_path = "./db/app.db"
origins = [
"*"
]

View file

@ -1,9 +1,10 @@
module main module main
import toml import toml
import os
const ( const (
path = './config.toml' path = config_path()
) )
struct Config { struct Config {
@ -13,14 +14,20 @@ struct Config {
redirect bool redirect bool
redirect_url string redirect_url string
db_path string db_path string
origins []string
} }
fn (config Config) copy() Config { fn (config Config) copy() Config {
return config return config
} }
fn load_config() Config { fn load_config() !Config {
config := toml.parse_file(path) or { panic(err) } if !os.is_file(path) {
return error('Config file does not exist or is not a file')
}
config := toml.parse_file(path) or {
return error('Error while parsing config file:\n' + err.msg())
}
return Config{ return Config{
host: config.value('host').string() host: config.value('host').string()
@ -29,5 +36,14 @@ fn load_config() Config {
redirect: config.value('redirect').bool() redirect: config.value('redirect').bool()
redirect_url: config.value('redirect_url').string() redirect_url: config.value('redirect_url').string()
db_path: config.value('db_path').string() db_path: config.value('db_path').string()
origins: config.value('origins').array().as_strings()
} }
} }
fn config_path() string {
path_env := $env('CONFIG_PATH')
if path_env.trim_space().len != 0 {
return path_env
}
return './config.toml'
}

View file

@ -1,20 +1,18 @@
module main module main
[table: 'Score']
struct ScoreRes { struct ScoreRes {
pub mut: pub mut:
id int [primary; sql: serial] id i64 [primary; sql: serial]
player string player string [nonull]
score int score int [nonull]
time i64 time i64 [nonull]
} }
fn (mut app App) create_tables() int { fn (mut app App) create_tables() {
return app.db.exec_none('CREATE TABLE IF NOT EXISTS ScoreRes ( sql app.db {
id INTEGER PRIMARY KEY, create table ScoreRes
player TEXT NOT NULL, }
score INTEGER NOT NULL,
time SQLITE_INT64_TYPE NOT NULL
)')
} }
fn (mut app App) insert_score(score ScoreRes) ScoreRes { fn (mut app App) insert_score(score ScoreRes) ScoreRes {
@ -29,7 +27,7 @@ fn (mut app App) insert_score(score ScoreRes) ScoreRes {
fn (mut app App) get_scores() []ScoreRes { fn (mut app App) get_scores() []ScoreRes {
return sql app.db { return sql app.db {
select from ScoreRes order by score desc select from ScoreRes order by score desc limit 1000
} }
} }

View file

@ -18,7 +18,7 @@ fn main() {
mut app_config := Config{} mut app_config := Config{}
lock app.config { lock app.config {
app.config = load_config() app.config = load_config() or { panic('Could not load config file: ' + err.msg()) }
app_config = app.config.copy() app_config = app.config.copy()
} }
@ -33,12 +33,13 @@ fn main() {
panic(err) panic(err)
} }
sql_code := app.create_tables() app.create_tables()
if sqlite.is_error(sql_code) { // orm does not yet return Results
println('Could not create tables!') // if sqlite.is_error(sql_code) {
panic('Error code ' + sql_code.str()) // println('Could not create tables!')
} // panic('Error code ' + sql_code.str())
// }
mut host := '::' mut host := '::'
if app_config.host != '' { if app_config.host != '' {

View file

@ -9,21 +9,24 @@ struct Status {
message string message string
} }
[get; head]
pub fn (mut app App) index() vweb.Result { pub fn (mut app App) index() vweb.Result {
rlock app.config { rlock app.config {
if app.config.redirect { if app.config.redirect {
app.redirect(app.config.redirect_url) return app.redirect(app.config.redirect_url)
} }
} }
return app.text('Hello :)') return app.not_found()
} }
['/api/v1/score/list'] ['/api/v1/score/list'; get; head]
pub fn (mut app App) score_list() vweb.Result { pub fn (mut app App) score_list() vweb.Result {
app.add_cors_headers()
if !app.auth() { if !app.auth() {
app.set_status(401, '') app.set_status(401, '')
return app.json(Status{401, 'OAuth token is missing'}) return app.json(Status{401, 'Access token is wrong or missing'})
} }
scores := app.get_scores() scores := app.get_scores()
@ -33,9 +36,11 @@ pub fn (mut app App) score_list() vweb.Result {
['/api/v1/score/submit'; post] ['/api/v1/score/submit'; post]
pub fn (mut app App) score_submit() vweb.Result { pub fn (mut app App) score_submit() vweb.Result {
app.add_cors_headers()
if !app.auth() { if !app.auth() {
app.set_status(401, '') app.set_status(401, '')
return app.json(Status{401, 'OAuth token is missing'}) return app.json(Status{401, 'Access token is wrong or missing'})
} }
body := json.decode(Score, app.req.data) or { body := json.decode(Score, app.req.data) or {
@ -43,6 +48,18 @@ pub fn (mut app App) score_submit() vweb.Result {
return app.json(Status{400, 'Bad JSON object'}) return app.json(Status{400, 'Bad JSON object'})
} }
if body.player.trim_space() == '' {
app.set_status(400, '')
return app.json(Status{400, 'Name cannot be empty'})
}
if body.score <= 0 {
app.set_status(400, '')
return app.json(Status{400, 'Illegal score'})
}
// TODO: Bad word filter
score := app.insert_score(ScoreRes{ score := app.insert_score(ScoreRes{
player: body.player player: body.player
score: body.score score: body.score
@ -52,6 +69,37 @@ pub fn (mut app App) score_submit() vweb.Result {
return app.json(score) return app.json(score)
} }
pub fn (mut app App) add_cors_headers() {
origin := app.get_header('origin')
mut origins := []string{}
rlock app.config {
origins = app.config.origins.clone()
}
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')
}
pub fn (mut app App) handle_cors() vweb.Result {
app.set_status(204, '')
app.add_cors_headers()
return app.ok('')
}
['/api/v1/score/list'; options]
pub fn (mut app App) handle_score_list_cors() vweb.Result {
return app.handle_cors()
}
['/api/v1/score/submit'; options]
pub fn (mut app App) handle_score_submit_cors() vweb.Result {
return app.handle_cors()
}
fn (mut app App) auth() bool { fn (mut app App) auth() bool {
auth_header := app.get_header('Authorization') auth_header := app.get_header('Authorization')
token := auth_header.after('Bearer ') token := auth_header.after('Bearer ')