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

Fix OpenAPI generation

couple of small bugs + org

+47 -26
+16 -8
src/endpoints/admin.tsx
··· 1 1 import { Hono } from "hono"; 2 + import { generateSpecs } from "hono-openapi"; 2 3 import { secureHeaders } from "hono/secure-headers"; 3 4 import { ContextVariables } from "../auth"; 5 + import { APP_NAME } from "../limits"; 4 6 import { authAdminOnlyMiddleware } from "../middleware/adminOnly"; 7 + import { corsHelperMiddleware } from "../middleware/corsHelper"; 5 8 import { Bindings } from "../types.d"; 6 9 import { getAllAbandonedMedia } from "../utils/db/file"; 7 10 import { runMaintenanceUpdates } from "../utils/db/maintain"; 8 11 import { makeInviteKey } from "../utils/inviteKeys"; 9 - import { corsHelperMiddleware } from "../middleware/corsHelper"; 10 12 import { cleanupAbandonedFiles, cleanUpPostsTask, schedulePostTask } from "../utils/scheduler"; 11 13 import { openapiRoutes } from "./openapi"; 12 - import { openAPIRouteHandler } from "hono-openapi"; 13 14 14 15 export const admin = new Hono<{ Bindings: Bindings, Variables: ContextVariables }>(); 15 16 ··· 62 63 }); 63 64 64 65 ////// OpenAPI Spec for WAF ///// 65 - admin.get('/openapi.json', 66 - openAPIRouteHandler(openapiRoutes, { 66 + admin.get('/openapi.json', async (c) => { 67 + const websiteURL: URL = new URL(c.req.url); 68 + const originStr: string = websiteURL.origin; 69 + const specs = await generateSpecs(openapiRoutes, { 67 70 documentation: { 68 71 info: { 69 - title: 'SkyScheduler API Routes', 72 + title: `${APP_NAME} API Routes`, 70 73 version: '1.0.0', 71 74 description: 'API Routes', 75 + termsOfService: `${originStr}/tos`, 76 + license: { 77 + name: "MIT", 78 + } 72 79 }, 73 80 openapi: "3.0", 74 81 servers: [ 75 - { url: 'https://skyscheduler.work', description: 'Production Server'} 82 + { url: originStr, description: 'Production Server'} 76 83 ], 77 84 }, 78 - }) 79 - ); 85 + }, c); 86 + return c.json(specs); 87 + });
+12 -6
src/endpoints/openapi.tsx
··· 1 - // Shitty file to help with automatically generating endpoint bindings so that we can dump them to the 1 + // Mediocre file to help with automatically generating endpoint bindings so that we can dump them to the 2 2 // Cloudflare WAF to protect/log against abuse 3 3 import { Context, Hono } from "hono"; 4 4 import { describeRoute, resolver, validator } from "hono-openapi"; 5 5 import { ContextVariables } from "../auth"; 6 6 import { Bindings } from "../types"; 7 - import { AccountDeleteSchema, AccountForgotSchema, CheckCallbackParam, PasswordResetPart } from "../validation/accountForgotDeleteSchema"; 8 - import { AccountResetSchema } from "../validation/accountResetSchema"; 7 + import { AccountDeleteSchema, AccountForgotSchema } from "../validation/accountForgotDeleteSchema"; 8 + import { 9 + AccountResetSchema, PasswordResetCheckCallbackParam, 10 + PasswordResetTokenParam 11 + } from "../validation/accountResetSchema"; 9 12 import { AccountUpdateSchema } from "../validation/accountUpdateSchema"; 10 13 import { LoginSchema } from "../validation/loginSchema"; 11 14 import { FileDeleteSchema } from "../validation/mediaSchema"; 12 15 import { EditSchema, PostSchema } from "../validation/postSchema"; 13 16 import { RepostSchema } from "../validation/repostSchema"; 14 - import { CheckGUIDSchema, CreateResponseSchema, FileOperationResponseSchema, GenericResponseSchema } from "../validation/responseSchema"; 17 + import { 18 + CheckFileSchema, CheckGUIDSchema, CreateResponseSchema, 19 + FileOperationResponseSchema, GenericResponseSchema 20 + } from "../validation/responseSchema"; 15 21 import { SignupSchema } from "../validation/signupSchema"; 16 22 17 23 export const openapiRoutes = new Hono<{ Bindings: Bindings, Variables: ContextVariables }>(); ··· 408 414 409 415 openapiRoutes.get("/preview/file/:id", describeRoute({ 410 416 description: "preview a file", 411 - }), validator("param", CheckGUIDSchema)); 417 + }), validator("param", CheckFileSchema)); 412 418 413 419 openapiRoutes.get("/api/auth/reset-password/:id", describeRoute({ 414 420 description: "resets a password" 415 - }), validator("param", PasswordResetPart), validator("query", CheckCallbackParam)); 421 + }), validator("param", PasswordResetTokenParam), validator("query", PasswordResetCheckCallbackParam));
+1 -11
src/validation/accountForgotDeleteSchema.ts
··· 1 - import z from "zod"; 2 1 import { PasswordSchema, UsernameSchema } from "./sharedValidations"; 3 - 4 - const uriComponent = z.codec(z.string(), z.string(), { 5 - decode: (encodedString) => decodeURIComponent(encodedString), 6 - encode: (decodedString) => encodeURIComponent(decodedString), 7 - }); 8 2 9 3 export const AccountDeleteSchema = PasswordSchema; 10 - export const AccountForgotSchema = UsernameSchema; 11 - export const CheckCallbackParam = z.object({ 12 - callbackURL: z.literal(z.encode(uriComponent, "/reset")) 13 - }); 14 - export const PasswordResetPart = z.string().min(20).max(64); 4 + export const AccountForgotSchema = UsernameSchema;
+14 -1
src/validation/accountResetSchema.ts
··· 13 13 .max(MAX_DASHBOARD_PASS, "confirm password too long") 14 14 .nonempty("confirm password cannot be empty") 15 15 .nonoptional(), 16 - }).refine((schema) => schema.confirmPassword === schema.password, "Passwords do not match"); 16 + }).refine((schema) => schema.confirmPassword === schema.password, "Passwords do not match"); 17 + 18 + // encoded strings 19 + const uriComponent = z.codec(z.string(), z.string(), { 20 + decode: (encodedString) => decodeURIComponent(encodedString), 21 + encode: (decodedString) => encodeURIComponent(decodedString), 22 + }); 23 + export const PasswordResetCheckCallbackParam = z.object({ 24 + callbackURL: z.literal(z.encode(uriComponent, "/reset")) 25 + }); 26 + 27 + export const PasswordResetTokenParam = z.object({ 28 + id: z.string().min(20).max(64) 29 + });
+4
src/validation/responseSchema.ts
··· 1 + // Everything in this file is used for OpenAPI route generation. 2 + // 3 + // This can be the generally expected results for most endpoints 4 + // but not all. 1 5 import * as z from "zod/v4"; 2 6 import { fileKeyRegex } from "./regexCases"; 3 7