╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1651 } │ │1652 } │ │1653 } │ │1654 + } │ │1655 +} │ │1656 + │ │1657 +// a comment or attachment a write names under a thread │ │1658 +pub const ThreadChild = union(enum) { │ │1659 + comment: *const [event_id_size]u8, │ │1660 + attach: *const [event_id_size]u8, │ │1661 +}; │ │1662 + │ │1663 +// whether `thread_id` names a thread of `thread_kind` in the repo's events, and │ │1664 +// `child`, when given, belongs to it. a caller's url can claim any kind for any │ │1665 +// id, so every write under a thread checks its claim here first. │ │1666 +pub fn threadHolds( │ │1667 + comptime repo_kind: rp.RepoKind, │ │1668 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │1669 + io: std.Io, │ │1670 + allocator: std.mem.Allocator, │ │1671 + repo: *rp.Repo(repo_kind, repo_opts), │ │1672 + thread_kind: EventKind, │ │1673 + thread_id: *const [event_id_size]u8, │ │1674 + child_maybe: ?ThreadChild, │ │1675 +) !bool { │ │1676 + const DB = EventDB(repo_opts.hash); │ │1677 + var event_db_maybe: ?LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try LocalEventDB(repo_opts.hash).openReadOnly(io, allocator, repo.core.repo_dir) else null; │ │1678 + defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); │ │1679 + const moment = (if (event_db_maybe) |*event_db| │ │1680 + currentMomentFromDb(repo_opts.hash, event_db.db) │ │1681 + else if (repo_kind == .git) │ │1682 + return false │ │1683 + else │ │1684 + currentMoment(repo_opts, repo)) catch return false; │ │1685 + │ │1686 + // only these kinds are threads │ │1687 + switch (thread_kind) { │ │1688 + .discuss, .issue, .patch => {}, │ │1689 + else => return false, │ │1690 + } │ │1691 + const kinds_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, event_index_key)) orelse return false; │ │1692 + const kinds = try DB.HashMap(.read_only).init(kinds_cursor); │ │1693 + if ((try readEventKind(repo_opts.hash, kinds, thread_id)) != thread_kind) return false; │ │1694 + │ │1695 + const child = child_maybe orelse return true; │ │1696 + var arena = std.heap.ArenaAllocator.init(allocator); │ │1697 + defer arena.deinit(); │ │1698 + const thread_id_hex = std.fmt.bytesToHex(thread_id.*, .lower); │ │1699 + switch (child) { │ │1700 + .comment => |id| { │ │1701 + const comment = (try readRecordSubset(Comment, struct { event: struct { thread_id: [event_id_size * 2]u8 } }, DB, repo_opts.hash, moment, &arena, id)) orelse return false; │ │1702 + return std.mem.eql(u8, &comment.event.thread_id, &thread_id_hex); │ │1703 + }, │ │1704 + .attach => |id| { │ │1705 + const attachment = (try readRecordSubset(Attachment, struct { event: struct { parent_id: [event_id_size * 2]u8 } }, DB, repo_opts.hash, moment, &arena, id)) orelse return false;│ │1706 + return std.mem.eql(u8, &attachment.event.parent_id, &thread_id_hex); │ │1707 + }, │ │1708 } │ │1709 } │ │1710 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1711 +// remove an event a thread's page names: the thread itself, or a comment or │ │1712 +// attachment, which must belong to it │ │1713 +pub fn removeInThread( │ │1714 + host: Host, │ │1715 + comptime repo_kind: rp.RepoKind, │ │1716 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │1717 + io: std.Io, │ │1718 + allocator: std.mem.Allocator, │ │1719 + repo: *rp.Repo(repo_kind, repo_opts), │ │1720 + thread_kind: EventKind, │ │1721 + thread_id: *const [event_id_size]u8, │ │1722 + kind: EventKind, │ │1723 + id: *const [event_id_size]u8, │ │1724 + author: CommitAuthor, │ │1725 +) !void { │ │1726 + const child_maybe: ?ThreadChild = switch (kind) { │ │1727 + .comment => .{ .comment = id }, │ │1728 + .attach => .{ .attach = id }, │ │1729 + else => null, │ │1730 + }; │ │1731 + if (child_maybe) |child| { │ │1732 + if (!try threadHolds(repo_kind, repo_opts, io, allocator, repo, thread_kind, thread_id, child)) return error.EventNotFound;│ │1733 + } │ │1734 + try remove(host, .repo, repo_kind, repo_opts, io, allocator, repo, id, kind, author); │ │1735 +} │ │1736 + │ │1737 pub fn readFromRepo( │ │1738 comptime T: type, │ │1739 comptime repo_kind: rp.RepoKind, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────╮ │1890 id: *const [event_id_size]u8, │ │1891 ) !?[]const u8 { │ │1892 const record_map_key = switch (kind) { │ │1813 - .issue => Issue.record_map_key, │ │1893 .discuss => Discussion.record_map_key,│ │1894 + .issue => Issue.record_map_key, │ │1895 .comment => Comment.record_map_key, │ │1896 .attach => Attachment.record_map_key, │ │1897 .patchrev => PatchRev.record_map_key, │ ╰────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────╮ │2032 return .{ .owner_id = owner_id.*, .repo_id = id_bytes }; │ │2033 } │ │2034 │ │1955 -// change the name, description, and read access of the repo named │ │1956 -// `old_name` in its owner's user repo │ │2035 +// change the name, description, read access, and tab roles of the repo│ │2036 +// named `old_name` in its owner's user repo │ │2037 pub fn updateRepo( │ │2038 io: std.Io, │ │2039 allocator: std.mem.Allocator, │ ╰─────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2044 name: []const u8, │ │2045 description: []const u8, │ │2046 read_access: Repo.Access, │ │2047 + discuss_role: ?Repo.Role, │ │2048 + issue_role: ?Repo.Role, │ │2049 + patch_role: ?Repo.Role, │ │2050 ) !void { │ │2051 try Repo.validateName(name); │ │2052 var user_repo = (try openUserRepo(io, allocator, users_dir, owner_id)) orelse return error.NotFound;│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2063 event.name = name; │ │2064 event.description = description; │ │2065 event.read_access = read_access; │ │2066 + event.discuss_role = discuss_role; │ │2067 + event.issue_role = issue_role; │ │2068 + event.patch_role = patch_role; │ │2069 try consume(.{ .server = .{ .users_dir = users_dir } }, .user, .xit, user_repo_opts, io, allocator, &user_repo, events_ref, &[_]EventWithId{.{│ │2070 .id = std.fmt.bytesToHex(found.event_id, .lower), │ │2071 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯