Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/api/accounts/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function OPTIONS() {
* - id (required): The unique identifier of the account (UUID)
*
* @param request - The request object
* @param params.params
* @param params - Route params containing the account ID
Comment on lines +26 to 27
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix redundant/ambiguous JSDoc param annotation.
@param params.params is unclear and overlaps with @param params. Keep one clear param entry for the destructured route params object.

Suggested JSDoc cleanup
- * `@param` params.params
- * `@param` params - Route params containing the account ID
+ * `@param` params - Route params promise containing the account ID
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @param params.params
* @param params - Route params containing the account ID
* `@param` params - Route params containing the account ID
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/accounts/`[id]/route.ts around lines 26 - 27, Remove the
redundant/ambiguous JSDoc line `@param params.params` and keep a single clear
`@param params` entry that documents the destructured route params object for
the route handler (e.g., explain it contains the account ID as params.id);
update the JSDoc for the route handler (the function that receives params) so it
only has one `@param params` description and, if helpful, a short `@property` or
parenthetical note for `id` to avoid ambiguity.

* @returns A NextResponse with account data
*/
Expand Down
2 changes: 2 additions & 0 deletions app/api/admins/coding/slack/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { getSlackTagsHandler } from "@/lib/admins/slack/getSlackTagsHandler";
* Pulls directly from the Slack API as the source of truth.
* Supports period filtering: all (default), daily, weekly, monthly.
* Requires admin authentication.
*
* @param request
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
return getSlackTagsHandler(request);
Expand Down
5 changes: 5 additions & 0 deletions app/api/admins/privy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import { getPrivyLoginsHandler } from "@/lib/admins/privy/getPrivyLoginsHandler"
* Returns Privy login statistics for the requested time period.
* Supports daily (last 24h), weekly (last 7 days), and monthly (last 30 days) periods.
* Requires admin authentication.
*
* @param request
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
return getPrivyLoginsHandler(request);
}

/**
*
*/
Comment on lines +18 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace empty JSDoc block with a meaningful description.
The empty comment block on OPTIONS adds noise without context; please document purpose/response briefly.

Suggested doc improvement
-/**
- *
- */
+/**
+ * CORS preflight handler for /api/admins/privy.
+ *
+ * `@returns` 204 response with CORS headers.
+ */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
*
*/
/**
* CORS preflight handler for /api/admins/privy.
*
* `@returns` 204 response with CORS headers.
*/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/admins/privy/route.ts` around lines 18 - 20, The empty JSDoc above
the OPTIONS handler should be replaced with a short meaningful description
explaining the purpose of the OPTIONS endpoint and what response/headers it
returns; update the comment above the OPTIONS function (the handler in route.ts)
to state that it handles CORS/preflight requests and lists allowed
methods/headers or points to the response shape so future readers know why it
exists.

export async function OPTIONS(): Promise<NextResponse> {
return new NextResponse(null, { status: 204, headers: getCorsHeaders() });
}
2 changes: 2 additions & 0 deletions app/api/coding-agent/[platform]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "@/lib/coding-agent/handlers/registerHandlers";
* Handles webhook verification handshakes (e.g. WhatsApp hub.challenge).
*
* @param request - The incoming verification request
* @param params.params
* @param params - Route params containing the platform name
*/
export async function GET(
Expand All @@ -34,6 +35,7 @@ export async function GET(
* Handles Slack and WhatsApp webhooks via dynamic [platform] segment.
*
* @param request - The incoming webhook request
* @param params.params
* @param params - Route params containing the platform name
*/
export async function POST(
Expand Down
51 changes: 51 additions & 0 deletions app/api/launch/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
import { generateCampaignHandler } from "@/lib/launch/generateCampaignHandler";

/**
* OPTIONS handler for CORS preflight requests.
*
* @returns Empty 200 response with CORS headers.
*/
export async function OPTIONS() {
return new NextResponse(null, {
status: 200,
headers: getCorsHeaders(),
});
}

/**
* POST /api/launch
*
* Streams an AI-generated music release campaign given artist and song details.
* Returns a text/plain stream with XML-style section markers that the client
* parses to render each campaign section in real-time.
*
* Authentication: x-api-key header OR Authorization: Bearer token required.
*
* Request body:
* - artist_name: string (required) — the artist's name
* - song_name: string (required) — the song or album name
* - genre: string (required) — musical genre
* - release_date: string (required) — release date (any format)
* - description: string (optional) — additional context for the AI
*
* Response: streaming text with section markers:
* [SECTION:press_release]...[/SECTION:press_release]
* [SECTION:spotify_pitch]...[/SECTION:spotify_pitch]
* [SECTION:instagram_captions]...[/SECTION:instagram_captions]
* [SECTION:tiktok_hooks]...[/SECTION:tiktok_hooks]
* [SECTION:fan_newsletter]...[/SECTION:fan_newsletter]
* [SECTION:curator_email]...[/SECTION:curator_email]
*
* @param request - The incoming request
* @returns Streaming text response or error
*/
export async function POST(request: NextRequest): Promise<Response> {
return generateCampaignHandler(request);
}
Comment on lines +45 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for test files covering the launch endpoint
echo "=== Looking for launch-related test files ==="
fd -e test.ts -e spec.ts launch

echo ""
echo "=== Searching for tests referencing /api/launch ==="
rg -l "api/launch" --glob "*test*" --glob "*spec*"

echo ""
echo "=== Searching for generateCampaignHandler tests ==="
rg -l "generateCampaignHandler" --glob "*test*" --glob "*spec*"

Repository: recoupable/api

Length of output: 257


🏁 Script executed:

# Search for all test files in the repository
find . -type f \( -name "*.test.ts" -o -name "*.spec.ts" -o -name "*.integration.ts" -o -name "*.e2e.ts" \) | head -30

Repository: recoupable/api

Length of output: 1735


🏁 Script executed:

# Check the structure of app/api directory and look for test patterns
fd -t f -e ts -e tsx "app/api" | grep -i test

Repository: recoupable/api

Length of output: 391


🏁 Script executed:

# Search for where generateCampaignHandler is defined and if it has any tests
rg "generateCampaignHandler" -A 5 -B 2 | head -50

Repository: recoupable/api

Length of output: 1573


🏁 Script executed:

# Check if launch route test file exists
ls -la app/api/launch/__tests__/ 2>/dev/null || echo "Directory does not exist"

# Search for any test files in launch directory
find app/api/launch -name "*.test.ts" -o -name "*.spec.ts" 2>/dev/null || echo "No test files found"

# Confirm the pattern - check if other api endpoints have __tests__ directories with route.test.ts
ls -1 app/api/*/__tests__/route.test.ts 2>/dev/null | head -5

Repository: recoupable/api

Length of output: 127


🏁 Script executed:

# Get more details on the generateCampaignHandler implementation to understand what needs testing
cat -n lib/launch/generateCampaignHandler.ts | head -60

Repository: recoupable/api

Length of output: 1549


Add integration tests for the /api/launch endpoint covering all success and error paths.

Integration tests are missing for this route handler. The codebase has validateLaunchBody.test.ts for schema validation, but lacks tests for the route handler itself (auth failures, validation failures, successful streaming). Other API endpoints in the codebase follow the established pattern with app/api/**/__tests__/route.test.ts files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/launch/route.ts` around lines 45 - 47, Add an integration test file
for the /api/launch route (e.g., app/api/launch/__tests__/route.test.ts) that
exercises the route handler POST by invoking the exported POST function and the
underlying generateCampaignHandler through realistic NextRequest objects:
include tests for auth failure (mock auth middleware or missing credentials and
assert 401), validation failure (send bodies that fail the launch schema and
assert 400 with validation message), and a successful streaming path (mock
dependencies used by generateCampaignHandler to return a streaming Response and
assert stream contents and status 200). Ensure tests reuse the existing schema
validator/fixtures (validateLaunchBody) and mock any external services or
environment used by generateCampaignHandler so tests are deterministic.


export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
export const revalidate = 0;
1 change: 1 addition & 0 deletions app/api/songs/analyze/presets/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export async function OPTIONS() {
* - status: "success"
* - presets: Array of { name, label, description, requiresAudio, responseFormat }
*
* @param request
* @returns A NextResponse with the list of available presets
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
Expand Down
4 changes: 4 additions & 0 deletions app/api/transcribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { NextRequest, NextResponse } from "next/server";
import { processAudioTranscription } from "@/lib/transcribe/processAudioTranscription";
import { formatTranscriptionError } from "@/lib/transcribe/types";

/**
*
* @param req
*/
export async function POST(req: NextRequest) {
try {
const body = await req.json();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({
validateAdminAuth: vi.fn(),
}));

/**
*
* @param url
*/
function createMockRequest(url: string): NextRequest {
return {
url,
Expand Down
4 changes: 4 additions & 0 deletions lib/admins/pr/__tests__/getPrMergedStatusHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ vi.mock("@/lib/github/fetchGithubPrStatus", () => ({
const PR_URL_1 = "https://github.com/recoupable/api/pull/42";
const PR_URL_2 = "https://github.com/recoupable/chat/pull/100";

/**
*
* @param urls
*/
function makeRequest(urls: string[] = [PR_URL_1]) {
const params = new URLSearchParams();
urls.forEach(url => params.append("pull_requests", url));
Expand Down
2 changes: 2 additions & 0 deletions lib/admins/pr/getPrStatusHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { fetchGithubPrStatus } from "@/lib/github/fetchGithubPrStatus";
* Uses the GitHub REST API to check each PR's state.
*
* Requires admin authentication.
*
* @param request
*/
export async function getPrStatusHandler(request: NextRequest): Promise<NextResponse> {
try {
Expand Down
3 changes: 3 additions & 0 deletions lib/admins/privy/countNewAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { getCutoffMs } from "./getCutoffMs";

/**
* Counts how many users in the list were created within the cutoff period.
*
* @param users
* @param period
*/
export function countNewAccounts(users: User[], period: PrivyLoginsPeriod): number {
const cutoffMs = getCutoffMs(period);
Expand Down
4 changes: 4 additions & 0 deletions lib/admins/privy/fetchPrivyLogins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export type FetchPrivyLoginsResult = {
totalPrivyUsers: number;
};

/**
*
* @param period
*/
export async function fetchPrivyLogins(period: PrivyLoginsPeriod): Promise<FetchPrivyLoginsResult> {
const isAll = period === "all";
const cutoffMs = getCutoffMs(period);
Expand Down
2 changes: 2 additions & 0 deletions lib/admins/privy/getCutoffMs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { PERIOD_DAYS } from "./periodDays";
* Returns the cutoff timestamp in milliseconds for a given period.
* Uses midnight UTC calendar day boundaries to match Privy dashboard behavior.
* Returns 0 for "all" (no cutoff).
*
* @param period
*/
export function getCutoffMs(period: PrivyLoginsPeriod): number {
if (period === "all") return 0;
Expand Down
2 changes: 2 additions & 0 deletions lib/admins/privy/getLatestVerifiedAt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { User } from "@privy-io/node";
/**
* Returns the most recent latest_verified_at (in ms) across all linked_accounts for a Privy user.
* Returns null if no linked account has a latest_verified_at.
*
* @param user
*/
export function getLatestVerifiedAt(user: User): number | null {
const linkedAccounts = user.linked_accounts;
Expand Down
2 changes: 2 additions & 0 deletions lib/admins/privy/toMs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* Normalizes a Privy timestamp to milliseconds.
* Privy docs say milliseconds but examples show seconds (10 digits).
*
* @param timestamp
*/
export function toMs(timestamp: number): number {
return timestamp > 1e12 ? timestamp : timestamp * 1000;
Expand Down
4 changes: 4 additions & 0 deletions lib/admins/slack/__tests__/getSlackTagsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const mockTags = [
},
];

/**
*
* @param period
*/
function makeRequest(period = "all") {
return new NextRequest(`https://example.com/api/admins/coding/slack?period=${period}`);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/admins/slack/__tests__/validateGetSlackTagsQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ vi.mock("@/lib/admins/validateAdminAuth", () => ({

const mockAuth = { accountId: "test-account", orgId: null, authToken: "token" };

/**
*
* @param period
*/
function makeRequest(period?: string) {
const url = period
? `https://example.com/api/admins/coding/slack?period=${period}`
Expand Down
4 changes: 4 additions & 0 deletions lib/admins/slack/extractGithubPrUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const PR_URL_EXACT = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
/**
* Extracts GitHub pull request URLs from a Slack message's text, attachments, and blocks.
* Handles plain URLs, Slack-formatted links, action button URLs, and Block Kit element URLs.
*
* @param text
* @param attachments
* @param blocks
*/
export function extractGithubPrUrls(
text: string,
Expand Down
4 changes: 4 additions & 0 deletions lib/admins/slack/fetchThreadPullRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ interface ConversationsRepliesResponse {
/**
* Fetches bot replies in a Slack thread and returns any GitHub PR URLs found.
* Extracts URLs from message text, attachment action buttons, and Block Kit blocks.
*
* @param token
* @param channel
* @param threadTs
*/
export async function fetchThreadPullRequests(
token: string,
Expand Down
1 change: 1 addition & 0 deletions lib/ai/getModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { GatewayLanguageModelEntry } from "@ai-sdk/gateway";

/**
* Returns a specific model by its ID from the list of available models.
*
* @param modelId - The ID of the model to find
* @returns The matching model or undefined if not found
*/
Expand Down
2 changes: 2 additions & 0 deletions lib/ai/isEmbedModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { GatewayLanguageModelEntry } from "@ai-sdk/gateway";
/**
* Determines if a model is an embedding model (not suitable for chat).
* Embed models typically have 0 output pricing since they only produce embeddings.
*
* @param m
*/
export const isEmbedModel = (m: GatewayLanguageModelEntry): boolean => {
const pricing = m.pricing;
Expand Down
5 changes: 5 additions & 0 deletions lib/artists/__tests__/createArtistPostHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ vi.mock("@/lib/auth/validateAuthContext", () => ({
validateAuthContext: (...args: unknown[]) => mockValidateAuthContext(...args),
}));

/**
*
* @param body
* @param headers
*/
function createRequest(body: unknown, headers: Record<string, string> = {}): NextRequest {
const defaultHeaders: Record<string, string> = {
"Content-Type": "application/json",
Expand Down
5 changes: 5 additions & 0 deletions lib/artists/__tests__/validateCreateArtistBody.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ vi.mock("@/lib/auth/validateAuthContext", () => ({
validateAuthContext: (...args: unknown[]) => mockValidateAuthContext(...args),
}));

/**
*
* @param body
* @param headers
*/
function createRequest(body: unknown, headers: Record<string, string> = {}): NextRequest {
const defaultHeaders: Record<string, string> = { "Content-Type": "application/json" };
return new NextRequest("http://localhost/api/artists", {
Expand Down
4 changes: 4 additions & 0 deletions lib/auth/__tests__/validateAuthContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ const mockGetAuthenticatedAccountId = vi.mocked(getAuthenticatedAccountId);
const mockValidateOrganizationAccess = vi.mocked(validateOrganizationAccess);
const mockCanAccessAccount = vi.mocked(canAccessAccount);

/**
*
* @param headers
*/
function createMockRequest(headers: Record<string, string> = {}): Request {
return {
headers: {
Expand Down
2 changes: 2 additions & 0 deletions lib/catalog/formatCatalogSongsAsCSV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { CatalogSong } from "./getCatalogSongs";

/**
* Formats catalog songs into the CSV-like format expected by the scorer
*
* @param songs
*/
export function formatCatalogSongsAsCSV(songs: CatalogSong[]): string {
const csvLines = songs.map(song => {
Expand Down
2 changes: 2 additions & 0 deletions lib/catalog/getCatalogDataAsCSV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { formatCatalogSongsAsCSV } from "./formatCatalogSongsAsCSV";

/**
* Gets all catalog songs and formats them as CSV for the scorer
*
* @param catalogId
*/
export async function getCatalogDataAsCSV(catalogId: string): Promise<string> {
const allSongs: CatalogSong[] = [];
Expand Down
7 changes: 7 additions & 0 deletions lib/catalog/getCatalogSongs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export interface CatalogSongsResponse {
error?: string;
}

/**
*
* @param catalogId
* @param pageSize
* @param page
* @param artistName
*/
export async function getCatalogSongs(
catalogId: string,
pageSize: number = 100,
Expand Down
4 changes: 4 additions & 0 deletions lib/catalog/getCatalogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export interface CatalogsResponse {
error?: string;
}

/**
*
* @param accountId
*/
export async function getCatalogs(accountId: string): Promise<CatalogsResponse> {
try {
const response = await fetch(
Expand Down
5 changes: 5 additions & 0 deletions lib/chat/__tests__/integration/chatEndToEnd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ const mockDeductCredits = vi.mocked(deductCredits);
const mockGenerateChatTitle = vi.mocked(generateChatTitle);

// Helper to create mock NextRequest
/**
*
* @param body
* @param headers
*/
function createMockRequest(body: unknown, headers: Record<string, string> = {}): Request {
return {
json: () => Promise.resolve(body),
Expand Down
2 changes: 2 additions & 0 deletions lib/chat/toolChains/getPrepareStepResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type PrepareStepOptions = {
/**
* Returns the next tool to run based on timeline progression through tool chains.
* Uses toolCallsContent to track exact execution order and position in sequence.
*
* @param options
*/
const getPrepareStepResult = (options: PrepareStepOptions): PrepareStepResult | undefined => {
const { steps } = options;
Expand Down
3 changes: 3 additions & 0 deletions lib/chats/processCompactChatRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ interface ProcessCompactChatRequestParams {
* Verifies the chat exists and the user has access before compacting.
*
* @param params - The parameters for processing the chat compaction.
* @param params.chatId
* @param params.prompt
* @param params.accountId
* @returns The result of the compaction attempt.
*/
export async function processCompactChatRequest({
Expand Down
6 changes: 6 additions & 0 deletions lib/coding-agent/__tests__/handleGitHubWebhook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ const BASE_PAYLOAD = {
},
};

/**
*
* @param body
* @param event
* @param signature
*/
function makeRequest(body: unknown, event = "issue_comment", signature = "valid") {
return {
text: () => Promise.resolve(JSON.stringify(body)),
Expand Down
3 changes: 3 additions & 0 deletions lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ beforeEach(() => {
process.env.GITHUB_TOKEN = "ghp_test";
});

/**
*
*/
function createMockBot() {
return { onAction: vi.fn() } as any;
}
Expand Down
Loading
Loading