fediglam/src/api/lib.zig

288 lines
10 KiB
Zig

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());
}