╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │248 .name = r.name, │ │249 .description = r.description, │ │250 .read_access = r.read_access, │ │250 - .owner_user_ids = &admin_user_id_hex, │ │251 }, │ │252 }, │ │253 + }; │ │254 + grant_events[i] = .{ │ │255 + .id = std.fmt.bytesToHex(evt.Grant.idOf(&repo_event_ids[i], &admin_user_id), .lower), │ │256 + .timestamp = @intCast(user_data.len + i + 1), │ │257 + .author = author, │ │258 + .event = .{ .grant = .{ .target_id = &repo_event_ids[i], .user_id = &admin_user_id, .role = .owner } },│ │259 }; │ │260 } │ │261 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │256 - // each user's repo holds the repos they own │ │262 + // each user's repo holds the repos they own and the grants on them │ │263 for (user_ids, 0..) |user_id, user_index| { │ │264 const user_repo_path = try evt.userRepoPath(arena.allocator(), users_dir, &user_id);│ │265 var user_repo = try evt.initUserRepo(io, allocator, user_repo_path); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │266 defer user_repo.deinit(io, allocator); │ │267 var owned: std.ArrayList(evt.EventWithId) = .empty; │ │262 - for (repo_data, repo_events) |r, event| { │ │263 - if (r.user_index == user_index) try owned.append(arena.allocator(), event); │ │268 + for (repo_data, repo_events, grant_events) |r, repo_event, grant_event| { │ │269 + if (r.user_index != user_index) continue; │ │270 + try owned.append(arena.allocator(), repo_event); │ │271 + try owned.append(arena.allocator(), grant_event); │ │272 } │ │273 if (owned.items.len > 0) try evt.consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, evt.user_repo_opts, io, allocator, &user_repo, evt.events_ref, owned.items);│ │274 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1953 role: evt.Repo.Role, │ │1954 email: []const u8, │ │1955 │ │1956 - // the session's viewer of `repo`, or null when logged out. local mode owns everything. │ │1957 - pub fn init(session: *Session, arena: *std.heap.ArenaAllocator, repo: evt.Repo.Record) !?Viewer {│ │1956 + // the session's viewer of a repo it holds `role` in, or null when logged │ │1957 + // out. local mode owns everything. │ │1958 + pub fn init(session: *Session, arena: *std.heap.ArenaAllocator, role: evt.Repo.Role) !?Viewer { │ │1959 if (session.data.host_kind == .local) return .{ .role = .owner, .email = "" }; │ │1960 const user_id = session.userId() orelse return null; │ │1961 const moment = session.haxy_moment orelse unreachable; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │1962 const user = (try activeUser(moment, arena, user_id)) orelse return null; │ │1962 - return .{ .role = evt.Repo.roleOf(repo, user_id), .email = user.event.email };│ │1963 + return .{ .role = role, .email = user.event.email }; │ │1964 } │ │1965 }; │ │1966 │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2081 const user = (try activeUser(moment, arena, user_id)) orelse return .login_required; │ │2082 │ │2083 const owner_repo = evt.parseOwnerRepoPath(identity) orelse return .repo_not_found; │ │2083 - const repo = (try evt.readRepoByOwnerAndName(io, arena.child_allocator, arena, moment, users_dir, owner_repo.owner, owner_repo.name)) orelse return .repo_not_found; │ │2084 - const role = evt.Repo.roleOf(repo.repo, user_id); │ │2084 + const repo = (try evt.readRepoByOwnerAndName(io, arena.child_allocator, arena, moment, users_dir, owner_repo.owner, owner_repo.name, user_id)) orelse return .repo_not_found;│ │2085 + const role = repo.role; │ │2086 // a repo the user can't read doesn't exist to them │ │2087 if (role == .none) return .repo_not_found; │ │2088 if (!role.atLeast(min_role)) return .forbidden; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │47 @memcpy(&target_id, fork_record.event.repo_id); │ │48 const owner_id = fork_record.event.repo_user_id[0..evt.event_id_size]; │ │49 │ │50 - const target_record = (try evt.readRepoById(io, gpa, arena, users_dir, owner_id, fork_record.event.repo_id)) orelse return error.NotFound;│ │50 + const target = (try evt.readRepoById(io, gpa, arena, users_dir, owner_id, &target_id, session.userId())) orelse return error.NotFound; │ │51 // a draft is as readable as the repo it targets │ │52 - if (evt.Repo.roleOf(target_record, session.userId()) == .none) return error.NotFound; │ │52 + if (target.role == .none) return error.NotFound; │ │53 + const target_record = target.repo; │ │54 const owner = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, haxy_moment, arena, owner_id)) orelse return error.NotFound; │ │55 if (!std.mem.eql(u8, target_record.event.name, identity.name)) return error.NotFound; │ │56 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │60 // the fork's viewer is the target repo's │ │61 const handle = ui.RepoHandle{ │ │62 .location = .{ .fork = .{ .identity = identity.identity, .id = &id_hex } },│ │62 - .viewer = try ui.Viewer.init(session, arena, target_record), │ │63 + .viewer = try ui.Viewer.init(session, arena, target.role), │ │64 }; │ │65 const fork_path = try fork.forkPath(aa, users_dir, &forker_id, &id); │ │66 // a fork shares its target's hash kind │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭──────────────────────────────────────────────────────────╮ │227 var source: ?ui.RepoSource = null; │ │228 var repo_id_maybe: ?[evt.event_id_size]u8 = null;│ │229 var repo: evt.Repo.Record = undefined; │ │230 + // local mode owns its repo │ │231 + var role: evt.Repo.Role = .owner; │ │232 var owner_name: []const u8 = undefined; │ │233 if (session.local) |local| { │ │234 source = local; │ ╰──────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │244 const haxy_moment = session.haxy_moment orelse return error.NoMoment; │ │245 const io = session.io orelse return error.NoMoment; │ │246 const users_dir = session.users_dir orelse return error.NoMoment; │ │245 - const found = (try evt.readRepoByOwnerAndName(io, arena.child_allocator, arena, haxy_moment, users_dir, repo_identity.owner, repo_identity.name)) orelse return error.NotFound; │ │247 + const found = (try evt.readRepoByOwnerAndName(io, arena.child_allocator, arena, haxy_moment, users_dir, repo_identity.owner, repo_identity.name, session.userId())) orelse return error.NotFound;│ │248 // a repo the session can't read doesn't exist to it │ │247 - if (evt.Repo.roleOf(found.repo, session.userId()) == .none) return error.NotFound; │ │249 + if (found.role == .none) return error.NotFound; │ │250 repo = found.repo; │ │251 + role = found.role; │ │252 repo_id_maybe = found.event_id; │ │253 │ │254 // resolve the creating user so the header can show their name to the left │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯