Add follow table
This commit is contained in:
parent
06aa3ad44e
commit
ebf8b34e15
3 changed files with 159 additions and 1 deletions
142
src/api/services/follows.zig
Normal file
142
src/api/services/follows.zig
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
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,
|
||||||
|
|
||||||
|
follower_id: Uuid,
|
||||||
|
following_id: Uuid,
|
||||||
|
|
||||||
|
created_at: DateTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
const now = DateTime.now();
|
||||||
|
const id = Uuid.randv4(util.getThreadPrng());
|
||||||
|
|
||||||
|
db.insert("follow", .{
|
||||||
|
.id = id,
|
||||||
|
.follower_id = follower_id,
|
||||||
|
.following_id = following_id,
|
||||||
|
.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,
|
||||||
|
|
||||||
|
follower_id: ?Uuid = null,
|
||||||
|
following_id: ?Uuid = null,
|
||||||
|
|
||||||
|
order_by: OrderBy = .created_at,
|
||||||
|
|
||||||
|
direction: Direction = .descending,
|
||||||
|
|
||||||
|
prev: ?struct {
|
||||||
|
id: Uuid,
|
||||||
|
order_val: union(OrderBy) {
|
||||||
|
created_at: DateTime,
|
||||||
|
},
|
||||||
|
} = null,
|
||||||
|
|
||||||
|
page_direction: PageDirection = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
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(
|
||||||
|
\\SELECT follow.follower_id, follow.followee_id, follow.created_at
|
||||||
|
\\FROM follow
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
if (args.follower_id != null) try builder.andWhere("follow.follower_id = $1");
|
||||||
|
if (args.following_id != null) try builder.andWhere("follow.following_id = $2");
|
||||||
|
|
||||||
|
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
|
||||||
|
\\LIMIT $6
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
const max_items = if (args.max_items > max_max_items) max_max_items else args.max_items;
|
||||||
|
const query_args = .{
|
||||||
|
args.follower_id,
|
||||||
|
args.following_id,
|
||||||
|
if (args.prev) |p| p.id else null,
|
||||||
|
if (args.prev) |p| p.created_at else null,
|
||||||
|
max_items,
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = try db.QueryRowsWithOptions(
|
||||||
|
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,
|
||||||
|
.created_at = results[0].created_at,
|
||||||
|
};
|
||||||
|
|
||||||
|
next_page.prev = .{
|
||||||
|
.id = results[results.len - 1].id,
|
||||||
|
.created_at = results[results.len - 1].created_at,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// TODO: this will give incorrect links on an empty page
|
||||||
|
|
||||||
|
return QueryResult{
|
||||||
|
.items = results,
|
||||||
|
.next_page = next_page,
|
||||||
|
.prev_page = prev_page,
|
||||||
|
};
|
||||||
|
}
|
|
@ -138,7 +138,7 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
|
||||||
max_items,
|
max_items,
|
||||||
.{ .allocator = alloc, .ignore_unused_arguments = true },
|
.{ .allocator = alloc, .ignore_unused_arguments = true },
|
||||||
);
|
);
|
||||||
errdefer util.deepFree(results);
|
errdefer util.deepFree(alloc, results);
|
||||||
|
|
||||||
var next_page = args;
|
var next_page = args;
|
||||||
var prev_page = args;
|
var prev_page = args;
|
||||||
|
|
|
@ -189,4 +189,20 @@ const migrations: []const Migration = &.{
|
||||||
\\DROP TABLE community;
|
\\DROP TABLE community;
|
||||||
,
|
,
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.name = "follows",
|
||||||
|
.up =
|
||||||
|
\\CREATE TABLE follow(
|
||||||
|
\\ id UUID NOT NULL PRIMARY KEY,
|
||||||
|
\\
|
||||||
|
\\ follower_id UUID NOT NULL,
|
||||||
|
\\ following_id UUID NOT NULL,
|
||||||
|
\\
|
||||||
|
\\ created_at TIMESTAMPTZ NOT NULL,
|
||||||
|
\\
|
||||||
|
\\ UNIQUE(follower_id, following_id)
|
||||||
|
\\);
|
||||||
|
,
|
||||||
|
.down = "DROP TABLE follow",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue