const std = @import("std"); const Storage = @import("root.zig"); const log = std.log.scoped(.user); const Self = @This(); pub const Info = struct { name: []const u8, full_name: []const u8, birthday: []const u8, hash: []const u8, is_admin: bool, pub fn clone(self: *const @This(), allocator: std.mem.Allocator) !@This() { var clone_self: @This() = undefined; var i: usize = 0; inline for (std.meta.fields(@This())) |field_info| { if (field_info.type == []const u8) { @field(clone_self, field_info.name) = try allocator.dupe( u8, @field(self, field_info.name), ); errdefer allocator.free(@field(clone_self, field_info.name)); } else { @field(clone_self, field_info.name) = @field(self, field_info.name); } i += 1; } return clone_self; } pub fn deinit(self: *const @This(), allocator: std.mem.Allocator) void { inline for (std.meta.fields(@This())) |field_info| { if (field_info.type == []const u8) { allocator.free(@field(self, field_info.name)); } } } }; dir: std.fs.Dir, info: Info, arena: std.heap.ArenaAllocator, pub fn open( storage: *Storage, name: []const u8, inner_allocator: std.mem.Allocator ) !Self { var arena: std.heap.ArenaAllocator = .init(inner_allocator); errdefer arena.deinit(); const allocator = arena.allocator(); var user_dir = try storage.dir.openDir("user", .{}); defer user_dir.close(); var dir = try user_dir.openDir(name, .{}); errdefer dir.close(); const file = try dir.openFile("info.json", .{ .lock = .shared }); defer file.close(); const content = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); const info = try std.json.parseFromSliceLeaky(Info, allocator, content, .{}); return .{ .dir = dir, .arena = arena, .info = info, }; } pub fn new( storage: *Storage, name: []const u8, full_name: []const u8, birthday: []const u8, password: []const u8, is_admin: bool, inner_allocator: std.mem.Allocator ) !Self { var arena: std.heap.ArenaAllocator = .init(inner_allocator); const allocator = arena.allocator(); errdefer arena.deinit(); var user_dir = try storage.dir.openDir("user", .{}); defer user_dir.close(); try user_dir.makeDir(name); var dir = try user_dir.openDir(name, .{}); errdefer dir.close(); var file = try dir.createFile("info.json", .{ .lock = .exclusive }); file.close(); const hash_buf = try allocator.alloc(u8, 256); const hash = try std.crypto.pwhash.bcrypt.strHash( password, .{ .params = .{ .rounds_log = 3, .silently_truncate_password = false, }, .encoding = .phc, }, hash_buf ); const info: Info = .{ .name = name, .full_name = full_name, .birthday = birthday, .hash = hash, .is_admin = is_admin, }; return .{ .dir = dir, .arena = arena, .info = info, }; } pub fn sync(self: *Self) !void { const file = try self.dir.openFile("info.json", .{ .mode = .write_only, .lock = .exclusive, }); defer file.close(); var buffer: [1024]u8 = undefined; var writer = file.writer(&buffer); var stringify = std.json.Stringify { .writer = &writer.interface, .options = .{ .whitespace = .indent_2 } }; try stringify.write(self.info); try writer.interface.flush(); } pub fn check_password(self: *Self, password: []const u8) bool { std.crypto.pwhash.bcrypt.strVerify(self.info.hash, password, .{ .silently_truncate_password = false, }) catch return false; return true; } pub fn deinit(self: *Self) void { self.dir.close(); self.arena.deinit(); self.* = undefined; } pub fn exists(storage: *Storage, name: []const u8) bool { var user = storage.dir.openDir("user", .{}) catch return false; defer user.close(); user.access(name, .{}) catch return false; return true; } pub fn image(storage: *Storage, name: []const u8) ?std.fs.File { var user = storage.dir.openDir("user", .{}) catch return null; defer user.close(); var profile = user.openDir(name, .{}) catch return null; defer profile.close(); return profile.openFile("image.jpg", .{ .lock = .shared }) catch null; } pub fn set_image( storage: *Storage, name: []const u8, reader: *std.Io.Reader, size: usize, ) !void { var user = try storage.dir.openDir("user", .{}); defer user.close(); var profile = try user.openDir(name, .{}); defer profile.close(); var image_file = try profile.createFile("image.jpg", .{ .lock = .exclusive }); defer image_file.close(); var buffer: [1024]u8 = undefined; var file_writer = image_file.writer(&buffer); log.info("uploaded to profile {s} [{} bytes]", .{ name, size }); try reader.streamExact(&file_writer.interface, size); try file_writer.interface.flush(); }