Some refactoring

This commit is contained in:
jaina heartles 2022-10-08 00:51:22 -07:00
parent b12b9c766f
commit 0e7484543e
6 changed files with 157 additions and 7 deletions

View file

@ -57,6 +57,8 @@ pub const NoteResponse = struct {
created_at: DateTime, created_at: DateTime,
}; };
pub const CommunityQueryArgs = services.communities.QueryArgs;
// Frees an api struct and its fields allocated from alloc // Frees an api struct and its fields allocated from alloc
pub fn free(alloc: std.mem.Allocator, val: anytype) void { pub fn free(alloc: std.mem.Allocator, val: anytype) void {
switch (@typeInfo(@TypeOf(val))) { switch (@typeInfo(@TypeOf(val))) {
@ -230,7 +232,7 @@ fn ApiConn(comptime DbConn: type) type {
}; };
} }
return error.Unauthorized; return error.TokenRequired;
} }
pub fn createCommunity(self: *Self, origin: []const u8) !services.communities.Community { pub fn createCommunity(self: *Self, origin: []const u8) !services.communities.Community {
@ -334,10 +336,12 @@ fn ApiConn(comptime DbConn: type) type {
} }
pub fn createNote(self: *Self, content: []const u8) !NoteResponse { pub fn createNote(self: *Self, content: []const u8) !NoteResponse {
// You cannot post on admin accounts
if (self.community.kind == .admin) return error.WrongCommunity; if (self.community.kind == .admin) return error.WrongCommunity;
const user_id = self.user_id orelse return error.TokenRequired;
const note_id = try services.notes.create(self.db, user_id, content); // Only authenticated users can post
const user_id = self.user_id orelse return error.TokenRequired;
const note_id = try services.notes.create(self.db, user_id, content, self.arena.allocator());
return self.getNote(note_id) catch |err| switch (err) { return self.getNote(note_id) catch |err| switch (err) {
error.NotFound => error.Unexpected, error.NotFound => error.Unexpected,

View file

@ -151,6 +151,8 @@ pub const QueryArgs = struct {
name, name,
host, host,
created_at, created_at,
pub const jsonStringify = util.jsonSerializeEnumAsString;
}; };
// Max items to fetch // Max items to fetch
@ -167,6 +169,8 @@ pub const QueryArgs = struct {
direction: enum { direction: enum {
ascending, ascending,
descending, descending,
pub const jsonStringify = util.jsonSerializeEnumAsString;
} = .ascending, } = .ascending,
// Page start parameter // Page start parameter
@ -190,6 +194,8 @@ pub const QueryArgs = struct {
page_direction: enum { page_direction: enum {
forward, forward,
backward, backward,
pub const jsonStringify = util.jsonSerializeEnumAsString;
} = .forward, } = .forward,
}; };

View file

@ -39,10 +39,15 @@ pub const GetError = error{
DatabaseFailure, DatabaseFailure,
NotFound, NotFound,
}; };
const selectStarFromNote = std.fmt.comptimePrint(
\\SELECT {s}
\\FROM note
\\
, .{util.comptimeJoin(",", std.meta.fieldNames(Note))});
pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) GetError!Note { pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) GetError!Note {
return db.queryRow( return db.queryRow(
Note, Note,
sql.selectStar(Note, "note") ++ selectStarFromNote ++
\\WHERE id = $1 \\WHERE id = $1
\\LIMIT 1 \\LIMIT 1
, ,

View file

@ -3,7 +3,8 @@ const root = @import("root");
const builtin = @import("builtin"); const builtin = @import("builtin");
const http = @import("http"); const http = @import("http");
const api = @import("./api.zig"); const api = @import("./api.zig");
const Uuid = @import("util").Uuid; const util = @import("util");
const Uuid = util.Uuid;
pub const auth = @import("./controllers/auth.zig"); pub const auth = @import("./controllers/auth.zig");
pub const communities = @import("./controllers/communities.zig"); pub const communities = @import("./controllers/communities.zig");
@ -57,6 +58,85 @@ pub const utils = struct {
return parsed; return parsed;
} }
pub fn parseQueryParams(comptime T: type, ctx: *http.server.Context) !T {
// TODO: clean up parsing
const path = ctx.request.path;
const start = (std.mem.indexOfScalar(u8, path, '?') orelse return error.NoQuery) + 1;
const rest = path[start..];
const query = std.mem.sliceTo(rest, '#');
const fake_url = util.Url{
.scheme = "",
.hostport = "",
.path = "",
.query = query,
.fragment = "",
};
var result: T = .{};
inline for (std.meta.fields(T)) |f| {
if (fake_url.getQuery(f.name)) |param| {
const F = if (comptime @typeInfo(f.field_type) == .Optional) std.meta.Child(f.field_type) else f.field_type;
std.log.debug("{}: {s}", .{ F, param });
@field(result, f.name) = switch (F) {
[]const u8 => param,
else => switch (@typeInfo(F)) {
.Struct => if (@hasDecl(F, "parse")) try F.parse(param) else @compileError("Invalid type " ++ @typeName(F)),
.Enum => std.meta.stringToEnum(F, param) orelse return error.ParseError,
.Int => try std.fmt.parseInt(F, param, 10),
else => {},
},
};
}
}
if (false) {
inline for (std.meta.fields(T)) |f| {
if (fake_url.getQuery(f.name)) |param| {
const F = if (comptime @typeInfo(f.field_type) == .Optional) std.meta.Child(f.field_type) else f.field_type;
switch (F) {
[]const u8 => @field(result, f.name) = param,
else => switch (@typeInfo(F)) {
.Struct,
.Opaque,
//.Union,
=> if (@hasDecl(F, "parse")) {
@compileLog(F);
if (true) @compileError(F);
@field(result, f.name) = try F.parse(param);
},
//.Int => @field(result, f.name) = try std.fmt.parseInt(F, param),
},
}
}
}
}
return result;
}
fn parseTypeFromQueryParams(comptime T: type, comptime name_prefix: []const u8, url: util.Url) !T {
var result: T = .{};
inline for (std.meta.fields(T)) |field| {
const FieldType = switch (@typeInfo(field.field_type)) {
.Optional => |info| info.child,
else => field.field_type,
};
_ = FieldType;
_ = result;
const qualified_name = name_prefix ++ field.name;
if (url.getQuery(qualified_name)) |param| {
_ = param;
}
}
}
pub fn freeRequestBody(value: anytype, alloc: std.mem.Allocator) void { pub fn freeRequestBody(value: anytype, alloc: std.mem.Allocator) void {
std.json.parseFree(@TypeOf(value), value, .{ .allocator = alloc }); std.json.parseFree(@TypeOf(value), value, .{ .allocator = alloc });
} }

View file

@ -26,8 +26,10 @@ const router = Router{
prepare(c.users.create), prepare(c.users.create),
//prepare(c.notes.create), prepare(c.notes.create),
//prepare(c.notes.get), prepare(c.notes.get),
//prepare(c.communities.query),
//Route.new(.GET, "/notes/:id/reacts", &c.notes.reacts.list), //Route.new(.GET, "/notes/:id/reacts", &c.notes.reacts.list),
//Route.new(.POST, "/notes/:id/reacts", &c.notes.reacts.create), //Route.new(.POST, "/notes/:id/reacts", &c.notes.reacts.create),

53
src/main/query.zig Normal file
View file

@ -0,0 +1,53 @@
const std = @import("std");
const ParamIter = struct {
remaining: []const u8,
target: []const u8,
fn next(self: *ParamIter) ?[]const u8 {
//
_ = self;
unreachable;
}
};
pub fn getParam(str: []const u8, param: []const u8) !?[]const u8 {
var iter = ParamIter{ .remaining = str, .target = param };
const result = iter.next() orelse return null;
if (iter.next() != null) return error.TooMany;
return result;
}
fn isScalarType(comptime T: type) bool {
return switch (T) {
[]const u8 => true,
else => switch (@typeInfo(T)) {
.Int, .Float, .Bool => true,
.Optional => |info| isScalarType(info.child),
.Enum => |info| if (info.is_exhaustive)
true
else
@compileError("Unsupported type " ++ @typeName(T)),
.Struct => false,
else => @compileError("Unsupported type " ++ @typeName(T)),
},
};
}
pub fn parseQueryArgs(comptime T: type, str: []const u8) !T {
var result = std.mem.zeroInit(T, .{});
_ = str;
for (std.meta.fields(T)) |field| {
const ParseType = switch (@typeInfo(field.field_type)) {
.Optional => |info| info.child,
else => field.field_type,
};
_ = ParseType;
}
return result;
}