Some refactoring
This commit is contained in:
parent
b12b9c766f
commit
0e7484543e
6 changed files with 157 additions and 7 deletions
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
,
|
,
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
53
src/main/query.zig
Normal 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;
|
||||||
|
}
|
Loading…
Reference in a new issue