╭ Discussion.zig ───────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const evt = @import("../event.zig"); 3 const xit = @import("xit"); 4 const rp = xit.repo; 5 const hash = xit.hash; 6 7 title: []const u8, 8 description: []const u8, 9 tags: []const u8, // space-separated 10 11 pub const Record = struct { 12 event: Self, 13 removed: bool = false, 14 author_email: ?[]const u8 = null, 15 created_order: u64 = 0, 16 updated_order: u64 = 0, 17 }; 18 19 const Self = @This(); 20 21 pub const tag_max_len = 64; 22 23 pub const merge_policy: evt.MergePolicy = .target_wins; 24 pub const record_map_key = "event-id->discussion"; 25 pub const all_id_set_key = "discussion-id-set"; 26 pub const active_id_set_key = "active-discussion-id-set"; 27 pub const tag_to_id_set_key = "tag->discussion-id-set"; 28 const activity_order_key = "discussion-id->activity-order"; 29 30 pub fn tagIterator(tags: []const u8) std.mem.SplitIterator(u8, .scalar) { 31 return std.mem.splitScalar(u8, tags, ' '); 32 } 33 34 pub fn fieldsValid(title: []const u8, tags: []const u8) bool { 35 if (!evt.titleValid(title)) return false; 36 var tag_iter = tagIterator(tags); 37 while (tag_iter.next()) |tag| { 38 if (tag.len > tag_max_len) return false; 39 } 40 return true; 41 } 42 43 pub fn consume( 44 comptime DB: type, 45 comptime hash_kind: hash.HashKind, 46 haxy_moment: DB.HashMap(.read_write), 47 event_id: *const [evt.event_id_size]u8, 48 record_maybe: ?Record, 49 arena: *std.heap.ArenaAllocator, 50 _: ?[]const u8, 51 ) !void { 52 const discussion_key = hash.hashInt(hash_kind, event_id); 53 const records = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, record_map_key))); 54 const active = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, active_id_set_key))); 55 const tags = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, tag_to_id_set_key))); 56 var existing_record_maybe: ?Record = null; 57 const existing_cursor_maybe = try records.getCursor(discussion_key); 58 if (existing_cursor_maybe) |existing_cursor| { 59 existing_record_maybe = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(existing_cursor)); 60 } 61 62 var record_to_write = record_maybe orelse try evt.removedRecord(Record, DB, hash_kind, haxy_moment.readOnly(), existing_record_maybe); 63 64 if (existing_record_maybe) |existing| { 65 record_to_write.created_order = existing.created_order; 66 record_to_write.author_email = existing.author_email; 67 } 68 69 const activity_order = if (existing_record_maybe != null) 70 try activityOrder(DB, hash_kind, haxy_moment.readOnly(), event_id) 71 else 72 record_to_write.created_order; 73 if (existing_record_maybe) |existing| { 74 if (!existing.removed) { 75 const old_key = evt.orderKeyDesc(activity_order, event_id); 76 _ = try active.remove(&old_key); 77 try removeFromTagSets(DB, tags, existing.event.tags, &old_key); 78 } 79 } 80 81 const record = try DB.HashMap(.read_write).init(try records.putCursor(discussion_key)); 82 try evt.upsert(Record, DB, hash_kind, record, record_to_write); 83 try evt.indexEvent(DB, hash_kind, haxy_moment, event_id, .discuss, existing_record_maybe, record_to_write); 84 85 const order_key = evt.orderKeyDesc(activity_order, event_id); 86 if (existing_cursor_maybe == null) { 87 const activity = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, activity_order_key))); 88 try activity.put(discussion_key, .{ .uint = activity_order }); 89 const ids = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, all_id_set_key))); 90 try ids.put(&evt.orderKeyDesc(record_to_write.created_order, event_id)); 91 } 92 93 if (!record_to_write.removed) { 94 try active.put(&order_key); 95 try addToTagSets(DB, tags, record_to_write.event.tags, &order_key); 96 } 97 } 98 99 pub fn touch( 100 comptime DB: type, 101 comptime hash_kind: hash.HashKind, 102 haxy_moment: DB.HashMap(.read_write), 103 event_id: *const [evt.event_id_size]u8, 104 order: u64, 105 arena: *std.heap.ArenaAllocator, 106 ) !void { 107 const discussion_key = hash.hashInt(hash_kind, event_id); 108 const records_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, record_map_key)) orelse return; 109 const records = try DB.HashMap(.read_only).init(records_cursor); 110 const record_cursor = try records.getCursor(discussion_key) orelse return; 111 const record = try evt.read(Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor)); 112 const current = try activityOrder(DB, hash_kind, haxy_moment.readOnly(), event_id); 113 if (order <= current) return; 114 115 const active = try DB.SortedSet(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, active_id_set_key))); 116 const tags = try DB.SortedMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, tag_to_id_set_key))); 117 if (!record.removed) { 118 const old_key = evt.orderKeyDesc(current, event_id); 119 _ = try active.remove(&old_key); 120 try removeFromTagSets(DB, tags, record.event.tags, &old_key); 121 } 122 123 const activity = try DB.HashMap(.read_write).init(try haxy_moment.putCursor(hash.hashInt(hash_kind, activity_order_key))); 124 try activity.put(discussion_key, .{ .uint = order }); 125 if (!record.removed) { 126 const new_key = evt.orderKeyDesc(order, event_id); 127 try active.put(&new_key); 128 try addToTagSets(DB, tags, record.event.tags, &new_key); 129 } 130 } 131 132 pub fn activityOrder( 133 comptime DB: type, 134 comptime hash_kind: hash.HashKind, 135 haxy_moment: DB.HashMap(.read_only), 136 event_id: *const [evt.event_id_size]u8, 137 ) !u64 { 138 const map_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, activity_order_key)) orelse return error.CursorNotFound; 139 const map = try DB.HashMap(.read_only).init(map_cursor); 140 const order_cursor = try map.getCursor(hash.hashInt(hash_kind, event_id)) orelse return error.CursorNotFound; 141 return try order_cursor.readUint(); 142 } 143 144 pub fn update( 145 host_kind: evt.HostKind, 146 comptime repo_kind: rp.RepoKind, 147 comptime repo_opts: rp.RepoOpts(repo_kind), 148 io: std.Io, 149 allocator: std.mem.Allocator, 150 repo: *rp.Repo(repo_kind, repo_opts), 151 id: *const [evt.event_id_size]u8, 152 title: []const u8, 153 tags: []const u8, 154 description: []const u8, 155 author: evt.CommitAuthor, 156 ) !void { 157 if (!fieldsValid(title, tags)) return error.InvalidFields; 158 159 var arena = std.heap.ArenaAllocator.init(allocator); 160 defer arena.deinit(); 161 const discussion = (try readById(repo_kind, repo_opts, io, allocator, &arena, repo, id)) orelse return error.NotFound; 162 if (discussion.removed) return error.NotFound; 163 164 try evt.consume(host_kind, .repo, repo_kind, repo_opts, io, allocator, repo, evt.events_ref, &.{.{ 165 .id = std.fmt.bytesToHex(id.*, .lower), 166 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), 167 .author = author, 168 .event = .{ .discuss = .{ 169 .title = title, 170 .tags = tags, 171 .description = description, 172 } }, 173 }}); 174 } 175 176 fn removeFromTagSets(comptime DB: type, tags: DB.SortedMap(.read_write), names: []const u8, order_key: []const u8) !void { 177 var tag_iter = tagIterator(names); 178 while (tag_iter.next()) |tag| { 179 if (tag.len == 0 or tag.len > tag_max_len) continue; 180 const cursor = try tags.putCursor(tag); 181 const set = try DB.SortedSet(.read_write).init(cursor); 182 _ = try set.remove(order_key); 183 if (0 == try set.count()) _ = try tags.remove(tag); 184 } 185 } 186 187 fn addToTagSets(comptime DB: type, tags: DB.SortedMap(.read_write), names: []const u8, order_key: []const u8) !void { 188 var tag_iter = tagIterator(names); 189 while (tag_iter.next()) |tag| { 190 if (tag.len == 0 or tag.len > tag_max_len) continue; 191 const set = try DB.SortedSet(.read_write).init(try tags.putCursor(tag)); 192 try set.put(order_key); 193 } 194 } 195 196 pub fn readById( 197 comptime repo_kind: rp.RepoKind, 198 comptime repo_opts: rp.RepoOpts(repo_kind), 199 io: std.Io, 200 allocator: std.mem.Allocator, 201 arena: *std.heap.ArenaAllocator, 202 repo: *rp.Repo(repo_kind, repo_opts), 203 id: *const [evt.event_id_size]u8, 204 ) !?Record { 205 const DB = evt.EventDB(repo_opts.hash); 206 var event_db_maybe: ?evt.LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try evt.LocalEventDB(repo_opts.hash).openReadOnly(io, allocator, repo.core.repo_dir) else null; 207 defer if (event_db_maybe) |*event_db| event_db.deinit(io, allocator); 208 const moment = (if (event_db_maybe) |*event_db| 209 evt.currentMomentFromDb(repo_opts.hash, event_db.db) 210 else if (repo_kind == .git) 211 return null 212 else 213 evt.currentMoment(repo_opts, repo)) catch return null; 214 const records_cursor = try moment.getCursor(hash.hashInt(repo_opts.hash, record_map_key)) orelse return null; 215 const records = try DB.HashMap(.read_only).init(records_cursor); 216 const record_cursor = try records.getCursor(hash.hashInt(repo_opts.hash, id)) orelse return null; 217 return try evt.read(Record, DB, repo_opts.hash, arena, try DB.HashMap(.read_only).init(record_cursor)); 218 } 219