╭ Comment.zig ──────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const evt = @import("../event.zig"); 3 const xit = @import("xit"); 4 const hash = xit.hash; 5 6 thread_id: [evt.event_id_size * 2]u8, 7 parent_id: [evt.event_id_size * 2]u8, 8 body: []const u8, 9 10 // what the db stores: the event's data plus the commit-derived fields 11 pub const Record = struct { 12 event: Self, 13 deleted: bool = false, 14 author_email: ?[]const u8 = null, 15 created_ts: u64 = 0, // the commit timestamp of the event that first created this comment 16 }; 17 18 const Self = @This(); 19 20 // the moment keys `evt.merge` reads and writes for this kind 21 pub const record_map_key = "event-id->comment"; 22 pub const id_set_key = "comment-id-set"; 23 pub const conflicts_key = "conflicted-comment-id->conflict"; 24 pub const id_to_field_to_oid_key = "comment-id->field->oid"; 25 26 // the indexes the thread reads 27 pub const thread_id_to_comment_id_set_key = "thread-id->comment-id-set"; 28 pub const parent_id_to_comment_id_set_key = "parent-id->comment-id-set"; 29 30 pub fn consume( 31 comptime DB: type, 32 comptime hash_kind: hash.HashKind, 33 haxy_moment: DB.HashMap(.read_write), 34 event_id: *const [evt.event_id_size]u8, 35 record_maybe: ?Record, 36 arena: *std.heap.ArenaAllocator, 37 // the commit this event came from, recorded against every field it 38 // changes. null from `merge`, which sets oids and conflicts itself. 39 event_oid: ?[]const u8, 40 ) !void { 41 const comment_key = hash.hashInt(hash_kind, event_id); 42 43 const event_id_to_comment_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key)); 44 const event_id_to_comment = try DB.HashMap(.read_write).init(event_id_to_comment_cursor); 45 46 const thread_id_to_comment_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, thread_id_to_comment_id_set_key)); 47 const thread_id_to_comment_id_set = try DB.HashMap(.read_write).init(thread_id_to_comment_id_set_cursor); 48 49 const parent_id_to_comment_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, parent_id_to_comment_id_set_key)); 50 const parent_id_to_comment_id_set = try DB.HashMap(.read_write).init(parent_id_to_comment_id_set_cursor); 51 52 const conflicts_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, conflicts_key)); 53 const conflicts = try DB.SortedMap(.read_write).init(conflicts_cursor); 54 55 const comment_id_to_field_to_oid_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, id_to_field_to_oid_key)); 56 const comment_id_to_field_to_oid = try DB.HashMap(.read_write).init(comment_id_to_field_to_oid_cursor); 57 58 var existing_record_maybe: ?Record = null; 59 const existing_cursor_maybe = try event_id_to_comment.getCursor(comment_key); 60 if (existing_cursor_maybe) |existing_cursor| { 61 const existing_comment = try DB.HashMap(.read_only).init(existing_cursor); 62 existing_record_maybe = try evt.read(Record, DB, hash_kind, arena, existing_comment); 63 } 64 65 var record_to_write = if (record_maybe) |record| 66 record 67 else blk: { 68 var record = existing_record_maybe orelse return error.EventNotFound; 69 record.deleted = true; 70 break :blk record; 71 }; 72 73 // references use the same lower-case hex form as event ids in json 74 var id_bytes: [evt.event_id_size]u8 = undefined; 75 _ = try std.fmt.hexToBytes(&id_bytes, &record_to_write.event.thread_id); 76 if (!std.mem.eql(u8, &record_to_write.event.thread_id, &std.fmt.bytesToHex(id_bytes, .lower))) return error.InvalidEventId; 77 _ = try std.fmt.hexToBytes(&id_bytes, &record_to_write.event.parent_id); 78 if (!std.mem.eql(u8, &record_to_write.event.parent_id, &std.fmt.bytesToHex(id_bytes, .lower))) return error.InvalidEventId; 79 80 if (existing_record_maybe) |existing_record| { 81 // updates preserve the original creation timestamp and author 82 record_to_write.created_ts = existing_record.created_ts; 83 record_to_write.author_email = existing_record.author_email; 84 85 // a comment cannot move between threads or positions in the thread 86 if (!std.mem.eql(u8, &existing_record.event.thread_id, &record_to_write.event.thread_id)) return error.ThreadChanged; 87 if (!std.mem.eql(u8, &existing_record.event.parent_id, &record_to_write.event.parent_id)) return error.ParentChanged; 88 89 // any event settles the conflict, since resolving in the ui may 90 // keep our own values and so change nothing to detect 91 if (event_oid != null or record_to_write.deleted) { 92 _ = try conflicts.remove(&evt.orderKeyDesc(existing_record.created_ts, event_id)); 93 } 94 } 95 96 try evt.writeOid(Self, DB, hash_kind, comment_id_to_field_to_oid, comment_key, if (existing_record_maybe) |existing| existing.event else null, record_to_write.event, event_oid); 97 98 const comment_cursor = try event_id_to_comment.putCursor(comment_key); 99 const comment = try DB.HashMap(.read_write).init(comment_cursor); 100 try evt.upsert(Record, DB, hash_kind, comment, record_to_write); 101 102 if (existing_cursor_maybe == null) { 103 const comment_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, id_set_key)); 104 const comment_id_set = try DB.SortedSet(.read_write).init(comment_id_set_cursor); 105 try comment_id_set.put(&evt.orderKeyDesc(record_to_write.created_ts, event_id)); 106 } 107 108 // tombstones stay in the thread so their replies remain connected 109 const thread_order_key = evt.orderKey(record_to_write.created_ts, event_id); 110 const thread_comments_cursor = try thread_id_to_comment_id_set.putCursor(hash.hashInt(hash_kind, &record_to_write.event.thread_id)); 111 const thread_comments = try DB.SortedSet(.read_write).init(thread_comments_cursor); 112 try thread_comments.put(&thread_order_key); 113 114 const replies_cursor = try parent_id_to_comment_id_set.putCursor(hash.hashInt(hash_kind, &record_to_write.event.parent_id)); 115 const replies = try DB.SortedSet(.read_write).init(replies_cursor); 116 try replies.put(&thread_order_key); 117 } 118