comptime dependency injection for ApiConn

This commit is contained in:
jaina heartles 2022-12-21 00:57:36 -08:00
parent b0db514adc
commit d8e4d6c82b

View file

@ -265,7 +265,7 @@ pub fn setupAdmin(db: sql.Db, origin: []const u8, username: []const u8, password
pub const ApiSource = struct {
db_conn_pool: *sql.ConnPool,
pub const Conn = ApiConn(sql.Db);
pub const Conn = ApiConn(sql.Db, services);
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 {
const Self = @This();
db: DbConn,
token_info: ?services.auth.TokenInfo = null,
token_info: ?models.auth.TokenInfo = null,
user_id: ?Uuid = null,
community: services.communities.Community,
community: models.communities.Community,
allocator: std.mem.Allocator,
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 {
return services.auth.login(
return models.auth.login(
self.db,
username,
self.community.id,
@ -351,7 +351,7 @@ fn ApiConn(comptime DbConn: type) type {
};
pub fn verifyAuthorization(self: *Self) !AuthorizationInfo {
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);
const username = try util.deepClone(self.allocator, user.username);
@ -370,21 +370,21 @@ fn ApiConn(comptime DbConn: type) type {
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()) {
return error.PermissionDenied;
}
const tx = try self.db.begin();
errdefer tx.rollback();
const community_id = try services.communities.create(
const community_id = try models.communities.create(
tx,
origin,
.{ .name = name },
self.allocator,
);
const community = services.communities.get(
const community = models.communities.get(
tx,
community_id,
self.allocator,
@ -412,18 +412,18 @@ fn ApiConn(comptime DbConn: type) type {
// Users can only make user invites
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,
.lifespan = options.lifespan,
.max_uses = options.max_uses,
.kind = options.kind,
}, 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);
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);
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.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return false;
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 {
const tx = try self.db.beginOrSavepoint();
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
null;
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");
const user_id = try services.auth.register(
const user_id = try models.auth.register(
tx,
username,
password,
@ -493,7 +493,7 @@ fn ApiConn(comptime DbConn: type) type {
.user => {},
.system => @panic("System user invites unimplemented"),
.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 {
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|
try std.fmt.allocPrint(
@ -573,7 +573,7 @@ fn ApiConn(comptime DbConn: type) type {
// Only authenticated users can post
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) {
error.NotFound => error.Unexpected,
@ -582,8 +582,8 @@ fn ApiConn(comptime DbConn: type) type {
}
pub fn getNote(self: *Self, note_id: Uuid) !NoteResponse {
const note = try services.notes.get(self.db, note_id, self.allocator);
const user = try services.actors.get(self.db, note.author_id, self.allocator);
const note = try models.notes.get(self.db, note_id, self.allocator);
const user = try models.actors.get(self.db, note.author_id, self.allocator);
// Only serve community-specific notes on unauthenticated requests
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;
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 {
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{
.items = result.items,
.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 {
var all_args = std.mem.zeroInit(NoteQueryArgs, args);
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{
.items = result.items,
.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 {
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;
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{
.items = result.items,
.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 {
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;
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{
.items = result.items,
.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 {
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;
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{
.items = result.items,
.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 {
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);
}
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);
}
@ -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| .{
.file = .{
.id = entry.id,
@ -699,7 +699,7 @@ fn ApiConn(comptime DbConn: type) type {
.path = entry.path,
.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 .{
.dir = .{
@ -712,7 +712,7 @@ fn ApiConn(comptime DbConn: type) type {
.children = blk: {
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| {
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 {
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,
.description = meta.description,
.content_type = meta.content_type,
@ -749,11 +749,11 @@ fn ApiConn(comptime DbConn: type) type {
}, body, self.allocator);
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 });
};
break :entry services.drive.create(
break :entry models.drive.create(
self.db,
user_id,
meta.dir,
@ -768,7 +768,7 @@ fn ApiConn(comptime DbConn: type) type {
const name = split.rest();
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,
user_id,
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 {
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);
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 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);
try services.drive.delete(self.db, entry.id, self.allocator);
if (entry.file_id) |file_id| try services.files.delete(self.db, file_id, self.allocator);
try models.drive.delete(self.db, entry.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 {
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);
}
pub fn driveGet(self: *Self, path: []const u8) !DriveEntry {
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);
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;
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);
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);
}
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);
return FileResult{
.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 {
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 {
const invite = services.invites.getByCode(
const invite = models.invites.getByCode(
self.db,
code,
self.community.id,