WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(web): add board proxy routes for structure management (ATB-47)

Malpercio 3be4ad30 83483054

+210
+210
apps/web/src/routes/admin.tsx
··· 1000 1000 return c.redirect("/admin/structure", 302); 1001 1001 }); 1002 1002 1003 + // ── POST /admin/structure/boards ────────────────────────────────────────── 1004 + 1005 + app.post("/admin/structure/boards", async (c) => { 1006 + const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie")); 1007 + if (!auth.authenticated) return c.redirect("/login"); 1008 + if (!canManageCategories(auth)) { 1009 + return c.html( 1010 + <BaseLayout title="Access Denied — atBB Forum" auth={auth}> 1011 + <PageHeader title="Forum Structure" /> 1012 + <p>You don&apos;t have permission to manage forum structure.</p> 1013 + </BaseLayout>, 1014 + 403 1015 + ); 1016 + } 1017 + 1018 + const cookie = c.req.header("cookie") ?? ""; 1019 + 1020 + let body: Record<string, string | File>; 1021 + try { 1022 + body = await c.req.parseBody(); 1023 + } catch (error) { 1024 + if (isProgrammingError(error)) throw error; 1025 + return c.redirect( 1026 + `/admin/structure?error=${encodeURIComponent("Invalid form submission.")}`, 1027 + 302 1028 + ); 1029 + } 1030 + 1031 + const name = typeof body.name === "string" ? body.name.trim() : ""; 1032 + if (!name) { 1033 + return c.redirect( 1034 + `/admin/structure?error=${encodeURIComponent("Board name is required.")}`, 1035 + 302 1036 + ); 1037 + } 1038 + 1039 + const categoryUri = typeof body.categoryUri === "string" ? body.categoryUri.trim() : ""; 1040 + if (!categoryUri) { 1041 + return c.redirect( 1042 + `/admin/structure?error=${encodeURIComponent("Category is required to create a board.")}`, 1043 + 302 1044 + ); 1045 + } 1046 + 1047 + const description = typeof body.description === "string" ? body.description.trim() || null : null; 1048 + const sortOrder = parseSortOrder(body.sortOrder); 1049 + 1050 + let appviewRes: Response; 1051 + try { 1052 + appviewRes = await fetch(`${appviewUrl}/api/admin/boards`, { 1053 + method: "POST", 1054 + headers: { "Content-Type": "application/json", Cookie: cookie }, 1055 + body: JSON.stringify({ name, description, sortOrder, categoryUri }), 1056 + }); 1057 + } catch (error) { 1058 + if (isProgrammingError(error)) throw error; 1059 + logger.error("Network error creating board", { 1060 + operation: "POST /admin/structure/boards", 1061 + error: error instanceof Error ? error.message : String(error), 1062 + }); 1063 + return c.redirect( 1064 + `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`, 1065 + 302 1066 + ); 1067 + } 1068 + 1069 + if (!appviewRes.ok) { 1070 + const msg = await extractAppviewError(appviewRes, "Failed to create board. Please try again."); 1071 + logger.error("AppView error creating board", { 1072 + operation: "POST /admin/structure/boards", 1073 + status: appviewRes.status, 1074 + }); 1075 + return c.redirect( 1076 + `/admin/structure?error=${encodeURIComponent(msg)}`, 1077 + 302 1078 + ); 1079 + } 1080 + 1081 + return c.redirect("/admin/structure", 302); 1082 + }); 1083 + 1084 + // ── POST /admin/structure/boards/:id/edit ───────────────────────────────── 1085 + 1086 + app.post("/admin/structure/boards/:id/edit", async (c) => { 1087 + const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie")); 1088 + if (!auth.authenticated) return c.redirect("/login"); 1089 + if (!canManageCategories(auth)) { 1090 + return c.html( 1091 + <BaseLayout title="Access Denied — atBB Forum" auth={auth}> 1092 + <PageHeader title="Forum Structure" /> 1093 + <p>You don&apos;t have permission to manage forum structure.</p> 1094 + </BaseLayout>, 1095 + 403 1096 + ); 1097 + } 1098 + 1099 + const boardId = c.req.param("id"); 1100 + const cookie = c.req.header("cookie") ?? ""; 1101 + 1102 + let body: Record<string, string | File>; 1103 + try { 1104 + body = await c.req.parseBody(); 1105 + } catch (error) { 1106 + if (isProgrammingError(error)) throw error; 1107 + return c.redirect( 1108 + `/admin/structure?error=${encodeURIComponent("Invalid form submission.")}`, 1109 + 302 1110 + ); 1111 + } 1112 + 1113 + const name = typeof body.name === "string" ? body.name.trim() : ""; 1114 + if (!name) { 1115 + return c.redirect( 1116 + `/admin/structure?error=${encodeURIComponent("Board name is required.")}`, 1117 + 302 1118 + ); 1119 + } 1120 + 1121 + const description = typeof body.description === "string" ? body.description.trim() || null : null; 1122 + const sortOrder = parseSortOrder(body.sortOrder); 1123 + 1124 + let appviewRes: Response; 1125 + try { 1126 + appviewRes = await fetch(`${appviewUrl}/api/admin/boards/${boardId}`, { 1127 + method: "PUT", 1128 + headers: { "Content-Type": "application/json", Cookie: cookie }, 1129 + body: JSON.stringify({ name, description, sortOrder }), 1130 + }); 1131 + } catch (error) { 1132 + if (isProgrammingError(error)) throw error; 1133 + logger.error("Network error editing board", { 1134 + operation: "POST /admin/structure/boards/:id/edit", 1135 + boardId, 1136 + error: error instanceof Error ? error.message : String(error), 1137 + }); 1138 + return c.redirect( 1139 + `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`, 1140 + 302 1141 + ); 1142 + } 1143 + 1144 + if (!appviewRes.ok) { 1145 + const msg = await extractAppviewError(appviewRes, "Failed to update board. Please try again."); 1146 + logger.error("AppView error editing board", { 1147 + operation: "POST /admin/structure/boards/:id/edit", 1148 + boardId, 1149 + status: appviewRes.status, 1150 + }); 1151 + return c.redirect( 1152 + `/admin/structure?error=${encodeURIComponent(msg)}`, 1153 + 302 1154 + ); 1155 + } 1156 + 1157 + return c.redirect("/admin/structure", 302); 1158 + }); 1159 + 1160 + // ── POST /admin/structure/boards/:id/delete ─────────────────────────────── 1161 + 1162 + app.post("/admin/structure/boards/:id/delete", async (c) => { 1163 + const auth = await getSessionWithPermissions(appviewUrl, c.req.header("cookie")); 1164 + if (!auth.authenticated) return c.redirect("/login"); 1165 + if (!canManageCategories(auth)) { 1166 + return c.html( 1167 + <BaseLayout title="Access Denied — atBB Forum" auth={auth}> 1168 + <PageHeader title="Forum Structure" /> 1169 + <p>You don&apos;t have permission to manage forum structure.</p> 1170 + </BaseLayout>, 1171 + 403 1172 + ); 1173 + } 1174 + 1175 + const boardId = c.req.param("id"); 1176 + const cookie = c.req.header("cookie") ?? ""; 1177 + 1178 + let appviewRes: Response; 1179 + try { 1180 + appviewRes = await fetch(`${appviewUrl}/api/admin/boards/${boardId}`, { 1181 + method: "DELETE", 1182 + headers: { Cookie: cookie }, 1183 + }); 1184 + } catch (error) { 1185 + if (isProgrammingError(error)) throw error; 1186 + logger.error("Network error deleting board", { 1187 + operation: "POST /admin/structure/boards/:id/delete", 1188 + boardId, 1189 + error: error instanceof Error ? error.message : String(error), 1190 + }); 1191 + return c.redirect( 1192 + `/admin/structure?error=${encodeURIComponent("Forum temporarily unavailable. Please try again.")}`, 1193 + 302 1194 + ); 1195 + } 1196 + 1197 + if (!appviewRes.ok) { 1198 + const msg = await extractAppviewError(appviewRes, "Failed to delete board. Please try again."); 1199 + logger.error("AppView error deleting board", { 1200 + operation: "POST /admin/structure/boards/:id/delete", 1201 + boardId, 1202 + status: appviewRes.status, 1203 + }); 1204 + return c.redirect( 1205 + `/admin/structure?error=${encodeURIComponent(msg)}`, 1206 + 302 1207 + ); 1208 + } 1209 + 1210 + return c.redirect("/admin/structure", 302); 1211 + }); 1212 + 1003 1213 return app; 1004 1214 }