From 6f30696d30ba07dd5dcf59265c7c97ff5059f4a8 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Sun, 1 Jan 2023 15:58:17 -0800 Subject: [PATCH] Create API Context --- src/api/lib.zig | 103 +++++++++++++++++++---------------- src/api/methods/auth.zig | 10 ++-- src/main/controllers.zig | 4 +- src/main/controllers/web.zig | 2 +- 4 files changed, 64 insertions(+), 55 deletions(-) diff --git a/src/api/lib.zig b/src/api/lib.zig index 80ebef3..774b1aa 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -287,8 +287,9 @@ pub const ApiSource = struct { return Conn{ .db = db, - .user_id = null, - .community = community, + .context = .{ + .community = community, + }, .allocator = alloc, }; } @@ -307,40 +308,48 @@ pub const ApiSource = struct { return Conn{ .db = db, - .token_info = token_info, - .user_id = token_info.user_id, - .community = community, + .context = .{ + .community = community, + .token_info = token_info, + }, .allocator = alloc, }; } }; +pub const ApiContext = struct { + token_info: ?Token.Info = null, + community: Community, + + pub fn userId(self: ApiContext) ?Uuid { + if (self.token_info) |t| return t.user_id else return null; + } +}; + fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return struct { const Self = @This(); db: DbConn, - token_info: ?Token.Info = null, - user_id: ?Uuid = null, - community: Community, + context: ApiContext, allocator: std.mem.Allocator, pub fn close(self: *Self) void { - util.deepFree(self.allocator, self.community); - if (self.token_info) |info| util.deepFree(self.allocator, info); + util.deepFree(self.allocator, self.context.community); + if (self.context.token_info) |info| util.deepFree(self.allocator, info); self.db.releaseConnection(); } fn isAdmin(self: *Self) bool { // TODO - return self.user_id != null and self.community.kind == .admin; + return self.context.userId() != null and self.context.community.kind == .admin; } pub fn login(self: *Self, username: []const u8, password: []const u8) !Token { return models.auth.login( self.db, username, - self.community.id, + self.context.community.id, password, self.allocator, ); @@ -355,7 +364,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { issued_at: DateTime, }; pub fn verifyAuthorization(self: *Self) !AuthorizationInfo { - if (self.token_info) |info| { + if (self.context.token_info) |info| { const user = try models.actors.get(self.db, info.user_id, self.allocator); defer util.deepFree(self.allocator, user); @@ -365,8 +374,8 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return AuthorizationInfo{ .id = user.id, .username = username, - .community_id = self.community.id, - .host = try util.deepClone(self.allocator, self.community.host), + .community_id = self.context.community.id, + .host = try util.deepClone(self.allocator, self.context.community.host), .issued_at = info.issued_at, }; @@ -405,14 +414,14 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { pub fn createInvite(self: *Self, options: InviteOptions) !InviteResponse { // Only logged in users can make invites - const user_id = self.user_id orelse return error.TokenRequired; + const user_id = self.context.userId() orelse return error.TokenRequired; const community_id = if (options.to_community) |id| blk: { // Only admins can send invites for other communities if (!self.isAdmin()) return error.PermissionDenied; break :blk id; - } else self.community.id; + } else self.context.community.id; // Users can only make user invites if (options.kind != .user and !self.isAdmin()) return error.PermissionDenied; @@ -438,7 +447,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } else try std.fmt.allocPrint( self.allocator, "{s}://{s}/invite/{s}", - .{ @tagName(self.community.scheme), self.community.host, invite.code }, + .{ @tagName(self.context.community.scheme), self.context.community.host, invite.code }, ); errdefer util.deepFree(self.allocator, url); @@ -468,25 +477,25 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { // pub fn register(self: *Self, username: []const u8, password: []const u8, opt: RegistrationOptions) !UserResponse { // const tx = try self.db.beginOrSavepoint(); // const maybe_invite = if (opt.invite_code) |code| - // try models.invites.getByCode(tx, code, self.community.id, self.allocator) + // try models.invites.getByCode(tx, code, self.context.community.id, self.allocator) // else // null; // defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv); // if (maybe_invite) |invite| { - // if (!Uuid.eql(invite.community_id, self.community.id)) return error.WrongCommunity; + // if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.WrongCommunity; // if (!isInviteValid(invite)) return error.InvalidInvite; // } // const invite_kind = if (maybe_invite) |inv| inv.kind else .user; - // if (self.community.kind == .admin) @panic("Unimplmented"); + // if (self.context.community.kind == .admin) @panic("Unimplmented"); // const user_id = try models.auth.register( // tx, // username, // password, - // self.community.id, + // self.context.community.id, // .{ // .invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null, // .email = opt.email, @@ -498,7 +507,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { // .user => {}, // .system => @panic("System user invites unimplemented"), // .community_owner => { - // try models.communities.transferOwnership(tx, self.community.id, user_id); + // try models.communities.transferOwnership(tx, self.context.community.id, user_id); // }, // } @@ -519,20 +528,20 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { try std.fmt.allocPrint( self.allocator, "{s}://{s}/media/{}", - .{ @tagName(self.community.scheme), self.community.host, fid }, + .{ @tagName(self.context.community.scheme), self.context.community.host, fid }, ) else try std.fmt.allocPrint( self.allocator, "{s}://{s}/{s}", - .{ @tagName(self.community.scheme), self.community.host, default_avatar }, + .{ @tagName(self.context.community.scheme), self.context.community.host, default_avatar }, ); errdefer self.allocator.free(avatar_url); const header_url = if (user.header_file_id) |fid| try std.fmt.allocPrint( self.allocator, "{s}://{s}/media/{}", - .{ @tagName(self.community.scheme), self.community.host, fid }, + .{ @tagName(self.context.community.scheme), self.context.community.host, fid }, ) else null; @@ -565,8 +574,8 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { const user = try self.getUserUnchecked(self.db, user_id); errdefer util.deepFree(self.allocator, user); - if (self.user_id == null) { - if (!Uuid.eql(self.community.id, user.community_id)) return error.NotFound; + if (self.context.userId() == null) { + if (!Uuid.eql(self.context.community.id, user.community_id)) return error.NotFound; } return user; @@ -574,10 +583,10 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { pub fn createNote(self: *Self, content: []const u8) !Note { // You cannot post on admin accounts - if (self.community.kind == .admin) return error.WrongCommunity; + if (self.context.community.kind == .admin) return error.WrongCommunity; // Only authenticated users can post - const user_id = self.user_id orelse return error.TokenRequired; + const user_id = self.context.userId() orelse return error.TokenRequired; const note_id = try models.notes.create(self.db, user_id, content, self.allocator); return self.getNote(note_id) catch |err| switch (err) { @@ -591,8 +600,8 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { errdefer util.deepFree(self.allocator, note); // Only serve community-specific notes on unauthenticated requests - if (self.user_id == null) { - if (!Uuid.eql(self.community.id, note.author.community_id)) return error.NotFound; + if (self.context.userId() == null) { + if (!Uuid.eql(self.context.community.id, note.author.community_id)) return error.NotFound; } return note; @@ -615,7 +624,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { pub fn localTimeline(self: *Self, args: TimelineArgs) !TimelineResult { var all_args = std.mem.zeroInit(Note.QueryArgs, args); - all_args.community_id = self.community.id; + all_args.community_id = self.context.community.id; const result = try models.notes.query(self.db, all_args, self.allocator); return TimelineResult{ .items = result.items, @@ -625,10 +634,10 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } pub fn homeTimeline(self: *Self, args: TimelineArgs) !TimelineResult { - if (self.user_id == null) return error.NoToken; + if (self.context.userId() == null) return error.NoToken; var all_args = std.mem.zeroInit(Note.QueryArgs, args); - all_args.followed_by = self.user_id; + all_args.followed_by = self.context.userId(); const result = try models.notes.query(self.db, all_args, self.allocator); return TimelineResult{ .items = result.items, @@ -660,12 +669,12 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } pub fn follow(self: *Self, followee: Uuid) !void { - const result = try models.follows.create(self.db, self.user_id orelse return error.NoToken, followee, self.allocator); + const result = try models.follows.create(self.db, self.context.userId() orelse return error.NoToken, followee, self.allocator); defer util.deepFree(self.allocator, result); } pub fn unfollow(self: *Self, followee: Uuid) !void { - const result = try models.follows.delete(self.db, self.user_id orelse return error.NoToken, followee, self.allocator); + const result = try models.follows.delete(self.db, self.context.userId() orelse return error.NoToken, followee, self.allocator); defer util.deepFree(self.allocator, result); } @@ -736,7 +745,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } pub fn driveUpload(self: *Self, meta: UploadFileArgs, body: []const u8) !DriveEntry { - const user_id = self.user_id orelse return error.NoToken; + const user_id = self.context.userId() orelse return error.NoToken; const file_id = try models.files.create(self.db, user_id, .{ .filename = meta.filename, .description = meta.description, @@ -782,14 +791,14 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } pub fn driveMkdir(self: *Self, parent_path: []const u8, name: []const u8) !DriveEntry { - const user_id = self.user_id orelse return error.NoToken; + const user_id = self.context.userId() orelse return error.NoToken; const entry = try models.drive.create(self.db, user_id, parent_path, name, null, self.allocator); errdefer util.deepFree(self.allocator, entry); return try self.backendDriveEntryToFrontend(entry, true); } pub fn driveDelete(self: *Self, path: []const u8) !void { - const user_id = self.user_id orelse return error.NoToken; + const user_id = self.context.userId() orelse return error.NoToken; const entry = try models.drive.stat(self.db, user_id, path, self.allocator); defer util.deepFree(self.allocator, entry); try models.drive.delete(self.db, entry.id, self.allocator); @@ -797,14 +806,14 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } pub fn driveMove(self: *Self, src: []const u8, dest: []const u8) !DriveEntry { - const user_id = self.user_id orelse return error.NoToken; + const user_id = self.context.userId() orelse return error.NoToken; try models.drive.move(self.db, user_id, src, dest, self.allocator); return try self.driveGet(dest); } pub fn driveGet(self: *Self, path: []const u8) !DriveEntry { - const user_id = self.user_id orelse return error.NoToken; + const user_id = self.context.userId() orelse return error.NoToken; const entry = try models.drive.stat(self.db, user_id, path, self.allocator); errdefer util.deepFree(self.allocator, entry); @@ -812,7 +821,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } pub fn driveUpdate(self: *Self, path: []const u8, meta: FileUpload.UpdateArgs) !DriveEntry { - const user_id = self.user_id orelse return error.NoToken; + const user_id = self.context.userId() orelse return error.NoToken; std.log.debug("{s}", .{path}); const entry = try models.drive.stat(self.db, user_id, path, self.allocator); defer util.deepFree(self.allocator, entry); @@ -834,7 +843,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { } pub fn updateUserProfile(self: *Self, id: Uuid, data: Actor.ProfileUpdateArgs) !void { - if (!Uuid.eql(id, self.user_id orelse return error.NoToken)) return error.AccessDenied; + if (!Uuid.eql(id, self.context.userId() orelse return error.NoToken)) return error.AccessDenied; try models.actors.updateProfile(self.db, id, data, self.allocator); } @@ -842,7 +851,7 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { const invite = models.invites.getByCode( self.db, code, - self.community.id, + self.context.community.id, self.allocator, ) catch |err| switch (err) { error.NotFound => return error.InvalidInvite, @@ -850,13 +859,13 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { }; errdefer util.deepFree(self.allocator, invite); - if (!Uuid.eql(invite.community_id, self.community.id)) return error.InvalidInvite; + if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.InvalidInvite; if (!isInviteValid(invite)) return error.InvalidInvite; const url = try std.fmt.allocPrint( self.allocator, "{s}://{s}/invite/{s}", - .{ @tagName(self.community.scheme), self.community.host, invite.code }, + .{ @tagName(self.context.community.scheme), self.context.community.host, invite.code }, ); errdefer util.deepFree(self.allocator, url); diff --git a/src/api/methods/auth.zig b/src/api/methods/auth.zig index 3829851..3f994d8 100644 --- a/src/api/methods/auth.zig +++ b/src/api/methods/auth.zig @@ -18,25 +18,25 @@ pub fn methods(comptime models: type) type { pub fn register(self: anytype, username: []const u8, password: []const u8, opt: RegistrationOptions) !types.Actor { const tx = try self.db.beginOrSavepoint(); const maybe_invite = if (opt.invite_code) |code| - try models.invites.getByCode(tx, code, self.community.id, self.allocator) + try models.invites.getByCode(tx, code, self.context.community.id, self.allocator) else null; defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv); if (maybe_invite) |invite| { - if (!Uuid.eql(invite.community_id, self.community.id)) return error.WrongCommunity; + if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.WrongCommunity; if (!isInviteValid(invite)) return error.InvalidInvite; } const invite_kind = if (maybe_invite) |inv| inv.kind else .user; - if (self.community.kind == .admin) @panic("Unimplmented"); + if (self.context.community.kind == .admin) @panic("Unimplmented"); const user_id = try models.auth.register( tx, username, password, - self.community.id, + self.context.community.id, .{ .invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null, .email = opt.email, @@ -48,7 +48,7 @@ pub fn methods(comptime models: type) type { .user => {}, .system => @panic("System user invites unimplemented"), .community_owner => { - try models.communities.transferOwnership(tx, self.community.id, user_id); + try models.communities.transferOwnership(tx, self.context.community.id, user_id); }, } diff --git a/src/main/controllers.zig b/src/main/controllers.zig index ad971b5..21305ad 100644 --- a/src/main/controllers.zig +++ b/src/main/controllers.zig @@ -282,7 +282,7 @@ pub const Response = struct { pub fn template(self: *Self, status_code: http.Status, srv: anytype, comptime templ: []const u8, data: anytype) !void { try self.headers.put("Content-Type", "text/html"); - const user = if (srv.user_id) |uid| try srv.getUser(uid) else null; + const user = if (srv.context.userId()) |uid| try srv.getUser(uid) else null; defer util.deepFree(srv.allocator, user); var stream = try self.open(status_code); @@ -300,7 +300,7 @@ pub const Response = struct { .{ .community = srv.community, .user = user, - .user_id = srv.user_id, + .user_id = srv.context.userId(), }, ); diff --git a/src/main/controllers/web.zig b/src/main/controllers/web.zig index 72bc1bd..9af7100 100644 --- a/src/main/controllers/web.zig +++ b/src/main/controllers/web.zig @@ -61,7 +61,7 @@ const index = struct { pub const method = .GET; pub fn handler(_: anytype, res: anytype, srv: anytype) !void { - if (srv.user_id == null) { + if (srv.context.userId() == null) { try res.headers.put("Location", about.path); return res.status(.see_other); }