comptime dependency injection for ApiConn

This commit is contained in:
jaina heartles 2022-12-21 00:57:36 -08:00
parent b0db514adc
commit d8e4d6c82b
1 changed files with 52 additions and 52 deletions

View File

@ -265,7 +265,7 @@ pub fn setupAdmin(db: sql.Db, origin: []const u8, username: []const u8, password
pub const ApiSource = struct { pub const ApiSource = struct {
db_conn_pool: *sql.ConnPool, db_conn_pool: *sql.ConnPool,
pub const Conn = ApiConn(sql.Db); pub const Conn = ApiConn(sql.Db, services);
const root_username = "root"; const root_username = "root";
@ -310,14 +310,14 @@ pub const ApiSource = struct {
} }
}; };
fn ApiConn(comptime DbConn: type) type { fn ApiConn(comptime DbConn: type, comptime models: anytype) type {
return struct { return struct {
const Self = @This(); const Self = @This();
db: DbConn, db: DbConn,
token_info: ?services.auth.TokenInfo = null, token_info: ?models.auth.TokenInfo = null,
user_id: ?Uuid = null, user_id: ?Uuid = null,
community: services.communities.Community, community: models.communities.Community,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
pub fn close(self: *Self) void { pub fn close(self: *Self) void {
@ -332,7 +332,7 @@ fn ApiConn(comptime DbConn: type) type {
} }
pub fn login(self: *Self, username: []const u8, password: []const u8) !LoginResponse { pub fn login(self: *Self, username: []const u8, password: []const u8) !LoginResponse {
return services.auth.login( return models.auth.login(
self.db, self.db,
username, username,
self.community.id, self.community.id,
@ -351,7 +351,7 @@ fn ApiConn(comptime DbConn: type) type {
}; };
pub fn verifyAuthorization(self: *Self) !AuthorizationInfo { pub fn verifyAuthorization(self: *Self) !AuthorizationInfo {
if (self.token_info) |info| { if (self.token_info) |info| {
const user = try services.actors.get(self.db, info.user_id, self.allocator); const user = try models.actors.get(self.db, info.user_id, self.allocator);
defer util.deepFree(self.allocator, user); defer util.deepFree(self.allocator, user);
const username = try util.deepClone(self.allocator, user.username); const username = try util.deepClone(self.allocator, user.username);
@ -370,21 +370,21 @@ fn ApiConn(comptime DbConn: type) type {
return error.TokenRequired; return error.TokenRequired;
} }
pub fn createCommunity(self: *Self, origin: []const u8, name: ?[]const u8) !services.communities.Community { pub fn createCommunity(self: *Self, origin: []const u8, name: ?[]const u8) !models.communities.Community {
if (!self.isAdmin()) { if (!self.isAdmin()) {
return error.PermissionDenied; return error.PermissionDenied;
} }
const tx = try self.db.begin(); const tx = try self.db.begin();
errdefer tx.rollback(); errdefer tx.rollback();
const community_id = try services.communities.create( const community_id = try models.communities.create(
tx, tx,
origin, origin,
.{ .name = name }, .{ .name = name },
self.allocator, self.allocator,
); );
const community = services.communities.get( const community = models.communities.get(
tx, tx,
community_id, community_id,
self.allocator, self.allocator,
@ -412,18 +412,18 @@ fn ApiConn(comptime DbConn: type) type {
// Users can only make user invites // Users can only make user invites
if (options.kind != .user and !self.isAdmin()) return error.PermissionDenied; if (options.kind != .user and !self.isAdmin()) return error.PermissionDenied;
const invite_id = try services.invites.create(self.db, user_id, community_id, .{ const invite_id = try models.invites.create(self.db, user_id, community_id, .{
.name = options.name, .name = options.name,
.lifespan = options.lifespan, .lifespan = options.lifespan,
.max_uses = options.max_uses, .max_uses = options.max_uses,
.kind = options.kind, .kind = options.kind,
}, self.allocator); }, self.allocator);
const invite = try services.invites.get(self.db, invite_id, self.allocator); const invite = try models.invites.get(self.db, invite_id, self.allocator);
errdefer util.deepFree(self.allocator, invite); errdefer util.deepFree(self.allocator, invite);
const url = if (options.to_community) |cid| blk: { const url = if (options.to_community) |cid| blk: {
const community = try services.communities.get(self.db, cid, self.allocator); const community = try models.communities.get(self.db, cid, self.allocator);
defer util.deepFree(self.allocator, community); defer util.deepFree(self.allocator, community);
break :blk try std.fmt.allocPrint( break :blk try std.fmt.allocPrint(
@ -454,7 +454,7 @@ fn ApiConn(comptime DbConn: type) type {
}; };
} }
fn isInviteValid(invite: services.invites.Invite) bool { fn isInviteValid(invite: models.invites.Invite) bool {
if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false; if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false;
if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return false; if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return false;
return true; return true;
@ -463,7 +463,7 @@ fn ApiConn(comptime DbConn: type) type {
pub fn register(self: *Self, username: []const u8, password: []const u8, opt: RegistrationOptions) !UserResponse { pub fn register(self: *Self, username: []const u8, password: []const u8, opt: RegistrationOptions) !UserResponse {
const tx = try self.db.beginOrSavepoint(); const tx = try self.db.beginOrSavepoint();
const maybe_invite = if (opt.invite_code) |code| const maybe_invite = if (opt.invite_code) |code|
try services.invites.getByCode(tx, code, self.community.id, self.allocator) try models.invites.getByCode(tx, code, self.community.id, self.allocator)
else else
null; null;
defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv); defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv);
@ -477,7 +477,7 @@ fn ApiConn(comptime DbConn: type) type {
if (self.community.kind == .admin) @panic("Unimplmented"); if (self.community.kind == .admin) @panic("Unimplmented");
const user_id = try services.auth.register( const user_id = try models.auth.register(
tx, tx,
username, username,
password, password,
@ -493,7 +493,7 @@ fn ApiConn(comptime DbConn: type) type {
.user => {}, .user => {},
.system => @panic("System user invites unimplemented"), .system => @panic("System user invites unimplemented"),
.community_owner => { .community_owner => {
try services.communities.transferOwnership(tx, self.community.id, user_id); try models.communities.transferOwnership(tx, self.community.id, user_id);
}, },
} }
@ -508,7 +508,7 @@ fn ApiConn(comptime DbConn: type) type {
} }
fn getUserUnchecked(self: *Self, db: anytype, user_id: Uuid) !UserResponse { fn getUserUnchecked(self: *Self, db: anytype, user_id: Uuid) !UserResponse {
const user = try services.actors.get(db, user_id, self.allocator); const user = try models.actors.get(db, user_id, self.allocator);
const avatar_url = if (user.avatar_file_id) |fid| const avatar_url = if (user.avatar_file_id) |fid|
try std.fmt.allocPrint( try std.fmt.allocPrint(
@ -573,7 +573,7 @@ fn ApiConn(comptime DbConn: type) type {
// Only authenticated users can post // Only authenticated users can post
const user_id = self.user_id orelse return error.TokenRequired; const user_id = self.user_id orelse return error.TokenRequired;
const note_id = try services.notes.create(self.db, user_id, content, self.allocator); const note_id = try models.notes.create(self.db, user_id, content, self.allocator);
return self.getNote(note_id) catch |err| switch (err) { return self.getNote(note_id) catch |err| switch (err) {
error.NotFound => error.Unexpected, error.NotFound => error.Unexpected,
@ -582,8 +582,8 @@ fn ApiConn(comptime DbConn: type) type {
} }
pub fn getNote(self: *Self, note_id: Uuid) !NoteResponse { pub fn getNote(self: *Self, note_id: Uuid) !NoteResponse {
const note = try services.notes.get(self.db, note_id, self.allocator); const note = try models.notes.get(self.db, note_id, self.allocator);
const user = try services.actors.get(self.db, note.author_id, self.allocator); const user = try models.actors.get(self.db, note.author_id, self.allocator);
// Only serve community-specific notes on unauthenticated requests // Only serve community-specific notes on unauthenticated requests
if (self.user_id == null) { if (self.user_id == null) {
@ -602,14 +602,14 @@ fn ApiConn(comptime DbConn: type) type {
}; };
} }
pub fn queryCommunities(self: *Self, args: services.communities.QueryArgs) !CommunityQueryResult { pub fn queryCommunities(self: *Self, args: models.communities.QueryArgs) !CommunityQueryResult {
if (!self.isAdmin()) return error.PermissionDenied; if (!self.isAdmin()) return error.PermissionDenied;
return try services.communities.query(self.db, args, self.allocator); return try models.communities.query(self.db, args, self.allocator);
} }
pub fn globalTimeline(self: *Self, args: TimelineArgs) !TimelineResult { pub fn globalTimeline(self: *Self, args: TimelineArgs) !TimelineResult {
const all_args = std.mem.zeroInit(NoteQueryArgs, args); const all_args = std.mem.zeroInit(NoteQueryArgs, args);
const result = try services.notes.query(self.db, all_args, self.allocator); const result = try models.notes.query(self.db, all_args, self.allocator);
return TimelineResult{ return TimelineResult{
.items = result.items, .items = result.items,
.prev_page = TimelineArgs.from(result.prev_page), .prev_page = TimelineArgs.from(result.prev_page),
@ -620,7 +620,7 @@ fn ApiConn(comptime DbConn: type) type {
pub fn localTimeline(self: *Self, args: TimelineArgs) !TimelineResult { pub fn localTimeline(self: *Self, args: TimelineArgs) !TimelineResult {
var all_args = std.mem.zeroInit(NoteQueryArgs, args); var all_args = std.mem.zeroInit(NoteQueryArgs, args);
all_args.community_id = self.community.id; all_args.community_id = self.community.id;
const result = try services.notes.query(self.db, all_args, self.allocator); const result = try models.notes.query(self.db, all_args, self.allocator);
return TimelineResult{ return TimelineResult{
.items = result.items, .items = result.items,
.prev_page = TimelineArgs.from(result.prev_page), .prev_page = TimelineArgs.from(result.prev_page),
@ -631,9 +631,9 @@ fn ApiConn(comptime DbConn: type) type {
pub fn homeTimeline(self: *Self, args: TimelineArgs) !TimelineResult { pub fn homeTimeline(self: *Self, args: TimelineArgs) !TimelineResult {
if (self.user_id == null) return error.NoToken; if (self.user_id == null) return error.NoToken;
var all_args = std.mem.zeroInit(services.notes.QueryArgs, args); var all_args = std.mem.zeroInit(models.notes.QueryArgs, args);
all_args.followed_by = self.user_id; all_args.followed_by = self.user_id;
const result = try services.notes.query(self.db, all_args, self.allocator); const result = try models.notes.query(self.db, all_args, self.allocator);
return TimelineResult{ return TimelineResult{
.items = result.items, .items = result.items,
.prev_page = TimelineArgs.from(result.prev_page), .prev_page = TimelineArgs.from(result.prev_page),
@ -642,9 +642,9 @@ fn ApiConn(comptime DbConn: type) type {
} }
pub fn queryFollowers(self: *Self, user_id: Uuid, args: FollowerQueryArgs) !FollowerQueryResult { 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(models.follows.QueryArgs, args);
all_args.followee_id = user_id; all_args.followee_id = user_id;
const result = try services.follows.query(self.db, all_args, self.allocator); const result = try models.follows.query(self.db, all_args, self.allocator);
return FollowerQueryResult{ return FollowerQueryResult{
.items = result.items, .items = result.items,
.prev_page = FollowQueryArgs.from(result.prev_page), .prev_page = FollowQueryArgs.from(result.prev_page),
@ -653,9 +653,9 @@ fn ApiConn(comptime DbConn: type) type {
} }
pub fn queryFollowing(self: *Self, user_id: Uuid, args: FollowingQueryArgs) !FollowingQueryResult { 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(models.follows.QueryArgs, args);
all_args.followed_by_id = user_id; all_args.followed_by_id = user_id;
const result = try services.follows.query(self.db, all_args, self.allocator); const result = try models.follows.query(self.db, all_args, self.allocator);
return FollowingQueryResult{ return FollowingQueryResult{
.items = result.items, .items = result.items,
.prev_page = FollowQueryArgs.from(result.prev_page), .prev_page = FollowQueryArgs.from(result.prev_page),
@ -664,12 +664,12 @@ fn ApiConn(comptime DbConn: type) type {
} }
pub fn follow(self: *Self, followee: Uuid) !void { 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.allocator); const result = try models.follows.create(self.db, self.user_id orelse return error.NoToken, followee, self.allocator);
defer util.deepFree(self.allocator, result); defer util.deepFree(self.allocator, result);
} }
pub fn unfollow(self: *Self, followee: Uuid) !void { 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.allocator); const result = try models.follows.delete(self.db, self.user_id orelse return error.NoToken, followee, self.allocator);
defer util.deepFree(self.allocator, result); defer util.deepFree(self.allocator, result);
} }
@ -690,7 +690,7 @@ fn ApiConn(comptime DbConn: type) type {
); );
} }
fn backendDriveEntryToFrontend(self: *Self, entry: services.drive.Entry, recurse: bool) !DriveEntry { fn backendDriveEntryToFrontend(self: *Self, entry: models.drive.Entry, recurse: bool) !DriveEntry {
return if (entry.file_id) |file_id| .{ return if (entry.file_id) |file_id| .{
.file = .{ .file = .{
.id = entry.id, .id = entry.id,
@ -699,7 +699,7 @@ fn ApiConn(comptime DbConn: type) type {
.path = entry.path, .path = entry.path,
.parent_directory_id = entry.parent_directory_id, .parent_directory_id = entry.parent_directory_id,
.meta = try services.files.get(self.db, file_id, self.allocator), .meta = try models.files.get(self.db, file_id, self.allocator),
}, },
} else .{ } else .{
.dir = .{ .dir = .{
@ -712,7 +712,7 @@ fn ApiConn(comptime DbConn: type) type {
.children = blk: { .children = blk: {
if (!recurse) break :blk null; if (!recurse) break :blk null;
const children = try services.drive.list(self.db, entry.id, self.allocator); const children = try models.drive.list(self.db, entry.id, self.allocator);
const result = self.allocator.alloc(DriveEntry, children.len) catch |err| { const result = self.allocator.alloc(DriveEntry, children.len) catch |err| {
util.deepFree(self.allocator, children); util.deepFree(self.allocator, children);
@ -741,7 +741,7 @@ fn ApiConn(comptime DbConn: type) type {
pub fn driveUpload(self: *Self, meta: UploadFileArgs, body: []const u8) !DriveEntry { 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.user_id orelse return error.NoToken;
const file_id = try services.files.create(self.db, user_id, .{ const file_id = try models.files.create(self.db, user_id, .{
.filename = meta.filename, .filename = meta.filename,
.description = meta.description, .description = meta.description,
.content_type = meta.content_type, .content_type = meta.content_type,
@ -749,11 +749,11 @@ fn ApiConn(comptime DbConn: type) type {
}, body, self.allocator); }, body, self.allocator);
const entry = entry: { const entry = entry: {
errdefer services.files.delete(self.db, file_id, self.allocator) catch |err| { errdefer models.files.delete(self.db, file_id, self.allocator) catch |err| {
std.log.err("Unable to delete file {}: {}", .{ file_id, err }); std.log.err("Unable to delete file {}: {}", .{ file_id, err });
}; };
break :entry services.drive.create( break :entry models.drive.create(
self.db, self.db,
user_id, user_id,
meta.dir, meta.dir,
@ -768,7 +768,7 @@ fn ApiConn(comptime DbConn: type) type {
const name = split.rest(); const name = split.rest();
const new_name = try std.fmt.bufPrint(&buf, "{s}.{s}.{s}", .{ name, file_id, ext }); const new_name = try std.fmt.bufPrint(&buf, "{s}.{s}.{s}", .{ name, file_id, ext });
break :entry try services.drive.create( break :entry try models.drive.create(
self.db, self.db,
user_id, user_id,
meta.dir, meta.dir,
@ -787,63 +787,63 @@ fn ApiConn(comptime DbConn: type) type {
pub fn driveMkdir(self: *Self, parent_path: []const u8, name: []const u8) !DriveEntry { 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.user_id orelse return error.NoToken;
const entry = try services.drive.create(self.db, user_id, parent_path, name, null, self.allocator); const entry = try models.drive.create(self.db, user_id, parent_path, name, null, self.allocator);
errdefer util.deepFree(self.allocator, entry); errdefer util.deepFree(self.allocator, entry);
return try self.backendDriveEntryToFrontend(entry, true); return try self.backendDriveEntryToFrontend(entry, true);
} }
pub fn driveDelete(self: *Self, path: []const u8) !void { pub fn driveDelete(self: *Self, path: []const u8) !void {
const user_id = self.user_id orelse return error.NoToken; const user_id = self.user_id orelse return error.NoToken;
const entry = try services.drive.stat(self.db, user_id, path, self.allocator); const entry = try models.drive.stat(self.db, user_id, path, self.allocator);
defer util.deepFree(self.allocator, entry); defer util.deepFree(self.allocator, entry);
try services.drive.delete(self.db, entry.id, self.allocator); try models.drive.delete(self.db, entry.id, self.allocator);
if (entry.file_id) |file_id| try services.files.delete(self.db, file_id, self.allocator); if (entry.file_id) |file_id| try models.files.delete(self.db, file_id, self.allocator);
} }
pub fn driveMove(self: *Self, src: []const u8, dest: []const u8) !DriveEntry { 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.user_id orelse return error.NoToken;
try services.drive.move(self.db, user_id, src, dest, self.allocator); try models.drive.move(self.db, user_id, src, dest, self.allocator);
return try self.driveGet(dest); return try self.driveGet(dest);
} }
pub fn driveGet(self: *Self, path: []const u8) !DriveEntry { pub fn driveGet(self: *Self, path: []const u8) !DriveEntry {
const user_id = self.user_id orelse return error.NoToken; const user_id = self.user_id orelse return error.NoToken;
const entry = try services.drive.stat(self.db, user_id, path, self.allocator); const entry = try models.drive.stat(self.db, user_id, path, self.allocator);
errdefer util.deepFree(self.allocator, entry); errdefer util.deepFree(self.allocator, entry);
return try self.backendDriveEntryToFrontend(entry, true); return try self.backendDriveEntryToFrontend(entry, true);
} }
pub fn driveUpdate(self: *Self, path: []const u8, meta: services.files.PartialMeta) !DriveEntry { pub fn driveUpdate(self: *Self, path: []const u8, meta: models.files.PartialMeta) !DriveEntry {
const user_id = self.user_id orelse return error.NoToken; const user_id = self.user_id orelse return error.NoToken;
std.log.debug("{s}", .{path}); std.log.debug("{s}", .{path});
const entry = try services.drive.stat(self.db, user_id, path, self.allocator); const entry = try models.drive.stat(self.db, user_id, path, self.allocator);
defer util.deepFree(self.allocator, entry); defer util.deepFree(self.allocator, entry);
std.log.debug("{}", .{entry.id}); std.log.debug("{}", .{entry.id});
try services.files.update(self.db, entry.file_id orelse return error.NotAFile, meta, self.allocator); try models.files.update(self.db, entry.file_id orelse return error.NotAFile, meta, self.allocator);
return try self.driveGet(path); return try self.driveGet(path);
} }
pub fn fileDereference(self: *Self, id: Uuid) !FileResult { pub fn fileDereference(self: *Self, id: Uuid) !FileResult {
const meta = try services.files.get(self.db, id, self.allocator); const meta = try models.files.get(self.db, id, self.allocator);
errdefer util.deepFree(self.allocator, meta); errdefer util.deepFree(self.allocator, meta);
return FileResult{ return FileResult{
.meta = meta, .meta = meta,
.data = try services.files.deref(self.allocator, id), .data = try models.files.deref(self.allocator, id),
}; };
} }
pub fn updateUserProfile(self: *Self, id: Uuid, data: PartialUserProfile) !void { pub fn updateUserProfile(self: *Self, id: Uuid, data: PartialUserProfile) !void {
if (!Uuid.eql(id, self.user_id orelse return error.NoToken)) return error.AccessDenied; if (!Uuid.eql(id, self.user_id orelse return error.NoToken)) return error.AccessDenied;
try services.actors.updateProfile(self.db, id, data, self.allocator); try models.actors.updateProfile(self.db, id, data, self.allocator);
} }
pub fn validateInvite(self: *Self, code: []const u8) !InviteResponse { pub fn validateInvite(self: *Self, code: []const u8) !InviteResponse {
const invite = services.invites.getByCode( const invite = models.invites.getByCode(
self.db, self.db,
code, code,
self.community.id, self.community.id,