╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Assignment.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
68 var existing_maybe: ?Record = null; 69 const existing_cursor_maybe = try records.getCursor(assignment_key); 70 if (existing_cursor_maybe) |cursor| { 71 existing_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(cursor)); 72 } 73 74 var record = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_maybe); 75 76 if (!record.removed) try validate(event_id, record); 77 78 if (existing_maybe) |existing| { 79 // updates preserve the original creation metadata 80 record.created_order = existing.created_order; 81 record.author_email = existing.author_email; 82 } 83 84 const assignment_cursor = try records.putCursor(assignment_key); 85 try evt.upsert(Record, DB, hash_kind, try DB.HashMap(.read_write).init(assignment_cursor), record); 86 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .assign, existing_maybe, record); 87 88 // the id set retains removed records so merges can carry removals 89 if (existing_cursor_maybe == null) { 90 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, all_id_set_key))); 91 try ids.put(&evt.orderKeyDesc(record.created_order, event_id)); 92 } 93 94 // the thread's set holds active assignments only, oldest first. the id 95 // fixes the thread, so an assignment never moves between sets. 96 const order_key = evt.orderKey(record.created_order, event_id); 97 const thread_assignments = try DB.SortedSet(.read_write).init(try by_thread.putCursor(hash.hashInt(hash_kind, &record.event.thread_id))); 98 if (record.removed) { 99 _ = try thread_assignments.remove(&order_key); 100 } else { 101 try thread_assignments.put(&order_key); 102 } 103 } 104 105 // assign `email` to a thread and return the event id. `repo` must be writable. 106 pub fn create( 107 host: evt.Host, 108 comptime repo_kind: rp.RepoKind, 109 comptime repo_opts: rp.RepoOpts(repo_kind), 110 io: std.Io, 111 allocator: std.mem.Allocator, 112 repo: *rp.Repo(repo_kind, repo_opts), 113 thread_kind: evt.EventKind, 114 thread_id: *const [evt.event_id_size * 2]u8, 115 email: []const u8, 116 author: evt.CommitAuthor, 117 ) ![evt.event_id_size * 2]u8 { 118 if (!emailValid(email)) return error.InvalidFields; 119 120 const thread_id_bytes = evt.parseEventId(thread_id) catch return error.NotFound; 121 if (!try evt.threadHolds(repo_kind, repo_opts, io, allocator, repo, thread_kind, &thread_id_bytes, null)) return error.NotFound; 122 123 const id_bytes = idOf(&thread_id_bytes, email); 124 { 125 var arena = std.heap.ArenaAllocator.init(allocator); 126 defer arena.deinit(); 127 if (try evt.readFromRepo(Self, repo_kind, repo_opts, io, allocator, &arena, repo, &id_bytes)) |existing| { 128 if (!existing.removed) return error.AlreadyAssigned; 129 } 130 } 131 132 const event_id = std.fmt.bytesToHex(id_bytes, .lower); 133 try evt.consume(host, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ 134 .id = event_id, 135 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 136 .author = author, 137 .event = .{ .assign = .{ .thread_id = thread_id.*, .email = email } }, 138 }}); 139 return event_id; 140 } 141 142 // read an assignment by event id, or null if the id isn't a known assignment 143 pub fn readById( 144 comptime DB: type, 145 comptime hash_kind: hash.HashKind, 146 haxy_moment: DB.HashMap(.read_only), 147 arena: *std.heap.ArenaAllocator, 148 assignment_id: []const u8, 149 ) !?Record { 150 return try evt.readRecordSubset(Self, Record, DB, hash_kind, haxy_moment, arena, assignment_id); 151 } 152 153 // the emails assigned to a thread, oldest first 154 pub fn load( 155 comptime hash_kind: hash.HashKind, 156 arena: *std.heap.ArenaAllocator, 157 haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), 158 thread_id: []const u8, 159 ) ![]const []const u8 { 160 const DB = evt.EventDB(hash_kind); 161 var emails: std.ArrayList([]const u8) = .empty; 162 const index_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, thread_id_to_assignment_id_set_key)) orelse return emails.items; 163 const index = try DB.HashMap(.read_only).init(index_cursor); 164 const set_cursor = try index.getCursor(hash.hashInt(hash_kind, thread_id)) orelse return emails.items; 165 const set = try DB.SortedSet(.read_only).init(set_cursor); 166 167 var iter = try set.iteratorFromIndex(0); 168 while (try iter.next()) |kv_cursor| { 169 const id_bytes = try evt.readOrderKeyId(DB, kv_cursor); 170 const record = (try evt.readRecordSubset(Self, struct { event: struct { email: []const u8 } }, DB, hash_kind, haxy_moment, arena, &id_bytes)) orelse continue; 171 try emails.append(arena.allocator(), record.event.email); 172 } 173 return emails.items; 174 } 175