const std = @import("std"); const sql = @import("sql"); const util = @import("util"); const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; const FileUpload = types.files.FileUpload; const CreateOptions = types.files.CreateOptions; const UpdateArgs = types.files.UpdateArgs; pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) !FileUpload { return try db.queryRow( FileUpload, \\SELECT \\ id, \\ owner_id, \\ size, \\ filename, \\ description, \\ content_type, \\ sensitive, \\ status, \\ created_at, \\ updated_at \\FROM file_upload \\WHERE id = $1 \\LIMIT 1 , .{id}, alloc, ); } pub fn update(db: anytype, id: Uuid, meta: UpdateArgs, alloc: std.mem.Allocator) !void { var builder = sql.QueryBuilder.init(alloc); defer builder.deinit(); try builder.appendSlice("UPDATE file_upload"); if (meta.filename) |_| try builder.set("filename", "$2"); if (meta.description) |_| try builder.set("description", "$3"); if (meta.content_type) |_| try builder.set("content_type", "$4"); if (meta.sensitive) |_| try builder.set("sensitive", "$5"); if (builder.set_statements_appended == 0) return error.NoChange; try builder.set("updated_at", "$6"); try builder.andWhere("id = $1"); try db.exec(try builder.terminate(), .{ id, meta.filename orelse null, meta.description orelse null, meta.content_type orelse null, meta.sensitive orelse null, DateTime.now(), }, alloc); } pub fn create(db: anytype, owner_id: Uuid, meta: CreateOptions, data: []const u8, alloc: std.mem.Allocator) !Uuid { const id = Uuid.randV4(util.getThreadPrng()); const now = DateTime.now(); try db.insert("file_upload", .{ .id = id, .owner_id = owner_id, .size = data.len, .filename = meta.filename, .description = meta.description, .content_type = meta.content_type, .sensitive = meta.sensitive, .status = FileUpload.Status.uploading, .created_at = now, .updated_at = now, }, alloc); saveFile(id, data) catch |err| { db.exec("DELETE FROM file_upload WHERE ID = $1", .{id}, alloc) catch |e| { std.log.err("Unable to remove file {} record in DB: {}", .{ id, e }); }; return err; }; try db.exec( \\UPDATE file_upload \\SET status = 'uploaded', updated_at = $2 \\WHERE id = $1 , .{ id, DateTime.now() }, alloc); return id; } pub fn delete(db: anytype, id: Uuid, alloc: std.mem.Allocator) !void { var dir = try std.fs.cwd().openDir(data_root, .{}); defer dir.close(); try dir.deleteFile(&id.toCharArray()); try db.exec( \\DELETE FROM file_upload \\WHERE id = $1 , .{id}, alloc); } const data_root = "./files"; fn saveFile(id: Uuid, data: []const u8) !void { var dir = try std.fs.cwd().openDir(data_root, .{}); defer dir.close(); var file = try dir.createFile(&id.toCharArray(), .{ .exclusive = true }); defer file.close(); try file.writer().writeAll(data); try file.sync(); } pub fn deref(alloc: std.mem.Allocator, id: Uuid) ![]const u8 { var dir = try std.fs.cwd().openDir(data_root, .{}); defer dir.close(); return dir.readFileAlloc(alloc, &id.toCharArray(), 1 << 32); }