╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────╮ │ 8 const ui = @import("./ui.zig"); │ │ 9 const fork = @import("./fork.zig"); │ │10 const pch = @import("./patch.zig"); │ │11 +const notify = @import("./notify.zig");│ │12 const xitui = xit.xitui; │ │13 const wgt = xitui.widget; │ │14 const Focus = xitui.focus.Focus; │ ╰───────────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │44 server: struct { │ │45 admin_repo_path: []const u8, │ │46 users_dir: []const u8, │ │47 + dirty_repos: *notify.DirtyRepos,│ │48 session_store: SessionStore, │ │49 git_http_port: ?u16, │ │50 git_ssh_port: ?u16, │ ╰────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │55 pub fn event(self: Host) evt.Host { │ │56 return switch (self) { │ │57 .local => .local, │ │56 - .server => |server| .{ .server = .{ .users_dir = server.users_dir } }, │ │58 + .server => |server| .{ .server = .{ .users_dir = server.users_dir, .dirty_repos = server.dirty_repos, .admin_repo_path = server.admin_repo_path } },│ │59 }; │ │60 } │ │61 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │159 switch (host) { │ │160 .server => |server| { │ │161 // "repo/new" and "user/new" come before "new", which would claim them │ │160 - const PostRoute = enum { login, logout, @"repo/new", @"user/new", user, settings, grant, revoke, promote, demote, new, edit, remove, open, close, resolve, publish, merge, squash, attach, assign, unassign, undo, clear }; │ │162 + const PostRoute = enum { login, logout, @"repo/new", @"user/new", user, settings, grant, revoke, promote, demote, new, edit, remove, open, close, resolve, publish, merge, squash, attach, assign, unassign, subscribe, mute, undo, clear };│ │163 inline for (@typeInfo(PostRoute).@"enum".field_names) |name| { │ │164 const suffix = "/" ++ name; │ │165 if (std.mem.endsWith(u8, path, suffix)) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │166 const base = path[0 .. path.len - suffix.len]; │ │167 return switch (@field(PostRoute, name)) { │ │168 .login => handleLogin(io, request, allocator, base, server.admin_repo_path, server.session_store), │ │167 - .logout => handleLogout(request, base, server.session_store), │ │169 + .logout => handleLogout(request, allocator, base, server.session_store), │ │170 .@"repo/new" => handleRepoNew(io, request, allocator, path, server.admin_repo_path, server.users_dir, server.session_store),│ │171 .@"user/new" => handleUserNew(io, request, allocator, path, server.admin_repo_path, server.users_dir, server.session_store),│ │170 - .user => handleUserSettings(io, request, allocator, path, server.admin_repo_path, server.users_dir, server.session_store), │ │172 + .user => handleUserSettings(io, request, allocator, base, server.admin_repo_path, server.users_dir, server.session_store), │ │173 .settings => handleRepoSettings(io, request, allocator, path, host), │ │174 .grant => handleRoles(io, request, allocator, base, host, .grant), │ │175 .revoke => handleRoles(io, request, allocator, base, host, .revoke), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │189 .attach => handleAttach(io, request, allocator, base, host), │ │190 .assign => handleAssign(io, request, allocator, base, host), │ │191 .unassign => handleUnassign(io, request, allocator, base, host), │ │192 + .subscribe => handleNotify(io, request, allocator, base, host, server.users_dir, .subscribe),│ │193 + .mute => handleNotify(io, request, allocator, base, host, server.users_dir, .mute), │ │194 }; │ │195 } │ │196 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │394 const haxy_moment = try evt.currentMoment(evt.admin_repo_opts, &repo); │ │395 const result = try evt.User.verifyCredentials(evt.AdminDB, evt.admin_repo_opts.hash, haxy_moment, &arena, username, password);│ │396 │ │393 - // on success, return to the page the login came from (base, or "/" at the │ │394 - // root); on failure, stay on its user tab to surface the error. │ │395 - const success_location: []const u8 = if (base.len == 0) "/" else base; │ │396 - const failure_location = try arena.allocator().print("{s}/user", .{base}); │ │397 + // on success, return to the page whose user tab the login came from; on │ │398 + // failure, stay on that tab to surface the error. │ │399 + const success_location = try userTabPageRoot(&arena, base); │ │400 + const failure_location = base; │ │401 │ │402 switch (result) { │ │403 .success => |user_id| { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │491 } } }; │ │492 } │ │493 │ │490 -fn handleLogout(request: *std.http.Server.Request, base: []const u8, session_store: SessionStore) !void { │ │494 +// the root of the page whose user tab route is `base` │ │495 +fn userTabPageRoot(arena: *std.heap.ArenaAllocator, base: []const u8) ![]const u8 { │ │496 + const route = ui.RoutablePage.fromUrl(base) orelse return "/"; │ │497 + return route.pageRoot().toUrl(arena); │ │498 +} │ │499 + │ │500 +fn handleLogout(request: *std.http.Server.Request, allocator: std.mem.Allocator, base: []const u8, session_store: SessionStore) !void {│ │501 // revoke the session server-side so the cookie is dead for anyone holding │ │502 // a copy, not just this browser. │ │503 if (getCookieValue(request, cookie_name)) |token| session_store.remove(token); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │494 - // return to the page the logout came from (base, or "/" at the root). │ │495 - const location: []const u8 = if (base.len == 0) "/" else base; │ │504 + var arena = std.heap.ArenaAllocator.init(allocator); │ │505 + defer arena.deinit(); │ │506 + // return to the page whose user tab the logout came from │ │507 + const location = try userTabPageRoot(&arena, base); │ │508 // close the connection instead of keeping it alive: we don't read the │ │509 // request body, and respond()'s keep-alive path would otherwise try to│ │510 // discard it — which asserts on a bodyless POST that carries no │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │853 }); │ │854 } │ │855 │ │844 -// change the logged-in user's settings, then return to the page that posted │ │845 -// the form, which a failure also goes back to. │ │856 +// change the logged-in user's settings, then return to the settings tab that│ │857 +// posted the form, which a failure also goes back to. │ │858 fn handleUserSettings( │ │859 io: std.Io, │ │860 request: *std.http.Server.Request, │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯