╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │344 .{ .name = "location", .value = location }, │ │345 }, │ │346 }); │ │347 +} │ │348 + │ │349 +// the email the request's session authors event commits as │ │350 +fn authorEmail( │ │351 + io: std.Io, │ │352 + allocator: std.mem.Allocator, │ │353 + arena: *std.heap.ArenaAllocator, │ │354 + request: *std.http.Server.Request, │ │355 + host: Host, │ │356 +) ![]const u8 { │ │357 + const remote = switch (host) { │ │358 + .remote => |remote| remote, │ │359 + .local => return "user@haxy", │ │360 + }; │ │361 + const token = getCookieValue(request, cookie_name) orelse return "user@haxy"; │ │362 + var user_id: [evt.event_id_size]u8 = undefined; │ │363 + if (!remote.session_store.lookup(token, &user_id)) return "user@haxy"; │ │364 + │ │365 + var repo = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = remote.admin_repo_path }); │ │366 + defer repo.deinit(io, allocator); │ │367 + const moment = try evt.currentMoment(evt.admin_repo_opts, &repo); │ │368 + const user = (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, &user_id)) orelse unreachable;│ │369 + return user.email; │ │370 } │ │371 │ │372 // create an issue in the repo the form's page names (base is │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │407 var id_bytes: [evt.event_id_size]u8 = undefined; │ │408 io.random(&id_bytes); │ │409 const event_id_hex = std.fmt.bytesToHex(id_bytes, .lower); │ │410 + │ │411 + var author_arena = std.heap.ArenaAllocator.init(allocator);│ │412 + defer author_arena.deinit(); │ │413 │ │414 const event = evt.EventWithId{ │ │415 .id = event_id_hex, │ ╰────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │416 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │417 + .author_email = try authorEmail(io, allocator, &author_arena, request, host),│ │418 .event = .{ .issue = .{ │ │419 .title = title, │ │420 .description = description, │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────╮ │505 host: Host, │ │506 parts: IssueBaseParts, │ │507 update: evt.Issue.Update,│ │508 + author_email: []const u8,│ │509 ) !void { │ │510 switch (host) { │ │511 .remote => |remote| {│ ╰──────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │521 │ │522 var repo = try rp.Repo(.xit, .{}).open(io, allocator, .{ .path = repo_path }); │ │523 defer repo.deinit(io, allocator); │ │496 - try evt.Issue.update(.xit, .{}, io, allocator, &repo, &parts.id_bytes, update); │ │524 + try evt.Issue.update(.xit, .{}, io, allocator, &repo, &parts.id_bytes, update, author_email);│ │525 }, │ │526 .local => |src| { │ │527 // the local forms post to "/issues/<id>/...", so the repo base │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │532 var any_repo = try rp.AnyRepo(repo_kind, .{}).open(io, allocator, .{ .path = src.path }); │ │533 defer any_repo.deinit(io, allocator); │ │534 switch (any_repo) { │ │507 - inline else => |*repo| try evt.Issue.update(repo_kind, repo.self_repo_opts, io, allocator, repo, &parts.id_bytes, update), │ │535 + inline else => |*repo| try evt.Issue.update(repo_kind, repo.self_repo_opts, io, allocator, repo, &parts.id_bytes, update, author_email),│ │536 } │ │537 }, │ │538 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │560 return; │ │561 }; │ │562 │ │535 - updateIssue(io, allocator, host, parts, .{ .status = status }) catch |err| switch (err) { │ │563 + var author_arena = std.heap.ArenaAllocator.init(allocator); │ │564 + defer author_arena.deinit(); │ │565 + const author_email = try authorEmail(io, allocator, &author_arena, request, host); │ │566 + │ │567 + updateIssue(io, allocator, host, parts, .{ .status = status }, author_email) catch |err| switch (err) {│ │568 error.NotFound => { │ │569 try request.respond(not_found, .{ │ │570 .status = .not_found, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │631 return; │ │632 }; │ │633 │ │634 + var author_arena = std.heap.ArenaAllocator.init(allocator); │ │635 + defer author_arena.deinit(); │ │636 + const author_email = try authorEmail(io, allocator, &author_arena, request, host);│ │637 + │ │638 updateIssue(io, allocator, host, parts, .{ .fields = .{ │ │639 .title = title, │ │640 .tags = tags, │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────╮ │641 .description = description, │ │606 - } }) catch |err| switch (err) { │ │642 + } }, author_email) catch |err| switch (err) {│ │643 error.NotFound => { │ │644 try request.respond(not_found, .{ │ │645 .status = .not_found, │ ╰──────────────────────────────────────────────────────╯