const std = @import("std"); const util = @import("util"); const sql = @import("sql"); const services = @import("./services.zig"); const types = @import("./types.zig"); const DateTime = util.DateTime; const Uuid = util.Uuid; pub usingnamespace types; pub const Actor = types.actors.Actor; pub const Community = types.communities.Community; pub const Invite = types.invites.Invite; pub const Note = types.notes.Note; pub const Token = types.tokens.Token; pub const ClusterMeta = struct { community_count: usize, user_count: usize, note_count: usize, }; pub fn isAdminSetup(db: sql.Db) !bool { const svc = services.Services(sql.Db){ .db = db }; _ = svc.getAdminCommunityId() catch |err| switch (err) { error.NotFound => return false, else => |e| return e, }; return true; } pub fn setupAdmin(db: sql.Db, origin: []const u8, username: []const u8, password: []const u8, allocator: std.mem.Allocator) anyerror!void { const svc = @import("./services.zig").Services(sql.Db){ .db = db }; const tx = try svc.beginTx(); errdefer tx.rollbackTx(); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const community_id = try tx.createCommunity( arena.allocator(), origin, .{ .name = "Cluster Admin", .kind = .admin }, ); const user = try @import("./methods/auth.zig").createLocalAccount( arena.allocator(), tx, .{ .username = username, .password = password, .community_id = community_id, .role = .admin, }, ); try tx.transferCommunityOwnership(community_id, user); try tx.commitTx(); std.log.info( "Created admin user {s} (id {}) with cluster admin origin {s} (id {})", .{ username, user, origin, community_id }, ); } pub const ApiSource = struct { db_conn_pool: *sql.ConnPool, pub const Conn = ApiConn(sql.Db, method_impl); const root_username = "root"; pub fn init(pool: *sql.ConnPool) !ApiSource { return ApiSource{ .db_conn_pool = pool, }; } pub fn connectUnauthorized(self: *ApiSource, host: []const u8, alloc: std.mem.Allocator) !Conn { const db = try self.db_conn_pool.acquire(); errdefer db.releaseConnection(); var conn = Conn{ .db = db, .context = .{ .community = undefined, }, .allocator = alloc, }; conn.context.community = try conn.getServices().getCommunityByHost(alloc, host); return conn; } pub fn connectToken(self: *ApiSource, host: []const u8, token: []const u8, alloc: std.mem.Allocator) !Conn { var conn = try self.connectUnauthorized(host, alloc); errdefer conn.close(); conn.context.token_info = try @import("./methods/auth.zig").verifyToken(alloc, conn.context, conn.getServices(), token); return conn; } }; pub const ApiContext = struct { token_info: ?Token.Info = null, community: Community, pub fn userId(self: ApiContext) ?Uuid { if (self.token_info) |t| return t.account_id else return null; } pub fn isAdmin(self: ApiContext) bool { return self.userId() != null and self.community.kind == .admin; } }; const method_impl = struct { const actors = @import("./methods/actors.zig"); const auth = @import("./methods/auth.zig"); const communities = @import("./methods/communities.zig"); const drive = @import("./methods/drive.zig"); const follows = @import("./methods/follows.zig"); const invites = @import("./methods/invites.zig"); const notes = @import("./methods/notes.zig"); const timelines = @import("./methods/timelines.zig"); }; fn ApiConn(comptime DbConn: type, comptime methods: anytype) type { return struct { const Self = @This(); const Services = @import("./services.zig").Services(DbConn); db: DbConn, context: ApiContext, allocator: std.mem.Allocator, pub fn close(self: *Self) void { util.deepFree(self.allocator, self.context.community); if (self.context.token_info) |info| util.deepFree(self.allocator, info); self.db.releaseConnection(); } fn getServices(self: *Self) Services { return Services{ .db = self.db }; } pub fn getCommunity(self: *Self, id: Uuid) !types.communities.Community { return try methods.communities.get(self.allocator, self.context, self.getServices(), id); } pub fn createCommunity(self: *Self, origin: []const u8, name: ?[]const u8) !Uuid { return try methods.communities.create( self.allocator, self.context, self.getServices(), origin, name, ); } pub fn createInvite(self: *Self, options: types.invites.CreateOptions) !Uuid { return methods.invites.create(self.allocator, self.context, self.getServices(), options); } pub fn getInvite(self: *Self, id: Uuid) !Invite { return try methods.invites.get(self.allocator, self.context, self.getServices(), id); } pub fn login(self: *Self, username: []const u8, password: []const u8) !Token { return methods.auth.login(self.allocator, self.context, self.getServices(), username, password); } pub fn register( self: *Self, username: []const u8, password: []const u8, opt: types.auth.RegistrationOptions, ) !Uuid { return methods.auth.register(self.allocator, self.context, self.getServices(), username, password, opt); } pub fn getActor(self: *Self, user_id: Uuid) !Actor { return methods.actors.get(self.allocator, self.context, self.getServices(), user_id); } pub fn createNote(self: *Self, content: []const u8) !Uuid { return methods.notes.create(self.allocator, self.context, self.getServices(), content); } pub fn getNote(self: *Self, note_id: Uuid) !Note { return methods.notes.get(self.allocator, self.context, self.getServices(), note_id); } pub fn queryCommunities(self: *Self, args: types.communities.QueryArgs) !types.communities.QueryResult { return methods.communities.query(self.allocator, self.context, self.getServices(), args); } pub fn globalTimeline(self: *Self, args: types.timelines.TimelineArgs) !methods.timelines.TimelineResult { return methods.timelines.globalTimeline(self.allocator, self.context, self.getServices(), args); } pub fn localTimeline(self: *Self, args: types.timelines.TimelineArgs) !methods.timelines.TimelineResult { return methods.timelines.localTimeline(self.allocator, self.context, self.getServices(), args); } pub fn homeTimeline(self: *Self, args: types.timelines.TimelineArgs) !methods.timelines.TimelineResult { return methods.timelines.homeTimeline(self.allocator, self.context, self.getServices(), args); } pub fn queryFollowers(self: *Self, user_id: Uuid, args: types.follows.FollowerQueryArgs) !types.follows.FollowerQueryResult { return methods.follows.queryFollowers(self.allocator, self.context, self.getServices(), user_id, args); } pub fn queryFollowing(self: *Self, user_id: Uuid, args: types.follows.FollowingQueryArgs) !types.follows.FollowingQueryResult { return methods.follows.queryFollowing(self.allocator, self.context, self.getServices(), user_id, args); } pub fn follow(self: *Self, followee: Uuid) !void { try methods.follows.follow(self.allocator, self.context, self.getServices(), followee); } pub fn unfollow(self: *Self, followee: Uuid) !void { try methods.follows.unfollow(self.allocator, self.context, self.getServices(), followee); } 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.allocator, ); } pub fn driveUpload(self: *Self, meta: types.drive.UploadArgs, body: []const u8) !Uuid { return try methods.drive.upload(self.allocator, self.context, self.getServices(), meta, body); } pub fn driveMkdir(self: *Self, parent_path: []const u8, name: []const u8) !Uuid { return try methods.drive.mkdir(self.allocator, self.context, self.getServices(), parent_path, name); } pub fn driveDelete(self: *Self, path: []const u8) !void { return try methods.drive.delete(self.allocator, self.context, self.getServices(), path); } pub fn driveMove(self: *Self, src: []const u8, dest: []const u8) !void { return try methods.drive.move(self.allocator, self.context, self.getServices(), src, dest); } pub fn driveGet(self: *Self, path: []const u8) !types.drive.DriveEntry { return try methods.drive.get(self.allocator, self.context, self.getServices(), path); } pub fn driveGetEntryById(self: *Self, id: Uuid) !types.drive.DriveEntry { return try methods.drive.getById(self.allocator, self.context, self.getServices(), id); } pub fn driveUpdate(self: *Self, path: []const u8, meta: types.files.UpdateArgs) !void { return try methods.drive.update(self.allocator, self.context, self.getServices(), path, meta); } pub fn fileDereference(self: *Self, id: Uuid) !types.files.DerefResult { return try methods.drive.dereference(self.allocator, self.context, self.getServices(), id); } pub fn updateUserProfile(self: *Self, id: Uuid, data: types.actors.ProfileUpdateArgs) !void { try methods.actors.updateProfile(self.allocator, self.context, self.getServices(), id, data); } pub fn validateInvite(self: *Self, code: []const u8) !Invite { return try methods.invites.getByCode(self.allocator, self.context, self.getServices(), code); } }; } test { std.testing.refAllDecls(@This()); }