Player registration system and admin deletion commands.
This commit is contained in:
parent
a818d383ec
commit
33f7e16f86
5 changed files with 370 additions and 21 deletions
|
@ -1,9 +1,6 @@
|
||||||
{
|
{
|
||||||
"listen_port": 3000,
|
"listen_port": 3000,
|
||||||
"db_path": "scores/",
|
"db_path": "scores/",
|
||||||
"levels": [
|
"admin_pw_hash": "",
|
||||||
"hills",
|
"levels": []
|
||||||
"canopy",
|
|
||||||
"mountain"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
54
src/main.zig
54
src/main.zig
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
|
const argon2 = std.crypto.pwhash.argon2;
|
||||||
|
|
||||||
const clap = @import("clap");
|
const clap = @import("clap");
|
||||||
const zap = @import("zap");
|
const zap = @import("zap");
|
||||||
|
@ -7,6 +8,7 @@ const lmdb = @import("lmdb-zig");
|
||||||
|
|
||||||
const Scores = @import("scores.zig");
|
const Scores = @import("scores.zig");
|
||||||
const ScoreWeb = @import("scoreweb.zig");
|
const ScoreWeb = @import("scoreweb.zig");
|
||||||
|
const PlayerWeb = @import("userweb.zig");
|
||||||
|
|
||||||
/// fallback request handler
|
/// fallback request handler
|
||||||
fn onRequest(r: zap.Request) void {
|
fn onRequest(r: zap.Request) void {
|
||||||
|
@ -17,6 +19,7 @@ fn onRequest(r: zap.Request) void {
|
||||||
pub const Config = struct {
|
pub const Config = struct {
|
||||||
listen_port: usize,
|
listen_port: usize,
|
||||||
db_path: []const u8,
|
db_path: []const u8,
|
||||||
|
admin_pw_hash: []const u8,
|
||||||
levels: []const []const u8,
|
levels: []const []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,13 +32,50 @@ pub fn main() !void {
|
||||||
// scope everything so we can detect leaks.
|
// scope everything so we can detect leaks.
|
||||||
{
|
{
|
||||||
// load config file
|
// load config file
|
||||||
var file = try std.fs.cwd().openFile("config.json", .{});
|
var file = try std.fs.cwd().openFile(
|
||||||
|
"config.json",
|
||||||
|
std.fs.File.OpenFlags{
|
||||||
|
.mode = .read_write,
|
||||||
|
},
|
||||||
|
);
|
||||||
defer file.close();
|
defer file.close();
|
||||||
const json_str = try file.readToEndAlloc(allocator, 2048);
|
const json_str = try file.readToEndAlloc(allocator, 2048);
|
||||||
defer allocator.free(json_str);
|
defer allocator.free(json_str);
|
||||||
const cfg_v = try json.parseFromSlice(Config, allocator, json_str, .{ .ignore_unknown_fields = true });
|
const cfg_v = try json.parseFromSlice(Config, allocator, json_str, .{ .ignore_unknown_fields = true });
|
||||||
defer cfg_v.deinit();
|
defer cfg_v.deinit();
|
||||||
const config = cfg_v.value;
|
var config = cfg_v.value;
|
||||||
|
|
||||||
|
// generate and store new admin password if one is not stored in config.
|
||||||
|
var hash_buf: [256]u8 = undefined;
|
||||||
|
if (config.admin_pw_hash.len == 0) {
|
||||||
|
try std.io.getStdOut().writeAll("Input admin password: ");
|
||||||
|
const stdin = std.io.getStdIn().reader();
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
if (try stdin.readUntilDelimiterOrEof(buf[0..], '\n')) |input| {
|
||||||
|
// const salt = std.crypto.random.int(u128);
|
||||||
|
const hash = try argon2.strHash(
|
||||||
|
input,
|
||||||
|
.{
|
||||||
|
.allocator = allocator,
|
||||||
|
.mode = .argon2id,
|
||||||
|
.params = .{
|
||||||
|
.t = 3,
|
||||||
|
.m = 1 << 16,
|
||||||
|
.p = 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hash_buf[0..],
|
||||||
|
);
|
||||||
|
config.admin_pw_hash = hash;
|
||||||
|
|
||||||
|
try file.seekTo(0);
|
||||||
|
try json.stringify(
|
||||||
|
config,
|
||||||
|
.{ .whitespace = .indent_2 },
|
||||||
|
file.writer(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// set up HTTP listener
|
// set up HTTP listener
|
||||||
var listener = zap.Endpoint.Listener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
|
@ -49,11 +89,17 @@ pub fn main() !void {
|
||||||
);
|
);
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
var score_web = try ScoreWeb.init(allocator, "/api/scores", config);
|
var scores = try Scores.init(allocator, config.db_path, config.levels);
|
||||||
defer score_web.deinit();
|
defer scores.deinit();
|
||||||
|
|
||||||
|
var score_web = try ScoreWeb.init(allocator, "/api/scores", &scores, config.admin_pw_hash);
|
||||||
|
defer score_web.deinit();
|
||||||
try listener.register(score_web.endpoint());
|
try listener.register(score_web.endpoint());
|
||||||
|
|
||||||
|
var player_web = try PlayerWeb.init(allocator, "/api/players", &scores, config.admin_pw_hash);
|
||||||
|
defer player_web.deinit();
|
||||||
|
try listener.register(player_web.endpoint());
|
||||||
|
|
||||||
try listener.listen();
|
try listener.listen();
|
||||||
std.debug.print("Listening on 0.0.0.0:{d}\n", .{config.listen_port});
|
std.debug.print("Listening on 0.0.0.0:{d}\n", .{config.listen_port});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
const lmdb = @import("lmdb-zig");
|
const lmdb = @import("lmdb-zig");
|
||||||
const pwhash = std.crypto.pwhash;
|
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
env: lmdb.Env,
|
env: lmdb.Env,
|
||||||
|
@ -34,6 +33,12 @@ pub const Score = packed struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, db_path: []const u8, level_ids: []const []const u8) !Self {
|
pub fn init(allocator: std.mem.Allocator, db_path: []const u8, level_ids: []const []const u8) !Self {
|
||||||
|
std.fs.cwd().makeDir(db_path) catch |err| {
|
||||||
|
if (err != error.PathAlreadyExists) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const env = try lmdb.Env.init(db_path, .{
|
const env = try lmdb.Env.init(db_path, .{
|
||||||
.max_num_dbs = level_ids.len + 1,
|
.max_num_dbs = level_ids.len + 1,
|
||||||
});
|
});
|
||||||
|
@ -115,6 +120,33 @@ pub fn deleteScore(self: *Self, level: []const u8, player: []const u8) !void {
|
||||||
try tx.commit();
|
try tx.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clearLevel(self: *Self, level: []const u8) !void {
|
||||||
|
const tx = try self.env.begin(.{});
|
||||||
|
errdefer tx.deinit();
|
||||||
|
const db = self.levels.get(level) orelse {
|
||||||
|
return error.LevelNotfound;
|
||||||
|
};
|
||||||
|
try tx.drop(db, .empty);
|
||||||
|
try tx.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deletePlayer(self: *Self, player: []const u8) !void {
|
||||||
|
const tx = try self.env.begin(.{});
|
||||||
|
errdefer tx.deinit();
|
||||||
|
try tx.del(self.players, player, .key);
|
||||||
|
|
||||||
|
var lvl_iter = self.levels.valueIterator();
|
||||||
|
while (lvl_iter.next()) |lvl| {
|
||||||
|
tx.del(lvl.*, player, .key) catch |err| {
|
||||||
|
if (err != error.not_found) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try tx.commit();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn listLevels(self: *Self) !std.ArrayList([]const u8) {
|
pub fn listLevels(self: *Self) !std.ArrayList([]const u8) {
|
||||||
var levels = try std.ArrayList([]const u8).initCapacity(self.allocator, self.levels.count());
|
var levels = try std.ArrayList([]const u8).initCapacity(self.allocator, self.levels.count());
|
||||||
errdefer levels.deinit();
|
errdefer levels.deinit();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
|
const argon2 = std.crypto.pwhash.argon2;
|
||||||
|
|
||||||
const zap = @import("zap");
|
const zap = @import("zap");
|
||||||
|
|
||||||
|
@ -8,15 +9,18 @@ const Config = @import("main.zig").Config;
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
parent_path_parts: usize,
|
parent_path_parts: usize,
|
||||||
scores: Scores,
|
scores: *Scores,
|
||||||
ep: zap.Endpoint,
|
ep: zap.Endpoint,
|
||||||
|
admin_pw_hash: []const u8,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, path: []const u8, config: Config) !Self {
|
const MAX_PLAYER_NAME = 10;
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, path: []const u8, scores: *Scores, admin_pw_hash: []const u8) !Self {
|
||||||
return Self{
|
return Self{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.scores = try Scores.init(allocator, config.db_path, config.levels),
|
.scores = scores,
|
||||||
.parent_path_parts = blk: {
|
.parent_path_parts = blk: {
|
||||||
var parts: usize = 0;
|
var parts: usize = 0;
|
||||||
var split = std.mem.splitScalar(u8, path, '/');
|
var split = std.mem.splitScalar(u8, path, '/');
|
||||||
|
@ -31,11 +35,12 @@ pub fn init(allocator: std.mem.Allocator, path: []const u8, config: Config) !Sel
|
||||||
.post = postScore,
|
.post = postScore,
|
||||||
.delete = deleteScore,
|
.delete = deleteScore,
|
||||||
}),
|
}),
|
||||||
|
.admin_pw_hash = admin_pw_hash,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.scores.deinit();
|
_ = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn endpoint(self: *Self) *zap.Endpoint {
|
pub fn endpoint(self: *Self) *zap.Endpoint {
|
||||||
|
@ -71,6 +76,18 @@ fn badRequest(r: zap.Request, body: []const u8) void {
|
||||||
r.sendBody(body) catch return;
|
r.sendBody(body) catch return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn isAuthorized(allocator: std.mem.Allocator, r: zap.Request, hash: []const u8) bool {
|
||||||
|
if (r.getHeader("authorization")) |pw| {
|
||||||
|
argon2.strVerify(hash, pw, .{ .allocator = allocator }) catch {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fn getScores(e: *zap.Endpoint, r: zap.Request) void {
|
fn getScores(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
const self: *Self = @fieldParentPtr("ep", e);
|
const self: *Self = @fieldParentPtr("ep", e);
|
||||||
|
|
||||||
|
@ -108,6 +125,10 @@ fn postScore(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
const time = parts.items[3];
|
const time = parts.items[3];
|
||||||
const difficulty = parts.items[4];
|
const difficulty = parts.items[4];
|
||||||
|
|
||||||
|
if (player.len > MAX_PLAYER_NAME) {
|
||||||
|
return badRequest(r, "Player name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
const new_score = Scores.Score{
|
const new_score = Scores.Score{
|
||||||
.score = std.fmt.parseInt(u64, score, 10) catch {
|
.score = std.fmt.parseInt(u64, score, 10) catch {
|
||||||
return badRequest(r, "Invalid score value.");
|
return badRequest(r, "Invalid score value.");
|
||||||
|
@ -153,11 +174,10 @@ fn postScore(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (new_score.score == old_score.score) {
|
if (new_score.score == old_score.score) {
|
||||||
if (@intFromEnum(new_score.difficulty) < @intFromEnum(old_score.difficulty)) {
|
if (@intFromEnum(new_score.difficulty) <= @intFromEnum(old_score.difficulty)) {
|
||||||
return;
|
if (new_score.time >= old_score.time) {
|
||||||
}
|
return;
|
||||||
if (new_score.time >= old_score.time) {
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,8 +190,35 @@ fn postScore(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deleteScore(e: *zap.Endpoint, r: zap.Request) void {
|
fn deleteScore(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
_ = e;
|
const self: *Self = @fieldParentPtr("ep", e);
|
||||||
_ = r;
|
|
||||||
|
if (!isAuthorized(self.allocator, r, self.admin_pw_hash)) {
|
||||||
|
r.setStatus(.unauthorized);
|
||||||
|
return r.sendBody("Not authorized.") catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.path) |path| {
|
||||||
|
const parts = self.partsOfPath(path) catch {
|
||||||
|
return serverError(r, "Path processing error.");
|
||||||
|
};
|
||||||
|
defer parts.deinit();
|
||||||
|
|
||||||
|
switch (parts.items.len) {
|
||||||
|
1 => {
|
||||||
|
self.scores.clearLevel(parts.items[0]) catch {
|
||||||
|
return serverError(r, "Error clearing scores.");
|
||||||
|
};
|
||||||
|
r.sendBody("Level's scores cleared!") catch return;
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
self.scores.deleteScore(parts.items[0], parts.items[1]) catch {
|
||||||
|
return serverError(r, "Error deleting score.");
|
||||||
|
};
|
||||||
|
r.sendBody("Score deleted!") catch return;
|
||||||
|
},
|
||||||
|
else => return badRequest(r, "Invalid request."),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listLevels(self: *Self, r: zap.Request) void {
|
fn listLevels(self: *Self, r: zap.Request) void {
|
||||||
|
@ -207,6 +254,10 @@ fn listScores(self: *Self, r: zap.Request, level: []const u8) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getScore(self: *Self, r: zap.Request, level: []const u8, player: []const u8) void {
|
fn getScore(self: *Self, r: zap.Request, level: []const u8, player: []const u8) void {
|
||||||
|
if (player.len > MAX_PLAYER_NAME) {
|
||||||
|
return badRequest(r, "Player name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
const score = self.scores.getScore(level, player) catch |err| switch (err) {
|
const score = self.scores.getScore(level, player) catch |err| switch (err) {
|
||||||
error.LevelNotfound => return notFound(r, "Level not found."),
|
error.LevelNotfound => return notFound(r, "Level not found."),
|
||||||
error.NoScoreForPlayer => return notFound(r, "No score for player."),
|
error.NoScoreForPlayer => return notFound(r, "No score for player."),
|
||||||
|
|
223
src/userweb.zig
Normal file
223
src/userweb.zig
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const json = std.json;
|
||||||
|
const argon2 = std.crypto.pwhash.argon2;
|
||||||
|
|
||||||
|
const zap = @import("zap");
|
||||||
|
|
||||||
|
const Scores = @import("scores.zig");
|
||||||
|
const Config = @import("main.zig").Config;
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
parent_path_parts: usize,
|
||||||
|
scores: *Scores,
|
||||||
|
ep: zap.Endpoint,
|
||||||
|
admin_pw_hash: []const u8,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const MAX_PLAYER_NAME = 10;
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, path: []const u8, scores: *Scores, admin_pw_hash: []const u8) !Self {
|
||||||
|
return Self{
|
||||||
|
.allocator = allocator,
|
||||||
|
.scores = scores,
|
||||||
|
.parent_path_parts = blk: {
|
||||||
|
var parts: usize = 0;
|
||||||
|
var split = std.mem.splitScalar(u8, path, '/');
|
||||||
|
while (split.next()) |_| {
|
||||||
|
parts += 1;
|
||||||
|
}
|
||||||
|
break :blk parts;
|
||||||
|
},
|
||||||
|
.ep = zap.Endpoint.init(.{
|
||||||
|
.path = path,
|
||||||
|
.get = getPlayers,
|
||||||
|
.post = postPlayer,
|
||||||
|
.delete = deletePlayer,
|
||||||
|
}),
|
||||||
|
.admin_pw_hash = admin_pw_hash,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn endpoint(self: *Self) *zap.Endpoint {
|
||||||
|
return &self.ep;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn partsOfPath(self: *Self, path: []const u8) !std.ArrayList([]const u8) {
|
||||||
|
var parts = std.ArrayList([]const u8).init(self.allocator);
|
||||||
|
errdefer parts.deinit();
|
||||||
|
var split = std.mem.splitScalar(u8, path, '/');
|
||||||
|
var n: usize = 0;
|
||||||
|
while (split.next()) |part| {
|
||||||
|
if (n >= self.parent_path_parts and part.len > 0) {
|
||||||
|
try parts.append(part);
|
||||||
|
}
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serverError(r: zap.Request, body: []const u8) void {
|
||||||
|
r.setStatus(.internal_server_error);
|
||||||
|
r.sendBody(body) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notFound(r: zap.Request, body: []const u8) void {
|
||||||
|
r.setStatus(.not_found);
|
||||||
|
r.sendBody(body) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn badRequest(r: zap.Request, body: []const u8) void {
|
||||||
|
r.setStatus(.bad_request);
|
||||||
|
r.sendBody(body) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isAuthorized(allocator: std.mem.Allocator, r: zap.Request, hash: []const u8) bool {
|
||||||
|
if (r.getHeader("authorization")) |pw| {
|
||||||
|
argon2.strVerify(hash, pw, .{ .allocator = allocator }) catch {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getPlayers(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
|
const self: *Self = @fieldParentPtr("ep", e);
|
||||||
|
|
||||||
|
if (r.path) |path| {
|
||||||
|
const parts = self.partsOfPath(path) catch {
|
||||||
|
return serverError(r, "Path processing error.");
|
||||||
|
};
|
||||||
|
defer parts.deinit();
|
||||||
|
|
||||||
|
switch (parts.items.len) {
|
||||||
|
0 => return self.listPlayers(r) catch {
|
||||||
|
return serverError(r, "Error listing players.");
|
||||||
|
},
|
||||||
|
1 => return self.getPlayerId(r, parts.items[0]) catch {
|
||||||
|
return serverError(r, "Error geting player ID");
|
||||||
|
},
|
||||||
|
else => return badRequest(r, "Invalid request."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn postPlayer(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
|
const self: *Self = @fieldParentPtr("ep", e);
|
||||||
|
|
||||||
|
if (r.path) |path| {
|
||||||
|
const parts = self.partsOfPath(path) catch {
|
||||||
|
return serverError(r, "Path processing error.");
|
||||||
|
};
|
||||||
|
defer parts.deinit();
|
||||||
|
|
||||||
|
switch (parts.items.len) {
|
||||||
|
2 => return self.touchPlayer(r, parts.items[0], parts.items[1]) catch {
|
||||||
|
return serverError(r, "Error registering player.");
|
||||||
|
},
|
||||||
|
else => return badRequest(r, "Invalid request."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deletePlayer(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
|
const self: *Self = @fieldParentPtr("ep", e);
|
||||||
|
|
||||||
|
if (!isAuthorized(self.allocator, r, self.admin_pw_hash)) {
|
||||||
|
r.setStatus(.unauthorized);
|
||||||
|
return r.sendBody("Not authorized.") catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.path) |path| {
|
||||||
|
const parts = self.partsOfPath(path) catch {
|
||||||
|
return serverError(r, "Path processing error.");
|
||||||
|
};
|
||||||
|
defer parts.deinit();
|
||||||
|
|
||||||
|
if (parts.items.len != 1) {
|
||||||
|
return badRequest(r, "Invalid request.");
|
||||||
|
}
|
||||||
|
const player = parts.items[0];
|
||||||
|
if (player.len > MAX_PLAYER_NAME) {
|
||||||
|
return badRequest(r, "Player name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scores.deletePlayer(player) catch |err| {
|
||||||
|
if (err != error.not_found) {
|
||||||
|
return serverError(r, "Error deleting player.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
r.sendBody("Player deleted!") catch return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn listPlayers(self: *Self, r: zap.Request) !void {
|
||||||
|
var players = std.ArrayList([]const u8).init(self.allocator);
|
||||||
|
defer players.deinit();
|
||||||
|
|
||||||
|
const tx = try self.scores.env.begin(.{ .read_only = true });
|
||||||
|
errdefer tx.deinit();
|
||||||
|
const db = self.scores.players;
|
||||||
|
|
||||||
|
var cursor = try tx.cursor(db);
|
||||||
|
while (try cursor.next_key()) |entry| {
|
||||||
|
try players.append(entry.key);
|
||||||
|
}
|
||||||
|
try tx.commit();
|
||||||
|
|
||||||
|
const str = try json.stringifyAlloc(self.allocator, players.items, .{});
|
||||||
|
defer self.allocator.free(str);
|
||||||
|
|
||||||
|
r.sendJson(str) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getPlayerId(self: *Self, r: zap.Request, player: []const u8) !void {
|
||||||
|
if (player.len > MAX_PLAYER_NAME) {
|
||||||
|
return badRequest(r, "Player name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = try self.scores.env.begin(.{ .read_only = true });
|
||||||
|
errdefer tx.deinit();
|
||||||
|
const db = self.scores.players;
|
||||||
|
const id = std.mem.bytesToValue(
|
||||||
|
u32,
|
||||||
|
tx.get(db, player) catch |err| switch (err) {
|
||||||
|
error.not_found => {
|
||||||
|
try tx.commit();
|
||||||
|
return notFound(r, "Player not registered.");
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
try tx.commit();
|
||||||
|
var str = std.ArrayList(u8).init(self.allocator);
|
||||||
|
defer str.deinit();
|
||||||
|
try std.fmt.formatInt(id, 10, .lower, .{}, str.writer());
|
||||||
|
r.sendJson(str.items) catch return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn touchPlayer(self: *Self, r: zap.Request, player: []const u8, id_str: []const u8) !void {
|
||||||
|
if (player.len > MAX_PLAYER_NAME) {
|
||||||
|
return badRequest(r, "Player name too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = try self.scores.env.begin(.{});
|
||||||
|
errdefer tx.deinit();
|
||||||
|
const db = self.scores.players;
|
||||||
|
const id = std.fmt.parseInt(u32, id_str, 10) catch {
|
||||||
|
tx.deinit();
|
||||||
|
return badRequest(r, "Invalid player id.");
|
||||||
|
};
|
||||||
|
try tx.put(db, player, std.mem.asBytes(&id), .{});
|
||||||
|
try tx.commit();
|
||||||
|
r.setStatus(.accepted);
|
||||||
|
r.sendBody("Player registered!") catch return;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue