Basic reacts
This commit is contained in:
parent
07019cb090
commit
718da7b408
5 changed files with 120 additions and 21 deletions
|
@ -50,8 +50,12 @@ fn reify(comptime T: type, id: Uuid, val: CreateInfo(T)) T {
|
|||
return result;
|
||||
}
|
||||
|
||||
pub const UserContext = struct {
|
||||
user: models.User,
|
||||
pub const ApiContext = struct {
|
||||
user_context: struct {
|
||||
user: models.User,
|
||||
},
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
};
|
||||
|
||||
pub const NoteCreate = struct {
|
||||
|
@ -69,26 +73,30 @@ pub const ApiServer = struct {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn makeUserContext(self: *ApiServer, token: []const u8, alloc: std.mem.Allocator) !UserContext {
|
||||
pub fn makeApiContext(self: *ApiServer, token: []const u8, alloc: std.mem.Allocator) !ApiContext {
|
||||
if (token.len == 0) return error.InvalidToken;
|
||||
|
||||
const user_handle = token;
|
||||
|
||||
const user = (try self.db.getBy(models.User, .handle, user_handle, alloc)) orelse return error.InvalidToken;
|
||||
|
||||
return UserContext{
|
||||
.user = user,
|
||||
return ApiContext{
|
||||
.user_context = .{
|
||||
.user = user,
|
||||
},
|
||||
|
||||
.alloc = alloc,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createNoteUser(self: *ApiServer, info: NoteCreate, ctx: UserContext) !models.Note {
|
||||
pub fn createNoteUser(self: *ApiServer, info: NoteCreate, ctx: ApiContext) !models.Note {
|
||||
const id = Uuid.randV4(self.prng.random());
|
||||
// TODO: check for dupes
|
||||
std.debug.print("user {s} making a note\n", .{ctx.user.handle});
|
||||
std.debug.print("user {s} making a note\n", .{ctx.user_context.user.handle});
|
||||
|
||||
const note = models.Note{
|
||||
.id = id,
|
||||
.author_id = ctx.user.id,
|
||||
.author_id = ctx.user_context.user.id,
|
||||
.content = info.content,
|
||||
|
||||
.created_at = DateTime.now(),
|
||||
|
@ -118,4 +126,12 @@ pub const ApiServer = struct {
|
|||
pub fn getUser(self: *ApiServer, id: Uuid, alloc: std.mem.Allocator) !?models.User {
|
||||
return self.db.getBy(models.User, .id, id, alloc);
|
||||
}
|
||||
|
||||
pub fn react(self: *ApiServer, note_id: Uuid, ctx: ApiContext) !void {
|
||||
try self.db.insert(models.Reaction, .{ .note_id = note_id, .reactor_id = ctx.user_context.user.id });
|
||||
}
|
||||
|
||||
pub fn listReacts(self: *ApiServer, note_id: Uuid, ctx: ApiContext) ![]models.Reaction {
|
||||
return try self.db.getWhereEq(models.Reaction, .note_id, note_id, ctx.alloc);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -48,12 +48,12 @@ const utils = struct {
|
|||
std.json.parseFree(@TypeOf(value), value, .{ .allocator = alloc });
|
||||
}
|
||||
|
||||
fn getUserContext(srv: *RequestServer, ctx: *http.server.Context) !api.UserContext {
|
||||
fn getApiContext(srv: *RequestServer, ctx: *http.server.Context) !api.ApiContext {
|
||||
const header = ctx.request.headers.get("authorization") orelse "(null)";
|
||||
|
||||
const token = header[("bearer ").len..];
|
||||
|
||||
return try srv.api.makeUserContext(token, srv.alloc);
|
||||
return try srv.api.makeApiContext(token, srv.alloc);
|
||||
// TODO: defer api.free(srv.alloc, user_ctx);
|
||||
}
|
||||
};
|
||||
|
@ -62,8 +62,8 @@ const RequestServer = root.RequestServer;
|
|||
const RouteArgs = http.RouteArgs;
|
||||
|
||||
pub fn createNote(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
const user_context = try utils.getUserContext(srv, ctx);
|
||||
// TODO: defer free usercontext
|
||||
const user_context = try utils.getApiContext(srv, ctx);
|
||||
// TODO: defer free ApiContext
|
||||
const info = try utils.parseRequestBody(api.NoteCreate, ctx, srv.alloc);
|
||||
defer utils.freeRequestBody(info, srv.alloc);
|
||||
|
||||
|
@ -102,11 +102,33 @@ pub fn getUser(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs)
|
|||
try utils.respondJson(ctx, .ok, user, srv.alloc);
|
||||
}
|
||||
|
||||
pub fn react(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void {
|
||||
const user_context = try utils.getApiContext(srv, ctx);
|
||||
// TODO: defer free ApiContext
|
||||
|
||||
const note_id = args.get("id") orelse return error.NotFound;
|
||||
const id = Uuid.parse(note_id) catch return utils.respondJson(ctx, .bad_request, .{ .@"error" = "Invalid UUID" }, srv.alloc);
|
||||
|
||||
try srv.api.react(id, user_context);
|
||||
try utils.respondJson(ctx, .created, .{}, srv.alloc);
|
||||
}
|
||||
|
||||
pub fn listReacts(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void {
|
||||
const user_context = try utils.getApiContext(srv, ctx);
|
||||
// TODO: defer free ApiContext
|
||||
|
||||
const note_id = args.get("id") orelse return error.NotFound;
|
||||
const id = Uuid.parse(note_id) catch return utils.respondJson(ctx, .bad_request, .{ .@"error" = "Invalid UUID" }, srv.alloc);
|
||||
|
||||
const reacts = try srv.api.listReacts(id, user_context);
|
||||
try utils.respondJson(ctx, .ok, .{ .items = reacts }, srv.alloc);
|
||||
}
|
||||
|
||||
pub fn authenticate(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
const user_ctx = try utils.getUserContext(srv, ctx);
|
||||
const user_ctx = try utils.getApiContext(srv, ctx);
|
||||
// TODO: defer api.free(srv.alloc, user_ctx);
|
||||
|
||||
try utils.respondJson(ctx, .ok, user_ctx, srv.alloc);
|
||||
try utils.respondJson(ctx, .ok, user_ctx.user_context, srv.alloc);
|
||||
}
|
||||
|
||||
pub fn healthcheck(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
|
|
|
@ -4,11 +4,13 @@ const models = @import("./models.zig");
|
|||
|
||||
const Uuid = @import("util").Uuid;
|
||||
const String = []const u8;
|
||||
const comptimePrint = std.fmt.comptimePrint;
|
||||
|
||||
fn tableName(comptime T: type) String {
|
||||
return switch (T) {
|
||||
models.Note => "note",
|
||||
models.User => "user",
|
||||
models.Reaction => "reaction",
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
@ -19,7 +21,7 @@ fn join(comptime vals: anytype, comptime joiner: String) String {
|
|||
|
||||
var result: String = "";
|
||||
for (vals) |v| {
|
||||
result = std.fmt.comptimePrint("{s}{s}{s}", .{ result, joiner, v });
|
||||
result = comptimePrint("{s}{s}{s}", .{ result, joiner, v });
|
||||
}
|
||||
|
||||
return result[joiner.len..];
|
||||
|
@ -30,14 +32,20 @@ const Query = struct {
|
|||
select: []const String,
|
||||
from: String,
|
||||
where: String = "id = ?",
|
||||
order_by: ?[]const String = null,
|
||||
group_by: ?[]const String = null,
|
||||
limit: ?usize = null,
|
||||
offset: ?usize = null,
|
||||
|
||||
pub fn str(comptime self: Query) String {
|
||||
comptime {
|
||||
const limit_expr = if (self.limit == null) "" else std.fmt.comptimePrint(" LIMIT {}", .{self.limit});
|
||||
return std.fmt.comptimePrint(
|
||||
"SELECT {s} FROM {s} WHERE {s}{s};",
|
||||
.{ join(self.select, ", "), self.from, self.where, limit_expr },
|
||||
const order_expr = if (self.order_by == null) "" else comptimePrint(" ORDER BY {s}", .{join(self.order_by.?, ", ")});
|
||||
const group_expr = if (self.group_by == null) "" else comptimePrint(" GROUP BY {s}", .{join(self.group_by.?, ", ")});
|
||||
const limit_expr = if (self.limit == null) "" else comptimePrint(" LIMIT {}", .{self.limit});
|
||||
const offset_expr = if (self.offset == null) "" else comptimePrint(" OFFSET {}", .{self.offset});
|
||||
return comptimePrint(
|
||||
"SELECT {s} FROM {s} WHERE {s}{s}{s}{s}{s};",
|
||||
.{ join(self.select, ", "), self.from, self.where, order_expr, group_expr, limit_expr, offset_expr },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -50,12 +58,12 @@ const Insert = struct {
|
|||
|
||||
pub fn str(comptime self: Insert) String {
|
||||
comptime {
|
||||
const row = std.fmt.comptimePrint(
|
||||
const row = comptimePrint(
|
||||
"({s})",
|
||||
.{join(.{"?"} ** self.columns.len, ", ")},
|
||||
);
|
||||
|
||||
return std.fmt.comptimePrint(
|
||||
return comptimePrint(
|
||||
"INSERT INTO {s} ({s}) VALUES {s};",
|
||||
.{ self.into, join(self.columns, ", "), join(.{row} ** self.count, ", ") },
|
||||
);
|
||||
|
@ -108,6 +116,17 @@ pub const Database = struct {
|
|||
\\
|
||||
\\ FOREIGN KEY (author_id) REFERENCES user(id)
|
||||
\\) STRICT;
|
||||
,
|
||||
\\CREATE TABLE IF NOT EXISTS
|
||||
\\reaction(
|
||||
\\ reactor_id TEXT NOT NULL,
|
||||
\\ note_id TEXT NOT NULL,
|
||||
\\
|
||||
\\ FOREIGN KEY(reactor_id) REFERENCES user(id),
|
||||
\\ FOREIGN KEY(note_id) REFERENCES note(id),
|
||||
\\
|
||||
\\ PRIMARY KEY(reactor_id, note_id)
|
||||
\\) STRICT;
|
||||
};
|
||||
|
||||
pub fn init() !Database {
|
||||
|
@ -160,6 +179,41 @@ pub const Database = struct {
|
|||
return result;
|
||||
}
|
||||
|
||||
pub fn getWhereEq(
|
||||
self: *Database,
|
||||
comptime T: type,
|
||||
comptime field: std.meta.FieldEnum(T),
|
||||
val: std.meta.fieldInfo(T, field).field_type,
|
||||
alloc: std.mem.Allocator,
|
||||
) ![]T {
|
||||
const field_name = std.meta.fieldInfo(T, field).name;
|
||||
const fields = comptime fieldsExcept(T, &.{field_name});
|
||||
const q = comptime (Query{
|
||||
.select = fields,
|
||||
.from = tableName(T),
|
||||
.where = field_name ++ " = ?",
|
||||
}).str();
|
||||
|
||||
var stmt = try self.db.prepare(q);
|
||||
defer stmt.finalize();
|
||||
|
||||
try stmt.bind(1, val);
|
||||
|
||||
var results = std.ArrayList(T).init(alloc);
|
||||
|
||||
while (try stmt.step()) |row| {
|
||||
var item: T = undefined;
|
||||
@field(item, field_name) = val;
|
||||
inline for (fields) |f, i| {
|
||||
@field(item, f) = row.getAlloc(@TypeOf(@field(item, f)), i, alloc) catch unreachable;
|
||||
}
|
||||
|
||||
try results.append(item);
|
||||
}
|
||||
|
||||
return results.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn countWhereEq(
|
||||
self: *Database,
|
||||
comptime T: type,
|
||||
|
|
|
@ -20,6 +20,8 @@ const router = Router{
|
|||
|
||||
Route.new(.GET, "/notes/:id", c.getNote),
|
||||
Route.new(.POST, "/notes", c.createNote),
|
||||
Route.new(.GET, "/notes/:id/reacts", c.listReacts),
|
||||
Route.new(.POST, "/notes/:id/reacts", c.react),
|
||||
Route.new(.GET, "/users/:id", c.getUser),
|
||||
Route.new(.POST, "/users", c.createUser),
|
||||
},
|
||||
|
|
|
@ -16,3 +16,8 @@ pub const User = struct {
|
|||
id: Uuid,
|
||||
handle: []const u8,
|
||||
};
|
||||
|
||||
pub const Reaction = struct {
|
||||
reactor_id: Uuid,
|
||||
note_id: Uuid,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue