╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1906 return .{ .owner_id = owner_id.*, .repo_id = id_bytes }; │ │1907 } │ │1908 │ │1909 +// create a user and their user repo, returning the new user's id │ │1910 +pub fn createUser( │ │1911 + io: std.Io, │ │1912 + allocator: std.mem.Allocator, │ │1913 + users_dir: []const u8, │ │1914 + admin_repo: *rp.Repo(.xit, admin_repo_opts), │ │1915 + name: []const u8, │ │1916 + email: []const u8, │ │1917 + password: []const u8, │ │1918 + password_again: []const u8, │ │1919 +) ![event_id_size]u8 { │ │1920 + try User.validateName(name); │ │1921 + if (email.len == 0) return error.EmailEmpty; │ │1922 + if (password.len == 0) return error.PasswordEmpty; │ │1923 + if (!std.mem.eql(u8, password, password_again)) return error.PasswordMismatch; │ │1924 + // a fresh admin repo has no moment until its first event │ │1925 + if (currentMoment(admin_repo_opts, admin_repo)) |moment| { │ │1926 + if (try User.readIdByName(AdminDB, admin_repo_opts.hash, moment, name) != null) return error.NameTaken; │ │1927 + var arena = std.heap.ArenaAllocator.init(allocator); │ │1928 + defer arena.deinit(); │ │1929 + if (try User.readByEmail(AdminDB, admin_repo_opts.hash, moment, &arena, email) != null) return error.EmailTaken; │ │1930 + } else |err| switch (err) { │ │1931 + error.NotFound => {}, │ │1932 + else => |e| return e, │ │1933 + } │ │1934 + │ │1935 + var password_hash_buf: [User.password_hash_max_len]u8 = undefined; │ │1936 + const password_hash = try User.hashPassword(password, &password_hash_buf, io); │ │1937 + │ │1938 + var id_bytes: [event_id_size]u8 = undefined; │ │1939 + io.random(&id_bytes); │ │1940 + │ │1941 + // the user repo comes first so a listed user can always create repos │ │1942 + { │ │1943 + const path = try userRepoPath(allocator, users_dir, &id_bytes); │ │1944 + defer allocator.free(path); │ │1945 + var user_repo = try initUserRepo(io, allocator, path); │ │1946 + defer user_repo.deinit(io, allocator); │ │1947 + } │ │1948 + │ │1949 + try consume(.{ .server = .{ .users_dir = users_dir } }, .admin, .xit, admin_repo_opts, io, allocator, admin_repo, events_ref, &[_]EventWithId{.{│ │1950 + .id = std.fmt.bytesToHex(id_bytes, .lower), │ │1951 + .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │1952 + .author = .{ .name = name, .email = email }, │ │1953 + .event = .{ .user = .{ .name = name, .email = email, .password_hash = password_hash } }, │ │1954 + }}); │ │1955 + return id_bytes; │ │1956 +} │ │1957 + │ │1958 // consume a new repo's event into its owner's user repo and mark it active │ │1959 fn writeRepoEvent( │ │1960 io: std.Io, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯