fediglam/src/api/services/follows.zig

143 lines
3.7 KiB
Zig
Raw Normal View History

2022-11-14 07:56:58 +00:00
const std = @import("std");
const util = @import("util");
const sql = @import("sql");
const common = @import("./common.zig");
const Uuid = util.Uuid;
const DateTime = util.DateTime;
pub const Follow = struct {
id: Uuid,
2022-11-15 04:25:59 +00:00
followed_by_id: Uuid,
followee_id: Uuid,
2022-11-14 07:56:58 +00:00
created_at: DateTime,
};
2022-11-15 04:25:59 +00:00
pub fn create(db: anytype, followed_by_id: Uuid, followee_id: Uuid, alloc: std.mem.Allocator) !void {
if (Uuid.eql(followed_by_id, followee_id)) return error.SelfFollow;
2022-11-14 07:56:58 +00:00
const now = DateTime.now();
2022-11-14 09:03:11 +00:00
const id = Uuid.randV4(util.getThreadPrng());
2022-11-14 07:56:58 +00:00
db.insert("follow", .{
.id = id,
2022-11-15 04:25:59 +00:00
.followed_by_id = followed_by_id,
.followee_id = followee_id,
2022-11-14 07:56:58 +00:00
.created_at = now,
}, alloc) catch |err| return switch (err) {
error.ForeignKeyViolation => error.NotFound,
error.UniqueViolation => error.NotUnique,
else => error.DatabaseFailure,
};
}
const max_max_items = 100;
pub const QueryArgs = struct {
pub const Direction = common.Direction;
pub const PageDirection = common.PageDirection;
pub const Prev = std.meta.Child(std.meta.fieldInfo(@This(), .prev).field_type);
pub const OrderBy = enum {
created_at,
};
max_items: usize = 20,
2022-11-15 04:25:59 +00:00
followed_by_id: ?Uuid = null,
followee_id: ?Uuid = null,
2022-11-14 07:56:58 +00:00
order_by: OrderBy = .created_at,
direction: Direction = .descending,
prev: ?struct {
id: Uuid,
order_val: union(OrderBy) {
created_at: DateTime,
},
} = null,
2022-11-14 08:14:29 +00:00
page_direction: PageDirection = .forward,
2022-11-14 07:56:58 +00:00
};
pub const QueryResult = struct {
items: []Follow,
prev_page: QueryArgs,
next_page: QueryArgs,
};
pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResult {
var builder = sql.QueryBuilder.init(alloc);
defer builder.deinit();
try builder.appendSlice(
2022-11-15 04:25:59 +00:00
\\SELECT follow.id, follow.followed_by_id, follow.followee_id, follow.created_at
2022-11-14 07:56:58 +00:00
\\FROM follow
\\
);
2022-11-15 04:25:59 +00:00
if (args.followed_by_id != null) try builder.andWhere("follow.followed_by_id = $1");
if (args.followee_id != null) try builder.andWhere("follow.followee_id = $2");
2022-11-14 07:56:58 +00:00
if (args.prev != null) {
try builder.andWhere("(follow.id, follow.created_at)");
switch (args.page_direction) {
.forward => try builder.appendSlice(" < "),
.backward => try builder.appendSlice(" > "),
}
try builder.appendSlice("($3, $4)");
}
try builder.appendSlice(
\\
\\ORDER BY follow.created_at DESC
2022-11-14 09:03:11 +00:00
\\LIMIT $5
2022-11-14 07:56:58 +00:00
\\
);
const max_items = if (args.max_items > max_max_items) max_max_items else args.max_items;
const query_args = .{
2022-11-15 04:25:59 +00:00
args.followed_by_id,
args.followee_id,
2022-11-14 07:56:58 +00:00
if (args.prev) |p| p.id else null,
2022-11-14 09:03:11 +00:00
if (args.prev) |p| p.order_val else null,
2022-11-14 07:56:58 +00:00
max_items,
};
2022-11-14 09:03:11 +00:00
const results = try db.queryRowsWithOptions(
2022-11-14 07:56:58 +00:00
Follow,
try builder.terminate(),
query_args,
max_items,
.{ .allocator = alloc, .ignore_unused_arguments = true },
);
errdefer util.deepFree(alloc, results);
var next_page = args;
var prev_page = args;
prev_page.page_direction = .backward;
next_page.page_direction = .forward;
if (results.len != 0) {
prev_page.prev = .{
.id = results[0].id,
2022-11-14 09:03:11 +00:00
.order_val = .{ .created_at = results[0].created_at },
2022-11-14 07:56:58 +00:00
};
next_page.prev = .{
.id = results[results.len - 1].id,
2022-11-14 09:03:11 +00:00
.order_val = .{ .created_at = results[results.len - 1].created_at },
2022-11-14 07:56:58 +00:00
};
}
// TODO: this will give incorrect links on an empty page
return QueryResult{
.items = results,
.next_page = next_page,
.prev_page = prev_page,
};
}