Schedule posts to Bluesky with Cloudflare workers. skyscheduler.work
cf tool bsky-tool cloudflare bluesky schedule bsky service social-media cloudflare-workers

minor cleanup

mostly just improving middleware schemas when login mismatches occur

+41 -20
+1 -1
src/endpoints/account.tsx
··· 133 133 134 134 // Redirect to home 135 135 c.header("HX-Redirect", "/"); 136 - return c.html("Success"); 136 + return c.text(""); 137 137 }); 138 138 139 139 account.post("/signup", verifyTurnstile, async (c: Context) => {
+11 -11
src/endpoints/post.tsx
··· 11 11 import { corsHelperMiddleware } from "../middleware/corsHelper"; 12 12 import { 13 13 Bindings, CreateObjectResponse, CreatePostQueryResponse, 14 - DeleteResponse, 15 - EmbedDataType, LooseObj 14 + DeleteResponse, EmbedDataType, LooseObj 16 15 } from "../types"; 17 16 import { 18 17 createPost, createRepost, ··· 29 28 30 29 post.use(secureHeaders()); 31 30 post.use(corsHelperMiddleware); 31 + post.use(authMiddleware); 32 32 33 33 // Create media upload 34 - post.post("/upload", authMiddleware, async (c: Context) => { 34 + post.post("/upload", async (c: Context) => { 35 35 const formData = await c.req.parseBody(); 36 36 const fileUploadResponse = await uploadFileR2(c, formData['file'], c.get("userId")); 37 37 if (fileUploadResponse.success === false) ··· 41 41 }); 42 42 43 43 // Delete an upload 44 - post.delete("/upload", authMiddleware, async (c: Context) => { 44 + post.delete("/upload", async (c: Context) => { 45 45 const body = await c.req.json(); 46 46 47 47 // Validate that this is a legitimate key ··· 57 57 }); 58 58 59 59 // Create post 60 - post.post("/create", authMiddleware, async (c: Context) => { 60 + post.post("/create", async (c: Context) => { 61 61 const body = await c.req.json(); 62 62 const response: CreatePostQueryResponse = await createPost(c, body); 63 63 if (!response.ok) { ··· 77 77 }); 78 78 79 79 // Create repost 80 - post.post("/create/repost", authMiddleware, async (c: Context) => { 80 + post.post("/create/repost", async (c: Context) => { 81 81 const body = await c.req.json(); 82 82 const response: CreateObjectResponse = await createRepost(c, body); 83 83 if (!response.ok) { ··· 87 87 }); 88 88 89 89 // Get all posts 90 - post.all("/all", authMiddleware, async (c: Context) => { 90 + post.all("/all", async (c: Context) => { 91 91 c.header("HX-Trigger-After-Swap", "timeSidebar"); 92 92 return c.html( 93 93 <ScheduledPostList ctx={c} /> ··· 95 95 }); 96 96 97 97 // Edit posts 98 - post.get("/edit/:id", authMiddleware, async (c: Context) => { 98 + post.get("/edit/:id", async (c: Context) => { 99 99 const { id } = c.req.param(); 100 100 if (!isValid(id)) 101 101 return c.html(<></>, 400); ··· 108 108 return c.html(<></>, 400); 109 109 }); 110 110 111 - post.post("/edit/:id", authMiddleware, async (c: Context) => { 111 + post.post("/edit/:id", async (c: Context) => { 112 112 const { id } = c.req.param(); 113 113 const swapErrEvents: string = "refreshPosts, scrollTop, scrollListTop"; 114 114 if (!isValid(id)) { ··· 183 183 return c.html(<b class="btn-error">Failed to process edit</b>, 400); 184 184 }); 185 185 186 - post.get("/edit/:id/cancel", authMiddleware, async (c: Context) => { 186 + post.get("/edit/:id/cancel", async (c: Context) => { 187 187 const { id } = c.req.param(); 188 188 if (!isValid(id)) 189 189 return c.html(<></>, 400); ··· 201 201 }); 202 202 203 203 // delete a post 204 - post.delete("/delete/:id", authMiddleware, async (c: Context) => { 204 + post.delete("/delete/:id", async (c: Context) => { 205 205 const { id } = c.req.param(); 206 206 if (isValid(id)) { 207 207 const response: DeleteResponse = await deletePost(c, id);
+6 -2
src/endpoints/preview.tsx
··· 3 3 import isEmpty from "just-is-empty"; 4 4 import { ContextVariables } from "../auth"; 5 5 import { BSKY_IMG_MIME_TYPES } from "../limits"; 6 - import { authMiddleware } from "../middleware/auth"; 6 + import { hasAuth, pullAuthData } from "../middleware/auth"; 7 7 import { corsHelperMiddleware } from "../middleware/corsHelper"; 8 8 import { Bindings } from "../types"; 9 9 import { FileContentSchema } from "../validation/mediaSchema"; ··· 13 13 preview.use(secureHeaders()); 14 14 preview.use(corsHelperMiddleware); 15 15 16 - preview.get("/file/:id", authMiddleware, async (c: Context) => { 16 + preview.get("/file/:id", pullAuthData, async (c: Context) => { 17 + if (!hasAuth(c)) { 18 + return c.redirect("/thumbs/missing.png"); 19 + } 20 + 17 21 const { id } = c.req.param(); 18 22 const validation = FileContentSchema.safeParse({content: id}); 19 23 if (!validation.success) {
+2 -2
src/index.tsx
··· 7 7 import { admin } from "./endpoints/admin"; 8 8 import { post } from "./endpoints/post"; 9 9 import { preview } from "./endpoints/preview"; 10 - import { authMiddleware } from "./middleware/auth"; 11 10 import { corsHelperMiddleware } from "./middleware/corsHelper"; 12 11 import { redirectToDashIfLogin } from "./middleware/redirectDash"; 12 + import { redirectHomeIfLogout } from "./middleware/redirectHome"; 13 13 import Dashboard from "./pages/dashboard"; 14 14 import ForgotPassword from "./pages/forgot"; 15 15 import Homepage from "./pages/homepage"; ··· 90 90 app.route("/preview", preview); 91 91 92 92 // Dashboard route 93 - app.get("/dashboard", authMiddleware, (c) => c.html(<Dashboard c={c} />)); 93 + app.get("/dashboard", redirectHomeIfLogout, (c) => c.html(<Dashboard c={c} />)); 94 94 95 95 // Login route 96 96 app.get("/login", redirectToDashIfLogin, (c) => c.html(<Login />));
+1 -1
src/middleware/adminOnly.ts
··· 9 9 await next(); 10 10 return; 11 11 } 12 - return c.json({ error: "Unauthorized" }, 401); 12 + return c.json({ ok: false, msg: "Unauthorized" }, 401); 13 13 } 14 14 15 15 export const authAdminOnlyMiddleware = every(authMiddleware, adminOnlyMiddleware);
+8 -3
src/middleware/auth.ts
··· 31 31 c.set("session", null); 32 32 } 33 33 await next(); 34 - } 34 + }; 35 + 35 36 export async function requireAuth(c: Context, next: any) { 36 - if (c.get("session") === null || c.get("userId") === null) { 37 - return c.json({ error: "Unauthorized" }, 401); 37 + if (!hasAuth(c)) { 38 + return c.json({ ok: false, msg: "Unauthorized" }, 401); 38 39 } 39 40 await next(); 41 + }; 42 + 43 + export function hasAuth(c: Context) { 44 + return (c.get("session") !== null && c.get("userId") !== null); 40 45 } 41 46 42 47 export const authMiddleware = every(pullAuthData, requireAuth);
+12
src/middleware/redirectHome.ts
··· 1 + import { Context } from "hono"; 2 + import { every } from "hono/combine"; 3 + import { hasAuth, pullAuthData } from "./auth"; 4 + 5 + export async function goHomeIfLogout(c: Context, next: any) { 6 + if (!hasAuth(c)) { 7 + return c.redirect("/"); 8 + } 9 + await next(); 10 + } 11 + 12 + export const redirectHomeIfLogout = every(pullAuthData, goHomeIfLogout);