From 22f2a033085d439d1c8bf1916ad6224a68c31be5 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 6 Dec 2022 21:41:01 -0800 Subject: [PATCH] Fix api types in drive api --- src/api/lib.zig | 162 ++++++++++++++++++++++------- src/api/services/common.zig | 35 +++++++ src/api/services/drive.zig | 25 +++-- src/main/controllers/api/drive.zig | 40 ++++--- 4 files changed, 200 insertions(+), 62 deletions(-) diff --git a/src/api/lib.zig b/src/api/lib.zig index 58602ba..0aa3f6a 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -146,8 +146,35 @@ pub const UploadFileArgs = struct { sensitive: bool, }; -pub const DriveEntry = services.drive.DriveEntry; +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, + }, +}; + pub const FileUpload = services.files.FileUpload; + pub const DriveGetResult = union(services.drive.Kind) { dir: struct { entry: DriveEntry, @@ -538,7 +565,56 @@ fn ApiConn(comptime DbConn: type) type { ); } - pub fn driveUpload(self: *Self, meta: UploadFileArgs, body: []const u8) !void { + fn backendDriveEntryToFrontend(self: *Self, entry: services.drive.Entry, recurse: bool) !DriveEntry { + 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, + + .meta = try services.files.get(self.db, file_id, self.allocator), + }, + } 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; + + const children = try services.drive.list(self.db, entry.id, self.allocator); + + 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 { const user_id = self.user_id orelse return error.NoToken; const file_id = try services.files.create(self.db, user_id, .{ .filename = meta.filename, @@ -547,38 +623,52 @@ fn ApiConn(comptime DbConn: type) type { .sensitive = meta.sensitive, }, body, self.allocator); - errdefer services.files.delete(self.db, file_id, self.allocator) catch |err| { - std.log.err("Unable to delete file {}: {}", .{ file_id, err }); - }; + const entry = entry: { + errdefer services.files.delete(self.db, file_id, self.allocator) catch |err| { + std.log.err("Unable to delete file {}: {}", .{ file_id, err }); + }; - services.drive.create(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 }); + break :entry services.drive.create( + 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 }); - try services.drive.create( - self.db, - user_id, - meta.dir, - new_name, - file_id, - self.allocator, - ); - }, - else => |e| return e, + break :entry try services.drive.create( + self.db, + user_id, + meta.dir, + new_name, + file_id, + self.allocator, + ); + }, + else => |e| return e, + }; }; + errdefer util.deepFree(self.allocator, entry); + + return try self.backendDriveEntryToFrontend(entry, true); } - pub fn driveMkdir(self: *Self, path: []const u8) !void { + pub fn driveMkdir(self: *Self, path: []const u8) !DriveEntry { const user_id = self.user_id orelse return error.NoToken; var split = std.mem.splitBackwards(u8, path, "/"); std.log.debug("{s}", .{path}); const base = split.first(); const dir = split.rest(); - try services.drive.create(self.db, user_id, dir, base, null, self.allocator); + const entry = try services.drive.create(self.db, user_id, dir, base, null, self.allocator); + errdefer util.deepFree(self.allocator, entry); + return try self.backendDriveEntryToFrontend(entry, true); } pub fn driveDelete(self: *Self, path: []const u8) !void { @@ -589,37 +679,31 @@ fn ApiConn(comptime DbConn: type) type { if (entry.file_id) |file_id| try services.files.delete(self.db, file_id, self.allocator); } - pub fn driveMove(self: *Self, src: []const u8, dest: []const u8) !void { + pub fn driveMove(self: *Self, src: []const u8, dest: []const u8) !DriveEntry { const user_id = self.user_id orelse return error.NoToken; try services.drive.move(self.db, user_id, src, dest, self.allocator); + + return try self.driveGet(dest); } - pub fn driveGet(self: *Self, path: []const u8) !DriveGetResult { + pub fn driveGet(self: *Self, path: []const u8) !DriveEntry { const user_id = self.user_id orelse return error.NoToken; const entry = try services.drive.stat(self.db, user_id, path, self.allocator); errdefer util.deepFree(self.allocator, entry); - if (entry.file_id) |file_id| return .{ - .file = .{ - .entry = entry, - .file = try services.files.get(self.db, file_id, self.allocator), - }, - } else return .{ - .dir = .{ - .entry = entry, - .children = try services.drive.list(self.db, entry.id, self.allocator), - }, - }; + return try self.backendDriveEntryToFrontend(entry, true); } - pub fn driveUpdate(self: *Self, path: []const u8, meta: services.files.PartialMeta) !void { + pub fn driveUpdate(self: *Self, path: []const u8, meta: services.files.PartialMeta) !DriveEntry { const user_id = self.user_id orelse return error.NoToken; std.log.debug("{s}", .{path}); const entry = try services.drive.stat(self.db, user_id, path, self.allocator); - errdefer util.deepFree(self.allocator, entry); + defer util.deepFree(self.allocator, entry); std.log.debug("{}", .{entry.id}); try services.files.update(self.db, entry.file_id orelse return error.NotAFile, meta, self.allocator); + + return try self.driveGet(path); } pub fn fileDereference(self: *Self, id: Uuid) !FileResult { diff --git a/src/api/services/common.zig b/src/api/services/common.zig index 8a407d5..3464bca 100644 --- a/src/api/services/common.zig +++ b/src/api/services/common.zig @@ -14,3 +14,38 @@ pub const PageDirection = enum { pub const jsonStringify = util.jsonSerializeEnumAsString; }; + +pub fn Partial(comptime T: type) type { + const t_fields = std.meta.fields(T); + var fields: [t_fields.len]std.builtin.Type.StructField = undefined; + for (std.meta.fields(T)) |f, i| fields[i] = .{ + .name = f.name, + .field_type = ?f.field_type, + .default_value = &@as(?f.field_type, null), + .is_comptime = false, + .alignment = @alignOf(?f.field_type), + }; + return @Type(.{ .Struct = .{ + .layout = .Auto, + .fields = &fields, + .decls = &.{}, + .is_tuple = false, + } }); +} + +pub fn Subset(comptime T: type, comptime selected: []const std.meta.FieldEnum(T)) type { + var fields: [selected.len]std.builtin.Type.StructField = undefined; + for (selected) |f, i| fields[i] = .{ + .name = @tagName(f), + .field_type = std.meta.fieldInfo(T, f).field_type, + .default_value = @as(?std.meta.fieldInfo(T, f).field_type, null), + .is_comptime = false, + .alignment = @alignOf(std.meta.fieldInfo(T, f).field_type), + }; + return @Type(.{ .Struct = .{ + .layout = .Auto, + .fields = &fields, + .decls = &.{}, + .is_tuple = false, + } }); +} diff --git a/src/api/services/drive.zig b/src/api/services/drive.zig index d7c3e40..e5c0125 100644 --- a/src/api/services/drive.zig +++ b/src/api/services/drive.zig @@ -10,7 +10,7 @@ pub const DriveOwner = union(enum) { community_id: Uuid, }; -pub const DriveEntry = struct { +pub const Entry = struct { id: Uuid, owner_id: Uuid, name: ?[]const u8, @@ -26,8 +26,8 @@ pub const Kind = enum { pub const jsonStringify = util.jsonSerializeEnumAsString; }; -pub fn stat(db: anytype, owner: Uuid, path: []const u8, alloc: std.mem.Allocator) !DriveEntry { - return (db.queryRow(DriveEntry, +pub fn stat(db: anytype, owner: Uuid, path: []const u8, alloc: std.mem.Allocator) !Entry { + return (db.queryRow(Entry, \\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id \\FROM drive_entry_path \\WHERE owner_id = $1 AND path = ('/' || $2) @@ -42,7 +42,7 @@ pub fn stat(db: anytype, owner: Uuid, path: []const u8, alloc: std.mem.Allocator } /// Creates a file or directory -pub fn create(db: anytype, owner: Uuid, dir: []const u8, name: []const u8, file_id: ?Uuid, alloc: std.mem.Allocator) !void { +pub fn create(db: anytype, owner: Uuid, dir: []const u8, name: []const u8, file_id: ?Uuid, alloc: std.mem.Allocator) !Entry { if (name.len == 0) return error.EmptyName; const id = Uuid.randV4(util.getThreadPrng()); @@ -65,6 +65,19 @@ pub fn create(db: anytype, owner: Uuid, dir: []const u8, name: []const u8, file_ }; try tx.commit(); + + const path = try std.mem.join(alloc, "/", if (dir.len == 0) &.{ "", name } else &.{ "", dir, name }); + errdefer alloc.free(path); + + return Entry{ + .id = id, + .owner_id = owner, + .name = try util.deepClone(alloc, name), + .path = path, + .parent_directory_id = parent.id, + .file_id = file_id, + .kind = if (file_id) |_| .file else .dir, + }; } pub fn delete(db: anytype, id: Uuid, alloc: std.mem.Allocator) !void { @@ -117,8 +130,8 @@ pub fn move(db: anytype, owner: Uuid, src: []const u8, dest: []const u8, alloc: } // TODO: paginate this -pub fn list(db: anytype, id: Uuid, alloc: std.mem.Allocator) ![]DriveEntry { - return (db.queryRows(DriveEntry, +pub fn list(db: anytype, id: Uuid, alloc: std.mem.Allocator) ![]Entry { + return (db.queryRows(Entry, \\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id \\FROM drive_entry_path \\WHERE parent_directory_id = $1 diff --git a/src/main/controllers/api/drive.zig b/src/main/controllers/api/drive.zig index 15a3f39..4650106 100644 --- a/src/main/controllers/api/drive.zig +++ b/src/main/controllers/api/drive.zig @@ -18,6 +18,7 @@ pub const get = struct { pub fn handler(req: anytype, res: anytype, srv: anytype) !void { const result = try srv.driveGet(req.args.path); + defer util.deepFree(srv.allocator, result); try res.json(.ok, result); } @@ -36,16 +37,16 @@ pub const upload = struct { pub fn handler(req: anytype, res: anytype, srv: anytype) !void { const f = req.body.file; - try srv.driveUpload(.{ + const result = try srv.driveUpload(.{ .dir = req.args.path, .filename = f.filename, .description = req.body.description, .content_type = f.content_type, .sensitive = req.body.sensitive, }, f.data); + errdefer util.deepFree(srv.allocator, result); - // TODO: print meta - try res.json(.created, .{}); + try res.json(.created, result); } }; @@ -67,9 +68,10 @@ pub const mkdir = struct { pub const Args = DriveArgs; pub fn handler(req: anytype, res: anytype, srv: anytype) !void { - try srv.driveMkdir(req.args.path); + const result = try srv.driveMkdir(req.args.path); + errdefer util.deepFree(srv.allocator, result); - return res.json(.created, .{}); + try res.json(.created, result); } }; @@ -81,20 +83,23 @@ pub const update = struct { // TODO: Validate that unhandled fields are equivalent to ones in the object pub const allow_unknown_fields_in_body = true; pub const Body = struct { - filename: ?[]const u8 = null, - description: ?[]const u8 = null, - content_type: ?[]const u8 = null, - sensitive: ?bool = null, + meta: struct { + filename: ?[]const u8 = null, + description: ?[]const u8 = null, + content_type: ?[]const u8 = null, + sensitive: ?bool = null, + }, }; pub fn handler(req: anytype, res: anytype, srv: anytype) !void { - try srv.driveUpdate(req.args.path, .{ - .filename = req.body.filename, - .description = req.body.description, - .content_type = req.body.content_type, - .sensitive = req.body.sensitive, + const result = try srv.driveUpdate(req.args.path, .{ + .filename = req.body.meta.filename, + .description = req.body.meta.description, + .content_type = req.body.meta.content_type, + .sensitive = req.body.meta.sensitive, }); - try res.json(.ok, .{}); + defer util.deepFree(srv.allocator, result); + try res.json(.ok, result); } }; @@ -106,9 +111,10 @@ pub const move = struct { pub fn handler(req: anytype, res: anytype, srv: anytype) !void { const destination = req.headers.get("Destination") orelse return error.NoDestination; - try srv.driveMove(req.args.path, destination); + const result = try srv.driveMove(req.args.path, destination); + defer util.deepFree(srv.allocator, result); try res.headers.put("Location", destination); - try res.json(.created, .{}); + try res.json(.created, result); } };