╭ changed ───────────────╮ │src/event/Assignment.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │25 │ │26 // the index a thread's view reads │ │27 pub const thread_id_to_assignment_id_set_key = "thread-id->assignment-id-set"; │ │28 +// the issues each email is assigned to, keyed like the issue status sets │ │29 +pub const assignee_to_issue_id_set_key = "assignee->issue-id-set"; │ │30 │ │31 +// the longest email an assignment holds │ │32 +pub const email_max_len = 254; │ │33 + │ │34 // the id of the one assignment an email can hold on a thread │ │35 pub fn idOf(thread_id: *const [evt.event_id_size]u8, email: []const u8) [evt.event_id_size]u8 {│ │36 var hasher = std.crypto.hash.sha2.Sha256.init(.{}); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │42 } │ │43 │ │44 fn emailValid(email: []const u8) bool { │ │40 - if (email.len == 0) return false; │ │45 + if (email.len == 0 or email.len > email_max_len) return false;│ │46 for (email) |ch| { │ │47 if (std.ascii.isWhitespace(ch)) return false; │ │48 } │ ╰──────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │104 _ = try thread_assignments.remove(&order_key); │ │105 } else { │ │106 try thread_assignments.put(&order_key); │ │107 + } │ │108 + │ │109 + // only an issue is indexed by assignee │ │110 + const thread_id = try evt.parseEventId(&record.event.thread_id); │ │111 + const issue = (try evt.readRecordSubset(evt.Issue, struct { created_order: u64, removed: bool }, DB, hash_kind, haxy_moment.readOnly(), arena, &thread_id)) orelse return;│ │112 + try indexIssue(DB, hash_kind, haxy_moment, record.event.email, &evt.orderKeyDesc(issue.created_order, &thread_id), !record.removed and !issue.removed); │ │113 +} │ │114 + │ │115 +// put or drop an issue's order key in `email`'s assignee set, pruning an emptied set │ │116 +pub fn indexIssue( │ │117 + comptime DB: type, │ │118 + comptime hash_kind: hash.HashKind, │ │119 + haxy_moment: DB.HashMap(.read_write), │ │120 + email: []const u8, │ │121 + order_key: []const u8, │ │122 + active: bool, │ │123 +) !void { │ │124 + const by_assignee = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, assignee_to_issue_id_set_key))); │ │125 + const email_key = hash.hashInt(hash_kind, email); │ │126 + if (active) { │ │127 + const issues = try DB.SortedSet(.read_write).init(try by_assignee.putCursor(email_key)); │ │128 + try issues.put(order_key); │ │129 + return; │ │130 } │ │131 + if (null == try by_assignee.getCursor(email_key)) return; │ │132 + const issues = try DB.SortedSet(.read_write).init(try by_assignee.putCursor(email_key)); │ │133 + _ = try issues.remove(order_key); │ │134 + if (0 == try issues.count()) _ = try by_assignee.remove(email_key); │ │135 } │ │136 │ │137 // assign `email` to a thread and return the event id. `repo` must be writable. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯