╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │26 │ │27 const Self = @This(); │ │28 │ │29 +pub const name_min_len = 5; │ │30 pub const name_max_len = 32; │ │31 │ │32 // the moment keys `evt.merge` reads and writes for this kind│ ╰─────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │40 │ │41 pub fn validateName(name: []const u8) !void { │ │42 if (name.len == 0) return error.NameEmpty; │ │43 + if (name.len < name_min_len) return error.NameTooShort; │ │44 if (name.len > name_max_len) return error.NameTooLong; │ │45 if (name[0] == '-' or name[name.len - 1] == '-') return error.InvalidName;│ │46 │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/test/event/admin.zig│ ╰────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │ 6 const hash = xit.hash; │ │ 7 │ │ 8 const fork_repo_opts: rp.RepoOpts(.xit) = .{ .is_test = true }; │ │ 9 -const author = evt.CommitAuthor{ .name = "haxy", .email = "user@haxy" }; │ │ 9 +const author = evt.CommitAuthor{ .name = "haxyuser", .email = "user@haxy" };│ │10 │ │11 test "user and repo" { │ │12 const io = std.testing.io; │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │127 }{ │ │128 .{ .name = "admin", .email = "admin@example.test" }, │ │129 .{ .name = "alice", .email = "alice@example.test" }, │ │130 - .{ .name = "bob", .email = "bob@example.test" }, │ │130 + .{ .name = "bobby", .email = "bobby@example.test" }, │ │131 .{ .name = "carol", .email = "carol@example.test" }, │ │132 - .{ .name = "dave", .email = "dave@example.test" }, │ │133 - .{ .name = "eve", .email = "eve@example.test" }, │ │132 + .{ .name = "david", .email = "david@example.test" }, │ │133 + .{ .name = "evelyn", .email = "evelyn@example.test" },│ │134 .{ .name = "frank", .email = "frank@example.test" }, │ │135 .{ .name = "grace", .email = "grace@example.test" }, │ │136 .{ .name = "henry", .email = "henry@example.test" }, │ ╰───────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │137 - .{ .name = "ivy", .email = "ivy@example.test" }, │ │138 - .{ .name = "jack", .email = "jack@example.test" }, │ │139 - .{ .name = "kate", .email = "kate@example.test" }, │ │140 - .{ .name = "liam", .email = "liam@example.test" }, │ │141 - .{ .name = "mona", .email = "mona@example.test" }, │ │142 - .{ .name = "noah", .email = "noah@example.test" }, │ │137 + .{ .name = "irene", .email = "irene@example.test" }, │ │138 + .{ .name = "jacob", .email = "jacob@example.test" }, │ │139 + .{ .name = "katie", .email = "katie@example.test" }, │ │140 + .{ .name = "logan", .email = "logan@example.test" }, │ │141 + .{ .name = "molly", .email = "molly@example.test" }, │ │142 + .{ .name = "nolan", .email = "nolan@example.test" }, │ │143 .{ .name = "olivia", .email = "olivia@example.test" },│ │144 .{ .name = "peter", .email = "peter@example.test" }, │ │145 .{ .name = "quinn", .email = "quinn@example.test" }, │ ╰───────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │146 .{ .name = "rachel", .email = "rachel@example.test" },│ │147 - .{ .name = "sam", .email = "sam@example.test" }, │ │148 - .{ .name = "tina", .email = "tina@example.test" }, │ │147 + .{ .name = "simon", .email = "simon@example.test" }, │ │148 + .{ .name = "tessa", .email = "tessa@example.test" }, │ │149 }; │ │150 │ │151 const repo_data = [_]struct { │ ╰───────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │165 .{ .user_index = 9, .name = "cpython", .description = "The Python programming language" }, │ │166 .{ .user_index = 10, .name = "docker", .description = "Container platform for developing, shipping, and running applications" },│ │167 .{ .user_index = 1, .name = "vim", .description = "The ubiquitous text editor" }, │ │168 - // private, so only bob and admin can see it │ │168 + // private, so only bobby and admin can see it │ │169 .{ .user_index = 2, .name = "neovim", .description = "Hyperextensible Vim-based text editor", .read_access = .private }, │ │170 .{ .user_index = 3, .name = "emacs", .description = "GNU Emacs source code mirror" }, │ │171 .{ .user_index = 4, .name = "tmux", .description = "Terminal multiplexer" }, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1296 comment.* = .{ │ │1297 .id = std.fmt.bytesToHex(comment_ids[i], .lower), │ │1298 .timestamp = @intCast(700 + i), │ │1299 - .author = .{ .name = if (i % 2 == 0) "alice" else "bob", .email = if (i % 2 == 0) "alice@example.test" else "bob@example.test" }, │ │1299 + .author = .{ .name = if (i % 2 == 0) "alice" else "bobby", .email = if (i % 2 == 0) "alice@example.test" else "bobby@example.test" },│ │1300 .event = .{ .comment = .{ │ │1301 .thread_id = std.fmt.bytesToHex(patch_ids[patch_ids.len - 1], .lower), │ │1302 .parent_id = switch (i) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1360 │ │1361 var theirs = ours; │ │1362 theirs[0].timestamp = 810; │ │1363 - theirs[0].author = .{ .name = "bob", .email = "bob@example.test" }; │ │1363 + theirs[0].author = .{ .name = "bobby", .email = "bobby@example.test" }; │ │1364 theirs[0].event.patch = theirs_values[0]; │ │1365 theirs[1].timestamp = 811; │ │1366 - theirs[1].author = .{ .name = "bob", .email = "bob@example.test" }; │ │1366 + theirs[1].author = .{ .name = "bobby", .email = "bobby@example.test" }; │ │1367 theirs[1].event.patch = theirs_values[1]; │ │1368 theirs[2].timestamp = 812; │ │1369 - theirs[2].author = .{ .name = "bob", .email = "bob@example.test" }; │ │1369 + theirs[2].author = .{ .name = "bobby", .email = "bobby@example.test" }; │ │1370 theirs[2].event.patch = theirs_values[2]; │ │1371 │ │1372 try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .repo, .xit, .{}, io, allocator, target_repo, evt.events_ref, &ours);│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯