Fix api types in drive api
This commit is contained in:
parent
9e66ef441b
commit
22f2a03308
4 changed files with 200 additions and 62 deletions
162
src/api/lib.zig
162
src/api/lib.zig
|
@ -146,8 +146,35 @@ pub const UploadFileArgs = struct {
|
||||||
sensitive: bool,
|
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 FileUpload = services.files.FileUpload;
|
||||||
|
|
||||||
pub const DriveGetResult = union(services.drive.Kind) {
|
pub const DriveGetResult = union(services.drive.Kind) {
|
||||||
dir: struct {
|
dir: struct {
|
||||||
entry: DriveEntry,
|
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 user_id = self.user_id orelse return error.NoToken;
|
||||||
const file_id = try services.files.create(self.db, user_id, .{
|
const file_id = try services.files.create(self.db, user_id, .{
|
||||||
.filename = meta.filename,
|
.filename = meta.filename,
|
||||||
|
@ -547,38 +623,52 @@ fn ApiConn(comptime DbConn: type) type {
|
||||||
.sensitive = meta.sensitive,
|
.sensitive = meta.sensitive,
|
||||||
}, body, self.allocator);
|
}, body, self.allocator);
|
||||||
|
|
||||||
errdefer services.files.delete(self.db, file_id, self.allocator) catch |err| {
|
const entry = entry: {
|
||||||
std.log.err("Unable to delete file {}: {}", .{ file_id, err });
|
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) {
|
break :entry services.drive.create(
|
||||||
error.PathAlreadyExists => {
|
self.db,
|
||||||
var buf: [256]u8 = undefined;
|
user_id,
|
||||||
var split = std.mem.splitBackwards(u8, meta.filename, ".");
|
meta.dir,
|
||||||
const ext = split.first();
|
meta.filename,
|
||||||
const name = split.rest();
|
file_id,
|
||||||
const new_name = try std.fmt.bufPrint(&buf, "{s}.{s}.{s}", .{ name, file_id, ext });
|
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(
|
break :entry try services.drive.create(
|
||||||
self.db,
|
self.db,
|
||||||
user_id,
|
user_id,
|
||||||
meta.dir,
|
meta.dir,
|
||||||
new_name,
|
new_name,
|
||||||
file_id,
|
file_id,
|
||||||
self.allocator,
|
self.allocator,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
else => |e| return e,
|
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;
|
const user_id = self.user_id orelse return error.NoToken;
|
||||||
var split = std.mem.splitBackwards(u8, path, "/");
|
var split = std.mem.splitBackwards(u8, path, "/");
|
||||||
std.log.debug("{s}", .{path});
|
std.log.debug("{s}", .{path});
|
||||||
const base = split.first();
|
const base = split.first();
|
||||||
const dir = split.rest();
|
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 {
|
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);
|
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;
|
const user_id = self.user_id orelse return error.NoToken;
|
||||||
try services.drive.move(self.db, user_id, src, dest, self.allocator);
|
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 user_id = self.user_id orelse return error.NoToken;
|
||||||
const entry = try services.drive.stat(self.db, user_id, path, self.allocator);
|
const entry = try services.drive.stat(self.db, user_id, path, self.allocator);
|
||||||
errdefer util.deepFree(self.allocator, entry);
|
errdefer util.deepFree(self.allocator, entry);
|
||||||
|
|
||||||
if (entry.file_id) |file_id| return .{
|
return try self.backendDriveEntryToFrontend(entry, true);
|
||||||
.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),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
const user_id = self.user_id orelse return error.NoToken;
|
||||||
std.log.debug("{s}", .{path});
|
std.log.debug("{s}", .{path});
|
||||||
const entry = try services.drive.stat(self.db, user_id, path, self.allocator);
|
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});
|
std.log.debug("{}", .{entry.id});
|
||||||
try services.files.update(self.db, entry.file_id orelse return error.NotAFile, meta, self.allocator);
|
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 {
|
pub fn fileDereference(self: *Self, id: Uuid) !FileResult {
|
||||||
|
|
|
@ -14,3 +14,38 @@ pub const PageDirection = enum {
|
||||||
|
|
||||||
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
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,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub const DriveOwner = union(enum) {
|
||||||
community_id: Uuid,
|
community_id: Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DriveEntry = struct {
|
pub const Entry = struct {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
owner_id: Uuid,
|
owner_id: Uuid,
|
||||||
name: ?[]const u8,
|
name: ?[]const u8,
|
||||||
|
@ -26,8 +26,8 @@ pub const Kind = enum {
|
||||||
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn stat(db: anytype, owner: Uuid, path: []const u8, alloc: std.mem.Allocator) !DriveEntry {
|
pub fn stat(db: anytype, owner: Uuid, path: []const u8, alloc: std.mem.Allocator) !Entry {
|
||||||
return (db.queryRow(DriveEntry,
|
return (db.queryRow(Entry,
|
||||||
\\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id
|
\\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id
|
||||||
\\FROM drive_entry_path
|
\\FROM drive_entry_path
|
||||||
\\WHERE owner_id = $1 AND path = ('/' || $2)
|
\\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
|
/// 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;
|
if (name.len == 0) return error.EmptyName;
|
||||||
|
|
||||||
const id = Uuid.randV4(util.getThreadPrng());
|
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();
|
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 {
|
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
|
// TODO: paginate this
|
||||||
pub fn list(db: anytype, id: Uuid, alloc: std.mem.Allocator) ![]DriveEntry {
|
pub fn list(db: anytype, id: Uuid, alloc: std.mem.Allocator) ![]Entry {
|
||||||
return (db.queryRows(DriveEntry,
|
return (db.queryRows(Entry,
|
||||||
\\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id
|
\\SELECT id, path, owner_id, name, file_id, kind, parent_directory_id
|
||||||
\\FROM drive_entry_path
|
\\FROM drive_entry_path
|
||||||
\\WHERE parent_directory_id = $1
|
\\WHERE parent_directory_id = $1
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub const get = struct {
|
||||||
|
|
||||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||||
const result = try srv.driveGet(req.args.path);
|
const result = try srv.driveGet(req.args.path);
|
||||||
|
defer util.deepFree(srv.allocator, result);
|
||||||
|
|
||||||
try res.json(.ok, result);
|
try res.json(.ok, result);
|
||||||
}
|
}
|
||||||
|
@ -36,16 +37,16 @@ pub const upload = struct {
|
||||||
|
|
||||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||||
const f = req.body.file;
|
const f = req.body.file;
|
||||||
try srv.driveUpload(.{
|
const result = try srv.driveUpload(.{
|
||||||
.dir = req.args.path,
|
.dir = req.args.path,
|
||||||
.filename = f.filename,
|
.filename = f.filename,
|
||||||
.description = req.body.description,
|
.description = req.body.description,
|
||||||
.content_type = f.content_type,
|
.content_type = f.content_type,
|
||||||
.sensitive = req.body.sensitive,
|
.sensitive = req.body.sensitive,
|
||||||
}, f.data);
|
}, f.data);
|
||||||
|
errdefer util.deepFree(srv.allocator, result);
|
||||||
|
|
||||||
// TODO: print meta
|
try res.json(.created, result);
|
||||||
try res.json(.created, .{});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,9 +68,10 @@ pub const mkdir = struct {
|
||||||
pub const Args = DriveArgs;
|
pub const Args = DriveArgs;
|
||||||
|
|
||||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
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
|
// TODO: Validate that unhandled fields are equivalent to ones in the object
|
||||||
pub const allow_unknown_fields_in_body = true;
|
pub const allow_unknown_fields_in_body = true;
|
||||||
pub const Body = struct {
|
pub const Body = struct {
|
||||||
filename: ?[]const u8 = null,
|
meta: struct {
|
||||||
description: ?[]const u8 = null,
|
filename: ?[]const u8 = null,
|
||||||
content_type: ?[]const u8 = null,
|
description: ?[]const u8 = null,
|
||||||
sensitive: ?bool = null,
|
content_type: ?[]const u8 = null,
|
||||||
|
sensitive: ?bool = null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||||
try srv.driveUpdate(req.args.path, .{
|
const result = try srv.driveUpdate(req.args.path, .{
|
||||||
.filename = req.body.filename,
|
.filename = req.body.meta.filename,
|
||||||
.description = req.body.description,
|
.description = req.body.meta.description,
|
||||||
.content_type = req.body.content_type,
|
.content_type = req.body.meta.content_type,
|
||||||
.sensitive = req.body.sensitive,
|
.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 {
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||||
const destination = req.headers.get("Destination") orelse return error.NoDestination;
|
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.headers.put("Location", destination);
|
||||||
try res.json(.created, .{});
|
try res.json(.created, result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue