╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │158 switch (host) { │ │159 .server => |server| { │ │160 // "repo/new" and "user/new" come before "new", which would claim them │ │161 - const PostRoute = enum { login, logout, @"repo/new", @"user/new", user, addkey, removekey, settings, grant, revoke, promote, demote, protect, unprotect, new, edit, remove, open, close, resolve, publish, merge, squash, attach, assign, unassign, subscribe, mute, undo }; │ │161 + const PostRoute = enum { login, logout, @"repo/new", @"user/new", user, addkey, removekey, delete, settings, grant, revoke, promote, demote, protect, unprotect, new, edit, remove, open, close, resolve, publish, merge, squash, attach, assign, unassign, subscribe, mute, undo };│ │162 inline for (@typeInfo(PostRoute).@"enum".field_names) |name| { │ │163 const suffix = "/" ++ name; │ │164 if (std.mem.endsWith(u8, path, suffix)) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │171 .user => handleUserSettings(io, request, allocator, base, server.admin_repo_path, server.users_dir, server.session_store), │ │172 .addkey => handleUserKeyAdd(io, request, allocator, base, server.admin_repo_path, server.users_dir, server.session_store), │ │173 .removekey => handleUserKeyRemove(io, request, allocator, base, server.admin_repo_path, server.users_dir, server.session_store),│ │174 + .delete => handleDelete(io, request, allocator, base, server), │ │175 .settings => handleRepoSettings(io, request, allocator, path, host), │ │176 .grant => handleMembers(io, request, allocator, base, host, .grant), │ │177 .revoke => handleMembers(io, request, allocator, base, host, .revoke), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 999 .extra_headers = &.{.{ .name = "location", .value = location }}, │ │1000 }); │ │1001 } │ │1002 + │ │1003 +// delete the repo, fork or user the delete tab url `base` names, as its main │ │1004 +// owner. a failure goes back to the tab. │ │1005 +fn handleDelete(io: std.Io, request: *std.http.Server.Request, allocator: std.mem.Allocator, base: []const u8, server: @FieldType(Host, "server")) !void { │ │1006 + const user_id = requestUserId(request, server.session_store) orelse return respondLoginRequired(request); │ │1007 + var arena = std.heap.ArenaAllocator.init(allocator); │ │1008 + defer arena.deinit(); │ │1009 + var admin_repo = try rp.Repo(.xit, evt.admin_repo_opts).open(io, allocator, .{ .path = server.admin_repo_path }); │ │1010 + defer admin_repo.deinit(io, allocator); │ │1011 + const user = (try ui.activeUser(try evt.currentMoment(evt.admin_repo_opts, &admin_repo), &arena, user_id)) orelse return respondLoginRequired(request); │ │1012 │ │1013 + const route = ui.RoutablePage.fromUrl(base) orelse return respondDeleteNotFound(request); │ │1014 + const target: ui.Delete.Target = switch (route) { │ │1015 + .repo_delete => |*name| .{ .repo = name.slice() }, │ │1016 + .fork_delete => |*f| .{ .fork = .{ .identity = f.name.slice(), .id = f.id.bytes } }, │ │1017 + else => if (route.userTab().view == .delete) .{ .user = user.event.name } else return respondDeleteNotFound(request), │ │1018 + }; │ │1019 + │ │1020 + const body = try readFormBody(request, allocator); │ │1021 + defer allocator.free(body); │ │1022 + const confirm = (try parseFormField(arena.allocator(), body, "confirm")) orelse ""; │ │1023 + const password = (try parseFormField(arena.allocator(), body, "password")) orelse ""; │ │1024 + │ │1025 + const failure = ui.Delete.perform(io, allocator, server.users_dir, &admin_repo, user_id, target, confirm, password) catch |err| switch (err) { │ │1026 + error.NotFound => return respondDeleteNotFound(request), │ │1027 + else => return err, │ │1028 + }; │ │1029 + if (failure) |value| return respondFormFailure(request, allocator, server.session_store, base, .{ .delete = .{ .failure = value, .target = std.meta.activeTag(target), .confirm = confirm } });│ │1030 + │ │1031 + switch (target) { │ │1032 + .repo, .fork => { │ │1033 + const location = try arena.allocator().print("/{s}", .{user.event.name}); │ │1034 + try request.respond("", .{ .status = .see_other, .keep_alive = false, .extra_headers = &.{.{ .name = "location", .value = location }} }); │ │1035 + }, │ │1036 + // log the deleted user out, as a logout does │ │1037 + .user => { │ │1038 + if (getCookieValue(request, cookie_name)) |token| server.session_store.remove(token); │ │1039 + try request.respond("", .{ │ │1040 + .status = .see_other, │ │1041 + .keep_alive = false, │ │1042 + .extra_headers = &.{ │ │1043 + .{ .name = "location", .value = "/" }, │ │1044 + .{ .name = "set-cookie", .value = cookie_name ++ "=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0" }, │ │1045 + }, │ │1046 + }); │ │1047 + }, │ │1048 + } │ │1049 +} │ │1050 + │ │1051 +fn respondDeleteNotFound(request: *std.http.Server.Request) !void { │ │1052 + try request.respond("nothing to delete", .{ │ │1053 + .status = .not_found, │ │1054 + .keep_alive = false, │ │1055 + .extra_headers = &.{.{ .name = "content-type", .value = "text/plain" }}, │ │1056 + }); │ │1057 +} │ │1058 + │ │1059 fn respondKeyNotFound(request: *std.http.Server.Request) !void { │ │1060 try request.respond("ssh key not found", .{ │ │1061 .status = .not_found, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1147 } │ │1148 const repo_id = evt.parseEventId(std.Io.Dir.path.basename(request_repo.source.path)) catch return respondRemoveNotFound(request); │ │1149 const repo_user_id = actor.repo_user_id orelse unreachable; │ │1150 + const identity = ui.RoutablePage.RepoIdentity.parse(repoBaseIdentity(base) orelse return respondRemoveNotFound(request)) orelse return respondRemoveNotFound(request);│ │1151 const fork_path = try fork.create(.{}, io, allocator, server.users_dir, .{ │ │1152 .id = event_id_hex, │ │1153 .user_id = user_id, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────╮ │1154 .repo_id = repo_id, │ │1155 .repo_user_id = repo_user_id,│ │1156 + .repo_name = identity.name, │ │1157 .title = title, │ │1158 .description = description, │ │1159 .labels = labels, │ ╰───────────────────────────────────────────────╯