const std = @import("std"); const util = @import("util"); const db = @import("./db.zig"); pub const models = @import("./models.zig"); pub const DateTime = util.DateTime; pub const Uuid = util.Uuid; // Frees an api struct and its fields allocated from alloc pub fn free(alloc: std.mem.Allocator, val: anytype) void { inline for (std.meta.fields(@TypeOf(val))) |f| { // TODO if (f.field_type == []u8 or f.field_type == []const u8) { alloc.free(@field(val, f.name)); } else if (f.field_type == Uuid or f.field_type == DateTime) { // nothing } else { @compileError("unsupported field type " ++ @typeName(f.field_type)); } } } pub fn CreateInfo(comptime T: type) type { const t_fields = std.meta.fields(T); var fields: [t_fields.len - 1]std.builtin.Type.StructField = undefined; var count = 0; inline for (t_fields) |f| { if (std.mem.eql(u8, f.name, "id")) continue; fields[count] = f; count += 1; } return @Type(.{ .Struct = .{ .layout = .Auto, .fields = &fields, .decls = &[0]std.builtin.Type.Declaration{}, .is_tuple = false, } }); } fn reify(comptime T: type, id: Uuid, val: CreateInfo(T)) T { var result: T = undefined; result.id = id; inline for (std.meta.fields(CreateInfo(T))) |f| { @field(result, f.name) = @field(val, f.name); } return result; } pub const ApiContext = struct { user_context: struct { user: models.User, }, alloc: std.mem.Allocator, }; pub const NoteCreate = struct { content: []const u8, }; pub const ApiServer = struct { prng: std.rand.DefaultPrng, db: db.Database, pub fn init(_: std.mem.Allocator) !ApiServer { return ApiServer{ .prng = std.rand.DefaultPrng.init(@bitCast(u64, std.time.milliTimestamp())), .db = try db.Database.init(), }; } 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 ApiContext{ .user_context = .{ .user = user, }, .alloc = alloc, }; } 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_context.user.handle}); const note = models.Note{ .id = id, .author_id = ctx.user_context.user.id, .content = info.content, .created_at = DateTime.now(), }; try self.db.insert(models.Note, note); return note; } pub fn createUser(self: *ApiServer, info: CreateInfo(models.User)) !models.User { const id = Uuid.randV4(self.prng.random()); if (try self.db.existsWhereEq(models.User, .handle, info.handle)) { return error.HandleNotAvailable; } const user = reify(models.User, id, info); try self.db.insert(models.User, user); return user; } pub fn getNote(self: *ApiServer, id: Uuid, alloc: std.mem.Allocator) !?models.Note { return self.db.getBy(models.Note, .id, id, alloc); } 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); } };