Skip to content

Commit 65cc22b

Browse files
📝 Add docstrings to feature/mirroring
Docstrings generation was requested by @joepduin. * #638 (comment) The following files were modified: * `apps/web/src/app/api/get-upload-url/route.ts` * `apps/web/src/app/api/sounds/search/route.ts` * `apps/web/src/app/api/transcribe/route.ts` * `apps/web/src/app/api/waitlist/export/route.ts` * `apps/web/src/components/editor/export-button.tsx` * `apps/web/src/components/editor/layout-guide-overlay.tsx` * `apps/web/src/components/editor/media-panel/tabbar.tsx` * `apps/web/src/components/editor/media-panel/views/captions.tsx` * `apps/web/src/components/editor/media-panel/views/media.tsx` * `apps/web/src/components/editor/media-panel/views/stickers.tsx` * `apps/web/src/components/editor/panel-base-view.tsx` * `apps/web/src/components/editor/panel-preset-selector.tsx` * `apps/web/src/components/editor/preview-panel.tsx` * `apps/web/src/components/editor/properties-panel/index.tsx` * `apps/web/src/components/editor/properties-panel/media-properties.tsx` * `apps/web/src/components/editor/properties-panel/text-properties.tsx` * `apps/web/src/components/editor/timeline/timeline-element.tsx` * `apps/web/src/components/editor/timeline/timeline-marker.tsx` * `apps/web/src/components/footer.tsx` * `apps/web/src/components/icons.tsx` * `apps/web/src/components/keyboard-shortcuts-help.tsx` * `apps/web/src/components/language-select.tsx` * `apps/web/src/components/theme-toggle.tsx` * `apps/web/src/components/ui/editable-timecode.tsx` * `apps/web/src/components/ui/font-picker.tsx` * `apps/web/src/components/ui/input-with-back.tsx` * `apps/web/src/hooks/use-edge-auto-scroll.ts` * `apps/web/src/hooks/use-frame-cache.ts` * `apps/web/src/hooks/use-highlight-scroll.ts` * `apps/web/src/hooks/use-infinite-scroll.ts` * `apps/web/src/hooks/use-sound-search.ts` * `apps/web/src/lib/editor-utils.ts` * `apps/web/src/lib/export.ts` * `apps/web/src/lib/iconify-api.ts` * `apps/web/src/lib/timeline-renderer.ts` * `apps/web/src/lib/transcription-utils.ts` * `apps/web/src/lib/zk-encryption.ts` * `apps/web/src/stores/text-properties-store.ts`
1 parent bee7aba commit 65cc22b

38 files changed

+2515
-1970
lines changed
Lines changed: 136 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,136 @@
1-
import { NextRequest, NextResponse } from "next/server";
2-
import { z } from "zod";
3-
import { AwsClient } from "aws4fetch";
4-
import { nanoid } from "nanoid";
5-
import { env } from "@/env";
6-
import { baseRateLimit } from "@/lib/rate-limit";
7-
import { isTranscriptionConfigured } from "@/lib/transcription-utils";
8-
9-
const uploadRequestSchema = z.object({
10-
fileExtension: z.enum(["wav", "mp3", "m4a", "flac"], {
11-
errorMap: () => ({
12-
message: "File extension must be wav, mp3, m4a, or flac",
13-
}),
14-
}),
15-
});
16-
17-
const apiResponseSchema = z.object({
18-
uploadUrl: z.string().url(),
19-
fileName: z.string().min(1),
20-
});
21-
22-
export async function POST(request: NextRequest) {
23-
try {
24-
// Rate limiting
25-
const ip = request.headers.get("x-forwarded-for") ?? "anonymous";
26-
const { success } = await baseRateLimit.limit(ip);
27-
28-
if (!success) {
29-
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
30-
}
31-
32-
// Check transcription configuration
33-
const transcriptionCheck = isTranscriptionConfigured();
34-
if (!transcriptionCheck.configured) {
35-
console.error(
36-
"Missing environment variables:",
37-
JSON.stringify(transcriptionCheck.missingVars)
38-
);
39-
40-
return NextResponse.json(
41-
{
42-
error: "Transcription not configured",
43-
message: `Auto-captions require environment variables: ${transcriptionCheck.missingVars.join(", ")}. Check README for setup instructions.`,
44-
},
45-
{ status: 503 }
46-
);
47-
}
48-
49-
// Parse and validate request body
50-
const rawBody = await request.json().catch(() => null);
51-
if (!rawBody) {
52-
return NextResponse.json(
53-
{ error: "Invalid JSON in request body" },
54-
{ status: 400 }
55-
);
56-
}
57-
58-
const validationResult = uploadRequestSchema.safeParse(rawBody);
59-
if (!validationResult.success) {
60-
return NextResponse.json(
61-
{
62-
error: "Invalid request parameters",
63-
details: validationResult.error.flatten().fieldErrors,
64-
},
65-
{ status: 400 }
66-
);
67-
}
68-
69-
const { fileExtension } = validationResult.data;
70-
71-
// Initialize R2 client
72-
const client = new AwsClient({
73-
accessKeyId: env.R2_ACCESS_KEY_ID,
74-
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
75-
});
76-
77-
// Generate unique filename with timestamp
78-
const timestamp = Date.now();
79-
const fileName = `audio/${timestamp}-${nanoid()}.${fileExtension}`;
80-
81-
// Create presigned URL
82-
const url = new URL(
83-
`https://${env.R2_BUCKET_NAME}.${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com/${fileName}`
84-
);
85-
86-
url.searchParams.set("X-Amz-Expires", "3600"); // 1 hour expiry
87-
88-
const signed = await client.sign(new Request(url, { method: "PUT" }), {
89-
aws: { signQuery: true },
90-
});
91-
92-
if (!signed.url) {
93-
throw new Error("Failed to generate presigned URL");
94-
}
95-
96-
// Prepare and validate response
97-
const responseData = {
98-
uploadUrl: signed.url,
99-
fileName,
100-
};
101-
102-
const responseValidation = apiResponseSchema.safeParse(responseData);
103-
if (!responseValidation.success) {
104-
console.error(
105-
"Invalid API response structure:",
106-
responseValidation.error
107-
);
108-
return NextResponse.json(
109-
{ error: "Internal response formatting error" },
110-
{ status: 500 }
111-
);
112-
}
113-
114-
return NextResponse.json(responseValidation.data);
115-
} catch (error) {
116-
console.error("Error generating upload URL:", error);
117-
return NextResponse.json(
118-
{
119-
error: "Failed to generate upload URL",
120-
message:
121-
error instanceof Error
122-
? error.message
123-
: "An unexpected error occurred",
124-
},
125-
{ status: 500 }
126-
);
127-
}
128-
}
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { z } from "zod";
3+
import { AwsClient } from "aws4fetch";
4+
import { nanoid } from "nanoid";
5+
import { env } from "@/env";
6+
import { baseRateLimit } from "@/lib/rate-limit";
7+
import { isTranscriptionConfigured } from "@/lib/transcription-utils";
8+
9+
const uploadRequestSchema = z.object({
10+
fileExtension: z.enum(["wav", "mp3", "m4a", "flac"], {
11+
errorMap: () => ({
12+
message: "File extension must be wav, mp3, m4a, or flac",
13+
}),
14+
}),
15+
});
16+
17+
const apiResponseSchema = z.object({
18+
uploadUrl: z.string().url(),
19+
fileName: z.string().min(1),
20+
});
21+
22+
/**
23+
* Generates a presigned upload URL and a unique filename for uploading an audio file to Cloudflare R2.
24+
*
25+
* Accepts a JSON request body with a `fileExtension` (one of "wav", "mp3", "m4a", "flac"). Applies client rate limiting and verifies required transcription environment configuration before producing a signed PUT URL valid for 1 hour.
26+
*
27+
* @param request - Incoming Next.js request whose JSON body must include `fileExtension`
28+
* @returns On success, an object with `uploadUrl` (the presigned PUT URL) and `fileName` (the generated object path). On failure, a JSON error object with an `error` field and optional `message`/`details`; responses use appropriate HTTP status codes (400, 429, 503, 500).
29+
*/
30+
export async function POST(request: NextRequest) {
31+
try {
32+
// Rate limiting
33+
const ip = request.headers.get("x-forwarded-for") ?? "anonymous";
34+
const { success } = await baseRateLimit.limit(ip);
35+
36+
if (!success) {
37+
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
38+
}
39+
40+
// Check transcription configuration
41+
const transcriptionCheck = isTranscriptionConfigured();
42+
if (!transcriptionCheck.configured) {
43+
console.error(
44+
"Missing environment variables:",
45+
JSON.stringify(transcriptionCheck.missingVars)
46+
);
47+
48+
return NextResponse.json(
49+
{
50+
error: "Transcription not configured",
51+
message: `Auto-captions require environment variables: ${transcriptionCheck.missingVars.join(", ")}. Check README for setup instructions.`,
52+
},
53+
{ status: 503 }
54+
);
55+
}
56+
57+
// Parse and validate request body
58+
const rawBody = await request.json().catch(() => null);
59+
if (!rawBody) {
60+
return NextResponse.json(
61+
{ error: "Invalid JSON in request body" },
62+
{ status: 400 }
63+
);
64+
}
65+
66+
const validationResult = uploadRequestSchema.safeParse(rawBody);
67+
if (!validationResult.success) {
68+
return NextResponse.json(
69+
{
70+
error: "Invalid request parameters",
71+
details: validationResult.error.flatten().fieldErrors,
72+
},
73+
{ status: 400 }
74+
);
75+
}
76+
77+
const { fileExtension } = validationResult.data;
78+
79+
// Initialize R2 client
80+
const client = new AwsClient({
81+
accessKeyId: env.R2_ACCESS_KEY_ID,
82+
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
83+
});
84+
85+
// Generate unique filename with timestamp
86+
const timestamp = Date.now();
87+
const fileName = `audio/${timestamp}-${nanoid()}.${fileExtension}`;
88+
89+
// Create presigned URL
90+
const url = new URL(
91+
`https://${env.R2_BUCKET_NAME}.${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com/${fileName}`
92+
);
93+
94+
url.searchParams.set("X-Amz-Expires", "3600"); // 1 hour expiry
95+
96+
const signed = await client.sign(new Request(url, { method: "PUT" }), {
97+
aws: { signQuery: true },
98+
});
99+
100+
if (!signed.url) {
101+
throw new Error("Failed to generate presigned URL");
102+
}
103+
104+
// Prepare and validate response
105+
const responseData = {
106+
uploadUrl: signed.url,
107+
fileName,
108+
};
109+
110+
const responseValidation = apiResponseSchema.safeParse(responseData);
111+
if (!responseValidation.success) {
112+
console.error(
113+
"Invalid API response structure:",
114+
responseValidation.error
115+
);
116+
return NextResponse.json(
117+
{ error: "Internal response formatting error" },
118+
{ status: 500 }
119+
);
120+
}
121+
122+
return NextResponse.json(responseValidation.data);
123+
} catch (error) {
124+
console.error("Error generating upload URL:", error);
125+
return NextResponse.json(
126+
{
127+
error: "Failed to generate upload URL",
128+
message:
129+
error instanceof Error
130+
? error.message
131+
: "An unexpected error occurred",
132+
},
133+
{ status: 500 }
134+
);
135+
}
136+
}

0 commit comments

Comments
 (0)