Conversation
📝 WalkthroughWalkthroughAdds end-to-end 3Speak → Hive linking: client helper, server POST route, unified user resolution (web cookie + HiveSigner), publish/waves integrations to fire-and-forget linking, video upload UI/props and i18n strings, and beneficiary enforcement for embeds. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client (publish / wave UI)
participant App as Next.js API (`/api/threespeak/link-hive`)
participant Embed as 3Speak Embed Service
participant HS as HiveSigner (mobile auth)
Client->>App: POST link request (permlink,hiveAuthor,hivePermlink,hiveTitle,hiveTags, isEditing)
Note right of App: resolveUser(req, body) reads cookie or calls HS (/api/me)
App->>Embed: POST /video/{permlink}/hive (JSON + X-API-Key)
Embed-->>App: 200 / 4xx / 5xx (JSON)
App-->>Client: Forward status + JSON or error
Note over Client,App: Client-side linkThreeSpeakEmbed may call linkVideoToHive (fire-and-forget)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/features/shared/video-upload-threespeak/index.tsx (1)
99-109:⚠️ Potential issue | 🟠 MajorReset local file state on upload failure to avoid a retry dead-end.
Because
hasFileat Line 260 depends onselectedFile, a failed upload keeps the selector hidden (empty catch at Line 107-109), so users cannot retry without closing the modal.Suggested fix
try { const result = await uploadVideo({ file, owner: activeUser.username, isShort: props.isShort }); if (result) { setVideoData({ embedUrl: result.embedUrl, permlink: result.permlink }); } - } catch { - // Error already shown by the mutation's error handler + } catch { + // Error already shown by the mutation's error handler + if (selectedFileUrlRef.current) { + URL.revokeObjectURL(selectedFileUrlRef.current); + selectedFileUrlRef.current = undefined; + } + setSelectedFile(null); + setSelectedFileType("video/mp4"); + setVideoPercentage(0); + e.target.value = ""; }Also applies to: 260-260
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/shared/video-upload-threespeak/index.tsx` around lines 99 - 109, The empty catch swallowing failures leaves selectedFile unchanged so hasFile stays true and the selector is hidden; update the upload error handling (the catch after the uploadVideo call in the component) to reset the local file state (e.g., call setSelectedFile(null) or setHasFile(false)) so the file selector reappears and the user can retry, and keep the mutation's error handler for showing the error message; reference the uploadVideo call, the catch block, and the selectedFile/hasFile state setters when making this change.
🧹 Nitpick comments (6)
apps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsx (1)
23-24: Avoid exposing an enabled video button when no handler is provided.
onShowVideoUploadis optional, but the button can still appear enabled with an undefinedonClick(no-op). Either make the prop required or conditionally render/disable the button when the handler is absent.Suggested fix (guard render)
- onShowVideoUpload?: () => void; + onShowVideoUpload?: () => void; hasVideo?: boolean; @@ - <EcencyConfigManager.Conditional - condition={({ thirdPartyFeatures }) => thirdPartyFeatures.threeSpeak.uploading.enabled} - > - <Button - appearance="gray-link" - icon={<UilVideo />} - onClick={onShowVideoUpload} - disabled={disabled || hasVideo} - title={i18next.t("video-upload.title-short")} - /> - </EcencyConfigManager.Conditional> + <EcencyConfigManager.Conditional + condition={({ thirdPartyFeatures }) => thirdPartyFeatures.threeSpeak.uploading.enabled} + > + {onShowVideoUpload ? ( + <Button + appearance="gray-link" + icon={<UilVideo />} + onClick={onShowVideoUpload} + disabled={disabled || hasVideo} + title={i18next.t("video-upload.title-short")} + /> + ) : null} + </EcencyConfigManager.Conditional>Also applies to: 53-57
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsx` around lines 23 - 24, The toolbar currently exposes an enabled video button while onShowVideoUpload is optional; update the WaveFormToolbar component (props onShowVideoUpload, hasVideo) so the video button is either not rendered or is visually/semantically disabled when onShowVideoUpload is undefined — e.g., guard the JSX that renders the video upload button (the element using onShowVideoUpload as onClick) or make onShowVideoUpload required in the component props; ensure any click handlers are not invoked when the prop is absent and that the button uses disabled attribute/aria-disabled for accessibility if kept visible.apps/web/src/api/threespeak-embed/api.ts (1)
167-175: Prefer an exportedinterfaceforlinkVideoToHiveparams.Using a named interface here improves reuse and keeps exported API contracts clearer.
♻️ Suggested change
+export interface LinkVideoToHiveParams { + /** Video permlink (from the embed URL, NOT the Hive post permlink) */ + videoPermlink: string; + hiveAuthor: string; + hivePermlink: string; + hiveTitle?: string; + hiveBody?: string; + hiveTags?: string[]; +} + export async function linkVideoToHive(params: { - /** Video permlink (from the embed URL, NOT the Hive post permlink) */ - videoPermlink: string; - hiveAuthor: string; - hivePermlink: string; - hiveTitle?: string; - hiveBody?: string; - hiveTags?: string[]; -}): Promise<void> { +}): Promise<void> {As per coding guidelines:
**/*.{ts,tsx}→ “Preferinterfacefor defining object shapes in TypeScript”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/api/threespeak-embed/api.ts` around lines 167 - 175, Replace the inline object type for the linkVideoToHive params with an exported interface (e.g., export interface LinkVideoToHiveParams) and update the function signature to accept params: LinkVideoToHiveParams; ensure the interface includes videoPermlink, hiveAuthor, hivePermlink, and optional hiveTitle, hiveBody, hiveTags so the function definition (linkVideoToHive) and any callers can import and reuse the type.apps/web/src/app/api/threespeak/upload-token/route.ts (1)
3-3: Use@/*alias for this internal import.Please replace the relative import with the project alias to keep imports consistent.
♻️ Suggested change
-import { resolveUser } from "../resolve-user"; +import { resolveUser } from "@/app/api/threespeak/resolve-user";As per coding guidelines:
apps/web/src/**/*.{ts,tsx}→ “Use path aliases@/*forsrc/*and@ui/*forsrc/features/ui/*in imports”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/api/threespeak/upload-token/route.ts` at line 3, Replace the relative import of resolveUser with the project path alias: locate the import statement importing resolveUser in route.ts (import { resolveUser } from "../resolve-user") and change it to use the "@/..." alias that maps to src (e.g. import from "@/app/api/threespeak/resolve-user" or the correct path under src), ensuring the module specifier follows the project's alias convention (@"/*") and updates any tooling/tsconfig paths if necessary.apps/web/src/app/api/threespeak/thumbnail/route.ts (1)
3-3: Use@/*alias for this import path.♻️ Suggested change
-import { resolveUser } from "../resolve-user"; +import { resolveUser } from "@/app/api/threespeak/resolve-user";As per coding guidelines:
apps/web/src/**/*.{ts,tsx}→ “Use path aliases@/*forsrc/*and@ui/*forsrc/features/ui/*in imports”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/api/threespeak/thumbnail/route.ts` at line 3, The import in route.ts uses a relative path for resolveUser; update the import to use the project path alias (`@/`*) instead of the relative "../resolve-user" so route.ts imports resolveUser via the @ alias (e.g., import { resolveUser } from '@/...') to comply with the apps/web src alias rules and ensure resolvable, consistent paths.apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts (1)
112-127: Extract this 3Speak linking branch into a shared helper.This logic is duplicated in
apps/web/src/app/publish/_api/use-publish.ts(Line 190-205). Centralizing URL match + permlink extraction + link call will prevent behavioral drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts` around lines 112 - 127, The 3Speak embedding/linking branch (uses hasThreeSpeakEmbed, extractPermlink, linkVideoToHive, editingEntry, raw, username, permlink, tags) is duplicated and should be moved into a shared helper; implement a single exported function (e.g., linkThreeSpeakEmbed(raw, { username, permlink, tags, editingEntry })) that encapsulates the regex match, permlink extraction via extractPermlink, the !editingEntry guard, and the fire-and-forget linkVideoToHive(...).catch(() => {}) call, then replace the inline block in both use-waves-api (this file) and the other module with a call to that helper to keep behavior identical.apps/web/src/app/api/threespeak/link-hive/route.ts (1)
32-33: Use camelCase local variable names and map external snake_case keys explicitly.Use camelCase internally for readability and consistency, while still sending snake_case fields to the 3Speak API.
Proposed refactor
- const { permlink, hive_author, hive_permlink, hive_title, hive_body, hive_tags } = body; + const { + permlink, + hive_author: hiveAuthor, + hive_permlink: hivePermlink, + hive_title: hiveTitle, + hive_body: hiveBody, + hive_tags: hiveTags + } = body; @@ - if (!permlink || !hive_author || !hive_permlink) { + if (!permlink || !hiveAuthor || !hivePermlink) { @@ - if (hive_author !== activeUser) { + if (hiveAuthor !== activeUser) { @@ body: JSON.stringify({ - hive_author, - hive_permlink, - hive_title: hive_title || undefined, - hive_body: hive_body || undefined, - hive_tags: hive_tags || undefined + hive_author: hiveAuthor, + hive_permlink: hivePermlink, + hive_title: hiveTitle || undefined, + hive_body: hiveBody || undefined, + hive_tags: hiveTags || undefined })As per coding guidelines, "**/*.{ts,tsx,js,jsx}: Use camelCase for variable and function names."
Also applies to: 58-64
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/api/threespeak/link-hive/route.ts` around lines 32 - 33, The destructured request body uses snake_case locals (permlink, hive_author, hive_permlink, hive_title, hive_body, hive_tags); change these to camelCase locals (e.g., permlink, hiveAuthor, hivePermlink, hiveTitle, hiveBody, hiveTags) and then explicitly map them back to snake_case when building the outgoing 3Speak payload (where the code constructs the request body around those fields and in the block referencing the same fields later). Update all uses of hive_author, hive_permlink, hive_title, hive_body, hive_tags in the route handler to the new camelCase names and ensure the final POST/submit payload uses the original snake_case keys expected by the 3Speak API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/app/api/threespeak/link-hive/route.ts`:
- Around line 23-79: Add a new Jest test suite modeled on the existing
import-route.spec.ts to exercise the POST handler in the link-hive route: write
tests that import the POST function and mock getConfig (to toggle
apiKey/embedEndpoint), resolveUser (to simulate authenticated vs unauthenticated
users and mismatched hive_author), and global fetch (to simulate upstream
success and non-ok responses); include tests for 401 when resolveUser returns
falsy, 403 when hive_author !== resolved user, 400 when
permlink/hive_author/hive_permlink are missing, 503 when getConfig().apiKey is
falsy, and successful response + upstream error handling when fetch returns
ok/non-ok; assert Response status and body for each branch and restore mocks
between tests.
- Around line 31-33: The request body is currently untyped and destructured
before validation; define a RequestPayload interface (e.g., with permlink,
hive_author, hive_permlink, hive_title, hive_body, hive_tags) and validate the
parsed JSON against it in the route handler before destructuring (replace direct
const body = await req.json(); const { ... } = body; with typed parsing/guard),
return a 400 response when validation fails (instead of letting it fall through
to the generic 500), update the error handling in the route function to
distinguish invalid payloads, and add unit/integration tests covering
valid/invalid payloads to satisfy coverage requirements.
- Around line 52-65: The route's external fetch uses unvalidated/unencoded
permlink and has no timeout; update the logic around the fetch in route.ts so
that permlink is validated against the expected pattern (e.g.,
/^[a-z0-9-]{1,255}$/) and reject requests that don't match, replace the raw
interpolation `${permlink}` with an encoded value using
encodeURIComponent(permlink) when building the URL (embedEndpoint + "/video/" +
encodedPermlink + "/hive"), and add a timeout using an AbortController (create a
controller, pass controller.signal to fetch, and call controller.abort() via
setTimeout) so the upstream call will be aborted after a reasonable timeout.
Ensure apiKey and other headers remain unchanged and that you handle
aborted/timeout errors and non-2xx responses appropriately in the same handler.
In `@apps/web/src/app/api/threespeak/resolve-user.ts`:
- Around line 30-32: The fetch to "https://hivesigner.com/api/me" currently has
no timeout; modify the call in resolve-user.ts (the code that assigns const res
= await fetch(...)) to use an AbortSignal with a timeout (e.g.,
AbortSignal.timeout(8000) or create an AbortController and call
setTimeout(controller.abort, 8000)) so the request is aborted after ~8s; pass
the signal in the fetch options (headers remain the same), handle the abort by
catching the thrown error and returning/propagating an appropriate timeout
response, and ensure any created timers/controllers are cleaned up if you use
AbortController.
In `@apps/web/src/app/api/threespeak/thumbnail/route.ts`:
- Around line 26-33: After resolving activeUser, verify the requester actually
owns the target permlink before changing thumbnail state: fetch the post/entity
by permlink (using the existing permlink variable) and compare its owner/author
identifier with activeUser (e.g., activeUser.id or activeUser.username); if the
post is not found return a 404 and if the owner does not match return a 403, and
only then proceed to update thumbnail_url; place this check immediately after
resolveUser(...) and before any mutation logic that updates the thumbnail.
In `@apps/web/src/app/api/threespeak/upload-token/route.ts`:
- Around line 23-30: Validate the incoming JSON and body shape before
destructuring and calling resolveUser: wrap await req.json() in a try/catch to
return 400 on parse errors, ensure the parsed body is a non-null object (e.g.,
typeof body === "object" && body !== null) before extracting owner and isShort,
and pass the validated/sanitized body to resolveUser(req, body); if validation
fails respond with Response.json({ error: "Invalid request body" }, { status:
400 }). This prevents destructuring/resolveUser from throwing and surfacing a
500.
In `@apps/web/src/app/publish/_api/use-publish.ts`:
- Around line 190-205: Add unit tests for the new 3Speak linking path in
use-publish.ts: write tests that mock linkVideoToHive and cover (1) body
containing a 3speak embed -> hasThreeSpeakEmbed returns true, extractPermlink
returns a permlink, and linkVideoToHive is invoked with correct args
(videoPermlink, hiveAuthor, hivePermlink, hiveTitle, hiveTags) and is not
awaited (fire-and-forget); (2) embed present but extractPermlink returns null ->
linkVideoToHive is NOT called; (3) no embed in body -> linkVideoToHive is NOT
called; and (4) linkVideoToHive rejects -> ensure the rejection is swallowed (no
thrown error). Use your test framework (jest + ts-jest/react-testing-library) to
mock functions (hasThreeSpeakEmbed, extractPermlink, linkVideoToHive) and assert
calls/absence of calls and that publish flow completes without throwing.
- Around line 190-205: Add a unit test for the publish flow that verifies
linkVideoToHive is invoked when the post body contains a 3Speak embed: mock/stub
linkVideoToHive and any network calls, construct a publish request through the
usePublish/publish mutation (or the exported publish function) with a body that
triggers hasThreeSpeakEmbed and returns an embed URL that extractPermlink can
parse, then assert linkVideoToHive was called with videoPermlink (from
extractPermlink), hiveAuthor (author), hivePermlink (permlink), hiveTitle
(title), and hiveTags (tags); ensure the test isolates side effects by
catching/rejecting any promises and marks the call as fire-and-forget (i.e.,
don't rely on its resolution).
In `@apps/web/src/features/shared/video-upload-threespeak/video-upload-item.tsx`:
- Around line 37-45: The progress bar div with role="progressbar" inside the
VideoUploadItem component (the element using completed and isUploading) lacks
ARIA metadata; add aria-valuemin="0", aria-valuemax="100", and
aria-valuenow={completed} to that div (ensure aria-valuenow is supplied as a
numeric value from the completed variable) so assistive tech can read the
progress state.
In `@apps/web/src/features/waves/components/wave-form/index.tsx`:
- Around line 292-300: The wave-form component opens the video-upload modal
unconditionally via the onShowVideoUpload handler (calling
setShowVideoUpload(true)), allowing logged-out users to open a modal that cannot
upload; fix by checking authentication before opening the modal: replace direct
setShowVideoUpload(true) calls in the onShowVideoUpload handlers with a guard
that verifies the active user (e.g., currentUser/activeUser/isAuthenticated) and
only calls setShowVideoUpload(true) when authenticated, otherwise show a login
prompt or noop; apply the same guarded change for both occurrences (the
onShowVideoUpload handler near the submit prop and the one in the 341-349 block)
so the VideoUploadThreeSpeak upload component isn’t opened for unauthenticated
users.
- Around line 341-349: Add unit tests for the new video-upload integration in
the wave form: write tests that mount the WaveForm component (or the WaveForm
toolbar wrapper) and assert the initial state and UI for showVideoUpload.
Simulate the toolbar action that calls onShowVideoUpload and verify
setShowVideoUpload toggles showVideoUpload, then simulate the VideoUpload
onVideoUploaded callback and assert setVideo is called with the embedUrl and
that showVideoUpload becomes false; also add a test for clearing video via
clearVideo to ensure video state resets. Reference the VideoUpload component,
the state setters setVideo/clearVideo and setShowVideoUpload, and the
onShowVideoUpload handler when locating functions to test.
In `@apps/web/src/features/waves/components/wave-form/wave-form-control.tsx`:
- Around line 104-110: The icon-only clear button (Button with
icon={<UilMultiply />} and onClick={clearVideo}) lacks an accessible name;
update that Button to include an accessible label (for example aria-label="Clear
video" or aria-labelledby pointing to a visually-hidden label) so screen readers
can announce its purpose, keeping the existing onClick handler clearVideo
unchanged.
---
Outside diff comments:
In `@apps/web/src/features/shared/video-upload-threespeak/index.tsx`:
- Around line 99-109: The empty catch swallowing failures leaves selectedFile
unchanged so hasFile stays true and the selector is hidden; update the upload
error handling (the catch after the uploadVideo call in the component) to reset
the local file state (e.g., call setSelectedFile(null) or setHasFile(false)) so
the file selector reappears and the user can retry, and keep the mutation's
error handler for showing the error message; reference the uploadVideo call, the
catch block, and the selectedFile/hasFile state setters when making this change.
---
Nitpick comments:
In `@apps/web/src/api/threespeak-embed/api.ts`:
- Around line 167-175: Replace the inline object type for the linkVideoToHive
params with an exported interface (e.g., export interface LinkVideoToHiveParams)
and update the function signature to accept params: LinkVideoToHiveParams;
ensure the interface includes videoPermlink, hiveAuthor, hivePermlink, and
optional hiveTitle, hiveBody, hiveTags so the function definition
(linkVideoToHive) and any callers can import and reuse the type.
In `@apps/web/src/app/api/threespeak/link-hive/route.ts`:
- Around line 32-33: The destructured request body uses snake_case locals
(permlink, hive_author, hive_permlink, hive_title, hive_body, hive_tags); change
these to camelCase locals (e.g., permlink, hiveAuthor, hivePermlink, hiveTitle,
hiveBody, hiveTags) and then explicitly map them back to snake_case when
building the outgoing 3Speak payload (where the code constructs the request body
around those fields and in the block referencing the same fields later). Update
all uses of hive_author, hive_permlink, hive_title, hive_body, hive_tags in the
route handler to the new camelCase names and ensure the final POST/submit
payload uses the original snake_case keys expected by the 3Speak API.
In `@apps/web/src/app/api/threespeak/thumbnail/route.ts`:
- Line 3: The import in route.ts uses a relative path for resolveUser; update
the import to use the project path alias (`@/`*) instead of the relative
"../resolve-user" so route.ts imports resolveUser via the @ alias (e.g., import
{ resolveUser } from '@/...') to comply with the apps/web src alias rules and
ensure resolvable, consistent paths.
In `@apps/web/src/app/api/threespeak/upload-token/route.ts`:
- Line 3: Replace the relative import of resolveUser with the project path
alias: locate the import statement importing resolveUser in route.ts (import {
resolveUser } from "../resolve-user") and change it to use the "@/..." alias
that maps to src (e.g. import from "@/app/api/threespeak/resolve-user" or the
correct path under src), ensuring the module specifier follows the project's
alias convention (@"/*") and updates any tooling/tsconfig paths if necessary.
In `@apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts`:
- Around line 112-127: The 3Speak embedding/linking branch (uses
hasThreeSpeakEmbed, extractPermlink, linkVideoToHive, editingEntry, raw,
username, permlink, tags) is duplicated and should be moved into a shared
helper; implement a single exported function (e.g., linkThreeSpeakEmbed(raw, {
username, permlink, tags, editingEntry })) that encapsulates the regex match,
permlink extraction via extractPermlink, the !editingEntry guard, and the
fire-and-forget linkVideoToHive(...).catch(() => {}) call, then replace the
inline block in both use-waves-api (this file) and the other module with a call
to that helper to keep behavior identical.
In `@apps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsx`:
- Around line 23-24: The toolbar currently exposes an enabled video button while
onShowVideoUpload is optional; update the WaveFormToolbar component (props
onShowVideoUpload, hasVideo) so the video button is either not rendered or is
visually/semantically disabled when onShowVideoUpload is undefined — e.g., guard
the JSX that renders the video upload button (the element using
onShowVideoUpload as onClick) or make onShowVideoUpload required in the
component props; ensure any click handlers are not invoked when the prop is
absent and that the button uses disabled attribute/aria-disabled for
accessibility if kept visible.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 06ca69cb-6655-4c12-9801-ef0c2d61b544
📒 Files selected for processing (15)
apps/web/src/api/threespeak-embed/api.tsapps/web/src/app/api/threespeak/link-hive/route.tsapps/web/src/app/api/threespeak/resolve-user.tsapps/web/src/app/api/threespeak/thumbnail/route.tsapps/web/src/app/api/threespeak/upload-token/route.tsapps/web/src/app/publish/_api/use-publish.tsapps/web/src/app/publish/_components/publish-editor-toolbar.tsxapps/web/src/features/i18n/locales/en-US.jsonapps/web/src/features/shared/video-upload-threespeak/index.tsxapps/web/src/features/shared/video-upload-threespeak/video-upload-item.tsxapps/web/src/features/waves/components/wave-form/api/use-community-api.tsapps/web/src/features/waves/components/wave-form/api/use-waves-api.tsapps/web/src/features/waves/components/wave-form/index.tsxapps/web/src/features/waves/components/wave-form/wave-form-control.tsxapps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsx
| export async function POST(req: NextRequest) { | ||
| const { embedEndpoint, apiKey } = getConfig(); | ||
|
|
||
| if (!apiKey) { | ||
| return Response.json({ error: "3Speak integration not configured" }, { status: 503 }); | ||
| } | ||
|
|
||
| try { | ||
| const body = await req.json(); | ||
| const { permlink, hive_author, hive_permlink, hive_title, hive_body, hive_tags } = body; | ||
|
|
||
| // Resolve authenticated user from cookie (web) or code token (mobile) | ||
| const activeUser = await resolveUser(req, body); | ||
| if (!activeUser) { | ||
| return Response.json({ error: "Authentication required" }, { status: 401 }); | ||
| } | ||
|
|
||
| if (!permlink || !hive_author || !hive_permlink) { | ||
| return Response.json( | ||
| { error: "permlink, hive_author, and hive_permlink are required" }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
|
|
||
| // Only the video owner can link their video | ||
| if (hive_author !== activeUser) { | ||
| return Response.json({ error: "hive_author must match the logged-in user" }, { status: 403 }); | ||
| } | ||
|
|
||
| const res = await fetch(`${embedEndpoint}/video/${permlink}/hive`, { | ||
| method: "POST", | ||
| headers: { | ||
| "X-API-Key": apiKey, | ||
| "Content-Type": "application/json" | ||
| }, | ||
| body: JSON.stringify({ | ||
| hive_author, | ||
| hive_permlink, | ||
| hive_title: hive_title || undefined, | ||
| hive_body: hive_body || undefined, | ||
| hive_tags: hive_tags || undefined | ||
| }) | ||
| }); | ||
|
|
||
| if (!res.ok) { | ||
| const text = await res.text().catch(() => ""); | ||
| console.error(`[3Speak] Hive link failed: ${res.status} ${text}`); | ||
| return Response.json({ error: "Failed to link video to Hive post" }, { status: res.status }); | ||
| } | ||
|
|
||
| const data = await res.json(); | ||
| return Response.json(data); | ||
| } catch (e) { | ||
| console.error("[3Speak] Hive link endpoint error:", e); | ||
| return Response.json({ error: "Internal server error" }, { status: 500 }); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether tests exist for the new 3Speak link-hive route and its status branches.
fd -t f '.*(test|spec)\.(ts|tsx)$' apps/web/src | while read -r f; do
rg -n -C2 'link-hive|/api/threespeak/link-hive|3Speak integration not configured|Authentication required|hive_author must match' "$f"
doneRepository: ecency/vision-next
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check test directory structure and look for 3Speak-related tests
echo "=== Checking apps/web/src/specs directory structure ==="
find apps/web/src/specs -type f -name "*.spec.ts*" 2>/dev/null | head -20
echo -e "\n=== Searching for 3Speak related test files ==="
fd -t f '3speak|threespeak' apps/web/src --type ts --type tsx 2>/dev/null | grep -E '(spec|test)'
echo -e "\n=== Checking for API route tests structure ==="
find apps/web/src -type d -name "api" | head -5
echo -e "\n=== Looking for any tests in app/api directory ==="
find apps/web/src/app/api -name "*.spec.ts*" 2>/dev/null | head -20Repository: ecency/vision-next
Length of output: 1623
🏁 Script executed:
#!/bin/bash
# Search all spec files for link-hive or threespeak route references
echo "=== Searching for link-hive in all spec files ==="
rg -i 'link-hive|link.*hive' apps/web/src/specs/ --type ts --type tsx
echo -e "\n=== Searching for threespeak routes in spec files ==="
rg 'threespeak' apps/web/src/specs/ --type ts --type tsx -i
echo -e "\n=== Checking content of existing 3speak test files ==="
echo "--- beneficiaries.spec.ts ---"
head -30 apps/web/src/specs/features/3speak/beneficiaries.spec.ts
echo -e "\n--- extract-permlink.spec.ts ---"
head -30 apps/web/src/specs/features/3speak/extract-permlink.spec.ts
echo -e "\n=== Checking specs/api/import-route.spec.ts structure ==="
head -40 apps/web/src/specs/api/import-route.spec.tsRepository: ecency/vision-next
Length of output: 3478
Add tests for the new 3Speak link-hive route covering all status branches.
The route handles multiple outcomes (401/403/400/503/upstream errors) but no test file exists for it. Per coding guidelines, "All new features in @ecency/web require tests." Create a test file following the pattern used in apps/web/src/specs/api/import-route.spec.ts to cover:
- 401 when unauthenticated
- 403 when
hive_authordoesn't match logged-in user - 400 when required fields are missing
- 503 when 3Speak not configured
- Successful link creation and upstream error handling
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/api/threespeak/link-hive/route.ts` around lines 23 - 79, Add
a new Jest test suite modeled on the existing import-route.spec.ts to exercise
the POST handler in the link-hive route: write tests that import the POST
function and mock getConfig (to toggle apiKey/embedEndpoint), resolveUser (to
simulate authenticated vs unauthenticated users and mismatched hive_author), and
global fetch (to simulate upstream success and non-ok responses); include tests
for 401 when resolveUser returns falsy, 403 when hive_author !== resolved user,
400 when permlink/hive_author/hive_permlink are missing, 503 when
getConfig().apiKey is falsy, and successful response + upstream error handling
when fetch returns ok/non-ok; assert Response status and body for each branch
and restore mocks between tests.
| const body = await req.json(); | ||
| const { permlink, hive_author, hive_permlink, hive_title, hive_body, hive_tags } = body; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Read the route file to verify the actual code
cat -n apps/web/src/app/api/threespeak/link-hive/route.tsRepository: ecency/vision-next
Length of output: 3295
🏁 Script executed:
# Check if TypeScript strict mode is enabled
cat apps/web/tsconfig.json | head -50Repository: ecency/vision-next
Length of output: 746
🏁 Script executed:
# Search for tests related to this endpoint
find apps/web -type f -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "link-hive\|threespeak" 2>/dev/null || echo "No tests found"Repository: ecency/vision-next
Length of output: 271
🏁 Script executed:
# Check if there's a test file specifically for this route
find apps/web -type f \( -name "*link-hive*" \)Repository: ecency/vision-next
Length of output: 44
Add request payload type definition and validation, plus missing tests.
TypeScript strict mode is enabled. The await req.json() result is untyped (unknown), and destructuring occurs before validation (lines 31–32). Define an interface for the request body, validate it before use, and return 400 for invalid JSON instead of falling through to the generic 500 error (lines 75–78). Additionally, this new route requires test coverage per coding guidelines.
Suggested approach
+interface LinkHiveRequestBody {
+ permlink: string;
+ hive_author: string;
+ hive_permlink: string;
+ hive_title?: string;
+ hive_body?: string;
+ hive_tags?: string[];
+}
+
+function isLinkHiveRequestBody(value: unknown): value is LinkHiveRequestBody {
+ if (!value || typeof value !== "object") return false;
+ const v = value as Record<string, unknown>;
+ return (
+ typeof v.permlink === "string" && v.permlink.trim().length > 0 &&
+ typeof v.hive_author === "string" && v.hive_author.trim().length > 0 &&
+ typeof v.hive_permlink === "string" && v.hive_permlink.trim().length > 0 &&
+ (v.hive_title === undefined || typeof v.hive_title === "string") &&
+ (v.hive_body === undefined || typeof v.hive_body === "string") &&
+ (v.hive_tags === undefined || Array.isArray(v.hive_tags))
+ );
+}
+
export async function POST(req: NextRequest) {
const { embedEndpoint, apiKey } = getConfig();
if (!apiKey) {
return Response.json({ error: "3Speak integration not configured" }, { status: 503 });
}
try {
- const body = await req.json();
+ let body: unknown;
+ try {
+ body = await req.json();
+ } catch {
+ return Response.json({ error: "Invalid JSON" }, { status: 400 });
+ }
+
+ if (!isLinkHiveRequestBody(body)) {
+ return Response.json({ error: "Invalid request payload" }, { status: 400 });
+ }
+
const { permlink, hive_author, hive_permlink, hive_title, hive_body, hive_tags } = body;Also applies to: lines 40–45 (validation), 73 (untyped response), 75–78 (error handling)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/api/threespeak/link-hive/route.ts` around lines 31 - 33, The
request body is currently untyped and destructured before validation; define a
RequestPayload interface (e.g., with permlink, hive_author, hive_permlink,
hive_title, hive_body, hive_tags) and validate the parsed JSON against it in the
route handler before destructuring (replace direct const body = await
req.json(); const { ... } = body; with typed parsing/guard), return a 400
response when validation fails (instead of letting it fall through to the
generic 500), update the error handling in the route function to distinguish
invalid payloads, and add unit/integration tests covering valid/invalid payloads
to satisfy coverage requirements.
| // Resolve authenticated user from cookie (web) or code token (mobile) | ||
| const activeUser = await resolveUser(req, body); | ||
| if (!activeUser) { | ||
| return Response.json({ error: "Authentication required" }, { status: 401 }); | ||
| } | ||
|
|
||
| if (!permlink || !thumbnail_url) { | ||
| return Response.json({ error: "permlink and thumbnail_url are required" }, { status: 400 }); |
There was a problem hiding this comment.
Authorization is incomplete for thumbnail updates.
Line 27 resolves activeUser, but the route only checks that a user exists. It does not verify that the requested permlink belongs to that user before mutating thumbnail state.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/api/threespeak/thumbnail/route.ts` around lines 26 - 33,
After resolving activeUser, verify the requester actually owns the target
permlink before changing thumbnail state: fetch the post/entity by permlink
(using the existing permlink variable) and compare its owner/author identifier
with activeUser (e.g., activeUser.id or activeUser.username); if the post is not
found return a 404 and if the owner does not match return a 403, and only then
proceed to update thumbnail_url; place this check immediately after
resolveUser(...) and before any mutation logic that updates the thumbnail.
| // Link video to Hive post so it appears in 3Speak feeds (fire-and-forget) | ||
| if (hasThreeSpeakEmbed(cleanBody)) { | ||
| const embedMatch = cleanBody.match(/https?:\/\/[a-z.]*3speak\.tv\/embed[?/][^\s<"']*/); | ||
| if (embedMatch) { | ||
| const videoPermlink = extractPermlink(embedMatch[0]); | ||
| if (videoPermlink) { | ||
| linkVideoToHive({ | ||
| videoPermlink, | ||
| hiveAuthor: author, | ||
| hivePermlink: permlink, | ||
| hiveTitle: title, | ||
| hiveTags: tags | ||
| }).catch(() => {}); // non-critical | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Add tests for this new publish-time 3Speak linking behavior.
This introduces new feature logic (embed detection + non-blocking link call), but no corresponding tests are included in the reviewed changes.
Would you like me to draft a focused test plan (unit/integration cases) for this path?
As per coding guidelines: apps/web/src/**/*.{ts,tsx} → “All new features in @ecency/web require tests”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/publish/_api/use-publish.ts` around lines 190 - 205, Add
unit tests for the new 3Speak linking path in use-publish.ts: write tests that
mock linkVideoToHive and cover (1) body containing a 3speak embed ->
hasThreeSpeakEmbed returns true, extractPermlink returns a permlink, and
linkVideoToHive is invoked with correct args (videoPermlink, hiveAuthor,
hivePermlink, hiveTitle, hiveTags) and is not awaited (fire-and-forget); (2)
embed present but extractPermlink returns null -> linkVideoToHive is NOT called;
(3) no embed in body -> linkVideoToHive is NOT called; and (4) linkVideoToHive
rejects -> ensure the rejection is swallowed (no thrown error). Use your test
framework (jest + ts-jest/react-testing-library) to mock functions
(hasThreeSpeakEmbed, extractPermlink, linkVideoToHive) and assert calls/absence
of calls and that publish flow completes without throwing.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect threeSpeak node serialization and publish body-building pipeline.
rg -n -C4 'threeSpeakVideo|set3SpeakVideo|renderHTML|renderText|serialize|buildClearBody|3speak' apps/web/srcRepository: ecency/vision-next
Length of output: 50374
🏁 Script executed:
#!/bin/bash
# Find use-publish.ts and examine how cleanBody is built
cat -n apps/web/src/app/publish/_api/use-publish.ts | head -220 | tail -100Repository: ecency/vision-next
Length of output: 4070
🏁 Script executed:
#!/bin/bash
# Find where cleanBody is defined/assigned in use-publish.ts
cat -n apps/web/src/app/publish/_api/use-publish.ts | head -120 | tail -50Repository: ecency/vision-next
Length of output: 2179
🏁 Script executed:
#!/bin/bash
# Find cleanBody definition - search from beginning of use-publish.ts
rg -n 'cleanBody\s*=' apps/web/src/app/publish/_api/use-publish.ts | head -5Repository: ecency/vision-next
Length of output: 195
🏁 Script executed:
#!/bin/bash
# Show the cleanBody definition context
cat -n apps/web/src/app/publish/_api/use-publish.ts | sed -n '55,68p'Repository: ecency/vision-next
Length of output: 633
🏁 Script executed:
#!/bin/bash
# Check for tests covering the linkVideoToHive feature in use-publish.ts
find apps/web/src -type f -name "*.spec.ts" -o -name "*.test.ts" | xargs rg -l "linkVideoToHive|use-publish" 2>/dev/nullRepository: ecency/vision-next
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Check if use-publish.ts has ANY test file
find apps/web/src -type f \( -name "*publish*spec*" -o -name "*publish*test*" \)Repository: ecency/vision-next
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if linkVideoToHive function has any tests
rg -l "linkVideoToHive" apps/web/src --type ts --type tsxRepository: ecency/vision-next
Length of output: 89
🏁 Script executed:
#!/bin/bash
# Check where linkVideoToHive is defined and used
rg -n "linkVideoToHive" apps/web/src --type typescriptRepository: ecency/vision-next
Length of output: 96
🏁 Script executed:
#!/bin/bash
# Check where linkVideoToHive is defined and used
rg -n "linkVideoToHive" apps/web/srcRepository: ecency/vision-next
Length of output: 717
🏁 Script executed:
#!/bin/bash
# Check for tests of the threespeak-embed api
find apps/web/src -path "*spec*" -o -path "*test*" | xargs rg -l "threespeak-embed|linkVideoToHive" 2>/dev/null || echo "No tests found for threespeak-embed API"Repository: ecency/vision-next
Length of output: 529
🏁 Script executed:
#!/bin/bash
# Search specifically for tests of linkVideoToHive
rg -n "linkVideoToHive" apps/web/src/specsRepository: ecency/vision-next
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check what 'content' is - trace its source in use-publish.ts
cat -n apps/web/src/app/publish/_api/use-publish.ts | sed -n '1,55p'Repository: ecency/vision-next
Length of output: 2537
🏁 Script executed:
#!/bin/bash
# Verify the renderHTML output from ThreeSpeakVideoExtension
cat -n apps/web/src/features/tiptap-editor/extensions/three-speak-video-extension.tsx | sed -n '120,132p'Repository: ecency/vision-next
Length of output: 402
Add test coverage for the new 3Speak video linking feature in the publish flow.
The new code at lines 190-205 integrates linkVideoToHive() into the publish mutation, but no tests exist for this integration. Per the coding guidelines, all new features in apps/web require tests. Add a test case verifying that linkVideoToHive() is called with the correct parameters when a post containing a 3Speak embed is published.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/publish/_api/use-publish.ts` around lines 190 - 205, Add a
unit test for the publish flow that verifies linkVideoToHive is invoked when the
post body contains a 3Speak embed: mock/stub linkVideoToHive and any network
calls, construct a publish request through the usePublish/publish mutation (or
the exported publish function) with a body that triggers hasThreeSpeakEmbed and
returns an embed URL that extractPermlink can parse, then assert linkVideoToHive
was called with videoPermlink (from extractPermlink), hiveAuthor (author),
hivePermlink (permlink), hiveTitle (title), and hiveTags (tags); ensure the test
isolates side effects by catching/rejecting any promises and marks the call as
fire-and-forget (i.e., don't rely on its resolution).
| <VideoUpload | ||
| show={showVideoUpload} | ||
| setShow={setShowVideoUpload} | ||
| isShort={true} | ||
| onVideoUploaded={(embedUrl) => { | ||
| setVideo(embedUrl); | ||
| setShowVideoUpload(false); | ||
| }} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Candidate files for wave/video upload tests:"
fd -t f 'wave-form|video-upload-threespeak|threespeak' apps/web
echo
echo "Existing tests referencing new wave video flow symbols:"
rg -n --glob '**/*.{test,spec}.{ts,tsx}' \
'showVideoUpload|onShowVideoUpload|clearVideo|video-attached|video-upload|WaveFormToolbar|WaveFormControl' apps/webRepository: ecency/vision-next
Length of output: 698
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for wave-form test files ==="
fd -t f '(wave-form|waves).*\.(test|spec)\.(ts|tsx|js)$' apps/web
echo "---"
fd -t f '__tests__' apps/web/src/features/waves/ 2>/dev/null || echo "No __tests__ directories found"
echo
echo "=== Search for VideoUpload component and its tests ==="
fd -t f 'VideoUpload|video-upload' apps/web/src
echo "---"
rg -n 'VideoUpload' apps/web/src/features/waves/components/wave-form/index.tsx -B 2 -A 2
echo
echo "=== Check test patterns in apps/web ==="
fd -t f '\.(test|spec)\.(ts|tsx)$' apps/web/src/features --max-depth 3 | head -20
echo
echo "=== Check if wave-form/index.tsx has any companion test files ==="
ls -la apps/web/src/features/waves/components/wave-form/ | grep -E '\.(test|spec)'|| echo "No test files found in wave-form directory"Repository: ecency/vision-next
Length of output: 2947
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check if VideoUpload component has test files ==="
find apps/web/src/features/shared/video-upload-threespeak -type f \( -name '*.test.*' -o -name '*.spec.*' \)
echo
echo "=== List all files in video-upload-threespeak directory ==="
ls -la apps/web/src/features/shared/video-upload-threespeak/Repository: ecency/vision-next
Length of output: 856
Add test coverage for the new wave video-upload integration.
Lines 341-349 introduce new behavior requiring test coverage: video state management (setVideo, clearVideo), modal flow (showVideoUpload, setShowVideoUpload), and the video upload callback. The toolbar action triggering the video modal (onShowVideoUpload at line 299) and subsequent state updates must be tested. Per coding guidelines, all new features in @ecency/web require tests.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/waves/components/wave-form/index.tsx` around lines 341
- 349, Add unit tests for the new video-upload integration in the wave form:
write tests that mount the WaveForm component (or the WaveForm toolbar wrapper)
and assert the initial state and UI for showVideoUpload. Simulate the toolbar
action that calls onShowVideoUpload and verify setShowVideoUpload toggles
showVideoUpload, then simulate the VideoUpload onVideoUploaded callback and
assert setVideo is called with the embedUrl and that showVideoUpload becomes
false; also add a test for clearing video via clearVideo to ensure video state
resets. Reference the VideoUpload component, the state setters
setVideo/clearVideo and setShowVideoUpload, and the onShowVideoUpload handler
when locating functions to test.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
apps/web/src/app/api/threespeak/thumbnail/route.ts (1)
35-43:⚠️ Potential issue | 🟠 MajorAuthorization is incomplete for thumbnail updates.
The route resolves
activeUserand confirms authentication, but does not verify that the requestedpermlinkbelongs to that user before calling the external thumbnail endpoint. Any authenticated user could potentially modify thumbnails for videos they don't own.Consider fetching the video metadata first to verify ownership, or passing the authenticated user to the upstream API and letting it enforce ownership.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/api/threespeak/thumbnail/route.ts` around lines 35 - 43, The route currently authenticates via resolveUser and sets activeUser but does not verify ownership of the provided permlink; fetch the video metadata (e.g., via the service that exposes video info or a function like getVideoByPermlink) and confirm that the video's ownerId or author matches activeUser.id (or alternatively include activeUser identity in the payload sent to the upstream thumbnail endpoint) before proceeding to call the external thumbnail update; update the route to reject with 403 when the owner check fails and only call the external API when ownership is confirmed.apps/web/src/features/waves/components/wave-form/index.tsx (1)
301-307:⚠️ Potential issue | 🟠 MajorAdd tests for the new video-upload wave flow.
This change introduces new user-visible behavior (auth-gated modal open + upload callback state update) and should be covered by specs.
As per coding guidelines
apps/web/src/**/*.{ts,tsx}: All new features in@ecency/webrequire tests.Also applies to: 349-357
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/waves/components/wave-form/index.tsx` around lines 301 - 307, Add unit/integration tests covering the new video-upload wave flow: simulate clicking the trigger that uses the onShowVideoUpload handler in the WaveForm component and assert that when activeUsername is falsy toggleUIProp("login") is called (login modal shown) and when activeUsername is truthy setShowVideoUpload(true) is called and the upload-callback state is updated; include tests for both branches referenced by onShowVideoUpload, the modal opening state (showVideoUpload), and the upload callback behavior introduced around the same change (lines near onShowVideoUpload and the related upload callback logic). Place tests under the apps/web test suite per apps/web/src/**/*.{ts,tsx} guidelines and mock or spy on toggleUIProp and setShowVideoUpload to verify interactions.
🧹 Nitpick comments (2)
apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts (1)
89-100: RedundanthasThreeSpeakEmbedcheck.The outer
if (hasThreeSpeakEmbed(raw))guard is redundant becauseenforceThreeSpeakBeneficiaryinternally callshasThreeSpeakEmbedand returns the original empty array unchanged when no embed is present. You can simplify this to:const beneficiaries = enforceThreeSpeakBeneficiary([], raw); if (beneficiaries.length > 0) { commentPayload.options = { beneficiaries: beneficiaries.map((b) => ({ account: b.account, weight: b.weight })) }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts` around lines 89 - 100, Remove the redundant outer guard hasThreeSpeakEmbed(raw) and call enforceThreeSpeakBeneficiary([], raw) directly; then if the returned beneficiaries array length > 0 set commentPayload.options to { beneficiaries: beneficiaries.map(b => ({ account: b.account, weight: b.weight })) }. This keeps the logic within the existing functions (enforceThreeSpeakBeneficiary and hasThreeSpeakEmbed) and updates commentPayload only when beneficiaries are present.apps/web/src/features/waves/components/wave-form/index.tsx (1)
349-357: Remove redundant modal close in upload callback.
VideoUploadalready callssetShow(false)afteronVideoUploaded, so callingsetShowVideoUpload(false)again here is unnecessary.♻️ Proposed cleanup
<VideoUpload show={showVideoUpload} setShow={setShowVideoUpload} isShort={true} onVideoUploaded={(embedUrl) => { setVideo(embedUrl); - setShowVideoUpload(false); }} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/waves/components/wave-form/index.tsx` around lines 349 - 357, The onVideoUploaded callback in the VideoUpload JSX redundantly calls setShowVideoUpload(false) even though VideoUpload itself closes the modal; update the inline handler passed to VideoUpload (the onVideoUploaded prop) to only call setVideo(embedUrl) and remove the extra setShowVideoUpload(false) call so the modal close is not invoked twice (refer to VideoUpload, onVideoUploaded, setVideo, and setShowVideoUpload).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/web/src/app/api/threespeak/thumbnail/route.ts`:
- Around line 35-43: The route currently authenticates via resolveUser and sets
activeUser but does not verify ownership of the provided permlink; fetch the
video metadata (e.g., via the service that exposes video info or a function like
getVideoByPermlink) and confirm that the video's ownerId or author matches
activeUser.id (or alternatively include activeUser identity in the payload sent
to the upstream thumbnail endpoint) before proceeding to call the external
thumbnail update; update the route to reject with 403 when the owner check fails
and only call the external API when ownership is confirmed.
In `@apps/web/src/features/waves/components/wave-form/index.tsx`:
- Around line 301-307: Add unit/integration tests covering the new video-upload
wave flow: simulate clicking the trigger that uses the onShowVideoUpload handler
in the WaveForm component and assert that when activeUsername is falsy
toggleUIProp("login") is called (login modal shown) and when activeUsername is
truthy setShowVideoUpload(true) is called and the upload-callback state is
updated; include tests for both branches referenced by onShowVideoUpload, the
modal opening state (showVideoUpload), and the upload callback behavior
introduced around the same change (lines near onShowVideoUpload and the related
upload callback logic). Place tests under the apps/web test suite per
apps/web/src/**/*.{ts,tsx} guidelines and mock or spy on toggleUIProp and
setShowVideoUpload to verify interactions.
---
Nitpick comments:
In `@apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts`:
- Around line 89-100: Remove the redundant outer guard hasThreeSpeakEmbed(raw)
and call enforceThreeSpeakBeneficiary([], raw) directly; then if the returned
beneficiaries array length > 0 set commentPayload.options to { beneficiaries:
beneficiaries.map(b => ({ account: b.account, weight: b.weight })) }. This keeps
the logic within the existing functions (enforceThreeSpeakBeneficiary and
hasThreeSpeakEmbed) and updates commentPayload only when beneficiaries are
present.
In `@apps/web/src/features/waves/components/wave-form/index.tsx`:
- Around line 349-357: The onVideoUploaded callback in the VideoUpload JSX
redundantly calls setShowVideoUpload(false) even though VideoUpload itself
closes the modal; update the inline handler passed to VideoUpload (the
onVideoUploaded prop) to only call setVideo(embedUrl) and remove the extra
setShowVideoUpload(false) call so the modal close is not invoked twice (refer to
VideoUpload, onVideoUploaded, setVideo, and setShowVideoUpload).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0b0f9937-db84-4d07-b1b2-163963e776ca
📒 Files selected for processing (14)
apps/web/src/api/threespeak-embed/api.tsapps/web/src/api/threespeak-embed/index.tsapps/web/src/api/threespeak-embed/link-after-broadcast.tsapps/web/src/app/api/threespeak/link-hive/route.tsapps/web/src/app/api/threespeak/resolve-user.tsapps/web/src/app/api/threespeak/thumbnail/route.tsapps/web/src/app/api/threespeak/upload-token/route.tsapps/web/src/app/publish/_api/use-publish.tsapps/web/src/features/shared/video-upload-threespeak/index.tsxapps/web/src/features/shared/video-upload-threespeak/video-upload-item.tsxapps/web/src/features/waves/components/wave-form/api/use-waves-api.tsapps/web/src/features/waves/components/wave-form/index.tsxapps/web/src/features/waves/components/wave-form/wave-form-control.tsxapps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- apps/web/src/features/waves/components/wave-form/wave-form-control.tsx
- apps/web/src/features/shared/video-upload-threespeak/video-upload-item.tsx
- apps/web/src/app/api/threespeak/upload-token/route.ts
- apps/web/src/features/shared/video-upload-threespeak/index.tsx
- apps/web/src/app/api/threespeak/resolve-user.ts
|
Caution Review failedAn error occurred during the review process. Please try again later. 📝 WalkthroughWalkthroughAdds 3Speak video-to-Hive linking: client helper, server route, unified user resolution (web + mobile), publish/waves integration to fire-and-forget link calls, updated upload UI and i18n strings. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client (publish/wave UI)
participant App as Next.js API (`/api/threespeak/link-hive`)
participant Embed as 3Speak Embed Service
participant HS as HiveSigner (mobile auth)
Client->>App: POST link request (hiveAuthor,hivePermlink,hiveTitle,hiveTags,permlink)
Note right of App: resolveUser(req, body) reads cookie or calls HS (/api/me)
App->>Embed: POST /video/{permlink}/hive (JSON + X-API-Key)
Embed-->>App: 200/4xx/5xx (JSON)
App-->>Client: Forward status + JSON or error
Note over Client,App: linkThreeSpeakEmbed may call client-side linkVideoToHive (fire-and-forget)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts (1)
89-98: Consider removing redundant mapping.The
enforceThreeSpeakBeneficiaryfunction already returns objects with{account, weight}structure (perbeneficiary.ts:38). The.map()recreates the same shape unnecessarily.♻️ Suggested simplification
// Add 3Speak beneficiary when the wave contains a video embed const beneficiaries = enforceThreeSpeakBeneficiary([], raw); if (beneficiaries.length > 0) { commentPayload.options = { - beneficiaries: beneficiaries.map((b) => ({ - account: b.account, - weight: b.weight - })) + beneficiaries }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts` around lines 89 - 98, The code redundantly maps beneficiaries even though enforceThreeSpeakBeneficiary already returns objects shaped as {account, weight}; update the assignment so when beneficiaries.length > 0 you set commentPayload.options.beneficiaries = beneficiaries directly (instead of beneficiaries.map(...)). Refer to enforceThreeSpeakBeneficiary, beneficiaries and commentPayload.options to locate where to replace the map with a direct assignment; keep the existing length check and object structure intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/web/src/features/waves/components/wave-form/api/use-waves-api.ts`:
- Around line 89-98: The code redundantly maps beneficiaries even though
enforceThreeSpeakBeneficiary already returns objects shaped as {account,
weight}; update the assignment so when beneficiaries.length > 0 you set
commentPayload.options.beneficiaries = beneficiaries directly (instead of
beneficiaries.map(...)). Refer to enforceThreeSpeakBeneficiary, beneficiaries
and commentPayload.options to locate where to replace the map with a direct
assignment; keep the existing length check and object structure intact.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a770f0fe-8ad8-444e-8b98-9ff59792143c
📒 Files selected for processing (2)
apps/web/src/features/waves/components/wave-form/api/use-waves-api.tsapps/web/src/features/waves/components/wave-form/index.tsx
Summary by CodeRabbit
New Features
Improvements