comptime dependency injection for ApiConn
This commit is contained in:
parent
b0db514adc
commit
d8e4d6c82b
1 changed files with 52 additions and 52 deletions
104
src/api/lib.zig
104
src/api/lib.zig
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue