2022-07-13 03:40:48 +00:00
|
|
|
const std = @import("std");
|
2022-07-13 04:16:33 +00:00
|
|
|
const util = @import("util");
|
2022-09-15 01:12:07 +00:00
|
|
|
const sql = @import("sql");
|
2022-07-13 03:40:48 +00:00
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
const DateTime = util.DateTime;
|
|
|
|
const Uuid = util.Uuid;
|
2022-07-13 03:40:48 +00:00
|
|
|
|
2022-12-10 10:51:59 +00:00
|
|
|
const default_avatar = "static/default_avi.png";
|
|
|
|
|
2022-09-07 23:14:52 +00:00
|
|
|
const services = struct {
|
2022-10-08 20:47:54 +00:00
|
|
|
const communities = @import("./services/communities.zig");
|
2022-10-12 03:06:29 +00:00
|
|
|
const actors = @import("./services/actors.zig");
|
2022-10-08 20:47:54 +00:00
|
|
|
const auth = @import("./services/auth.zig");
|
2022-12-06 09:48:36 +00:00
|
|
|
const drive = @import("./services/drive.zig");
|
|
|
|
const files = @import("./services/files.zig");
|
2022-10-08 20:47:54 +00:00
|
|
|
const invites = @import("./services/invites.zig");
|
|
|
|
const notes = @import("./services/notes.zig");
|
2022-11-14 08:14:29 +00:00
|
|
|
const follows = @import("./services/follows.zig");
|
2022-09-08 05:10:58 +00:00
|
|
|
};
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
const types = @import("./services/types.zig");
|
|
|
|
|
|
|
|
pub const QueryResult = types.QueryResult;
|
|
|
|
|
|
|
|
pub const Account = types.Account;
|
|
|
|
pub const Actor = types.Actor;
|
|
|
|
pub const Community = types.Community;
|
|
|
|
pub const Invite = types.Invite;
|
|
|
|
pub const Note = types.Note;
|
|
|
|
pub const Token = types.Token;
|
|
|
|
|
2022-11-19 11:13:05 +00:00
|
|
|
pub const ClusterMeta = struct {
|
|
|
|
community_count: usize,
|
|
|
|
user_count: usize,
|
|
|
|
note_count: usize,
|
|
|
|
};
|
|
|
|
|
2022-10-11 03:28:23 +00:00
|
|
|
pub const RegistrationOptions = struct {
|
|
|
|
invite_code: ?[]const u8 = null,
|
2022-09-08 07:06:55 +00:00
|
|
|
email: ?[]const u8 = null,
|
2022-09-08 06:56:29 +00:00
|
|
|
};
|
|
|
|
|
2022-10-11 03:28:23 +00:00
|
|
|
pub const InviteOptions = struct {
|
2022-12-21 15:19:13 +00:00
|
|
|
pub const Kind = Invite.Kind;
|
2022-09-08 05:10:58 +00:00
|
|
|
|
|
|
|
name: ?[]const u8 = null,
|
2022-10-04 02:41:59 +00:00
|
|
|
lifespan: ?DateTime.Duration = null,
|
2022-12-12 03:52:11 +00:00
|
|
|
max_uses: ?usize = null,
|
2022-09-08 05:10:58 +00:00
|
|
|
|
2022-10-04 05:57:09 +00:00
|
|
|
// admin only options
|
|
|
|
kind: Kind = .user,
|
|
|
|
to_community: ?Uuid = null,
|
2022-09-07 23:14:52 +00:00
|
|
|
};
|
|
|
|
|
2022-09-08 07:52:23 +00:00
|
|
|
pub const UserResponse = struct {
|
|
|
|
id: Uuid,
|
|
|
|
|
|
|
|
username: []const u8,
|
|
|
|
host: []const u8,
|
|
|
|
|
2022-12-07 07:12:11 +00:00
|
|
|
display_name: ?[]const u8,
|
|
|
|
bio: []const u8,
|
|
|
|
|
|
|
|
avatar_file_id: ?Uuid,
|
2022-12-10 10:51:59 +00:00
|
|
|
avatar_url: []const u8,
|
|
|
|
|
2022-12-07 07:12:11 +00:00
|
|
|
header_file_id: ?Uuid,
|
2022-12-10 10:51:59 +00:00
|
|
|
header_url: ?[]const u8,
|
2022-12-07 07:12:11 +00:00
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
profile_fields: []const Actor.ProfileField,
|
2022-12-07 07:12:11 +00:00
|
|
|
|
|
|
|
community_id: Uuid,
|
|
|
|
|
2022-09-08 07:52:23 +00:00
|
|
|
created_at: DateTime,
|
2022-12-07 07:12:11 +00:00
|
|
|
updated_at: DateTime,
|
2022-09-08 07:52:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pub const NoteResponse = struct {
|
|
|
|
id: Uuid,
|
|
|
|
author: struct {
|
|
|
|
id: Uuid,
|
|
|
|
username: []const u8,
|
|
|
|
host: []const u8,
|
|
|
|
},
|
|
|
|
|
|
|
|
content: []const u8,
|
|
|
|
created_at: DateTime,
|
|
|
|
};
|
|
|
|
|
2022-11-14 07:00:20 +00:00
|
|
|
pub const TimelineArgs = struct {
|
2022-12-21 15:19:13 +00:00
|
|
|
pub const PageDirection = Note.QueryArgs.PageDirection;
|
|
|
|
pub const Prev = Note.QueryArgs.Prev;
|
2022-11-14 07:00:20 +00:00
|
|
|
|
|
|
|
max_items: usize = 20,
|
|
|
|
|
|
|
|
created_before: ?DateTime = null,
|
|
|
|
created_after: ?DateTime = null,
|
|
|
|
|
|
|
|
prev: ?Prev = null,
|
|
|
|
|
|
|
|
page_direction: PageDirection = .forward,
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
fn from(args: Note.QueryArgs) TimelineArgs {
|
2022-11-14 07:00:20 +00:00
|
|
|
return .{
|
|
|
|
.max_items = args.max_items,
|
|
|
|
.created_before = args.created_before,
|
|
|
|
.created_after = args.created_after,
|
|
|
|
.prev = args.prev,
|
|
|
|
.page_direction = args.page_direction,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
pub const TimelineResult = struct {
|
2022-12-21 15:19:13 +00:00
|
|
|
items: []Note,
|
2022-11-14 07:00:20 +00:00
|
|
|
|
|
|
|
prev_page: TimelineArgs,
|
|
|
|
next_page: TimelineArgs,
|
|
|
|
};
|
|
|
|
|
2022-11-14 09:03:11 +00:00
|
|
|
const FollowQueryArgs = struct {
|
2022-11-14 08:14:29 +00:00
|
|
|
pub const OrderBy = services.follows.QueryArgs.OrderBy;
|
|
|
|
pub const Direction = services.follows.QueryArgs.Direction;
|
|
|
|
pub const PageDirection = services.follows.QueryArgs.PageDirection;
|
|
|
|
pub const Prev = services.follows.QueryArgs.Prev;
|
|
|
|
|
|
|
|
max_items: usize = 20,
|
|
|
|
|
|
|
|
order_by: OrderBy = .created_at,
|
|
|
|
|
|
|
|
direction: Direction = .descending,
|
|
|
|
|
|
|
|
prev: ?Prev = null,
|
|
|
|
|
|
|
|
page_direction: PageDirection = .forward,
|
|
|
|
|
|
|
|
fn from(args: services.follows.QueryArgs) FollowQueryArgs {
|
|
|
|
return .{
|
|
|
|
.max_items = args.max_items,
|
|
|
|
.order_by = args.order_by,
|
|
|
|
.direction = args.direction,
|
|
|
|
.prev = args.prev,
|
|
|
|
.page_direction = args.page_direction,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-14 09:03:11 +00:00
|
|
|
const FollowQueryResult = struct {
|
2022-11-14 08:14:29 +00:00
|
|
|
items: []services.follows.Follow,
|
|
|
|
|
|
|
|
prev_page: FollowQueryArgs,
|
|
|
|
next_page: FollowQueryArgs,
|
|
|
|
};
|
|
|
|
|
2022-11-14 09:03:11 +00:00
|
|
|
pub const FollowerQueryArgs = FollowQueryArgs;
|
|
|
|
pub const FollowerQueryResult = FollowQueryResult;
|
|
|
|
pub const FollowingQueryArgs = FollowQueryArgs;
|
|
|
|
pub const FollowingQueryResult = FollowQueryResult;
|
|
|
|
|
2022-12-03 15:09:29 +00:00
|
|
|
pub const UploadFileArgs = struct {
|
|
|
|
filename: []const u8,
|
2022-12-06 09:48:36 +00:00
|
|
|
dir: []const u8,
|
2022-12-03 15:09:29 +00:00
|
|
|
description: ?[]const u8,
|
|
|
|
content_type: []const u8,
|
|
|
|
sensitive: bool,
|
|
|
|
};
|
|
|
|
|
2022-12-07 05:41:01 +00:00
|
|
|
pub const DriveEntry = union(enum) {
|
|
|
|
const Kind = services.drive.Kind;
|
|
|
|
dir: struct {
|
|
|
|
id: Uuid,
|
|
|
|
owner_id: Uuid,
|
|
|
|
name: ?[]const u8,
|
|
|
|
path: []const u8,
|
|
|
|
parent_directory_id: ?Uuid,
|
|
|
|
|
|
|
|
kind: Kind = .dir,
|
|
|
|
|
|
|
|
// If null = not enumerated
|
|
|
|
children: ?[]const DriveEntry,
|
|
|
|
},
|
|
|
|
file: struct {
|
|
|
|
id: Uuid,
|
|
|
|
owner_id: Uuid,
|
|
|
|
name: ?[]const u8,
|
|
|
|
path: []const u8,
|
|
|
|
parent_directory_id: ?Uuid,
|
|
|
|
|
|
|
|
kind: Kind = .file,
|
|
|
|
|
|
|
|
meta: FileUpload,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub const FileUpload = types.FileUpload;
|
2022-12-07 05:41:01 +00:00
|
|
|
|
2022-12-06 09:48:36 +00:00
|
|
|
pub const DriveGetResult = union(services.drive.Kind) {
|
|
|
|
dir: struct {
|
|
|
|
entry: DriveEntry,
|
|
|
|
children: []DriveEntry,
|
|
|
|
},
|
|
|
|
file: struct {
|
|
|
|
entry: DriveEntry,
|
|
|
|
file: FileUpload,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-12-06 10:53:41 +00:00
|
|
|
pub const FileResult = struct {
|
2022-12-21 15:19:13 +00:00
|
|
|
meta: FileUpload,
|
2022-12-06 10:53:41 +00:00
|
|
|
data: []const u8,
|
|
|
|
};
|
|
|
|
|
2022-12-12 03:52:11 +00:00
|
|
|
pub const InviteResponse = struct {
|
2022-12-10 09:41:56 +00:00
|
|
|
code: []const u8,
|
2022-12-21 15:19:13 +00:00
|
|
|
kind: Invite.Kind,
|
2022-12-10 09:41:56 +00:00
|
|
|
name: []const u8,
|
|
|
|
creator: UserResponse,
|
2022-12-12 03:52:11 +00:00
|
|
|
|
|
|
|
url: []const u8,
|
|
|
|
|
|
|
|
community_id: Uuid,
|
|
|
|
|
|
|
|
created_at: DateTime,
|
|
|
|
expires_at: ?DateTime,
|
|
|
|
|
|
|
|
times_used: usize,
|
|
|
|
max_uses: ?usize,
|
2022-12-10 09:41:56 +00:00
|
|
|
};
|
|
|
|
|
2022-10-04 05:41:22 +00:00
|
|
|
pub fn isAdminSetup(db: sql.Db) !bool {
|
2022-09-29 21:52:01 +00:00
|
|
|
_ = services.communities.adminCommunityId(db) catch |err| switch (err) {
|
|
|
|
error.NotFound => return false,
|
|
|
|
else => return err,
|
|
|
|
};
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-10-04 05:41:22 +00:00
|
|
|
pub fn setupAdmin(db: sql.Db, origin: []const u8, username: []const u8, password: []const u8, allocator: std.mem.Allocator) anyerror!void {
|
2022-09-29 21:52:01 +00:00
|
|
|
const tx = try db.begin();
|
|
|
|
errdefer tx.rollback();
|
|
|
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
|
|
defer arena.deinit();
|
|
|
|
|
|
|
|
try tx.setConstraintMode(.deferred);
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
const community_id = try services.communities.create(
|
2022-09-29 21:52:01 +00:00
|
|
|
tx,
|
|
|
|
origin,
|
|
|
|
.{ .name = "Cluster Admin", .kind = .admin },
|
2022-10-04 02:41:59 +00:00
|
|
|
arena.allocator(),
|
2022-09-29 21:52:01 +00:00
|
|
|
);
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
const user = try services.auth.register(tx, username, password, community_id, .{ .kind = .admin }, arena.allocator());
|
2022-09-29 21:52:01 +00:00
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
try services.communities.transferOwnership(tx, community_id, user);
|
2022-09-29 21:52:01 +00:00
|
|
|
|
|
|
|
try tx.commit();
|
|
|
|
|
|
|
|
std.log.info(
|
|
|
|
"Created admin user {s} (id {}) with cluster admin origin {s} (id {})",
|
2022-10-04 02:41:59 +00:00
|
|
|
.{ username, user, origin, community_id },
|
2022-09-29 21:52:01 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-07-26 02:07:05 +00:00
|
|
|
pub const ApiSource = struct {
|
2022-10-13 06:19:59 +00:00
|
|
|
db_conn_pool: *sql.ConnPool,
|
2022-07-15 00:58:08 +00:00
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
pub const Conn = ApiConn(sql.Db, services);
|
2022-07-26 02:07:05 +00:00
|
|
|
|
2022-09-05 08:52:49 +00:00
|
|
|
const root_username = "root";
|
|
|
|
|
2022-10-13 06:19:59 +00:00
|
|
|
pub fn init(pool: *sql.ConnPool) !ApiSource {
|
2022-09-29 21:52:01 +00:00
|
|
|
return ApiSource{
|
2022-10-13 06:19:59 +00:00
|
|
|
.db_conn_pool = pool,
|
2022-09-05 08:52:49 +00:00
|
|
|
};
|
2022-09-07 23:14:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn connectUnauthorized(self: *ApiSource, host: []const u8, alloc: std.mem.Allocator) !Conn {
|
2022-10-13 06:19:59 +00:00
|
|
|
const db = try self.db_conn_pool.acquire();
|
2022-10-16 12:48:12 +00:00
|
|
|
errdefer db.releaseConnection();
|
2022-11-20 23:42:34 +00:00
|
|
|
const community = try services.communities.getByHost(db, host, alloc);
|
2022-09-05 08:52:49 +00:00
|
|
|
|
2022-07-26 02:07:05 +00:00
|
|
|
return Conn{
|
2022-10-04 05:41:22 +00:00
|
|
|
.db = db,
|
2022-09-07 23:14:52 +00:00
|
|
|
.user_id = null,
|
2022-09-29 21:52:01 +00:00
|
|
|
.community = community,
|
2022-11-20 23:42:34 +00:00
|
|
|
.allocator = alloc,
|
2022-07-26 02:07:05 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-09-05 08:52:49 +00:00
|
|
|
pub fn connectToken(self: *ApiSource, host: []const u8, token: []const u8, alloc: std.mem.Allocator) !Conn {
|
2022-10-13 06:19:59 +00:00
|
|
|
const db = try self.db_conn_pool.acquire();
|
2022-10-16 12:48:12 +00:00
|
|
|
errdefer db.releaseConnection();
|
2022-11-20 23:42:34 +00:00
|
|
|
const community = try services.communities.getByHost(db, host, alloc);
|
2022-07-17 23:21:03 +00:00
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
const token_info = try services.auth.verifyToken(
|
2022-10-04 05:41:22 +00:00
|
|
|
db,
|
2022-10-04 02:41:59 +00:00
|
|
|
token,
|
|
|
|
community.id,
|
2022-11-20 23:42:34 +00:00
|
|
|
alloc,
|
2022-10-04 02:41:59 +00:00
|
|
|
);
|
2022-07-17 23:21:03 +00:00
|
|
|
|
2022-09-07 23:14:52 +00:00
|
|
|
return Conn{
|
2022-10-04 05:41:22 +00:00
|
|
|
.db = db,
|
2022-10-04 02:41:59 +00:00
|
|
|
.token_info = token_info,
|
2022-10-12 03:06:29 +00:00
|
|
|
.user_id = token_info.user_id,
|
2022-09-29 21:52:01 +00:00
|
|
|
.community = community,
|
2022-11-20 23:42:34 +00:00
|
|
|
.allocator = alloc,
|
2022-09-07 23:14:52 +00:00
|
|
|
};
|
2022-07-13 03:40:48 +00:00
|
|
|
}
|
2022-07-26 02:07:05 +00:00
|
|
|
};
|
2022-07-13 03:40:48 +00:00
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
fn ApiConn(comptime DbConn: type, comptime models: anytype) type {
|
2022-07-26 02:07:05 +00:00
|
|
|
return struct {
|
|
|
|
const Self = @This();
|
2022-07-18 07:37:10 +00:00
|
|
|
|
2022-07-26 02:07:05 +00:00
|
|
|
db: DbConn,
|
2022-12-21 15:19:13 +00:00
|
|
|
token_info: ?Token.Info = null,
|
2022-10-04 02:41:59 +00:00
|
|
|
user_id: ?Uuid = null,
|
2022-12-21 15:19:13 +00:00
|
|
|
community: Community,
|
2022-11-20 23:42:34 +00:00
|
|
|
allocator: std.mem.Allocator,
|
2022-07-18 06:11:42 +00:00
|
|
|
|
2022-07-26 02:07:05 +00:00
|
|
|
pub fn close(self: *Self) void {
|
2022-11-20 23:42:34 +00:00
|
|
|
util.deepFree(self.allocator, self.community);
|
2022-12-18 14:21:04 +00:00
|
|
|
if (self.token_info) |info| util.deepFree(self.allocator, info);
|
2022-10-13 06:19:59 +00:00
|
|
|
self.db.releaseConnection();
|
2022-07-26 02:07:05 +00:00
|
|
|
}
|
2022-07-18 06:11:42 +00:00
|
|
|
|
2022-09-08 05:10:58 +00:00
|
|
|
fn isAdmin(self: *Self) bool {
|
|
|
|
// TODO
|
2022-09-29 21:52:01 +00:00
|
|
|
return self.user_id != null and self.community.kind == .admin;
|
2022-09-08 05:10:58 +00:00
|
|
|
}
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub fn login(self: *Self, username: []const u8, password: []const u8) !Token {
|
2022-12-21 08:57:36 +00:00
|
|
|
return models.auth.login(
|
2022-10-04 02:41:59 +00:00
|
|
|
self.db,
|
|
|
|
username,
|
|
|
|
self.community.id,
|
|
|
|
password,
|
2022-11-20 23:42:34 +00:00
|
|
|
self.allocator,
|
2022-10-04 02:41:59 +00:00
|
|
|
);
|
2022-07-26 02:07:05 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
pub const AuthorizationInfo = struct {
|
|
|
|
id: Uuid,
|
2022-09-05 10:33:54 +00:00
|
|
|
username: []const u8,
|
2022-10-04 02:41:59 +00:00
|
|
|
community_id: Uuid,
|
|
|
|
host: []const u8,
|
|
|
|
|
|
|
|
issued_at: DateTime,
|
2022-09-05 10:33:54 +00:00
|
|
|
};
|
2022-10-04 02:41:59 +00:00
|
|
|
pub fn verifyAuthorization(self: *Self) !AuthorizationInfo {
|
|
|
|
if (self.token_info) |info| {
|
2022-12-21 08:57:36 +00:00
|
|
|
const user = try models.actors.get(self.db, info.user_id, self.allocator);
|
2022-11-20 23:42:34 +00:00
|
|
|
defer util.deepFree(self.allocator, user);
|
2022-10-04 02:41:59 +00:00
|
|
|
|
2022-12-18 14:21:04 +00:00
|
|
|
const username = try util.deepClone(self.allocator, user.username);
|
|
|
|
errdefer util.deepFree(self.allocator, username);
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
return AuthorizationInfo{
|
|
|
|
.id = user.id,
|
2022-12-18 14:21:04 +00:00
|
|
|
.username = username,
|
2022-10-04 02:41:59 +00:00
|
|
|
.community_id = self.community.id,
|
2022-12-18 14:21:04 +00:00
|
|
|
.host = try util.deepClone(self.allocator, self.community.host),
|
2022-10-04 02:41:59 +00:00
|
|
|
|
|
|
|
.issued_at = info.issued_at,
|
2022-09-05 10:33:54 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-10-08 07:51:22 +00:00
|
|
|
return error.TokenRequired;
|
2022-09-05 10:33:54 +00:00
|
|
|
}
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub fn createCommunity(self: *Self, origin: []const u8, name: ?[]const u8) !Community {
|
2022-09-08 05:10:58 +00:00
|
|
|
if (!self.isAdmin()) {
|
|
|
|
return error.PermissionDenied;
|
2022-09-07 23:14:52 +00:00
|
|
|
}
|
2022-07-26 02:07:05 +00:00
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
const tx = try self.db.begin();
|
|
|
|
errdefer tx.rollback();
|
2022-12-21 08:57:36 +00:00
|
|
|
const community_id = try models.communities.create(
|
2022-10-04 02:41:59 +00:00
|
|
|
tx,
|
|
|
|
origin,
|
2022-12-12 03:52:11 +00:00
|
|
|
.{ .name = name },
|
2022-11-20 23:42:34 +00:00
|
|
|
self.allocator,
|
2022-10-04 02:41:59 +00:00
|
|
|
);
|
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
const community = models.communities.get(
|
2022-10-04 02:41:59 +00:00
|
|
|
tx,
|
|
|
|
community_id,
|
2022-11-20 23:42:34 +00:00
|
|
|
self.allocator,
|
2022-10-04 02:41:59 +00:00
|
|
|
) catch |err| return switch (err) {
|
|
|
|
error.NotFound => error.DatabaseError,
|
|
|
|
else => |err2| err2,
|
|
|
|
};
|
|
|
|
|
|
|
|
try tx.commit();
|
|
|
|
|
|
|
|
return community;
|
2022-07-26 02:07:05 +00:00
|
|
|
}
|
2022-07-27 05:02:09 +00:00
|
|
|
|
2022-12-12 03:52:11 +00:00
|
|
|
pub fn createInvite(self: *Self, options: InviteOptions) !InviteResponse {
|
2022-09-08 05:10:58 +00:00
|
|
|
// Only logged in users can make invites
|
2022-09-08 07:52:23 +00:00
|
|
|
const user_id = self.user_id orelse return error.TokenRequired;
|
2022-07-27 05:02:09 +00:00
|
|
|
|
2022-10-04 05:57:09 +00:00
|
|
|
const community_id = if (options.to_community) |id| blk: {
|
|
|
|
// Only admins can send invites for other communities
|
2022-09-08 05:10:58 +00:00
|
|
|
if (!self.isAdmin()) return error.PermissionDenied;
|
2022-07-27 05:02:09 +00:00
|
|
|
|
2022-10-04 05:57:09 +00:00
|
|
|
break :blk id;
|
2022-09-29 21:52:01 +00:00
|
|
|
} else self.community.id;
|
2022-07-27 05:02:09 +00:00
|
|
|
|
2022-09-08 05:10:58 +00:00
|
|
|
// Users can only make user invites
|
2022-10-04 02:41:59 +00:00
|
|
|
if (options.kind != .user and !self.isAdmin()) return error.PermissionDenied;
|
2022-07-27 05:02:09 +00:00
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
const invite_id = try models.invites.create(self.db, user_id, community_id, options.name orelse "", .{
|
2022-10-04 02:41:59 +00:00
|
|
|
.lifespan = options.lifespan,
|
2022-07-27 05:02:09 +00:00
|
|
|
.max_uses = options.max_uses,
|
2022-10-04 02:41:59 +00:00
|
|
|
.kind = options.kind,
|
2022-11-20 23:42:34 +00:00
|
|
|
}, self.allocator);
|
2022-10-04 02:41:59 +00:00
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
const invite = try models.invites.get(self.db, invite_id, self.allocator);
|
2022-12-12 03:52:11 +00:00
|
|
|
errdefer util.deepFree(self.allocator, invite);
|
|
|
|
|
|
|
|
const url = if (options.to_community) |cid| blk: {
|
2022-12-21 08:57:36 +00:00
|
|
|
const community = try models.communities.get(self.db, cid, self.allocator);
|
2022-12-12 03:52:11 +00:00
|
|
|
defer util.deepFree(self.allocator, community);
|
|
|
|
|
|
|
|
break :blk try std.fmt.allocPrint(
|
|
|
|
self.allocator,
|
|
|
|
"{s}://{s}/invite/{s}",
|
|
|
|
.{ @tagName(community.scheme), community.host, invite.code },
|
|
|
|
);
|
|
|
|
} else try std.fmt.allocPrint(
|
|
|
|
self.allocator,
|
|
|
|
"{s}://{s}/invite/{s}",
|
|
|
|
.{ @tagName(self.community.scheme), self.community.host, invite.code },
|
|
|
|
);
|
|
|
|
errdefer util.deepFree(self.allocator, url);
|
|
|
|
|
|
|
|
const user = try self.getUserUnchecked(self.db, user_id);
|
|
|
|
|
|
|
|
return InviteResponse{
|
|
|
|
.code = invite.code,
|
|
|
|
.kind = invite.kind,
|
|
|
|
.name = invite.name,
|
|
|
|
.creator = user,
|
|
|
|
.url = url,
|
|
|
|
.community_id = invite.community_id,
|
|
|
|
.created_at = invite.created_at,
|
|
|
|
.expires_at = invite.expires_at,
|
|
|
|
.times_used = invite.times_used,
|
|
|
|
.max_uses = invite.max_uses,
|
|
|
|
};
|
2022-07-27 05:02:09 +00:00
|
|
|
}
|
2022-09-08 06:56:29 +00:00
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
fn isInviteValid(invite: Invite) bool {
|
2022-12-10 06:38:04 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-11 03:28:23 +00:00
|
|
|
pub fn register(self: *Self, username: []const u8, password: []const u8, opt: RegistrationOptions) !UserResponse {
|
2022-11-20 23:42:34 +00:00
|
|
|
const tx = try self.db.beginOrSavepoint();
|
2022-10-11 03:28:23 +00:00
|
|
|
const maybe_invite = if (opt.invite_code) |code|
|
2022-12-21 08:57:36 +00:00
|
|
|
try models.invites.getByCode(tx, code, self.community.id, self.allocator)
|
2022-10-11 03:28:23 +00:00
|
|
|
else
|
|
|
|
null;
|
2022-11-20 23:42:34 +00:00
|
|
|
defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv);
|
2022-10-11 03:28:23 +00:00
|
|
|
|
|
|
|
if (maybe_invite) |invite| {
|
|
|
|
if (!Uuid.eql(invite.community_id, self.community.id)) return error.WrongCommunity;
|
2022-12-10 06:38:04 +00:00
|
|
|
if (!isInviteValid(invite)) return error.InvalidInvite;
|
2022-10-11 03:28:23 +00:00
|
|
|
}
|
2022-09-08 06:56:29 +00:00
|
|
|
|
2022-10-11 03:28:23 +00:00
|
|
|
const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
|
2022-09-08 06:56:29 +00:00
|
|
|
|
2022-09-29 21:52:01 +00:00
|
|
|
if (self.community.kind == .admin) @panic("Unimplmented");
|
2022-09-08 06:56:29 +00:00
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
const user_id = try models.auth.register(
|
2022-11-20 23:42:34 +00:00
|
|
|
tx,
|
2022-10-11 03:28:23 +00:00
|
|
|
username,
|
|
|
|
password,
|
2022-10-04 05:50:09 +00:00
|
|
|
self.community.id,
|
2022-11-10 09:53:09 +00:00
|
|
|
.{
|
|
|
|
.invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null,
|
|
|
|
.email = opt.email,
|
|
|
|
},
|
2022-11-20 23:42:34 +00:00
|
|
|
self.allocator,
|
2022-10-04 05:50:09 +00:00
|
|
|
);
|
2022-09-08 06:56:29 +00:00
|
|
|
|
2022-10-11 03:28:23 +00:00
|
|
|
switch (invite_kind) {
|
2022-09-08 06:56:29 +00:00
|
|
|
.user => {},
|
|
|
|
.system => @panic("System user invites unimplemented"),
|
|
|
|
.community_owner => {
|
2022-12-21 08:57:36 +00:00
|
|
|
try models.communities.transferOwnership(tx, self.community.id, user_id);
|
2022-09-08 06:56:29 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-12-10 09:40:53 +00:00
|
|
|
const user = self.getUserUnchecked(tx, user_id) catch |err| switch (err) {
|
|
|
|
error.NotFound => return error.Unexpected,
|
|
|
|
else => |e| return e,
|
2022-09-08 06:56:29 +00:00
|
|
|
};
|
2022-12-10 09:40:53 +00:00
|
|
|
errdefer util.deepFree(self.allocator, user);
|
|
|
|
|
|
|
|
try tx.commit();
|
|
|
|
return user;
|
2022-09-08 06:56:29 +00:00
|
|
|
}
|
2022-09-08 07:52:23 +00:00
|
|
|
|
2022-12-10 09:40:53 +00:00
|
|
|
fn getUserUnchecked(self: *Self, db: anytype, user_id: Uuid) !UserResponse {
|
2022-12-21 08:57:36 +00:00
|
|
|
const user = try models.actors.get(db, user_id, self.allocator);
|
2022-09-08 07:52:23 +00:00
|
|
|
|
2022-12-10 10:51:59 +00:00
|
|
|
const avatar_url = if (user.avatar_file_id) |fid|
|
|
|
|
try std.fmt.allocPrint(
|
|
|
|
self.allocator,
|
|
|
|
"{s}://{s}/media/{}",
|
|
|
|
.{ @tagName(self.community.scheme), self.community.host, fid },
|
|
|
|
)
|
|
|
|
else
|
|
|
|
try std.fmt.allocPrint(
|
|
|
|
self.allocator,
|
|
|
|
"{s}://{s}/{s}",
|
|
|
|
.{ @tagName(self.community.scheme), self.community.host, default_avatar },
|
|
|
|
);
|
|
|
|
errdefer self.allocator.free(avatar_url);
|
|
|
|
const header_url = if (user.header_file_id) |fid|
|
|
|
|
try std.fmt.allocPrint(
|
|
|
|
self.allocator,
|
|
|
|
"{s}://{s}/media/{}",
|
|
|
|
.{ @tagName(self.community.scheme), self.community.host, fid },
|
|
|
|
)
|
|
|
|
else
|
|
|
|
null;
|
|
|
|
|
2022-09-08 07:52:23 +00:00
|
|
|
return UserResponse{
|
|
|
|
.id = user.id,
|
2022-12-07 07:12:11 +00:00
|
|
|
|
2022-09-08 07:52:23 +00:00
|
|
|
.username = user.username,
|
|
|
|
.host = user.host,
|
2022-12-07 07:12:11 +00:00
|
|
|
|
|
|
|
.display_name = user.display_name,
|
|
|
|
.bio = user.bio,
|
|
|
|
|
|
|
|
.avatar_file_id = user.avatar_file_id,
|
2022-12-10 10:51:59 +00:00
|
|
|
.avatar_url = avatar_url,
|
|
|
|
|
2022-12-07 07:12:11 +00:00
|
|
|
.header_file_id = user.header_file_id,
|
2022-12-10 10:51:59 +00:00
|
|
|
.header_url = header_url,
|
2022-12-07 07:12:11 +00:00
|
|
|
|
|
|
|
.profile_fields = user.profile_fields,
|
|
|
|
|
|
|
|
.community_id = user.community_id,
|
|
|
|
|
2022-09-08 07:52:23 +00:00
|
|
|
.created_at = user.created_at,
|
2022-12-07 07:12:11 +00:00
|
|
|
.updated_at = user.updated_at,
|
2022-09-08 07:52:23 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-12-10 06:37:03 +00:00
|
|
|
pub fn getUser(self: *Self, user_id: Uuid) !UserResponse {
|
2022-12-10 09:40:53 +00:00
|
|
|
const user = try self.getUserUnchecked(self.db, user_id);
|
2022-12-10 06:37:03 +00:00
|
|
|
errdefer util.deepFree(self.allocator, user);
|
|
|
|
|
|
|
|
if (self.user_id == null) {
|
|
|
|
if (!Uuid.eql(self.community.id, user.community_id)) return error.NotFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub fn createNote(self: *Self, content: []const u8) !Note {
|
2022-10-08 07:51:22 +00:00
|
|
|
// You cannot post on admin accounts
|
2022-09-29 21:52:01 +00:00
|
|
|
if (self.community.kind == .admin) return error.WrongCommunity;
|
2022-09-08 07:52:23 +00:00
|
|
|
|
2022-10-08 07:51:22 +00:00
|
|
|
// Only authenticated users can post
|
|
|
|
const user_id = self.user_id orelse return error.TokenRequired;
|
2022-12-21 08:57:36 +00:00
|
|
|
const note_id = try models.notes.create(self.db, user_id, content, self.allocator);
|
2022-09-08 07:52:23 +00:00
|
|
|
|
|
|
|
return self.getNote(note_id) catch |err| switch (err) {
|
|
|
|
error.NotFound => error.Unexpected,
|
|
|
|
else => err,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub fn getNote(self: *Self, note_id: Uuid) !Note {
|
2022-12-21 08:57:36 +00:00
|
|
|
const note = try models.notes.get(self.db, note_id, self.allocator);
|
2022-12-21 15:19:13 +00:00
|
|
|
errdefer util.deepFree(self.allocator, note);
|
2022-09-08 07:52:23 +00:00
|
|
|
|
|
|
|
// Only serve community-specific notes on unauthenticated requests
|
|
|
|
if (self.user_id == null) {
|
2022-12-21 15:19:13 +00:00
|
|
|
if (!Uuid.eql(self.community.id, note.author.community_id)) return error.NotFound;
|
2022-09-08 07:52:23 +00:00
|
|
|
}
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
return note;
|
2022-09-08 07:52:23 +00:00
|
|
|
}
|
2022-09-10 04:02:51 +00:00
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub fn queryCommunities(self: *Self, args: Community.QueryArgs) !QueryResult(Community) {
|
2022-09-10 04:02:51 +00:00
|
|
|
if (!self.isAdmin()) return error.PermissionDenied;
|
2022-12-21 08:57:36 +00:00
|
|
|
return try models.communities.query(self.db, args, self.allocator);
|
2022-09-10 04:02:51 +00:00
|
|
|
}
|
2022-11-12 12:39:49 +00:00
|
|
|
|
2022-11-14 07:00:20 +00:00
|
|
|
pub fn globalTimeline(self: *Self, args: TimelineArgs) !TimelineResult {
|
2022-12-21 15:19:13 +00:00
|
|
|
const all_args = std.mem.zeroInit(Note.QueryArgs, args);
|
2022-12-21 08:57:36 +00:00
|
|
|
const result = try models.notes.query(self.db, all_args, self.allocator);
|
2022-11-14 07:00:20 +00:00
|
|
|
return TimelineResult{
|
|
|
|
.items = result.items,
|
|
|
|
.prev_page = TimelineArgs.from(result.prev_page),
|
|
|
|
.next_page = TimelineArgs.from(result.next_page),
|
|
|
|
};
|
2022-11-12 12:39:49 +00:00
|
|
|
}
|
2022-11-12 13:23:55 +00:00
|
|
|
|
2022-11-14 07:00:20 +00:00
|
|
|
pub fn localTimeline(self: *Self, args: TimelineArgs) !TimelineResult {
|
2022-12-21 15:19:13 +00:00
|
|
|
var all_args = std.mem.zeroInit(Note.QueryArgs, args);
|
2022-11-14 07:00:20 +00:00
|
|
|
all_args.community_id = self.community.id;
|
2022-12-21 08:57:36 +00:00
|
|
|
const result = try models.notes.query(self.db, all_args, self.allocator);
|
2022-11-14 07:00:20 +00:00
|
|
|
return TimelineResult{
|
|
|
|
.items = result.items,
|
|
|
|
.prev_page = TimelineArgs.from(result.prev_page),
|
|
|
|
.next_page = TimelineArgs.from(result.next_page),
|
|
|
|
};
|
2022-11-12 13:23:55 +00:00
|
|
|
}
|
2022-11-14 08:14:29 +00:00
|
|
|
|
2022-11-14 23:00:01 +00:00
|
|
|
pub fn homeTimeline(self: *Self, args: TimelineArgs) !TimelineResult {
|
|
|
|
if (self.user_id == null) return error.NoToken;
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
var all_args = std.mem.zeroInit(Note.QueryArgs, args);
|
2022-11-14 23:00:01 +00:00
|
|
|
all_args.followed_by = self.user_id;
|
2022-12-21 08:57:36 +00:00
|
|
|
const result = try models.notes.query(self.db, all_args, self.allocator);
|
2022-11-14 23:00:01 +00:00
|
|
|
return TimelineResult{
|
|
|
|
.items = result.items,
|
|
|
|
.prev_page = TimelineArgs.from(result.prev_page),
|
|
|
|
.next_page = TimelineArgs.from(result.next_page),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-11-14 09:03:11 +00:00
|
|
|
pub fn queryFollowers(self: *Self, user_id: Uuid, args: FollowerQueryArgs) !FollowerQueryResult {
|
2022-12-21 08:57:36 +00:00
|
|
|
var all_args = std.mem.zeroInit(models.follows.QueryArgs, args);
|
2022-11-15 04:25:59 +00:00
|
|
|
all_args.followee_id = user_id;
|
2022-12-21 08:57:36 +00:00
|
|
|
const result = try models.follows.query(self.db, all_args, self.allocator);
|
2022-11-14 09:03:11 +00:00
|
|
|
return FollowerQueryResult{
|
2022-11-14 08:14:29 +00:00
|
|
|
.items = result.items,
|
|
|
|
.prev_page = FollowQueryArgs.from(result.prev_page),
|
|
|
|
.next_page = FollowQueryArgs.from(result.next_page),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-11-14 09:03:11 +00:00
|
|
|
pub fn queryFollowing(self: *Self, user_id: Uuid, args: FollowingQueryArgs) !FollowingQueryResult {
|
2022-12-21 08:57:36 +00:00
|
|
|
var all_args = std.mem.zeroInit(models.follows.QueryArgs, args);
|
2022-11-15 04:25:59 +00:00
|
|
|
all_args.followed_by_id = user_id;
|
2022-12-21 08:57:36 +00:00
|
|
|
const result = try models.follows.query(self.db, all_args, self.allocator);
|
2022-11-14 09:03:11 +00:00
|
|
|
return FollowingQueryResult{
|
2022-11-14 08:14:29 +00:00
|
|
|
.items = result.items,
|
|
|
|
.prev_page = FollowQueryArgs.from(result.prev_page),
|
|
|
|
.next_page = FollowQueryArgs.from(result.next_page),
|
|
|
|
};
|
|
|
|
}
|
2022-11-14 09:03:11 +00:00
|
|
|
|
|
|
|
pub fn follow(self: *Self, followee: Uuid) !void {
|
2022-12-21 08:57:36 +00:00
|
|
|
const result = try models.follows.create(self.db, self.user_id orelse return error.NoToken, followee, self.allocator);
|
2022-11-20 23:42:34 +00:00
|
|
|
defer util.deepFree(self.allocator, result);
|
2022-11-14 09:03:11 +00:00
|
|
|
}
|
2022-11-19 11:13:05 +00:00
|
|
|
|
2022-11-19 11:33:35 +00:00
|
|
|
pub fn unfollow(self: *Self, followee: Uuid) !void {
|
2022-12-21 08:57:36 +00:00
|
|
|
const result = try models.follows.delete(self.db, self.user_id orelse return error.NoToken, followee, self.allocator);
|
2022-11-20 23:42:34 +00:00
|
|
|
defer util.deepFree(self.allocator, result);
|
2022-11-19 11:33:35 +00:00
|
|
|
}
|
|
|
|
|
2022-11-19 11:13:05 +00:00
|
|
|
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'
|
|
|
|
,
|
|
|
|
.{},
|
2022-11-20 23:42:34 +00:00
|
|
|
self.allocator,
|
2022-11-19 11:13:05 +00:00
|
|
|
);
|
|
|
|
}
|
2022-12-03 07:44:27 +00:00
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
fn backendDriveEntryToFrontend(self: *Self, entry: models.drive.Entry, recurse: bool) !DriveEntry {
|
2022-12-07 05:41:01 +00:00
|
|
|
return if (entry.file_id) |file_id| .{
|
|
|
|
.file = .{
|
|
|
|
.id = entry.id,
|
|
|
|
.owner_id = entry.owner_id,
|
|
|
|
.name = entry.name,
|
|
|
|
.path = entry.path,
|
|
|
|
.parent_directory_id = entry.parent_directory_id,
|
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
.meta = try models.files.get(self.db, file_id, self.allocator),
|
2022-12-07 05:41:01 +00:00
|
|
|
},
|
|
|
|
} else .{
|
|
|
|
.dir = .{
|
|
|
|
.id = entry.id,
|
|
|
|
.owner_id = entry.owner_id,
|
|
|
|
.name = entry.name,
|
|
|
|
.path = entry.path,
|
|
|
|
.parent_directory_id = entry.parent_directory_id,
|
|
|
|
|
|
|
|
.children = blk: {
|
|
|
|
if (!recurse) break :blk null;
|
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
const children = try models.drive.list(self.db, entry.id, self.allocator);
|
2022-12-07 05:41:01 +00:00
|
|
|
|
|
|
|
const result = self.allocator.alloc(DriveEntry, children.len) catch |err| {
|
|
|
|
util.deepFree(self.allocator, children);
|
|
|
|
return err;
|
|
|
|
};
|
|
|
|
var count: usize = 0;
|
|
|
|
errdefer for (children) |child, i| {
|
|
|
|
if (i < count)
|
|
|
|
util.deepFree(self.allocator, result[i])
|
|
|
|
else
|
|
|
|
util.deepFree(self.allocator, child);
|
|
|
|
};
|
|
|
|
defer self.allocator.free(children);
|
|
|
|
errdefer self.allocator.free(result);
|
|
|
|
|
|
|
|
for (children) |child, i| {
|
|
|
|
result[i] = try self.backendDriveEntryToFrontend(child, false);
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break :blk result;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn driveUpload(self: *Self, meta: UploadFileArgs, body: []const u8) !DriveEntry {
|
2022-12-03 15:09:29 +00:00
|
|
|
const user_id = self.user_id orelse return error.NoToken;
|
2022-12-21 08:57:36 +00:00
|
|
|
const file_id = try models.files.create(self.db, user_id, .{
|
2022-12-03 15:09:29 +00:00
|
|
|
.filename = meta.filename,
|
|
|
|
.description = meta.description,
|
|
|
|
.content_type = meta.content_type,
|
|
|
|
.sensitive = meta.sensitive,
|
|
|
|
}, body, self.allocator);
|
2022-12-06 09:48:36 +00:00
|
|
|
|
2022-12-07 05:41:01 +00:00
|
|
|
const entry = entry: {
|
2022-12-21 08:57:36 +00:00
|
|
|
errdefer models.files.delete(self.db, file_id, self.allocator) catch |err| {
|
2022-12-07 05:41:01 +00:00
|
|
|
std.log.err("Unable to delete file {}: {}", .{ file_id, err });
|
|
|
|
};
|
2022-12-06 09:48:36 +00:00
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
break :entry models.drive.create(
|
2022-12-07 05:41:01 +00:00
|
|
|
self.db,
|
|
|
|
user_id,
|
|
|
|
meta.dir,
|
|
|
|
meta.filename,
|
|
|
|
file_id,
|
|
|
|
self.allocator,
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
error.PathAlreadyExists => {
|
|
|
|
var buf: [256]u8 = undefined;
|
|
|
|
var split = std.mem.splitBackwards(u8, meta.filename, ".");
|
|
|
|
const ext = split.first();
|
|
|
|
const name = split.rest();
|
|
|
|
const new_name = try std.fmt.bufPrint(&buf, "{s}.{s}.{s}", .{ name, file_id, ext });
|
|
|
|
|
2022-12-21 08:57:36 +00:00
|
|
|
break :entry try models.drive.create(
|
2022-12-07 05:41:01 +00:00
|
|
|
self.db,
|
|
|
|
user_id,
|
|
|
|
meta.dir,
|
|
|
|
new_name,
|
|
|
|
file_id,
|
|
|
|
self.allocator,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
2022-12-06 09:48:36 +00:00
|
|
|
};
|
2022-12-07 05:41:01 +00:00
|
|
|
errdefer util.deepFree(self.allocator, entry);
|
|
|
|
|
|
|
|
return try self.backendDriveEntryToFrontend(entry, true);
|
2022-12-03 15:09:29 +00:00
|
|
|
}
|
|
|
|
|
2022-12-15 14:29:56 +00:00
|
|
|
pub fn driveMkdir(self: *Self, parent_path: []const u8, name: []const u8) !DriveEntry {
|
2022-12-03 07:44:27 +00:00
|
|
|
const user_id = self.user_id orelse return error.NoToken;
|
2022-12-21 08:57:36 +00:00
|
|
|
const entry = try models.drive.create(self.db, user_id, parent_path, name, null, self.allocator);
|
2022-12-07 05:41:01 +00:00
|
|
|
errdefer util.deepFree(self.allocator, entry);
|
|
|
|
return try self.backendDriveEntryToFrontend(entry, true);
|
2022-12-06 09:48:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn driveDelete(self: *Self, path: []const u8) !void {
|
|
|
|
const user_id = self.user_id orelse return error.NoToken;
|
2022-12-21 08:57:36 +00:00
|
|
|
const entry = try models.drive.stat(self.db, user_id, path, self.allocator);
|
2022-12-06 09:48:36 +00:00
|
|
|
defer util.deepFree(self.allocator, entry);
|
2022-12-21 08:57:36 +00:00
|
|
|
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);
|
2022-12-06 09:48:36 +00:00
|
|
|
}
|
|
|
|
|
2022-12-07 05:41:01 +00:00
|
|
|
pub fn driveMove(self: *Self, src: []const u8, dest: []const u8) !DriveEntry {
|
2022-12-06 09:48:36 +00:00
|
|
|
const user_id = self.user_id orelse return error.NoToken;
|
2022-12-21 08:57:36 +00:00
|
|
|
try models.drive.move(self.db, user_id, src, dest, self.allocator);
|
2022-12-07 05:41:01 +00:00
|
|
|
|
|
|
|
return try self.driveGet(dest);
|
2022-12-06 09:48:36 +00:00
|
|
|
}
|
|
|
|
|
2022-12-07 05:41:01 +00:00
|
|
|
pub fn driveGet(self: *Self, path: []const u8) !DriveEntry {
|
2022-12-06 09:48:36 +00:00
|
|
|
const user_id = self.user_id orelse return error.NoToken;
|
2022-12-21 08:57:36 +00:00
|
|
|
const entry = try models.drive.stat(self.db, user_id, path, self.allocator);
|
2022-12-06 09:48:36 +00:00
|
|
|
errdefer util.deepFree(self.allocator, entry);
|
|
|
|
|
2022-12-07 05:41:01 +00:00
|
|
|
return try self.backendDriveEntryToFrontend(entry, true);
|
2022-12-06 09:48:36 +00:00
|
|
|
}
|
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub fn driveUpdate(self: *Self, path: []const u8, meta: FileUpload.UpdateArgs) !DriveEntry {
|
2022-12-06 09:48:36 +00:00
|
|
|
const user_id = self.user_id orelse return error.NoToken;
|
|
|
|
std.log.debug("{s}", .{path});
|
2022-12-21 08:57:36 +00:00
|
|
|
const entry = try models.drive.stat(self.db, user_id, path, self.allocator);
|
2022-12-07 05:41:01 +00:00
|
|
|
defer util.deepFree(self.allocator, entry);
|
2022-12-06 09:48:36 +00:00
|
|
|
|
|
|
|
std.log.debug("{}", .{entry.id});
|
2022-12-21 08:57:36 +00:00
|
|
|
try models.files.update(self.db, entry.file_id orelse return error.NotAFile, meta, self.allocator);
|
2022-12-07 05:41:01 +00:00
|
|
|
|
|
|
|
return try self.driveGet(path);
|
2022-12-03 07:44:27 +00:00
|
|
|
}
|
2022-12-06 10:53:41 +00:00
|
|
|
|
|
|
|
pub fn fileDereference(self: *Self, id: Uuid) !FileResult {
|
2022-12-21 08:57:36 +00:00
|
|
|
const meta = try models.files.get(self.db, id, self.allocator);
|
2022-12-06 10:53:41 +00:00
|
|
|
errdefer util.deepFree(self.allocator, meta);
|
|
|
|
|
|
|
|
return FileResult{
|
|
|
|
.meta = meta,
|
2022-12-21 08:57:36 +00:00
|
|
|
.data = try models.files.deref(self.allocator, id),
|
2022-12-06 10:53:41 +00:00
|
|
|
};
|
|
|
|
}
|
2022-12-07 09:59:46 +00:00
|
|
|
|
2022-12-21 15:19:13 +00:00
|
|
|
pub fn updateUserProfile(self: *Self, id: Uuid, data: Actor.ProfileUpdateArgs) !void {
|
2022-12-07 09:59:46 +00:00
|
|
|
if (!Uuid.eql(id, self.user_id orelse return error.NoToken)) return error.AccessDenied;
|
2022-12-21 08:57:36 +00:00
|
|
|
try models.actors.updateProfile(self.db, id, data, self.allocator);
|
2022-12-07 09:59:46 +00:00
|
|
|
}
|
2022-12-10 09:41:56 +00:00
|
|
|
|
2022-12-12 03:52:11 +00:00
|
|
|
pub fn validateInvite(self: *Self, code: []const u8) !InviteResponse {
|
2022-12-21 08:57:36 +00:00
|
|
|
const invite = models.invites.getByCode(
|
2022-12-10 09:41:56 +00:00
|
|
|
self.db,
|
|
|
|
code,
|
|
|
|
self.community.id,
|
|
|
|
self.allocator,
|
|
|
|
) catch |err| switch (err) {
|
|
|
|
error.NotFound => return error.InvalidInvite,
|
|
|
|
else => return error.DatabaseFailure,
|
|
|
|
};
|
|
|
|
errdefer util.deepFree(self.allocator, invite);
|
|
|
|
|
|
|
|
if (!Uuid.eql(invite.community_id, self.community.id)) return error.InvalidInvite;
|
|
|
|
if (!isInviteValid(invite)) return error.InvalidInvite;
|
|
|
|
|
2022-12-12 03:52:11 +00:00
|
|
|
const url = try std.fmt.allocPrint(
|
|
|
|
self.allocator,
|
|
|
|
"{s}://{s}/invite/{s}",
|
|
|
|
.{ @tagName(self.community.scheme), self.community.host, invite.code },
|
|
|
|
);
|
|
|
|
errdefer util.deepFree(self.allocator, url);
|
|
|
|
|
2022-12-10 09:41:56 +00:00
|
|
|
const creator = self.getUserUnchecked(self.db, invite.created_by) catch |err| switch (err) {
|
|
|
|
error.NotFound => return error.Unexpected,
|
|
|
|
else => return error.DatabaseFailure,
|
|
|
|
};
|
|
|
|
|
2022-12-12 03:52:11 +00:00
|
|
|
return InviteResponse{
|
2022-12-10 09:41:56 +00:00
|
|
|
.code = invite.code,
|
|
|
|
.name = invite.name,
|
|
|
|
.kind = invite.kind,
|
|
|
|
.creator = creator,
|
2022-12-12 03:52:11 +00:00
|
|
|
|
|
|
|
.url = url,
|
|
|
|
|
|
|
|
.community_id = invite.community_id,
|
|
|
|
|
|
|
|
.created_at = invite.created_at,
|
|
|
|
.expires_at = invite.expires_at,
|
|
|
|
|
|
|
|
.times_used = invite.times_used,
|
|
|
|
.max_uses = invite.max_uses,
|
2022-12-10 09:41:56 +00:00
|
|
|
};
|
|
|
|
}
|
2022-07-26 02:07:05 +00:00
|
|
|
};
|
|
|
|
}
|
2022-12-21 15:19:13 +00:00
|
|
|
|
|
|
|
// test "register" {
|
|
|
|
// const TestDb = void;
|
|
|
|
// const exp_code = "abcd";
|
|
|
|
// const exp_community = Uuid.parse("a210c035-c9e1-4361-82a2-aaeac8e40dc6") catch unreachable;
|
|
|
|
// var conn = ApiConn(TestDb, struct {
|
|
|
|
// const invites = struct {
|
|
|
|
// fn getByCode(_: TestDb, code: []const u8, community_id: Uuid, alloc: std.mem.Allocator) !services.invites.Invite {
|
|
|
|
// try std.testing.expectEqualStrings(exp_code, code);
|
|
|
|
// try std.testing.expectEqual(exp_community, community_id);
|
|
|
|
|
|
|
|
// return try util.deepClone(alloc, services.invites.Invite{
|
|
|
|
// .id = Uuid.parse("eac18f43-4dcc-489f-9fb5-4c1633e7b4e0") catch unreachable,
|
|
|
|
|
|
|
|
// .created_by = Uuid.parse("6d951fcc-1c9f-497b-9c96-31dfb9873708") catch unreachable,
|
|
|
|
// .community_id = exp_community,
|
|
|
|
// .name = "test invite",
|
|
|
|
// .code = exp_code,
|
|
|
|
|
|
|
|
// .created_at = DateTime.parse("2022-12-21T09:05:50Z") catch unreachable,
|
|
|
|
// .times_used = 0,
|
|
|
|
|
|
|
|
// .expires_at = null,
|
|
|
|
// .max_uses = null,
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
// const auth = struct {
|
|
|
|
// fn register(
|
|
|
|
// _: TestDb,
|
|
|
|
// username: []const u8,
|
|
|
|
// password: []const u8,
|
|
|
|
// community_id: Uuid,
|
|
|
|
// _: RegistrationOptions,
|
|
|
|
// _: std.mem.Allocator,
|
|
|
|
// ) !Uuid {
|
|
|
|
// try std.testing.expectEqualStrings("root", username);
|
|
|
|
// try std.testing.expectEqualStrings("password", password);
|
|
|
|
// try std.testing.expectEqual(exp_community, community_id);
|
|
|
|
|
|
|
|
// return Uuid.parse("6d951fcc-1c9f-497b-9c96-31dfb9873708") catch unreachable;
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
// }){};
|
|
|
|
// defer conn.close();
|
|
|
|
|
|
|
|
// const result = try conn.register("root", "password", .{});
|
|
|
|
// try std.allocator.
|
|
|
|
|
|
|
|
// }
|