From 4e85c0225b3892d83aa04a3864ff947dca536cd3 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sat, 19 Nov 2022 03:13:05 -0800 Subject: [PATCH 1/2] Sample Cluster admin page --- src/api/lib.zig | 23 ++++++++++++++++++ src/main/controllers/web.zig | 16 +++++++++++++ .../web/cluster/overview.tmpl.html | 24 +++++++++++++++++++ src/template/lib.zig | 4 +++- 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/main/controllers/web/cluster/overview.tmpl.html diff --git a/src/api/lib.zig b/src/api/lib.zig index 4b2a434..5db4791 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -14,6 +14,12 @@ const services = struct { const follows = @import("./services/follows.zig"); }; +pub const ClusterMeta = struct { + community_count: usize, + user_count: usize, + note_count: usize, +}; + pub const RegistrationOptions = struct { invite_code: ?[]const u8 = null, email: ?[]const u8 = null, @@ -485,5 +491,22 @@ fn ApiConn(comptime DbConn: type) type { 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); } + + pub fn getClusterMeta(self: *Self) !ClusterMeta { + return try self.db.queryRow( + ClusterMeta, + \\SELECT + \\ COUNT(DISTINCT note.id) AS note_count, + \\ COUNT(DISTINCT actor.id) AS user_count, + \\ COUNT(DISTINCT community.id) AS community_count + \\FROM note, actor, community + \\WHERE + \\ actor.community_id = community.id AND + \\ community.kind != 'admin' + , + .{}, + self.arena.allocator(), + ); + } }; } diff --git a/src/main/controllers/web.zig b/src/main/controllers/web.zig index 183c3ce..9ee8ac4 100644 --- a/src/main/controllers/web.zig +++ b/src/main/controllers/web.zig @@ -5,6 +5,7 @@ pub const routes = .{ about, login, global_timeline, + cluster.overview, }; const index = struct { @@ -70,3 +71,18 @@ const global_timeline = struct { }); } }; + +const cluster = struct { + const overview = struct { + pub const path = "/cluster/overview"; + pub const method = .GET; + + pub fn handler(_: anytype, res: anytype, srv: anytype) !void { + const meta = try srv.getClusterMeta(); + try res.template(.ok, @embedFile("./web/cluster/overview.tmpl.html"), .{ + .community = srv.community, + .meta = meta, + }); + } + }; +}; diff --git a/src/main/controllers/web/cluster/overview.tmpl.html b/src/main/controllers/web/cluster/overview.tmpl.html new file mode 100644 index 0000000..3b55caf --- /dev/null +++ b/src/main/controllers/web/cluster/overview.tmpl.html @@ -0,0 +1,24 @@ + + + + + { .community.name } + + +
+

{ .community.name }

+
+ +
+

Cluster Overview

+
+ Home to { .meta.community_count } communities + hosting { .meta.user_count } users. +
+
+ + diff --git a/src/template/lib.zig b/src/template/lib.zig index aef9e1e..8c6c8e9 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -59,7 +59,9 @@ fn executeStatement(writer: anytype, comptime stmt: Statement, args: anytype, ca } fn print(writer: anytype, arg: anytype) !void { - if (comptime std.meta.trait.isZigString(@TypeOf(arg))) return writer.writeAll(arg); + const T = @TypeOf(arg); + if (comptime std.meta.trait.isZigString(T)) return writer.writeAll(arg); + if (comptime std.meta.trait.isNumber(T)) return std.fmt.format(writer, "{}", .{arg}); @compileLog(@TypeOf(arg)); @compileError("TODO"); From 252c12403a6c9aea0c5c340d53233addd44dced5 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sat, 19 Nov 2022 03:33:35 -0800 Subject: [PATCH 2/2] Add unfollow endpoint --- src/api/lib.zig | 5 +++++ src/api/services/follows.zig | 11 +++++++++++ src/main/controllers.zig | 1 + src/main/controllers/api/users/follows.zig | 15 +++++++++++++++ 4 files changed, 32 insertions(+) diff --git a/src/api/lib.zig b/src/api/lib.zig index 5db4791..21f9249 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -492,6 +492,11 @@ fn ApiConn(comptime DbConn: type) type { defer util.deepFree(self.arena.allocator(), result); } + pub fn unfollow(self: *Self, followee: Uuid) !void { + const result = try services.follows.delete(self.db, self.user_id orelse return error.NoToken, followee, self.arena.allocator()); + defer util.deepFree(self.arena.allocator(), result); + } + pub fn getClusterMeta(self: *Self) !ClusterMeta { return try self.db.queryRow( ClusterMeta, diff --git a/src/api/services/follows.zig b/src/api/services/follows.zig index dc4f5c7..7da65b7 100644 --- a/src/api/services/follows.zig +++ b/src/api/services/follows.zig @@ -33,6 +33,17 @@ pub fn create(db: anytype, followed_by_id: Uuid, followee_id: Uuid, alloc: std.m }; } +pub fn delete(db: anytype, followed_by_id: Uuid, followee_id: Uuid, alloc: std.mem.Allocator) !void { + // TODO: Measure count and report success + db.exec( + \\DELETE FROM follow + \\WHERE followed_by_id = $1 AND followee_id = $2 + , + .{ followed_by_id, followee_id }, + alloc, + ) catch return error.DatabaseFailure; +} + const max_max_items = 100; pub const QueryArgs = struct { diff --git a/src/main/controllers.zig b/src/main/controllers.zig index 38c81ae..e88ed3b 100644 --- a/src/main/controllers.zig +++ b/src/main/controllers.zig @@ -50,6 +50,7 @@ const routes = .{ timelines.local, timelines.home, follows.create, + follows.delete, follows.query_followers, follows.query_following, } ++ web.routes; diff --git a/src/main/controllers/api/users/follows.zig b/src/main/controllers/api/users/follows.zig index 8665718..6e3e6e7 100644 --- a/src/main/controllers/api/users/follows.zig +++ b/src/main/controllers/api/users/follows.zig @@ -19,6 +19,21 @@ pub const create = struct { } }; +pub const delete = struct { + pub const method = .DELETE; + pub const path = "/api/v0/users/:id/follow"; + + pub const Args = struct { + id: Uuid, + }; + + pub fn handler(req: anytype, res: anytype, srv: anytype) !void { + try srv.unfollow(req.args.id); + + try res.json(.ok, .{}); + } +}; + pub const query_followers = struct { pub const method = .GET; pub const path = "/api/v0/users/:id/followers";