-
Notifications
You must be signed in to change notification settings - Fork 37
feat: Add app metadata endpoint and fix instance naming #714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { OpenAPIRoute, contentJson } from "chanfana"; | ||
| import { Context } from "hono"; | ||
| import { z } from "zod"; | ||
|
|
||
| // Public metadata response - no sensitive data | ||
| const AppMetadataSchema = z.object({ | ||
| slug: z.string(), | ||
| title: z.string().optional(), | ||
| remixOf: z.string().nullable().optional(), | ||
| hasScreenshot: z.boolean(), | ||
| hasIcon: z.boolean(), | ||
| }); | ||
|
|
||
| export class AppGet extends OpenAPIRoute { | ||
| schema = { | ||
| tags: ["Apps"], | ||
| summary: "Get app metadata by slug", | ||
| request: { | ||
| params: z.object({ | ||
| slug: z.string(), | ||
| }), | ||
| }, | ||
| responses: { | ||
| "200": { | ||
| description: "Returns app metadata", | ||
| ...contentJson( | ||
| z.object({ | ||
| success: z.boolean(), | ||
| app: AppMetadataSchema, | ||
| }), | ||
| ), | ||
| }, | ||
| "404": { | ||
| description: "App not found", | ||
| ...contentJson( | ||
| z.object({ | ||
| error: z.string(), | ||
| }), | ||
| ), | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| async handle(c: Context<{ Bindings: Env }>) { | ||
| const { slug } = c.req.param(); | ||
| const kv = c.env.KV; | ||
|
|
||
| // Fetch app from KV by slug | ||
| const appData = await kv.get(slug); | ||
|
|
||
| if (!appData) { | ||
| return c.json({ error: "App not found" }, 404); | ||
| } | ||
|
|
||
| const app = JSON.parse(appData); | ||
|
|
||
| // Return only public metadata - no code, userId, or chatId | ||
| return c.json({ | ||
| success: true, | ||
| app: { | ||
| slug: app.slug, | ||
| title: app.title || slug, | ||
| remixOf: app.remixOf || null, | ||
| hasScreenshot: app.hasScreenshot || false, | ||
| hasIcon: app.hasIcon || false, | ||
| }, | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,11 +55,44 @@ function VibeInstanceViewerContent() { | |
| const fullId = `${titleId}-${installId}`; | ||
| const instanceExists = instances.some((inst) => inst._id === fullId); | ||
|
|
||
| // Create instance if it doesn't exist (lazy creation for Fresh Data) | ||
| // Pass the installId explicitly to ensure correct _id is created | ||
| // Create instance if it doesn't exist - fetch real title from hosting API | ||
| if (!instanceExists) { | ||
| // Let error throw - no catch handler | ||
| createInstance("Fresh Data", {}, installId); | ||
| let cancelled = false; | ||
|
|
||
| const fetchAndCreateInstance = async () => { | ||
| try { | ||
| // Fetch app metadata from hosting API | ||
| const apiBaseUrl = | ||
| import.meta.env.VITE_API_BASE_URL || | ||
| "https://vibes-hosting-v2-preview.jchris.workers.dev"; | ||
| const response = await fetch(`${apiBaseUrl}/api/apps/${titleId}`); | ||
|
Comment on lines
+65
to
+68
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The API base URL is hardcoded to a preview workers domain as a fallback. That’s useful for development but risky in production: if It’s usually better for production builds to fail fast (or at least log loudly) when critical configuration is missing instead of silently using a preview URL. SuggestionConsider moving the default into a clearly dev-only path or asserting that const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
if (!apiBaseUrl) {
if (import.meta.env.DEV) {
console.warn(
"VITE_API_BASE_URL not set; falling back to preview hosting endpoint for dev only.",
);
} else {
throw new Error("VITE_API_BASE_URL must be configured in production.");
}
}
const response = await fetch(`${apiBaseUrl ?? "https://vibes-hosting-v2-preview.jchris.workers.dev"}/api/apps/${titleId}`);This keeps the convenient dev fallback but prevents silent misrouting in production. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion. |
||
|
|
||
| let title = titleId; // Fallback to slug if fetch fails | ||
| if (response.ok) { | ||
| const data = (await response.json()) as { | ||
| app?: { title?: string }; | ||
| }; | ||
| title = data.app?.title || titleId; | ||
| } | ||
|
|
||
| if (!cancelled) { | ||
| // Create instance with real title | ||
| await createInstance(title, {}, installId); | ||
| } | ||
| } catch (error) { | ||
| // If fetch fails, use slug as title | ||
| console.warn("Failed to fetch app metadata, using slug:", error); | ||
| if (!cancelled) { | ||
| await createInstance(titleId, {}, installId); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| void fetchAndCreateInstance(); | ||
|
|
||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| } | ||
| }, [titleId, installId, instances, createInstance, isCreating]); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kv.get(slug)returns a string that is parsed directly withJSON.parsewithout any validation or defensive handling. If the KV entry is malformed JSON or not in the expected shape, this will throw, returning a 500 instead of a controlled 4xx/5xx response and bypassing yourAppMetadataSchema. This is an example of unsafe but type-valid JSON parsing the instructions warn about.Since this is a public-facing endpoint, consider validating the parsed value against
AppMetadataSchema(or a backend version of it) and handling parse/validation errors gracefully so the API contract stays stable and you don’t accidentally expose unexpected fields if the stored schema changes.Suggestion
You could wrap the JSON parse in a
try/catchand validate with Zod before responding, e.g.:This keeps the public response shape aligned with
AppMetadataSchemaand prevents runtime crashes due to bad KV contents. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.