-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add 20 research API endpoints #366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: test
Are you sure you want to change the base?
Changes from all commits
6d1f805
9b9f60a
ed94ece
c62fc77
e65ef6a
46d9701
0dcd869
ae185ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchAlbumsHandler } from "@/lib/research/getResearchAlbumsHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/albums — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/albums — Artist album discography with release dates. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON album list or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchAlbumsHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchAudienceHandler } from "@/lib/research/getResearchAudienceHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/audience — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/audience — Audience demographics by platform. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON audience demographics or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchAudienceHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchCareerHandler } from "@/lib/research/getResearchCareerHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/career — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/career — Artist career history and milestones. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON career timeline or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchCareerHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchChartsHandler } from "@/lib/research/getResearchChartsHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/charts — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/charts — Global chart positions by platform and country. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON chart positions or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchChartsHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchCitiesHandler } from "@/lib/research/getResearchCitiesHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/cities — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/cities — Geographic listening data for an artist. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON city-level listener data or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchCitiesHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchCuratorHandler } from "@/lib/research/getResearchCuratorHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/curator — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/curator — Playlist curator details. Requires `?platform=` and `?id=` query params. | ||
| * | ||
| * @param request - must include `platform` and `id` query params | ||
| * @returns JSON curator profile or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchCuratorHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { postResearchDeepHandler } from "@/lib/research/postResearchDeepHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/deep — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * POST /api/research/deep — Deep, comprehensive research with citations. Body: `{ query }`. | ||
| * | ||
| * @param request - JSON body with `query` string | ||
| * @returns JSON research report with citations or error | ||
| */ | ||
| export async function POST(request: NextRequest) { | ||
| return postResearchDeepHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchDiscoverHandler } from "@/lib/research/getResearchDiscoverHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/discover — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/discover — Discover artists by genre, country, and growth criteria. Supports `?genre=`, `?country=`, `?sort=`, `?limit=` filters. | ||
| * | ||
| * @param request - filter criteria via query params | ||
| * @returns JSON array of matching artists or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchDiscoverHandler(request); | ||
| } | ||
|
Comment on lines
+1
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate route and handler symbols
fd --type f 'route.ts' app/api/research | sort
rg -n 'getResearchDiscoverHandler|/api/research/discover'
# Find tests referencing discover endpoint/handler
rg -n --iglob '*{test,spec}*.{ts,tsx,js,jsx}' \
'/api/research/discover|getResearchDiscoverHandler'
# Check for explicit error-path assertions (status codes/messages)
rg -n --iglob '*{test,spec}*.{ts,tsx,js,jsx}' \
'discover.*(400|401|402|500)|status\s*[:=]\s*(400|401|402|500)|insufficient|invalid|missing'Repository: recoupable/api Length of output: 1160 🏁 Script executed: cat -n lib/research/getResearchDiscoverHandler.tsRepository: recoupable/api Length of output: 2924 Add comprehensive test coverage and implement Zod query validation for this endpoint. Missing test coverage for success and error paths (credit insufficiency, auth failure, proxy failures). Additionally, query parameters should be validated using a Zod schema ( The handler correctly uses
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — JSDoc rewritten with meaningful one-liner descriptions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Two items from the original review are still outstanding:
Would you like to address these in this PR, or shall I open a follow-up issue to track them? 🧠 Learnings used |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { postResearchEnrichHandler } from "@/lib/research/postResearchEnrichHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/enrich — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * POST /api/research/enrich — Enrich an entity with structured web research data. Body: `{ url, prompt? }`. | ||
| * | ||
| * @param request - JSON body with `url` and optional `prompt` | ||
| * @returns JSON enriched entity data or error | ||
| */ | ||
| export async function POST(request: NextRequest) { | ||
| return postResearchEnrichHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { postResearchExtractHandler } from "@/lib/research/postResearchExtractHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/extract — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * POST /api/research/extract — Extract clean markdown from URLs. Body: `{ urls }`. | ||
| * | ||
| * @param request - JSON body with `urls` array | ||
| * @returns JSON extracted markdown content or error | ||
| */ | ||
| export async function POST(request: NextRequest) { | ||
| return postResearchExtractHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchFestivalsHandler } from "@/lib/research/getResearchFestivalsHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/festivals — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/festivals — List of music festivals. | ||
| * | ||
| * @param request - optional filter query params | ||
| * @returns JSON festival list or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchFestivalsHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchGenresHandler } from "@/lib/research/getResearchGenresHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/genres — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/genres — All available genre IDs and names. | ||
| * | ||
| * @param request - no required query params | ||
| * @returns JSON genre list or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchGenresHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchInsightsHandler } from "@/lib/research/getResearchInsightsHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/insights — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/insights — Noteworthy highlights and trending metrics for an artist. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON insights data or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchInsightsHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchInstagramPostsHandler } from "@/lib/research/getResearchInstagramPostsHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/instagram-posts — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/instagram-posts — Recent Instagram posts for an artist. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON Instagram posts or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchInstagramPostsHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchLookupHandler } from "@/lib/research/getResearchLookupHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/lookup — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/lookup — Resolve a Spotify artist URL to cross-platform IDs. Requires `?url=` query param. | ||
| * | ||
| * @param request - must include `url` query param (Spotify URL) | ||
| * @returns JSON cross-platform IDs or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchLookupHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchMetricsHandler } from "@/lib/research/getResearchMetricsHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/metrics — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/metrics — Platform-specific streaming and social metrics. Requires `?artist=` and `?source=` query params. | ||
| * | ||
| * @param request - must include `artist` and `source` query params | ||
| * @returns JSON metrics data or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchMetricsHandler(request); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getCorsHeaders } from "@/lib/networking/getCorsHeaders"; | ||
| import { getResearchMilestonesHandler } from "@/lib/research/getResearchMilestonesHandler"; | ||
|
|
||
| /** | ||
| * OPTIONS /api/research/milestones — CORS preflight. | ||
| * | ||
| * @returns CORS-enabled 200 response | ||
| */ | ||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 200, headers: getCorsHeaders() }); | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/research/milestones — Artist activity feed: playlist adds, chart entries, events. Requires `?artist=` query param. | ||
| * | ||
| * @param request - must include `artist` query param | ||
| * @returns JSON milestone activity feed or error | ||
| */ | ||
| export async function GET(request: NextRequest) { | ||
| return getResearchMilestonesHandler(request); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.