╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────╮ │src/event/Repo.zig│ ╰──────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │153 try repo_id_set.put(&order_key); │ │154 } │ │155 │ │156 + // the user's repos, ordered by creation time, newest first │ │157 const active = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, active_id_set_key)));│ │158 if (record_to_write.removed) { │ │159 _ = try active.remove(&order_key); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │160 } else { │ │161 try active.put(&order_key); │ │176 - const repo_path = try record_to_write.indexKey(arena.allocator()); │ │177 - try name_to_repo_id.put(hash.hashInt(hash_kind, repo_path), .{ .bytes = event_id }); │ │178 - │ │179 - // each user's repos are ordered by creation time, newest first │ │180 - const user_repos_cursor = try user_id_to_repo_id_set.putCursor(hash.hashInt(hash_kind, record_to_write.event.user_id));│ │181 - const user_repos = try DB.SortedSet(.read_write).init(user_repos_cursor); │ │182 - try user_repos.put(&order_key); │ │162 + try name_to_repo_id.put(hash.hashInt(hash_kind, record_to_write.event.name), .{ .bytes = event_id }); │ │163 } │ │164 } │ │165 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │170 event_id: [evt.event_id_size]u8, │ │171 }; │ │172 │ │193 -// read a repo by its owner's name and repo name. the owner name is resolved to │ │194 -// a user id via name->user-id (the repo index is keyed by "user-id/repo-name"), │ │195 -// matching the /repo/username/reponame url. │ │196 -pub fn readByOwnerAndName( │ │173 +// read a repo by its name from its owner's user repo │ │174 +pub fn readByName( │ │175 comptime DB: type, │ │176 comptime hash_kind: hash.HashKind, │ │199 - haxy_moment: DB.HashMap(.read_only), │ │177 + user_moment: DB.HashMap(.read_only), │ │178 arena: *std.heap.ArenaAllocator, │ │201 - owner_name: []const u8, │ │202 - repo_name: []const u8, │ │179 + name: []const u8, │ │180 ) !?RepoWithId { │ │204 - // owner name -> user id │ │205 - const name_to_user_id_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "name->user-id")) orelse return null; │ │206 - const name_to_user_id = try DB.HashMap(.read_only).init(name_to_user_id_cursor); │ │207 - const user_id_cursor = try name_to_user_id.getCursor(hash.hashInt(hash_kind, owner_name)) orelse return null; │ │208 - var user_id: [evt.event_id_size]u8 = undefined; │ │209 - _ = try user_id_cursor.readBytes(&user_id); │ │210 - │ │211 - // "user-id/repo-name" -> repo event id │ │212 - const repo_path = try (Record{ .event = .{ .user_id = user_id[0..], .name = repo_name, .description = "" } }).indexKey(arena.allocator());│ │213 - const name_to_repo_id_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "name->repo-id")) orelse return null; │ │181 + const name_to_repo_id_cursor = try user_moment.getCursor(hash.hashInt(hash_kind, name_index_key)) orelse return null; │ │182 const name_to_repo_id = try DB.HashMap(.read_only).init(name_to_repo_id_cursor); │ │215 - const repo_id_cursor = try name_to_repo_id.getCursor(hash.hashInt(hash_kind, repo_path)) orelse return null; │ │183 + const repo_id_cursor = try name_to_repo_id.getCursor(hash.hashInt(hash_kind, name)) orelse return null; │ │184 var repo_id: [evt.event_id_size]u8 = undefined; │ │185 _ = try repo_id_cursor.readBytes(&repo_id); │ │218 - │ │219 - const event_id_to_repo_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->repo")) orelse return null; │ │220 - const event_id_to_repo = try DB.HashMap(.read_only).init(event_id_to_repo_cursor); │ │221 - const repo_cursor = try event_id_to_repo.getCursor(hash.hashInt(hash_kind, &repo_id)) orelse return null; │ │222 - const repo_map = try DB.HashMap(.read_only).init(repo_cursor); │ │223 - return .{ .repo = try evt.read(Record, DB, hash_kind, arena, repo_map), .event_id = repo_id }; │ │186 + const repo = (try readById(DB, hash_kind, user_moment, arena, &repo_id)) orelse return null; │ │187 + return .{ .repo = repo, .event_id = repo_id }; │ │188 } │ │189 │ │190 pub fn readById( │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │17 removed: bool = false, │ │18 created_order: u64 = 0, │ │19 updated_order: u64 = 0, │ │20 - │ │21 - // a user's key in the name index │ │22 - pub fn indexKey(self: Record, allocator: std.mem.Allocator) ![]const u8 {│ │23 - _ = allocator; │ │24 - return self.event.name; │ │25 - } │ │20 }; │ │21 │ │22 // the subset of a user that anyone may see │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │118 try active.put(&order_key); │ │119 try name_to_user_id.put(hash.hashInt(hash_kind, record_to_write.event.name), .{ .bytes = event_id }); │ │120 try email_to_user_id.put(hash.hashInt(hash_kind, record_to_write.event.email), .{ .bytes = event_id }); │ │127 - } │ │128 - │ │129 - const became_removed = record_to_write.removed and if (existing_record_maybe) |existing| !existing.removed else true;│ │130 - if (became_removed) { │ │131 - // the user's repos go with them: the repo name index is keyed by owner │ │132 - // id, so one left behind is listed but can never be resolved again │ │133 - const user_id_to_repo_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repo-id-set"));│ │134 - const user_id_to_repo_id_set = try DB.HashMap(.read_write).init(user_id_to_repo_id_set_cursor); │ │135 - if (try user_id_to_repo_id_set.getCursor(user_key)) |user_repos_cursor| { │ │136 - const user_repos = try DB.SortedSet(.read_only).init(user_repos_cursor); │ │137 - │ │138 - // collected up front, since removing a repo removes it from this set │ │139 - var repo_ids: std.ArrayList([evt.event_id_size]u8) = .empty; │ │140 - var repo_iter = try user_repos.iteratorFromIndex(0); │ │141 - while (try repo_iter.next()) |kv_pair_cursor| { │ │142 - try repo_ids.append(arena.allocator(), try evt.readOrderKeyId(DB, kv_pair_cursor)); │ │143 - } │ │144 - │ │145 - for (repo_ids.items) |*repo_id| { │ │146 - try evt.Repo.consume(DB, hash_kind, haxy_moment, repo_id, null, arena, null); │ │147 - } │ │148 - } │ │149 - _ = try user_id_to_repo_id_set.remove(user_key); │ │121 } │ │122 } │ │123 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │203 const user_map = try userMap(DB, hash_kind, haxy_moment, &user_id) orelse return null; │ │204 const record = try evt.read(struct { event: Public }, DB, hash_kind, arena, user_map); │ │205 return record.event; │ │206 +} │ │207 + │ │208 +// a user's id via the name->user-id index │ │209 +pub fn readIdByName( │ │210 + comptime DB: type, │ │211 + comptime hash_kind: hash.HashKind, │ │212 + haxy_moment: DB.HashMap(.read_only), │ │213 + name: []const u8, │ │214 +) !?[evt.event_id_size]u8 { │ │215 + return try readIdFromIndex(DB, hash_kind, haxy_moment, name_index_key, name); │ │216 +} │ │217 + │ │218 +// a user's id via the email->user-id index │ │219 +pub fn readIdByEmail( │ │220 + comptime DB: type, │ │221 + comptime hash_kind: hash.HashKind, │ │222 + haxy_moment: DB.HashMap(.read_only), │ │223 + email: []const u8, │ │224 +) !?[evt.event_id_size]u8 { │ │225 + return try readIdFromIndex(DB, hash_kind, haxy_moment, email_to_user_id_key, email); │ │226 +} │ │227 + │ │228 +fn readIdFromIndex( │ │229 + comptime DB: type, │ │230 + comptime hash_kind: hash.HashKind, │ │231 + haxy_moment: DB.HashMap(.read_only), │ │232 + index_key: []const u8, │ │233 + key: []const u8, │ │234 +) !?[evt.event_id_size]u8 { │ │235 + const index_cursor = (try haxy_moment.getCursor(hash.hashInt(hash_kind, index_key))) orelse return null;│ │236 + const index = try DB.HashMap(.read_only).init(index_cursor); │ │237 + const user_id_cursor = (try index.getCursor(hash.hashInt(hash_kind, key))) orelse return null; │ │238 + var user_id: [evt.event_id_size]u8 = undefined; │ │239 + _ = try user_id_cursor.readBytes(&user_id); │ │240 + return user_id; │ │241 } │ │242 │ │243 // a user's record map via the event-id->user index │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───╮ │src/fork.zig│ ╰────────────╯ ╭────────────────────────────────────────────╮ │37 id: [evt.event_id_size * 2]u8, │ │38 user_id: [evt.event_id_size]u8, │ │39 repo_id: [evt.event_id_size]u8, │ │40 + repo_user_id: [evt.event_id_size]u8,│ │41 title: []const u8, │ │42 description: []const u8, │ │43 labels: []const u8, │ ╰────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │51 io: std.Io, │ │52 allocator: std.mem.Allocator, │ │53 repo_root_path: []const u8, │ │53 - admin_repo: *rp.Repo(.xit, evt.admin_repo_opts), │ │54 + user_repo: *rp.Repo(.xit, evt.user_repo_opts), │ │55 input: CreateInput, │ │56 ) ![]u8 { │ │57 if (!evt.Patch.fieldsValid(input.title, input.labels)) return error.InvalidPatch;│ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │65 // make sure the fork id doesn't already exist │ │66 var arena = std.heap.ArenaAllocator.init(allocator); │ │67 defer arena.deinit(); │ │67 - const existing = if (evt.currentMoment(evt.admin_repo_opts, admin_repo)) |moment| │ │68 - try evt.Fork.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, &arena, &fork_id)│ │69 - else |err| switch (err) { │ │70 - error.NotFound => null, │ │71 - else => |other| return other, │ │72 - }; │ │68 + const existing = if (try evt.userMoment(user_repo)) |moment| │ │69 + try evt.Fork.readById(evt.UserDB, evt.user_repo_opts.hash, moment, &arena, &fork_id) │ │70 + else │ │71 + null; │ │72 if (existing != null) return error.InvalidPatchDraft; │ │73 │ │74 // get the target repo │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │122 core: *rp.Repo(.xit, repo_opts).Core, │ │123 io: std.Io, │ │124 allocator: std.mem.Allocator, │ │125 + forker_id: [evt.event_id_size * 2]u8, │ │126 │ │127 pub fn run(ctx: @This(), cursor: *DB.Cursor(.read_write)) !void {│ │128 var moment = try DB.HashMap(.read_write).init(cursor.*); │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯