feat: cleanup, status codes, Bearer auth, env vars

This commit is contained in:
Index 2025-06-11 07:15:05 -05:00
parent 0c09378dff
commit d8b1870059
7 changed files with 73 additions and 55 deletions

View file

@ -3,3 +3,5 @@
A Cloudflare Worker which works in conjunction with https://github.com/indexxing/Bluesky-Alt-Text. All endpoints are based off the original cloud function made by [symmetricalboy](https://github.com/symmetricalboy) [here](https://github.com/symmetricalboy/gen-alt-text/blob/main/functions/index.js), just in Typescript and prepackaged into a Cloudflare Worker environment. A Cloudflare Worker which works in conjunction with https://github.com/indexxing/Bluesky-Alt-Text. All endpoints are based off the original cloud function made by [symmetricalboy](https://github.com/symmetricalboy) [here](https://github.com/symmetricalboy/gen-alt-text/blob/main/functions/index.js), just in Typescript and prepackaged into a Cloudflare Worker environment.
Documentation is served at the root of the worker deployment. There is a root path variable specified in the entrypoint file because my setup involves using a worker route wildcard on my custom domain. Documentation is served at the root of the worker deployment. There is a root path variable specified in the entrypoint file because my setup involves using a worker route wildcard on my custom domain.
**Note:** This does not support video captioning, or uploading larger media for processing yet like the source cloud function.

View file

@ -8,7 +8,7 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
summary: "Condense a given text based on a directive", summary: "Condense a given text based on a directive",
security: [ security: [
{ {
apiKey: [], bearerAuth: [],
}, },
], ],
request: { request: {
@ -67,8 +67,7 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
try { try {
const res = await c.var.gemini.models.generateContent({ const res = await c.var.gemini.models.generateContent({
// * Original cloud function used "gemini-2.0-flash", but I think the lite version should work fine too. model: c.env.GEMINI_MODEL,
model: "gemini-2.0-flash-lite",
contents: [{ contents: [{
parts: [ parts: [
{ {
@ -79,14 +78,16 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
], ],
}], }],
config: { config: {
temperature: 0.2, temperature: c.env.GEMINI_CONDENSE_TEMPERATURE,
maxOutputTokens: 1024, maxOutputTokens: c.env.GEMINI_CONDENSE_MAX_OUTPUT_TOKENS,
}, },
}); });
const condensedText = res.candidates?.[0]?.content?.parts?.[0] const condensedText = res.candidates?.[0]?.content?.parts?.[0]
?.text; ?.text;
if (!condensedText) { if (!condensedText) {
c.status(502); // Bad response from upstream API resulting in "Bad Gateway" status
return { return {
success: false, success: false,
error: "Failed to condense text.", error: "Failed to condense text.",
@ -95,12 +96,15 @@ export class CondenseTextEndpoint extends OpenAPIRoute {
return { return {
success: true, success: true,
altText: condensedText, text: condensedText,
tokens: res.usageMetadata.totalTokenCount ?? 0,
}; };
} catch (e) { } catch (e) {
c.status(500);
return { return {
success: false, success: false,
message: e, error: e,
}; };
} }
} }

View file

@ -52,7 +52,7 @@ export class GenerateEndpoint extends OpenAPIRoute {
summary: "Generates alt text for a given image.", summary: "Generates alt text for a given image.",
security: [ security: [
{ {
apiKey: [], bearerAuth: [],
}, },
], ],
request: { request: {
@ -145,17 +145,16 @@ export class GenerateEndpoint extends OpenAPIRoute {
try { try {
const res = await c.var.gemini.models.generateContent({ const res = await c.var.gemini.models.generateContent({
// * Original cloud function used "gemini-2.0-flash", but I think the lite version should work fine too. model: c.env.GEMINI_MODEL,
model: "gemini-2.0-flash-lite",
contents: [ contents: [
{ text: systemInstructions }, { text: systemInstructions },
{ inlineData: { mimeType: mimeType, data: base64Data } }, { inlineData: { mimeType: mimeType, data: base64Data } },
], ],
config: { config: {
temperature: 0.2, // Lower temperature for more deterministic output temperature: c.env.GEMINI_GENERATE_TEMPERATURE,
maxOutputTokens: 2048, // Allow for longer descriptions if needed maxOutputTokens: c.env.GEMINI_GENERATE_MAX_OUTPUT_TOKENS,
topP: 0.95, topP: c.env.GEMINI_GENERATE_TOP_P,
topK: 64, topK: c.env.GEMINI_GENERATE_TOP_K,
}, },
}); });
@ -170,12 +169,13 @@ export class GenerateEndpoint extends OpenAPIRoute {
return { return {
success: true, success: true,
altText: generatedText, text: generatedText,
tokens: res.usageMetadata.totalTokenCount ?? 0,
}; };
} catch (e) { } catch (e) {
return { return {
success: false, success: false,
message: e, error: e,
}; };
} }
} }

View file

@ -16,21 +16,12 @@ const rootPath = "/api/altText/";
app.use( app.use(
"*", "*",
cors({ cors({
origin: (origin) => { origin: [
const allowedOrigins = [ "https://indexx.dev",
"https://indexx.dev", "chrome-extension://",
"moz-extension://", "safari-web-extension://",
"chrome-extension://", "moz-extension://",
]; ],
if (
origin &&
allowedOrigins.some((allowed) => origin.startsWith(allowed))
) {
return origin;
}
return null;
},
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["*"], allowHeaders: ["*"],
maxAge: 600, maxAge: 600,
@ -51,10 +42,10 @@ const openapi = fromHono(app, {
openapi_url: rootPath + "openapi.json", openapi_url: rootPath + "openapi.json",
}); });
openapi.registry.registerComponent("securitySchemes", "apiKey", { openapi.registry.registerComponent("securitySchemes", "bearerAuth", {
type: "apiKey", type: "http",
name: "Authorization", scheme: "bearer",
in: "header", bearerFormat: "API key",
}); });
// Define Middlewares // Define Middlewares

View file

@ -5,22 +5,35 @@ export async function authMiddleware(
c: Context<{ Bindings: Env; Variables: Variables }>, c: Context<{ Bindings: Env; Variables: Variables }>,
next: Next, next: Next,
) { ) {
const authToken = c.req.header("Authorization"); if (!c.env.AUTH_TOKEN) {
if (!authToken) {
c.status(401);
return c.json({ return c.json({
success: false, success: false,
error: "No authentication token provided.", error: "Authentication token is not specified in worker secrets.",
}); }, 500);
} }
if (authToken !== c.env.AUTH_TOKEN) { const authToken = c.req.header("Authorization");
c.status(403);
if (!authToken || !authToken.startsWith("Bearer ")) {
return c.json(
{
success: false,
error:
"Authentication token missing or malformed. Expected 'Bearer <token>'.",
},
401,
{
"WWW-Authenticate": "Bearer",
},
);
}
const token = authToken.split(" ")[1];
if (token !== c.env.AUTH_TOKEN) {
return c.json({ return c.json({
success: false, success: false,
error: "Authentication token provided is invalid.", error: "Authentication token provided is invalid.",
}); }, 401);
} }
await next(); await next();

View file

@ -1,6 +1,4 @@
import { DateTime, Str } from "chanfana";
import type { Context } from "hono"; import type { Context } from "hono";
import { z } from "zod";
import type { GoogleGenAI } from "@google/genai"; import type { GoogleGenAI } from "@google/genai";
export type AppContext = Context<{ Bindings: Env; Variables: Variables }>; export type AppContext = Context<{ Bindings: Env; Variables: Variables }>;
@ -11,6 +9,15 @@ export interface Env {
ABSOLUTE_MAX_LENGTH: number; ABSOLUTE_MAX_LENGTH: number;
MAX_DIRECT_BLOB_SIZE: number; MAX_DIRECT_BLOB_SIZE: number;
GEMINI_MODEL: string;
GEMINI_GENERATE_TEMPERATURE: number;
GEMINI_GENERATE_MAX_OUTPUT_TOKENS: number;
GEMINI_GENERATE_TOP_P: number;
GEMINI_GENERATE_TOP_K: number;
GEMINI_CONDENSE_TEMPERATURE: number;
GEMINI_CONDENSE_MAX_OUTPUT_TOKENS: number;
// Secrets // Secrets
AUTH_TOKEN: string; AUTH_TOKEN: string;
GEMINI_API_KEY: string; GEMINI_API_KEY: string;
@ -19,11 +26,3 @@ export interface Env {
export type Variables = { export type Variables = {
gemini: GoogleGenAI; gemini: GoogleGenAI;
}; };
export const Task = z.object({
name: Str({ example: "lorem" }),
slug: Str(),
description: Str({ required: false }),
completed: z.boolean().default(false),
due_date: DateTime(),
});

View file

@ -12,6 +12,15 @@
"vars": { "vars": {
"MAX_ALT_TEXT_LENGTH": 2000, "MAX_ALT_TEXT_LENGTH": 2000,
"ABSOLUTE_MAX_LENGTH": 5000, "ABSOLUTE_MAX_LENGTH": 5000,
"MAX_DIRECT_BLOB_SIZE": 5242880 "MAX_DIRECT_BLOB_SIZE": 5242880,
"GEMINI_MODEL": "gemini-2.0-flash-lite",
"GEMINI_GENERATE_TEMPERATURE": 0.2,
"GEMINI_GENERATE_MAX_OUTPUT_TOKENS": 2048,
"GEMINI_GENERATE_TOP_P": 0.95,
"GEMINI_GENERATE_TOP_K": 64,
"GEMINI_CONDENSE_TEMPERATURE": 0.2,
"GEMINI_CONDENSE_MAX_OUTPUT_TOKENS": 1024
} }
} }