From 39565bccf00601d3c14ef02a4f9e5328aa805213 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Mon, 2 Jan 2023 17:21:08 -0800 Subject: [PATCH] fuck --- src/api/lib.zig | 156 ++----------- src/api/methods/auth.zig | 12 +- src/api/methods/communities.zig | 45 ++++ src/api/methods/timelines.zig | 91 ++++++++ src/api/services.zig | 254 ++++++++++++++++++--- src/api/services/accounts.zig | 17 +- src/api/services/actors.zig | 2 +- src/api/services/communities.zig | 13 +- src/api/services/files.zig | 2 +- src/api/services/invites.zig | 4 +- src/api/services/notes.zig | 3 +- src/api/services/tokens.zig | 7 +- src/api/services/types.zig | 373 +++++++++++++++++++++++++++++++ src/api/types.zig | 363 +----------------------------- 14 files changed, 785 insertions(+), 557 deletions(-) create mode 100644 src/api/methods/communities.zig create mode 100644 src/api/methods/timelines.zig create mode 100644 src/api/services/types.zig diff --git a/src/api/lib.zig b/src/api/lib.zig index 5f0ddcf..e6a815f 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -1,35 +1,19 @@ 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; const default_avatar = "static/default_avi.png"; -const services = struct { - pub const communities = @import("./services/communities.zig"); - pub const actors = @import("./services/actors.zig"); - pub const drive = @import("./services/drive.zig"); - pub const files = @import("./services/files.zig"); - pub const invites = @import("./services/invites.zig"); - pub const notes = @import("./services/notes.zig"); - pub const follows = @import("./services/follows.zig"); - - pub const accounts = @import("./services/accounts.zig"); - pub const tokens = @import("./services/tokens.zig"); -}; - -test { - _ = @import("./methods/auth.zig"); -} - -const types = @import("./types.zig"); - -pub const QueryResult = types.QueryResult; +const QueryResult = types.QueryResult; +pub usingnamespace types; pub const Account = types.Account; -pub const Actor = types.Actor; +pub const Actor = types.actors.Actor; pub const Community = types.Community; pub const Invite = types.Invite; pub const Note = types.Note; @@ -93,37 +77,6 @@ pub const NoteResponse = struct { created_at: DateTime, }; -pub const TimelineArgs = struct { - pub const PageDirection = Note.QueryArgs.PageDirection; - pub const Prev = Note.QueryArgs.Prev; - - max_items: usize = 20, - - created_before: ?DateTime = null, - created_after: ?DateTime = null, - - prev: ?Prev = null, - - page_direction: PageDirection = .forward, - - fn from(args: Note.QueryArgs) TimelineArgs { - 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 { - items: []Note, - - prev_page: TimelineArgs, - next_page: TimelineArgs, -}; - const FollowQueryArgs = struct { pub const OrderBy = services.follows.QueryArgs.OrderBy; pub const Direction = services.follows.QueryArgs.Direction; @@ -322,9 +275,15 @@ pub const ApiContext = struct { } }; +const methods = struct { + const auth = @import("./methods/auth.zig"); + const timelines = @import("./methods/timelines.zig"); +}; + fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return struct { const Self = @This(); + const Services = @import("./services.zig").Services(DbConn); db: DbConn, context: ApiContext, @@ -341,6 +300,10 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return self.context.userId() != null and self.context.community.kind == .admin; } + fn getServices(self: *Self) Services { + return Services{ .db = self.db }; + } + pub const AuthorizationInfo = struct { id: Uuid, username: []const u8, @@ -459,17 +422,10 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return true; } - const Services = @import("./services.zig").Services(DbConn); - fn getServices(self: *Self) Services { - return Services{ .db = self.db }; - } pub fn login(self: *Self, username: []const u8, password: []const u8) !Token { return methods.auth.login(self.allocator, self.context, self.getServices(), username, password); } - const methods = struct { - const auth = @import("./methods/auth.zig"); - }; pub fn register( self: *Self, username: []const u8, @@ -479,54 +435,6 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return methods.auth.register(self.allocator, self.context, self.getServices(), username, password, opt); } - //pub usingnamespace @import("./methods/auth.zig").methods(models); - // 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 models.invites.getByCode(tx, code, self.context.community.id, self.allocator) - // else - // null; - // defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv); - - // if (maybe_invite) |invite| { - // if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.WrongCommunity; - // if (!isInviteValid(invite)) return error.InvalidInvite; - // } - - // const invite_kind = if (maybe_invite) |inv| inv.kind else .user; - - // if (self.context.community.kind == .admin) @panic("Unimplmented"); - - // const user_id = try models.auth.register( - // tx, - // username, - // password, - // self.context.community.id, - // .{ - // .invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null, - // .email = opt.email, - // }, - // self.allocator, - // ); - - // switch (invite_kind) { - // .user => {}, - // .system => @panic("System user invites unimplemented"), - // .community_owner => { - // try models.communities.transferOwnership(tx, self.context.community.id, user_id); - // }, - // } - - // const user = self.getUserUnchecked(tx, user_id) catch |err| switch (err) { - // error.NotFound => return error.Unexpected, - // else => |e| return e, - // }; - // errdefer util.deepFree(self.allocator, user); - - // try tx.commit(); - // return user; - // } - fn getUserUnchecked(self: *Self, db: anytype, user_id: Uuid) !UserResponse { const user = try models.actors.get(db, user_id, self.allocator); @@ -618,38 +526,16 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return try models.communities.query(self.db, args, self.allocator); } - pub fn globalTimeline(self: *Self, args: TimelineArgs) !TimelineResult { - const all_args = std.mem.zeroInit(Note.QueryArgs, args); - const result = try models.notes.query(self.db, all_args, self.allocator); - return TimelineResult{ - .items = result.items, - .prev_page = TimelineArgs.from(result.prev_page), - .next_page = TimelineArgs.from(result.next_page), - }; + pub fn globalTimeline(self: *Self, args: services.timelines.TimelineArgs) !methods.timelines.TimelineResult { + return methods.timelines.globalTimeline(self.allocator, self.context, self.getServices(), args); } - pub fn localTimeline(self: *Self, args: TimelineArgs) !TimelineResult { - var all_args = std.mem.zeroInit(Note.QueryArgs, args); - all_args.community_id = self.context.community.id; - const result = try models.notes.query(self.db, all_args, self.allocator); - return TimelineResult{ - .items = result.items, - .prev_page = TimelineArgs.from(result.prev_page), - .next_page = TimelineArgs.from(result.next_page), - }; + pub fn localTimeline(self: *Self, args: services.timelines.TimelineArgs) !methods.timelines.TimelineResult { + return methods.timelines.localTimeline(self.allocator, self.context, self.getServices(), args); } - pub fn homeTimeline(self: *Self, args: TimelineArgs) !TimelineResult { - if (self.context.userId() == null) return error.NoToken; - - var all_args = std.mem.zeroInit(Note.QueryArgs, args); - all_args.followed_by = self.context.userId(); - const result = try models.notes.query(self.db, all_args, self.allocator); - return TimelineResult{ - .items = result.items, - .prev_page = TimelineArgs.from(result.prev_page), - .next_page = TimelineArgs.from(result.next_page), - }; + pub fn homeTimeline(self: *Self, args: services.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: FollowerQueryArgs) !FollowerQueryResult { diff --git a/src/api/methods/auth.zig b/src/api/methods/auth.zig index 2a7316c..5639385 100644 --- a/src/api/methods/auth.zig +++ b/src/api/methods/auth.zig @@ -1,18 +1,21 @@ const std = @import("std"); const util = @import("util"); const types = @import("../types.zig"); +const pkg = @import("../lib.zig"); +const services = @import("../services.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; -const Invite = @import("../lib.zig").Invite; -pub const Token = types.Token; +const ApiContext = pkg.ApiContext; +const Invite = types.Invite; +const Token = types.Token; -pub const RegistrationOptions = struct { +const RegistrationOptions = struct { invite_code: ?[]const u8 = null, email: ?[]const u8 = null, }; -pub const AccountCreateOptions = @import("../services/accounts.zig").CreateOptions; +const AccountCreateOptions = services.accounts.CreateOptions; fn isInviteValid(invite: Invite) bool { if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false; @@ -108,7 +111,6 @@ pub fn verifyToken(alloc: std.mem.Allocator, ctx: ApiContext, svcs: anytype, tok return .{ .user_id = info.account_id, .issued_at = info.issued_at }; } -const ApiContext = @import("../lib.zig").ApiContext; pub fn login( alloc: std.mem.Allocator, ctx: ApiContext, diff --git a/src/api/methods/communities.zig b/src/api/methods/communities.zig new file mode 100644 index 0000000..466c120 --- /dev/null +++ b/src/api/methods/communities.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const util = @import("util"); +const types = @import("../types.zig"); + +const Uuid = util.Uuid; +const DateTime = util.DateTime; +const QueryResult = types.QueryResult; +const Community = types.Community; + +pub fn methods(comptime models: type) type { + return struct { + pub fn createCommunity(self: anytype, origin: []const u8, name: ?[]const u8) !Community { + if (!self.isAdmin()) { + return error.PermissionDenied; + } + + const tx = try self.db.begin(); + errdefer tx.rollback(); + const community_id = try models.communities.create( + tx, + origin, + .{ .name = name }, + self.allocator, + ); + + const community = models.communities.get( + tx, + community_id, + self.allocator, + ) catch |err| return switch (err) { + error.NotFound => error.DatabaseError, + else => |err2| err2, + }; + + try tx.commit(); + + return community; + } + + pub fn queryCommunities(self: anytype, args: Community.QueryArgs) !QueryResult(Community) { + if (!self.context.isAdmin()) return error.PermissionDenied; + return try models.communities.query(self.db, args, self.allocator); + } + }; +} diff --git a/src/api/methods/timelines.zig b/src/api/methods/timelines.zig new file mode 100644 index 0000000..a6a6f47 --- /dev/null +++ b/src/api/methods/timelines.zig @@ -0,0 +1,91 @@ +const std = @import("std"); +const util = @import("util"); +const pkg = @import("../lib.zig"); + +const ApiContext = pkg.ApiContext; +const Uuid = util.Uuid; +const DateTime = util.DateTime; + +const services = @import("../services.zig"); +const Note = services.Note; +const QueryArgs = services.notes.QueryArgs; + +pub const TimelineArgs = struct { + pub const PageDirection = QueryArgs.PageDirection; + pub const Prev = QueryArgs.Prev; + + max_items: usize = 20, + + created_before: ?DateTime = null, + created_after: ?DateTime = null, + + prev: ?Prev = null, + + page_direction: PageDirection = .forward, + + fn from(args: QueryArgs) TimelineArgs { + 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 { + items: []Note, + + prev_page: TimelineArgs, + next_page: TimelineArgs, +}; + +pub fn globalTimeline( + alloc: std.mem.Allocator, + _: ApiContext, + svcs: anytype, + args: TimelineArgs, +) !TimelineResult { + const all_args = std.mem.zeroInit(QueryArgs, args); + const result = try svcs.queryNotes(alloc, all_args); + return TimelineResult{ + .items = result.items, + .prev_page = TimelineArgs.from(result.prev_page), + .next_page = TimelineArgs.from(result.next_page), + }; +} + +pub fn localTimeline( + alloc: std.mem.Allocator, + ctx: ApiContext, + svcs: anytype, + args: TimelineArgs, +) !TimelineResult { + var all_args = std.mem.zeroInit(QueryArgs, args); + all_args.community_id = ctx.community.id; + const result = try svcs.queryNotes(alloc, all_args); + return TimelineResult{ + .items = result.items, + .prev_page = TimelineArgs.from(result.prev_page), + .next_page = TimelineArgs.from(result.next_page), + }; +} + +pub fn homeTimeline( + alloc: std.mem.Allocator, + ctx: ApiContext, + svcs: anytype, + args: TimelineArgs, +) !TimelineResult { + if (ctx.userId() == null) return error.NoToken; + + var all_args = std.mem.zeroInit(QueryArgs, args); + all_args.followed_by = ctx.userId(); + const result = try svcs.queryNotes(alloc, all_args); + return TimelineResult{ + .items = result.items, + .prev_page = TimelineArgs.from(result.prev_page), + .next_page = TimelineArgs.from(result.next_page), + }; +} diff --git a/src/api/services.zig b/src/api/services.zig index cee6c94..82347eb 100644 --- a/src/api/services.zig +++ b/src/api/services.zig @@ -4,21 +4,30 @@ const util = @import("util"); const Uuid = util.Uuid; const DateTime = util.DateTime; -const communities = @import("./services/communities.zig"); -const actors = @import("./services/actors.zig"); -const drive = @import("./services/drive.zig"); -const files = @import("./services/files.zig"); -const invites = @import("./services/invites.zig"); -const notes = @import("./services/notes.zig"); -const follows = @import("./services/follows.zig"); -const accounts = @import("./services/accounts.zig"); -const tokens = @import("./services/tokens.zig"); +const impl = struct { + const communities = @import("./services/communities.zig"); + const actors = @import("./services/actors.zig"); + const drive = @import("./services/drive.zig"); + const files = @import("./services/files.zig"); + const invites = @import("./services/invites.zig"); + const notes = @import("./services/notes.zig"); + const follows = @import("./services/follows.zig"); + const accounts = @import("./services/accounts.zig"); + const tokens = @import("./services/tokens.zig"); +}; -pub const Account = accounts.Account; -pub const Credentials = accounts.Credentials; -pub const Actor = actors.Actor; -pub const Invite = invites.Invite; -pub const Token = tokens.Token; +const types = @import("./services/types.zig"); +pub usingnamespace types; + +pub const Account = types.accounts.Account; +pub const Credentials = types.accounts.Credentials; +pub const Actor = types.actors.Actor; +pub const Community = types.communities.Community; +pub const DriveEntry = types.drive.DriveEntry; +pub const FileUpload = types.files.FileUpload; +pub const Invite = types.invites.Invite; +pub const Note = types.notes.Note; +pub const Token = types.tokens.Token; pub fn Services(comptime Db: type) type { return struct { @@ -45,9 +54,9 @@ pub fn Services(comptime Db: type) type { alloc: std.mem.Allocator, actor: Uuid, password_hash: []const u8, - options: accounts.CreateOptions, + options: types.accounts.CreateOptions, ) !void { - return try accounts.create(self.db, actor, password_hash, options, alloc); + return try impl.accounts.create(self.db, actor, password_hash, options, alloc); } pub fn getCredentialsByUsername( @@ -56,7 +65,7 @@ pub fn Services(comptime Db: type) type { username: []const u8, community_id: Uuid, ) !Credentials { - return try accounts.getCredentialsByUsername(self.db, username, community_id, alloc); + return try impl.accounts.getCredentialsByUsername(self.db, username, community_id, alloc); } pub fn createActor( @@ -66,7 +75,7 @@ pub fn Services(comptime Db: type) type { community_id: Uuid, lax_username: bool, // TODO: remove this ) !Uuid { - return try actors.create(self.db, username, community_id, lax_username, alloc); + return try impl.actors.create(self.db, username, community_id, lax_username, alloc); } pub fn getActor( @@ -74,16 +83,50 @@ pub fn Services(comptime Db: type) type { alloc: std.mem.Allocator, user_id: Uuid, ) !Actor { - return try actors.get(self.db, user_id, alloc); + return try impl.actors.get(self.db, user_id, alloc); + } + + pub fn lookupActorByUsername( + self: Self, + alloc: std.mem.Allocator, + username: []const u8, + community_id: Uuid, + ) !Actor { + return try impl.actors.lookupByUsername(self.db, username, community_id, alloc); + } + + pub fn updateActorProfile( + self: Self, + alloc: std.mem.Allocator, + actor_id: Uuid, + new: types.actors.ProfileUpdateArgs, + ) !Actor { + return try impl.actors.updateProfile(self.db, actor_id, new, alloc); } pub fn createCommunity( self: Self, alloc: std.mem.Allocator, origin: []const u8, - options: communities.CreateOptions, + options: types.communities.CreateOptions, ) !Uuid { - return try communities.create(self.db, origin, options, alloc); + return try impl.communities.create(self.db, origin, options, alloc); + } + + pub fn getCommunity( + self: Self, + alloc: std.mem.Allocator, + id: Uuid, + ) !Community { + return try impl.communities.get(self.db, id, alloc); + } + + pub fn getCommunityByHost( + self: Self, + alloc: std.mem.Allocator, + host: []const u8, + ) !Community { + return try impl.communities.getByHost(self.db, host, alloc); } pub fn transferCommunityOwnership( @@ -91,7 +134,168 @@ pub fn Services(comptime Db: type) type { community_id: Uuid, owner_id: Uuid, ) !void { - return try communities.transferOwnership(self.db, community_id, owner_id); + return try impl.communities.transferOwnership(self.db, community_id, owner_id); + } + + pub fn statDriveEntry( + self: Self, + alloc: std.mem.Allocator, + owner_id: Uuid, + path: []const u8, + ) !DriveEntry { + return try impl.drive.stat(self.db, owner_id, path, alloc); + } + + pub fn createDriveEntry( + self: Self, + alloc: std.mem.Allocator, + owner_id: Uuid, + containing_path: []const u8, + name: []const u8, + file_id: ?Uuid, + ) !Uuid { + return try impl.drive.create(self.db, owner_id, containing_path, name, file_id, alloc); + } + + pub fn deleteDriveEntry( + self: Self, + alloc: std.mem.Allocator, + entry_id: Uuid, + ) !void { + return try impl.drive.delete(self.db, entry_id, alloc); + } + + pub fn moveDriveEntry( + self: Self, + alloc: std.mem.Allocator, + owner_id: Uuid, + src: []const u8, + dest: []const u8, + ) !void { + return try impl.drive.move(self.db, owner_id, src, dest, alloc); + } + + // TODO: paginate + pub fn listDriveEntry( + self: Self, + alloc: std.mem.Allocator, + owner_id: Uuid, + path: []const u8, + ) ![]DriveEntry { + return try impl.drive.list(self.db, owner_id, path, alloc); + } + + pub fn createFile( + self: Self, + alloc: std.mem.Allocator, + owner_id: Uuid, + meta: types.files.CreateOptions, + data: []const u8, + ) !Uuid { + return try impl.files.create(self.db, owner_id, meta, data, alloc); + } + + pub fn deleteFile( + self: Self, + alloc: std.mem.Allocator, + id: Uuid, + ) !void { + return try impl.files.delete(self.db, id, alloc); + } + + pub fn statFile( + self: Self, + alloc: std.mem.Allocator, + id: Uuid, + ) !FileUpload { + return try impl.files.get(self.db, id, alloc); + } + + pub fn derefFile( + _: Self, + alloc: std.mem.Allocator, + id: Uuid, + ) ![]const u8 { + return try impl.files.deref(alloc, id); + } + + pub fn updateFileMetadata( + self: Self, + alloc: std.mem.Allocator, + id: Uuid, + meta: types.files.UpdateArgs, + ) !FileUpload { + return try impl.files.update(self.db, id, meta, alloc); + } + + pub fn createFollow( + self: Self, + alloc: std.mem.Allocator, + followed_by: Uuid, + followee: Uuid, + ) !void { + return try impl.follows.create(self.db, followed_by, followee, alloc); + } + + pub fn deleteFollow( + self: Self, + alloc: std.mem.Allocator, + followed_by: Uuid, + followee: Uuid, + ) !void { + return try impl.follows.delete(self.db, followed_by, followee, alloc); + } + + pub fn queryFollows( + self: Self, + alloc: std.mem.Allocator, + args: types.follows.QueryArgs, + ) !types.follows.QueryResult { + return try impl.follows.query(self.db, args, alloc); + } + + pub fn createInvite( + self: Self, + alloc: std.mem.Allocator, + created_by: Uuid, + community_id: Uuid, + name: []const u8, + options: types.invites.CreateOptions, + ) !Invite { + return try impl.invites.create(self.db, created_by, community_id, name, options, alloc); + } + + pub fn getInvite( + self: Self, + alloc: std.mem.Allocator, + invite_id: Uuid, + ) !Invite { + return try impl.invites.get(self.db, invite_id, alloc); + } + + pub fn createNote( + self: Self, + alloc: std.mem.Allocator, + author_id: Uuid, + content: []const u8, + ) !Uuid { + return try impl.notes.create(self.db, author_id, content, alloc); + } + + pub fn getNote( + self: Self, + alloc: std.mem.Allocator, + id: Uuid, + ) !Note { + return try impl.notes.get(self.db, id, alloc); + } + + pub fn queryNotes( + self: Self, + alloc: std.mem.Allocator, + args: types.notes.QueryArgs, + ) !types.notes.QueryResult { + return try impl.notes.query(self.db, args, alloc); } pub fn getInviteByCode( @@ -100,7 +304,7 @@ pub fn Services(comptime Db: type) type { code: []const u8, community_id: Uuid, ) !Invite { - return try invites.getByCode(self.db, code, community_id, alloc); + return try impl.invites.getByCode(self.db, code, community_id, alloc); } pub fn createToken( @@ -109,7 +313,7 @@ pub fn Services(comptime Db: type) type { account_id: Uuid, hash: []const u8, ) !void { - return try tokens.create(self.db, account_id, hash, alloc); + return try impl.tokens.create(self.db, account_id, hash, alloc); } pub fn getTokenByHash( @@ -118,7 +322,7 @@ pub fn Services(comptime Db: type) type { hash: []const u8, community_id: Uuid, ) !Token { - return try tokens.getByHash(self.db, hash, community_id, alloc); + return try impl.tokens.getByHash(self.db, hash, community_id, alloc); } }; } diff --git a/src/api/services/accounts.zig b/src/api/services/accounts.zig index 1f28846..e97014a 100644 --- a/src/api/services/accounts.zig +++ b/src/api/services/accounts.zig @@ -1,18 +1,12 @@ const std = @import("std"); const util = @import("util"); +const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; -pub const Role = enum { - user, - admin, -}; -pub const CreateOptions = struct { - invite_id: ?Uuid = null, - email: ?[]const u8 = null, - role: Role = .user, -}; +const CreateOptions = types.accounts.CreateOptions; +const Credentials = types.accounts.Credentials; /// Creates a local account with the given information pub fn create( @@ -40,11 +34,6 @@ pub fn create( tx.commitOrRelease() catch return error.DatabaseFailure; } -pub const Credentials = struct { - account_id: Uuid, - password_hash: []const u8, -}; - pub fn getCredentialsByUsername(db: anytype, username: []const u8, community_id: Uuid, alloc: std.mem.Allocator) !Credentials { return db.queryRow( Credentials, diff --git a/src/api/services/actors.zig b/src/api/services/actors.zig index 9c6dab2..bc8578f 100644 --- a/src/api/services/actors.zig +++ b/src/api/services/actors.zig @@ -3,7 +3,7 @@ const util = @import("util"); const sql = @import("sql"); const common = @import("./common.zig"); const files = @import("./files.zig"); -const types = @import("../types.zig"); +const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; diff --git a/src/api/services/communities.zig b/src/api/services/communities.zig index 216be86..dcd7e7e 100644 --- a/src/api/services/communities.zig +++ b/src/api/services/communities.zig @@ -3,14 +3,15 @@ const builtin = @import("builtin"); const util = @import("util"); const sql = @import("sql"); const actors = @import("./actors.zig"); -const types = @import("../types.zig"); +const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; -const Community = types.Community; -pub const CreateOptions = Community.CreateOptions; -const QueryArgs = Community.QueryArgs; -const QueryResult = types.QueryResult(Community); +const Community = types.communities.Community; +const Scheme = types.communities.Scheme; +const CreateOptions = types.communities.CreateOptions; +const QueryArgs = types.communities.QueryArgs; +const QueryResult = types.communities.QueryResult; pub const CreateError = error{ UnsupportedScheme, @@ -21,7 +22,7 @@ pub const CreateError = error{ pub fn create(db: anytype, origin: []const u8, options: CreateOptions, alloc: std.mem.Allocator) CreateError!Uuid { const scheme_len = std.mem.indexOfScalar(u8, origin, ':') orelse return error.InvalidOrigin; const scheme_str = origin[0..scheme_len]; - const scheme = std.meta.stringToEnum(Community.Scheme, scheme_str) orelse return error.UnsupportedScheme; + const scheme = std.meta.stringToEnum(Scheme, scheme_str) orelse return error.UnsupportedScheme; // host must be in the format "{scheme}://{host}" if (origin.len <= scheme_len + ("://").len or diff --git a/src/api/services/files.zig b/src/api/services/files.zig index 9b4412d..ca8361d 100644 --- a/src/api/services/files.zig +++ b/src/api/services/files.zig @@ -1,7 +1,7 @@ const std = @import("std"); const sql = @import("sql"); const util = @import("util"); -const types = @import("../types.zig"); +const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; diff --git a/src/api/services/invites.zig b/src/api/services/invites.zig index a9eddb8..dff5401 100644 --- a/src/api/services/invites.zig +++ b/src/api/services/invites.zig @@ -1,11 +1,11 @@ const std = @import("std"); const builtin = @import("builtin"); const util = @import("util"); -const types = @import("../types.zig"); +const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; -pub const Invite = types.Invite; +const Invite = types.Invite; // 9 random bytes = 12 random b64 const rand_len = 8; diff --git a/src/api/services/notes.zig b/src/api/services/notes.zig index 0cce202..0d682c5 100644 --- a/src/api/services/notes.zig +++ b/src/api/services/notes.zig @@ -1,8 +1,7 @@ const std = @import("std"); const util = @import("util"); const sql = @import("sql"); -const common = @import("./common.zig"); -const types = @import("../types.zig"); +const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; diff --git a/src/api/services/tokens.zig b/src/api/services/tokens.zig index 1ed0a5b..48477cd 100644 --- a/src/api/services/tokens.zig +++ b/src/api/services/tokens.zig @@ -4,12 +4,7 @@ const util = @import("util"); const Uuid = util.Uuid; const DateTime = util.DateTime; -pub const Token = struct { - account_id: Uuid, - issued_at: DateTime, - - hash: []const u8, -}; +const Token = @import("./types.zig").tokens.Token; pub fn create(db: anytype, account_id: Uuid, hash: []const u8, alloc: std.mem.Allocator) !void { const now = DateTime.now(); diff --git a/src/api/services/types.zig b/src/api/services/types.zig new file mode 100644 index 0000000..7005d18 --- /dev/null +++ b/src/api/services/types.zig @@ -0,0 +1,373 @@ +const util = @import("util"); + +const Uuid = util.Uuid; +const DateTime = util.DateTime; + +const common = struct { + const Direction = enum { + ascending, + descending, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + const PageDirection = enum { + forward, + backward, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + fn QueryResult(comptime R: type, comptime A: type) type { + return struct { + items: []R, + + next_page: A, + prev_page: A, + }; + } +}; + +pub const accounts = struct { + pub const Role = enum { + user, + admin, + }; + + pub const CreateOptions = struct { + invite_id: ?Uuid = null, + email: ?[]const u8 = null, + role: Role = .user, + }; + + pub const Credentials = struct { + account_id: Uuid, + password_hash: []const u8, + }; +}; + +pub const actors = struct { + pub const Actor = struct { + id: Uuid, + + username: []const u8, + host: []const u8, + community_id: Uuid, + + display_name: ?[]const u8, + bio: []const u8, + + avatar_file_id: ?Uuid, + header_file_id: ?Uuid, + + profile_fields: []const ProfileField, + + created_at: DateTime, + updated_at: DateTime, + + pub const sql_serialize = struct { + pub const profile_fields = .json; + }; + }; + + pub const ProfileField = struct { + key: []const u8, + value: []const u8, + }; + + // TODO: get rid of this + pub const Profile = struct { + display_name: ?[]const u8, + bio: []const u8, + avatar_file_id: ?Uuid, + header_file_id: ?Uuid, + profile_fields: []const ProfileField, + + pub const sql_serialize = struct { + pub const profile_fields = .json; + }; + }; + + pub const ProfileUpdateArgs = struct { + display_name: ??[]const u8, + bio: ?[]const u8, + avatar_file_id: ??Uuid, + header_file_id: ??Uuid, + profile_fields: ?[]const ProfileField, + + pub const sql_serialize = struct { + pub const profile_fields = .json; + }; + }; +}; + +pub const communities = struct { + pub const Community = struct { + id: Uuid, + + owner_id: ?Uuid, + host: []const u8, + name: []const u8, + + scheme: Scheme, + kind: Kind, + created_at: DateTime, + }; + + pub const Kind = enum { + admin, + local, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + pub const Scheme = enum { + https, + http, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + pub const CreateOptions = struct { + name: ?[]const u8 = null, + kind: Kind = .local, + }; + + pub const QueryArgs = struct { + pub const OrderBy = enum { + name, + host, + created_at, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + pub const Direction = common.Direction; + pub const PageDirection = common.PageDirection; + pub const Prev = struct { + id: Uuid, + order_val: OrderVal, + }; + pub const OrderVal = union(OrderBy) { + name: []const u8, + host: []const u8, + created_at: DateTime, + }; + + // Max items to fetch + max_items: usize = 20, + + // Selection filters + owner_id: ?Uuid = null, // searches for communities owned by this user + like: ?[]const u8 = null, // searches for communities with host or name LIKE '%?%' + created_before: ?DateTime = null, + created_after: ?DateTime = null, + + // Ordering parameter + order_by: OrderBy = .created_at, + direction: Direction = .ascending, + + // Page start parameter(s) + // This struct is a reference to the last value scanned + // If prev is present, then prev.order_val must have the same tag as order_by + // "prev" here refers to it being the previous value returned. It may be that + // prev refers to the item directly after the results you are about to recieve, + // if you are querying the previous page. + prev: ?Prev = null, + + // What direction to scan the page window + // If "forward", then "prev" is interpreted as the item directly before the items + // to query, in the direction of "direction" above. If "backward", then the opposite + page_direction: PageDirection = .forward, + }; + + pub const QueryResult = common.QueryResult(Community, QueryArgs); +}; + +pub const drive = struct { + pub const DriveEntry = struct { + pub const Kind = enum { + dir, + file, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + id: Uuid, + owner_id: Uuid, + + name: ?[]const u8, + + path: []const u8, + parent_directory_id: ?Uuid, + + file_id: ?Uuid, + kind: Kind, + }; +}; + +pub const files = struct { + pub const FileUpload = struct { + id: Uuid, + + owner_id: Uuid, + size: usize, + + filename: []const u8, + description: ?[]const u8, + content_type: ?[]const u8, + sensitive: bool, + + status: Status, + + created_at: DateTime, + updated_at: DateTime, + }; + + pub const Status = enum { + uploading, + uploaded, + external, + deleted, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + + pub const CreateOptions = struct { + filename: []const u8, + description: ?[]const u8, + content_type: ?[]const u8, + sensitive: bool, + }; + + pub const UpdateArgs = struct { + filename: ?[]const u8, + description: ?[]const u8, + content_type: ?[]const u8, + sensitive: ?bool, + }; +}; + +pub const invites = struct { + const UseCount = usize; + pub const Invite = struct { + id: Uuid, + + created_by: Uuid, // User ID + community_id: Uuid, + name: []const u8, + code: []const u8, + + created_at: DateTime, + times_used: UseCount, + + expires_at: ?DateTime, + max_uses: ?UseCount, + + kind: Kind, + }; + pub const Kind = enum { + system, + community_owner, + user, + + pub const jsonStringify = util.jsonSerializeEnumAsString; + }; + pub const CreateOptions = struct { + name: ?[]const u8 = null, + max_uses: ?UseCount = null, + lifespan: ?DateTime.Duration = null, + kind: Kind = .user, + to_community: ?Uuid = null, + }; + + pub const InternalCreateOptions = struct { + name: ?[]const u8 = null, + max_uses: ?UseCount = null, + lifespan: ?DateTime.Duration = null, + kind: Kind = .user, + }; +}; + +pub const follows = struct { + pub const Follow = struct { + id: Uuid, + + followed_by_id: Uuid, + followee_id: Uuid, + + created_at: DateTime, + }; + pub const QueryArgs = struct { + pub const OrderBy = enum { + created_at, + }; + + pub const Direction = common.Direction; + pub const PageDirection = common.PageDirection; + pub const Prev = struct { + id: Uuid, + order_val: union(OrderBy) { + created_at: DateTime, + }, + }; + + max_items: usize = 20, + + followed_by_id: ?Uuid = null, + followee_id: ?Uuid = null, + + order_by: OrderBy = .created_at, + + direction: Direction = .descending, + + prev: ?Prev = null, + + page_direction: PageDirection = .forward, + }; + + pub const QueryResult = common.QueryResult(Follow, QueryArgs); +}; + +pub const notes = struct { + pub const Note = struct { + id: Uuid, + + author_id: actors.Actor, // TODO + content: []const u8, + created_at: DateTime, + + // TODO: This sucks + pub const sql_serialize = struct { + pub const @"author.profile_fields" = .json; + }; + }; + pub const QueryArgs = struct { + pub const PageDirection = common.PageDirection; + pub const Prev = struct { + id: Uuid, + created_at: DateTime, + }; + + max_items: usize = 20, + + created_before: ?DateTime = null, + created_after: ?DateTime = null, + community_id: ?Uuid = null, + followed_by: ?Uuid = null, + + prev: ?Prev = null, + + page_direction: PageDirection = .forward, + }; + pub const QueryResult = common.QueryResult(Note, QueryArgs); +}; + +pub const tokens = struct { + pub const Token = struct { + account_id: Uuid, + issued_at: DateTime, + + hash: []const u8, + }; +}; diff --git a/src/api/types.zig b/src/api/types.zig index 7bd3a13..cef3525 100644 --- a/src/api/types.zig +++ b/src/api/types.zig @@ -1,362 +1,5 @@ -const util = @import("util"); +const services = @import("../services.zig"); -const Uuid = util.Uuid; -const DateTime = util.DateTime; - -const common = struct { - const Direction = enum { - ascending, - descending, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; - - const PageDirection = enum { - forward, - backward, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; -}; - -pub fn QueryResult(comptime T: type) type { - return QueryResultArguments(T, T.QueryArgs); -} - -pub fn QueryResultArguments(comptime T: type, comptime A: type) type { - return struct { - items: []T, - - next_page: A, - prev_page: A, - }; -} - -pub const Account = struct { - pub const Auth = struct { - password_hash: []const u8, - updated_at: DateTime, - }; - pub const Kind = enum { - user, - admin, - }; - - id: Uuid, - invite_id: ?Uuid, - email: ?[]const u8, - kind: Kind, -}; - -pub const Actor = struct { - pub const ProfileField = struct { - key: []const u8, - value: []const u8, - }; - - id: Uuid, - - username: []const u8, - host: []const u8, - community_id: Uuid, - - display_name: ?[]const u8, - bio: []const u8, - - avatar_file_id: ?Uuid, - header_file_id: ?Uuid, - - profile_fields: []const ProfileField, - - created_at: DateTime, - updated_at: DateTime, - - // TODO: get rid of this - pub const Profile = struct { - display_name: ?[]const u8, - bio: []const u8, - avatar_file_id: ?Uuid, - header_file_id: ?Uuid, - profile_fields: []const ProfileField, - - pub const sql_serialize = struct { - pub const profile_fields = .json; - }; - }; - - pub const ProfileUpdateArgs = struct { - display_name: ??[]const u8, - bio: ?[]const u8, - avatar_file_id: ??Uuid, - header_file_id: ??Uuid, - profile_fields: ?[]const ProfileField, - - pub const sql_serialize = struct { - pub const profile_fields = .json; - }; - }; - - pub const sql_serialize = struct { - pub const profile_fields = .json; - }; -}; - -pub const Community = struct { - pub const Kind = enum { - admin, - local, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; - - pub const Scheme = enum { - https, - http, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; - - id: Uuid, - - owner_id: ?Uuid, - host: []const u8, - name: []const u8, - - scheme: Scheme, - kind: Kind, - created_at: DateTime, - - pub const CreateOptions = struct { - name: ?[]const u8 = null, - kind: Kind = .local, - }; - - pub const QueryArgs = struct { - pub const OrderBy = enum { - name, - host, - created_at, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; - - pub const Direction = common.Direction; - pub const PageDirection = common.PageDirection; - pub const Prev = struct { - id: Uuid, - order_val: OrderVal, - }; - pub const OrderVal = union(OrderBy) { - name: []const u8, - host: []const u8, - created_at: DateTime, - }; - - // Max items to fetch - max_items: usize = 20, - - // Selection filters - owner_id: ?Uuid = null, // searches for communities owned by this user - like: ?[]const u8 = null, // searches for communities with host or name LIKE '%?%' - created_before: ?DateTime = null, - created_after: ?DateTime = null, - - // Ordering parameter - order_by: OrderBy = .created_at, - direction: Direction = .ascending, - - // Page start parameter(s) - // This struct is a reference to the last value scanned - // If prev is present, then prev.order_val must have the same tag as order_by - // "prev" here refers to it being the previous value returned. It may be that - // prev refers to the item directly after the results you are about to recieve, - // if you are querying the previous page. - prev: ?Prev = null, - - // What direction to scan the page window - // If "forward", then "prev" is interpreted as the item directly before the items - // to query, in the direction of "direction" above. If "backward", then the opposite - page_direction: PageDirection = .forward, - }; -}; - -pub const DriveEntry = struct { - pub const Kind = enum { - dir, - file, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; - - id: Uuid, - owner_id: Uuid, - - name: ?[]const u8, - - path: []const u8, - parent_directory_id: ?Uuid, - - file_id: ?Uuid, - kind: Kind, -}; - -pub const FileUpload = struct { - pub const Status = enum { - uploading, - uploaded, - external, - deleted, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; - - id: Uuid, - - owner_id: Uuid, - size: usize, - - filename: []const u8, - description: ?[]const u8, - content_type: ?[]const u8, - sensitive: bool, - - status: Status, - - created_at: DateTime, - updated_at: DateTime, - - pub const CreateOptions = struct { - filename: []const u8, - description: ?[]const u8, - content_type: ?[]const u8, - sensitive: bool, - }; - - pub const UpdateArgs = struct { - filename: ?[]const u8, - description: ?[]const u8, - content_type: ?[]const u8, - sensitive: ?bool, - }; -}; - -pub const Invite = struct { - const UseCount = usize; - - pub const Kind = enum { - system, - community_owner, - user, - - pub const jsonStringify = util.jsonSerializeEnumAsString; - }; - - id: Uuid, - - created_by: Uuid, // User ID - community_id: Uuid, - name: []const u8, - code: []const u8, - - created_at: DateTime, - times_used: UseCount, - - expires_at: ?DateTime, - max_uses: ?UseCount, - - kind: Kind, - - pub const CreateOptions = struct { - name: ?[]const u8 = null, - max_uses: ?UseCount = null, - lifespan: ?DateTime.Duration = null, - kind: Kind = .user, - to_community: ?Uuid = null, - }; - - pub const InternalCreateOptions = struct { - name: ?[]const u8 = null, - max_uses: ?UseCount = null, - lifespan: ?DateTime.Duration = null, - kind: Kind = .user, - }; -}; - -pub const Follow = struct { - id: Uuid, - - followed_by_id: Uuid, - followee_id: Uuid, - - created_at: DateTime, - - pub const QueryArgs = struct { - pub const OrderBy = enum { - created_at, - }; - - pub const Direction = common.Direction; - pub const PageDirection = common.PageDirection; - pub const Prev = struct { - id: Uuid, - order_val: union(OrderBy) { - created_at: DateTime, - }, - }; - - max_items: usize = 20, - - followed_by_id: ?Uuid = null, - followee_id: ?Uuid = null, - - order_by: OrderBy = .created_at, - - direction: Direction = .descending, - - prev: ?Prev = null, - - page_direction: PageDirection = .forward, - }; -}; - -pub const Note = struct { - id: Uuid, - - author: Actor, - content: []const u8, - created_at: DateTime, - - pub const QueryArgs = struct { - pub const PageDirection = common.PageDirection; - pub const Prev = struct { - id: Uuid, - created_at: DateTime, - }; - - max_items: usize = 20, - - created_before: ?DateTime = null, - created_after: ?DateTime = null, - community_id: ?Uuid = null, - followed_by: ?Uuid = null, - - prev: ?Prev = null, - - page_direction: PageDirection = .forward, - }; - - // TODO: This sucks - pub const sql_serialize = struct { - pub const @"author.profile_fields" = .json; - }; -}; - -pub const Token = struct { - pub const Info = struct { - user_id: Uuid, - issued_at: DateTime, - }; - - value: []const u8, - info: Info, +pub const actors = struct { + pub const Actor = services.actors.Actor; };