feat(api): Add persona endpoint for retrieving persona information and clips
- Implement `/api/persona` GET endpoint to fetch persona details - Add Swagger documentation for the new persona API endpoint - Update docs page to include new `/api/persona` route description - Extend SunoApi class with `getPersonaPaginated` method to support persona data retrieval
This commit is contained in:
parent
c3a8c568a5
commit
2bc500723f
61
src/app/api/persona/route.ts
Normal file
61
src/app/api/persona/route.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { NextResponse, NextRequest } from "next/server";
|
||||
import { sunoApi } from "@/lib/SunoApi";
|
||||
import { corsHeaders } from "@/lib/utils";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
if (req.method === 'GET') {
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const personaId = url.searchParams.get('id');
|
||||
const page = url.searchParams.get('page');
|
||||
|
||||
if (personaId == null) {
|
||||
return new NextResponse(JSON.stringify({ error: 'Missing parameter id' }), {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const pageNumber = page ? parseInt(page) : 1;
|
||||
const personaInfo = await (await sunoApi()).getPersonaPaginated(personaId, pageNumber);
|
||||
|
||||
return new NextResponse(JSON.stringify(personaInfo), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching persona:', error);
|
||||
|
||||
return new NextResponse(JSON.stringify({ error: 'Internal server error' }), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...corsHeaders
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return new NextResponse('Method Not Allowed', {
|
||||
headers: {
|
||||
Allow: 'GET',
|
||||
...corsHeaders
|
||||
},
|
||||
status: 405
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function OPTIONS(request: Request) {
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: corsHeaders
|
||||
});
|
||||
}
|
@ -33,6 +33,7 @@ export default function Docs() {
|
||||
- \`/api/get_aligned_lyrics\`: Get list of timestamps for each word in the lyrics
|
||||
- \`/api/clip\`: Get clip information based on ID passed as query parameter \`id\`
|
||||
- \`/api/concat\`: Generate the whole song from extensions
|
||||
- \`/api/persona\`: Get persona information and clips based on ID and page number
|
||||
\`\`\`
|
||||
|
||||
Feel free to explore the detailed API parameters and conduct tests on this page.
|
||||
|
@ -588,6 +588,149 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/persona": {
|
||||
"get": {
|
||||
"summary": "Get persona information and clips.",
|
||||
"description": "Retrieve persona information, including associated clips and pagination data.",
|
||||
"tags": ["default"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"description": "Persona ID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Page number (defaults to 1)",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"persona": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Persona ID"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Persona name"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Persona description"
|
||||
},
|
||||
"image_s3_id": {
|
||||
"type": "string",
|
||||
"description": "Persona image URL"
|
||||
},
|
||||
"root_clip_id": {
|
||||
"type": "string",
|
||||
"description": "Root clip ID"
|
||||
},
|
||||
"clip": {
|
||||
"type": "object",
|
||||
"description": "Root clip information"
|
||||
},
|
||||
"persona_clips": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"clip": {
|
||||
"type": "object",
|
||||
"description": "Clip information"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"is_suno_persona": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this is a Suno official persona"
|
||||
},
|
||||
"is_public": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this persona is public"
|
||||
},
|
||||
"upvote_count": {
|
||||
"type": "integer",
|
||||
"description": "Number of upvotes"
|
||||
},
|
||||
"clip_count": {
|
||||
"type": "integer",
|
||||
"description": "Number of clips"
|
||||
}
|
||||
}
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer",
|
||||
"description": "Total number of results"
|
||||
},
|
||||
"current_page": {
|
||||
"type": "integer",
|
||||
"description": "Current page number"
|
||||
},
|
||||
"is_following": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the current user is following this persona"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Missing parameter id",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string",
|
||||
"example": "Missing parameter id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string",
|
||||
"example": "Internal server error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
|
@ -39,6 +39,34 @@ export interface AudioInfo {
|
||||
error_message?: string; // Error message if any
|
||||
}
|
||||
|
||||
interface PersonaResponse {
|
||||
persona: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
image_s3_id: string;
|
||||
root_clip_id: string;
|
||||
clip: any; // You can define a more specific type if needed
|
||||
user_display_name: string;
|
||||
user_handle: string;
|
||||
user_image_url: string;
|
||||
persona_clips: Array<{
|
||||
clip: any; // You can define a more specific type if needed
|
||||
}>;
|
||||
is_suno_persona: boolean;
|
||||
is_trashed: boolean;
|
||||
is_owned: boolean;
|
||||
is_public: boolean;
|
||||
is_public_approved: boolean;
|
||||
is_loved: boolean;
|
||||
upvote_count: number;
|
||||
clip_count: number;
|
||||
};
|
||||
total_results: number;
|
||||
current_page: number;
|
||||
is_following: boolean;
|
||||
}
|
||||
|
||||
class SunoApi {
|
||||
private static BASE_URL: string = 'https://studio-api.prod.suno.com';
|
||||
private static CLERK_BASE_URL: string = 'https://clerk.suno.com';
|
||||
@ -801,6 +829,24 @@ class SunoApi {
|
||||
monthly_usage: response.data.monthly_usage
|
||||
};
|
||||
}
|
||||
|
||||
public async getPersonaPaginated(personaId: string, page: number = 1): Promise<PersonaResponse> {
|
||||
await this.keepAlive(false);
|
||||
|
||||
const url = `${SunoApi.BASE_URL}/api/persona/get-persona-paginated/${personaId}/?page=${page}`;
|
||||
|
||||
logger.info(`Fetching persona data: ${url}`);
|
||||
|
||||
const response = await this.client.get(url, {
|
||||
timeout: 10000 // 10 seconds timeout
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error('Error response: ' + response.statusText);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const sunoApi = async (cookie?: string) => {
|
||||
|
Loading…
Reference in New Issue
Block a user