Following/follower endpoints

This commit is contained in:
jaina heartles 2022-11-14 01:03:11 -08:00
parent 568c6cd6b6
commit 763327292a
5 changed files with 89 additions and 14 deletions

View file

@ -91,7 +91,7 @@ pub const TimelineResult = struct {
next_page: TimelineArgs, next_page: TimelineArgs,
}; };
pub const FollowQueryArgs = struct { const FollowQueryArgs = struct {
pub const OrderBy = services.follows.QueryArgs.OrderBy; pub const OrderBy = services.follows.QueryArgs.OrderBy;
pub const Direction = services.follows.QueryArgs.Direction; pub const Direction = services.follows.QueryArgs.Direction;
pub const PageDirection = services.follows.QueryArgs.PageDirection; pub const PageDirection = services.follows.QueryArgs.PageDirection;
@ -118,13 +118,18 @@ pub const FollowQueryArgs = struct {
} }
}; };
pub const FollowQueryResult = struct { const FollowQueryResult = struct {
items: []services.follows.Follow, items: []services.follows.Follow,
prev_page: FollowQueryArgs, prev_page: FollowQueryArgs,
next_page: FollowQueryArgs, next_page: FollowQueryArgs,
}; };
pub const FollowerQueryArgs = FollowQueryArgs;
pub const FollowerQueryResult = FollowQueryResult;
pub const FollowingQueryArgs = FollowQueryArgs;
pub const FollowingQueryResult = FollowQueryResult;
pub fn isAdminSetup(db: sql.Db) !bool { pub fn isAdminSetup(db: sql.Db) !bool {
_ = services.communities.adminCommunityId(db) catch |err| switch (err) { _ = services.communities.adminCommunityId(db) catch |err| switch (err) {
error.NotFound => return false, error.NotFound => return false,
@ -441,26 +446,31 @@ fn ApiConn(comptime DbConn: type) type {
}; };
} }
pub fn getFollowers(self: *Self, user_id: Uuid, args: FollowQueryArgs) !FollowQueryResult { pub fn queryFollowers(self: *Self, user_id: Uuid, args: FollowerQueryArgs) !FollowerQueryResult {
var all_args = std.mem.zeroInit(services.follows.QueryArgs, args); var all_args = std.mem.zeroInit(services.follows.QueryArgs, args);
all_args.following_id = user_id; all_args.following_id = user_id;
const result = try services.follows.query(self.db, all_args, self.arena.allocator()); const result = try services.follows.query(self.db, all_args, self.arena.allocator());
return TimelineResult{ return FollowerQueryResult{
.items = result.items, .items = result.items,
.prev_page = FollowQueryArgs.from(result.prev_page), .prev_page = FollowQueryArgs.from(result.prev_page),
.next_page = FollowQueryArgs.from(result.next_page), .next_page = FollowQueryArgs.from(result.next_page),
}; };
} }
pub fn getFollowing(self: *Self, user_id: Uuid, args: FollowQueryArgs) !FollowQueryResult { pub fn queryFollowing(self: *Self, user_id: Uuid, args: FollowingQueryArgs) !FollowingQueryResult {
var all_args = std.mem.zeroInit(services.follows.QueryArgs, args); var all_args = std.mem.zeroInit(services.follows.QueryArgs, args);
all_args.follower_id = user_id; all_args.follower_id = user_id;
const result = try services.follows.query(self.db, all_args, self.arena.allocator()); const result = try services.follows.query(self.db, all_args, self.arena.allocator());
return TimelineResult{ return FollowingQueryResult{
.items = result.items, .items = result.items,
.prev_page = FollowQueryArgs.from(result.prev_page), .prev_page = FollowQueryArgs.from(result.prev_page),
.next_page = FollowQueryArgs.from(result.next_page), .next_page = FollowQueryArgs.from(result.next_page),
}; };
} }
pub fn follow(self: *Self, followee: Uuid) !void {
const result = try services.follows.create(self.db, self.user_id orelse return error.NoToken, followee, self.arena.allocator());
defer util.deepFree(self.arena.allocator(), result);
}
}; };
} }

View file

@ -19,7 +19,7 @@ pub const Follow = struct {
pub fn create(db: anytype, follower_id: Uuid, following_id: Uuid, alloc: std.mem.Allocator) !void { pub fn create(db: anytype, follower_id: Uuid, following_id: Uuid, alloc: std.mem.Allocator) !void {
if (Uuid.eql(follower_id, following_id)) return error.SelfFollow; if (Uuid.eql(follower_id, following_id)) return error.SelfFollow;
const now = DateTime.now(); const now = DateTime.now();
const id = Uuid.randv4(util.getThreadPrng()); const id = Uuid.randV4(util.getThreadPrng());
db.insert("follow", .{ db.insert("follow", .{
.id = id, .id = id,
@ -75,7 +75,7 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
defer builder.deinit(); defer builder.deinit();
try builder.appendSlice( try builder.appendSlice(
\\SELECT follow.follower_id, follow.followee_id, follow.created_at \\SELECT follow.id, follow.follower_id, follow.following_id, follow.created_at
\\FROM follow \\FROM follow
\\ \\
); );
@ -95,7 +95,7 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
try builder.appendSlice( try builder.appendSlice(
\\ \\
\\ORDER BY follow.created_at DESC \\ORDER BY follow.created_at DESC
\\LIMIT $6 \\LIMIT $5
\\ \\
); );
@ -104,11 +104,11 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
args.follower_id, args.follower_id,
args.following_id, args.following_id,
if (args.prev) |p| p.id else null, if (args.prev) |p| p.id else null,
if (args.prev) |p| p.created_at else null, if (args.prev) |p| p.order_val else null,
max_items, max_items,
}; };
const results = try db.QueryRowsWithOptions( const results = try db.queryRowsWithOptions(
Follow, Follow,
try builder.terminate(), try builder.terminate(),
query_args, query_args,
@ -124,12 +124,12 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
if (results.len != 0) { if (results.len != 0) {
prev_page.prev = .{ prev_page.prev = .{
.id = results[0].id, .id = results[0].id,
.created_at = results[0].created_at, .order_val = .{ .created_at = results[0].created_at },
}; };
next_page.prev = .{ next_page.prev = .{
.id = results[results.len - 1].id, .id = results[results.len - 1].id,
.created_at = results[results.len - 1].created_at, .order_val = .{ .created_at = results[results.len - 1].created_at },
}; };
} }
// TODO: this will give incorrect links on an empty page // TODO: this will give incorrect links on an empty page

View file

@ -11,6 +11,7 @@ pub const auth = @import("./controllers/auth.zig");
pub const communities = @import("./controllers/communities.zig"); pub const communities = @import("./controllers/communities.zig");
pub const invites = @import("./controllers/invites.zig"); pub const invites = @import("./controllers/invites.zig");
pub const users = @import("./controllers/users.zig"); pub const users = @import("./controllers/users.zig");
pub const follows = @import("./controllers/users/follows.zig");
pub const notes = @import("./controllers/notes.zig"); pub const notes = @import("./controllers/notes.zig");
pub const streaming = @import("./controllers/streaming.zig"); pub const streaming = @import("./controllers/streaming.zig");
pub const timelines = @import("./controllers/timelines.zig"); pub const timelines = @import("./controllers/timelines.zig");
@ -45,6 +46,9 @@ const routes = .{
streaming.streaming, streaming.streaming,
timelines.global, timelines.global,
timelines.local, timelines.local,
follows.create,
follows.query_followers,
follows.query_following,
}; };
pub fn Context(comptime Route: type) type { pub fn Context(comptime Route: type) type {
@ -90,6 +94,8 @@ pub fn Context(comptime Route: type) type {
} }
} }
if (path_iter.next() != null) return null;
return args; return args;
} }

View file

@ -0,0 +1,54 @@
const api = @import("api");
const util = @import("util");
const controller_utils = @import("../../controllers.zig").helpers;
const Uuid = util.Uuid;
pub const create = struct {
pub const method = .POST;
pub const path = "/users/:id/follow";
pub const Args = struct {
id: Uuid,
};
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
try srv.follow(req.args.id);
try res.json(.created, .{});
}
};
pub const query_followers = struct {
pub const method = .GET;
pub const path = "/users/:id/followers";
pub const Args = struct {
id: Uuid,
};
pub const Query = api.FollowingQueryArgs;
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
const results = try srv.queryFollowers(req.args.id, req.query);
try controller_utils.paginate(srv.community, path, results, res, req.allocator);
}
};
pub const query_following = struct {
pub const method = .GET;
pub const path = "/users/:id/following";
pub const Args = struct {
id: Uuid,
};
pub const Query = api.FollowerQueryArgs;
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
const results = try srv.queryFollowing(req.args.id, req.query);
try controller_utils.paginate(srv.community, path, results, res, req.allocator);
}
};

View file

@ -127,7 +127,11 @@ fn parse(comptime T: type, comptime prefix: []const u8, comptime name: []const u
if (try parse(F, prefix ++ "." ++ name, field.name, fields)) |v| { if (try parse(F, prefix ++ "." ++ name, field.name, fields)) |v| {
maybe_value = v; maybe_value = v;
} else if (field.default_value) |default| { } else if (field.default_value) |default| {
if (comptime @sizeOf(F) != 0) {
maybe_value = @ptrCast(*const F, @alignCast(@alignOf(F), default)).*; maybe_value = @ptrCast(*const F, @alignCast(@alignOf(F), default)).*;
} else {
maybe_value = std.mem.zeroes(F);
}
} }
if (maybe_value) |v| { if (maybe_value) |v| {
@ -139,6 +143,7 @@ fn parse(comptime T: type, comptime prefix: []const u8, comptime name: []const u
if (fields_specified == 0) { if (fields_specified == 0) {
return null; return null;
} else if (fields_specified != info.fields.len) { } else if (fields_specified != info.fields.len) {
std.log.debug("{} {s} {s}", .{ T, prefix, name });
return error.PartiallySpecifiedStruct; return error.PartiallySpecifiedStruct;
} else { } else {
return result; return result;