╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Assignment.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
49 if (std.ascii.isWhitespace(ch)) return false; 50 } 51 return true; 52 } 53 54 // only what the event says about itself is checked. an assignment naming a 55 // missing thread is inert. 56 fn validate(event_id: *const [evt.event_id_size]u8, record: Record) !void { 57 const thread_id = try evt.parseEventId(&record.event.thread_id); 58 if (!emailValid(record.event.email)) return error.InvalidEmail; 59 if (!std.mem.eql(u8, event_id, &idOf(&thread_id, record.event.email))) return error.InvalidAssignmentId; 60 } 61 62 pub fn consume( 63 comptime DB: type, 64 comptime hash_kind: hash.HashKind, 65 haxy_moment: DB.HashMap(.read_write), 66 event_id: *const [evt.event_id_size]u8, 67 record_maybe: ?Record, 68 arena: *std.heap.ArenaAllocator, 69 _: ?[]const u8, 70 ) !void { 71 const assignment_key = hash.hashInt(hash_kind, event_id); 72 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 73 const by_thread = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, thread_id_to_assignment_id_set_key))); 74 75 var existing_maybe: ?Record = null; 76 const existing_cursor_maybe = try records.getCursor(assignment_key); 77 if (existing_cursor_maybe) |cursor| { 78 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 79 } 80 81 var record = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_maybe); 82 83 if (!record.removed) try validate(event_id, record); 84 85 if (existing_maybe) |existing| { 86 // updates preserve the original creation metadata 87 record.created_order = existing.created_order; 88 record.author_email = existing.author_email; 89 } 90 91 const assignment_cursor = try records.putCursor(assignment_key); 92 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(assignment_cursor), record); 93 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .assign, existing_maybe, record); 94 95 // the id set retains removed records so merges can carry removals 96 if (existing_cursor_maybe == null) { 97 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, all_id_set_key))); 98 try ids.put(&evt.orderKeyDesc(record.created_order, event_id)); 99 } 100 101 // the thread's set holds active assignments only, oldest first. the id 102 // fixes the thread, so an assignment never moves between sets. 103 const order_key = evt.orderKey(record.created_order, event_id); 104 const thread_id = try evt.parseEventId(&record.event.thread_id); 105 const thread_assignments = try DB.SortedSet(.read_write).init(try by_thread.putCursor(hash.hashInt(hash_kind, &thread_id))); 106 if (record.removed) { 107 _ = try thread_assignments.remove(&order_key); 108 } else { 109 try thread_assignments.put(&order_key); 110 } 111 112 // only an issue is indexed by assignee 113 const issue = (try evt.readRecordSubset(evt.Issue, struct { created_order: u64, removed: bool }, DB, hash_kind, haxy_moment.readOnly(), arena, &thread_id)) orelse return; 114 try indexIssue(DB, hash_kind, haxy_moment, record.event.email, &evt.orderKeyDesc(issue.created_order, &thread_id), !record.removed and !issue.removed); 115 } 116 117 // put or drop an issue's order key in `email`'s assignee set, pruning an emptied set 118 pub fn indexIssue( 119 comptime DB: type, 120 comptime hash_kind: hash.HashKind, 121 haxy_moment: DB.HashMap(.read_write), 122 email: []const u8, 123 order_key: []const u8, 124 active: bool, 125 ) !void { 126 const by_assignee = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, assignee_to_issue_id_set_key))); 127 const email_key = hash.hashInt(hash_kind, email); 128 if (active) { 129 const issues = try DB.SortedSet(.read_write).init(try by_assignee.putCursor(email_key)); 130 try issues.put(order_key); 131 return; 132 } 133 if (null == try by_assignee.getCursor(email_key)) return; 134 const issues = try DB.SortedSet(.read_write).init(try by_assignee.putCursor(email_key)); 135 _ = try issues.remove(order_key); 136 if (0 == try issues.count()) _ = try by_assignee.remove(email_key); 137 } 138 139 // assign `email` to a thread and return the event id. `repo` must be writable. 140 pub fn create( 141 host: evt.Host, 142 comptime repo_kind: rp.RepoKind, 143 comptime repo_opts: rp.RepoOpts(repo_kind), 144 io: std.Io, 145 allocator: std.mem.Allocator, 146 repo: *rp.Repo(repo_kind, repo_opts), 147 thread_kind: evt.EventKind, 148 thread_id: *const [evt.event_id_size * 2]u8, 149 email: []const u8, 150 author: evt.CommitAuthor, 151 ) ![evt.event_id_size * 2]u8 { 152 if (!emailValid(email)) return error.InvalidFields; 153 154 const thread_id_bytes = evt.parseEventId(thread_id) catch return error.NotFound; 155 if (!try evt.threadHolds(repo_kind, repo_opts, io, allocator, repo, thread_kind, &thread_id_bytes, null)) return error.NotFound; 156 157 const id_bytes = idOf(&thread_id_bytes, email); 158 { 159 var arena = std.heap.ArenaAllocator.init(allocator); 160 defer arena.deinit(); 161 if (try evt.readFromRepo(Self, repo_kind, repo_opts, io, allocator, &arena, repo, &id_bytes)) |existing| { 162 if (!existing.removed) return error.AlreadyAssigned; 163 } 164 } 165 166 const event_id = std.fmt.bytesToHex(id_bytes, .lower); 167 try evt.consume(host, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ 168 .id = event_id, 169 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 170 .author = author, 171 .event = .{ .assign = .{ .thread_id = thread_id.*, .email = email } }, 172 }}); 173 return event_id; 174 } 175 176 // read an assignment by event id, or null if the id isn't a known assignment 177 pub fn readById( 178 comptime DB: type, 179 comptime hash_kind: hash.HashKind, 180 haxy_moment: DB.HashMap(.read_only), 181 arena: *std.heap.ArenaAllocator, 182 assignment_id: []const u8, 183 ) !?Record { 184 return try evt.readRecordSubset(Self, Record, DB, hash_kind, haxy_moment, arena, assignment_id); 185 } 186 187 // the emails assigned to a thread, oldest first 188 pub fn load( 189 comptime hash_kind: hash.HashKind, 190 arena: *std.heap.ArenaAllocator, 191 haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), 192 thread_id: *const [evt.event_id_size]u8, 193 ) ![]const []const u8 { 194 const DB = evt.EventDB(hash_kind); 195 var emails: std.ArrayList([]const u8) = .empty; 196 const index_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, thread_id_to_assignment_id_set_key)) orelse return emails.items; 197 const index = try DB.HashMap(.read_only).init(index_cursor); 198 const set_cursor = try index.getCursor(hash.hashInt(hash_kind, thread_id)) orelse return emails.items; 199 const set = try DB.SortedSet(.read_only).init(set_cursor); 200 201 var iter = try set.iteratorFromIndex(0); 202 while (try iter.next()) |kv_cursor| { 203 const id_bytes = try evt.readOrderKeyId(DB, kv_cursor); 204 const record = (try evt.readRecordSubset(Self, struct { event: struct { email: []const u8 } }, DB, hash_kind, haxy_moment, arena, &id_bytes)) orelse continue; 205 try emails.append(arena.allocator(), record.event.email); 206 } 207 return emails.items; 208 } 209