From 3ddf698e68049b43a34328a9c8e196b4a4f7b352 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 13 Nov 2025 20:55:16 -0500 Subject: [PATCH 1/5] add new route for /artist/socials/scrape --- routes.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/routes.ts b/routes.ts index c2417c8..a634cf7 100644 --- a/routes.ts +++ b/routes.ts @@ -7,7 +7,10 @@ import { generateImageHandler } from "./controllers/ImageGenerationController"; import { getCommentsHandler } from "./controllers/CommentsController"; import { getArtistSegmentsHandler } from "./controllers/ArtistSegmentsController"; import { getSegmentFansHandler } from "./controllers/SegmentFansController"; -import { getArtistSocialsHandler } from "./controllers/ArtistSocialsController"; +import { + getArtistSocialsHandler, + postArtistSocialsScrapeHandler, +} from "./controllers/ArtistSocialsController"; import { getSocialPostsHandler } from "./controllers/SocialPostsController"; import { getPostCommentsHandler } from "./controllers/PostCommentsController"; import { @@ -94,7 +97,8 @@ routes.get("/comments", getCommentsHandler as any); routes.get("/artist/segments", getArtistSegmentsHandler as any); routes.get("/segment/fans", getSegmentFansHandler as any); -routes.get("/artist/socials", getArtistSocialsHandler as any); +routes.get("/artist/socials", getArtistSocialsHandler); +routes.post("/artist/socials/scrape", postArtistSocialsScrapeHandler); routes.get("/social/posts", getSocialPostsHandler as any); routes.post("/social/scrape", postSocialScrapeHandler as any); routes.get("/post/comments", getPostCommentsHandler as any); From 377ebef7074a8d63bc753a476417bd990b656897 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 13 Nov 2025 21:06:06 -0500 Subject: [PATCH 2/5] API - /api/artist/socials/scrape --- .../getArtistSocialsHandler.ts} | 22 +++--- controllers/ArtistSocialsController/index.ts | 2 + .../postArtistSocialsScrapeHandler.ts | 74 +++++++++++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) rename controllers/{ArtistSocialsController.ts => ArtistSocialsController/getArtistSocialsHandler.ts} (66%) create mode 100644 controllers/ArtistSocialsController/index.ts create mode 100644 controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts diff --git a/controllers/ArtistSocialsController.ts b/controllers/ArtistSocialsController/getArtistSocialsHandler.ts similarity index 66% rename from controllers/ArtistSocialsController.ts rename to controllers/ArtistSocialsController/getArtistSocialsHandler.ts index bc6a98a..eba3d40 100644 --- a/controllers/ArtistSocialsController.ts +++ b/controllers/ArtistSocialsController/getArtistSocialsHandler.ts @@ -1,18 +1,16 @@ -import { Request, Response } from "express"; -import { getArtistSocials } from "../lib/supabase/getArtistSocials"; +import type { RequestHandler } from "express"; +import { getArtistSocials } from "../../lib/supabase/getArtistSocials"; /** - * Handler for GET /api/artist/socials + * Handler for GET /artist/socials * Retrieves all social media profiles associated with an artist account */ -export const getArtistSocialsHandler = async (req: Request, res: Response) => { +export const getArtistSocialsHandler: RequestHandler = async (req, res) => { try { - // Get query parameters const { artist_account_id, page, limit } = req.query; - // Validate required parameters if (!artist_account_id || typeof artist_account_id !== "string") { - return res.status(400).json({ + res.status(400).json({ status: "error", message: "Missing required parameter: artist_account_id", socials: [], @@ -23,21 +21,22 @@ export const getArtistSocialsHandler = async (req: Request, res: Response) => { total_pages: 0, }, }); + return; } - // Call the database function with parameters const result = await getArtistSocials({ artist_account_id, page: typeof page === "string" ? parseInt(page, 10) : undefined, limit: typeof limit === "string" ? parseInt(limit, 10) : undefined, }); - // Return the response - return res.status(result.status === "success" ? 200 : 500).json(result); + const statusCode = result.status === "success" ? 200 : 500; + res.status(statusCode).json(result); + return; } catch (error) { console.error("[ERROR] getArtistSocialsHandler error:", error); - return res.status(500).json({ + res.status(500).json({ status: "error", message: error instanceof Error ? error.message : "An unknown error occurred", @@ -49,5 +48,6 @@ export const getArtistSocialsHandler = async (req: Request, res: Response) => { total_pages: 0, }, }); + return; } }; diff --git a/controllers/ArtistSocialsController/index.ts b/controllers/ArtistSocialsController/index.ts new file mode 100644 index 0000000..a1da8b3 --- /dev/null +++ b/controllers/ArtistSocialsController/index.ts @@ -0,0 +1,2 @@ +export { getArtistSocialsHandler } from "./getArtistSocialsHandler"; +export { postArtistSocialsScrapeHandler } from "./postArtistSocialsScrapeHandler"; diff --git a/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts new file mode 100644 index 0000000..1893d64 --- /dev/null +++ b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts @@ -0,0 +1,74 @@ +import type { RequestHandler } from "express"; +import { getAccountSocials } from "../../lib/supabase/getAccountSocials"; +import { scrapeProfileUrl } from "../../lib/apify/scrapeProfileUrl"; + +type ArtistSocialScrapeResult = { + runId: string | null; + datasetId: string | null; + error: string | null; +}; + +export const postArtistSocialsScrapeHandler: RequestHandler = async ( + req, + res +) => { + try { + const { artist_account_id } = req.body ?? {}; + + if (!artist_account_id || typeof artist_account_id !== "string") { + res.status(400).json({ + status: "error", + message: "artist_account_id body parameter is required", + }); + return; + } + + const { status, socials } = await getAccountSocials(artist_account_id); + + if (status === "error") { + res.status(500).json({ + status: "error", + message: "Failed to fetch artist socials", + }); + return; + } + + if (!socials.length) { + res.json([]); + return; + } + + const results: ArtistSocialScrapeResult[] = await Promise.all( + socials.map(async (social) => { + const scrapeResult = await scrapeProfileUrl( + social.profile_url ?? null, + social.username ?? "" + ); + + if (!scrapeResult) { + return { + runId: null, + datasetId: null, + error: "Unsupported or missing profile URL", + }; + } + + return { + runId: scrapeResult.runId, + datasetId: scrapeResult.datasetId, + error: scrapeResult.error, + }; + }) + ); + + res.json(results); + return; + } catch (error) { + console.error("[ERROR] postArtistSocialsScrapeHandler error:", error); + res.status(500).json({ + status: "error", + message: "Internal server error", + }); + return; + } +}; From 94f1eedaa3b5834191e2d2248c9a862abefb26dd Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 13 Nov 2025 21:13:13 -0500 Subject: [PATCH 3/5] DRY ArtistSocialScrapeResult type --- .../postArtistSocialsScrapeHandler.ts | 13 +++++-------- lib/apify/scrapeProfileUrl.ts | 7 +++++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts index 1893d64..94bdb47 100644 --- a/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts +++ b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts @@ -1,12 +1,9 @@ import type { RequestHandler } from "express"; import { getAccountSocials } from "../../lib/supabase/getAccountSocials"; -import { scrapeProfileUrl } from "../../lib/apify/scrapeProfileUrl"; - -type ArtistSocialScrapeResult = { - runId: string | null; - datasetId: string | null; - error: string | null; -}; +import { + ProfileScrapeResult, + scrapeProfileUrl, +} from "../../lib/apify/scrapeProfileUrl"; export const postArtistSocialsScrapeHandler: RequestHandler = async ( req, @@ -38,7 +35,7 @@ export const postArtistSocialsScrapeHandler: RequestHandler = async ( return; } - const results: ArtistSocialScrapeResult[] = await Promise.all( + const results: ProfileScrapeResult[] = await Promise.all( socials.map(async (social) => { const scrapeResult = await scrapeProfileUrl( social.profile_url ?? null, diff --git a/lib/apify/scrapeProfileUrl.ts b/lib/apify/scrapeProfileUrl.ts index 7df93fe..b786dd6 100644 --- a/lib/apify/scrapeProfileUrl.ts +++ b/lib/apify/scrapeProfileUrl.ts @@ -16,9 +16,12 @@ export interface ProfileScrapeResult { runId: string | null; datasetId: string | null; error: string | null; - supported: boolean; } +export type ScrapeProfileResult = ProfileScrapeResult & { + supported: boolean; +}; + const PLATFORM_SCRAPERS: Array<{ match: (url: string) => boolean; scraper: ScrapeRunner; @@ -54,7 +57,7 @@ const PLATFORM_SCRAPERS: Array<{ export const scrapeProfileUrl = async ( profileUrl: string | null | undefined, username: string -): Promise => { +): Promise => { if (!profileUrl) { return null; } From b67215cf8639ec58aaf460e06aac5a1d603ab964 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 13 Nov 2025 21:23:05 -0500 Subject: [PATCH 4/5] filter unsupported platforms from the results. --- .../postArtistSocialsScrapeHandler.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts index 94bdb47..7733416 100644 --- a/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts +++ b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts @@ -2,6 +2,7 @@ import type { RequestHandler } from "express"; import { getAccountSocials } from "../../lib/supabase/getAccountSocials"; import { ProfileScrapeResult, + ScrapeProfileResult, scrapeProfileUrl, } from "../../lib/apify/scrapeProfileUrl"; @@ -35,29 +36,25 @@ export const postArtistSocialsScrapeHandler: RequestHandler = async ( return; } - const results: ProfileScrapeResult[] = await Promise.all( + const resultsWithNulls = await Promise.all( socials.map(async (social) => { const scrapeResult = await scrapeProfileUrl( social.profile_url ?? null, social.username ?? "" ); - if (!scrapeResult) { - return { - runId: null, - datasetId: null, - error: "Unsupported or missing profile URL", - }; - } - - return { - runId: scrapeResult.runId, - datasetId: scrapeResult.datasetId, - error: scrapeResult.error, - }; + return scrapeResult; }) ); + const results: ProfileScrapeResult[] = resultsWithNulls + .filter((result): result is ScrapeProfileResult => result !== null) + .map(({ runId, datasetId, error }) => ({ + runId, + datasetId, + error, + })); + res.json(results); return; } catch (error) { From 22a97a7ea8622cf160f24e2c2519b80c6b5805e7 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Thu, 13 Nov 2025 21:24:54 -0500 Subject: [PATCH 5/5] SRP - scrapeProfileUrlBatch --- .../postArtistSocialsScrapeHandler.ts | 28 ++++--------------- lib/apify/scrapeProfileUrlBatch.ts | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 22 deletions(-) create mode 100644 lib/apify/scrapeProfileUrlBatch.ts diff --git a/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts index 7733416..f976913 100644 --- a/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts +++ b/controllers/ArtistSocialsController/postArtistSocialsScrapeHandler.ts @@ -1,10 +1,6 @@ import type { RequestHandler } from "express"; import { getAccountSocials } from "../../lib/supabase/getAccountSocials"; -import { - ProfileScrapeResult, - ScrapeProfileResult, - scrapeProfileUrl, -} from "../../lib/apify/scrapeProfileUrl"; +import { scrapeProfileUrlBatch } from "../../lib/apify/scrapeProfileUrlBatch"; export const postArtistSocialsScrapeHandler: RequestHandler = async ( req, @@ -36,25 +32,13 @@ export const postArtistSocialsScrapeHandler: RequestHandler = async ( return; } - const resultsWithNulls = await Promise.all( - socials.map(async (social) => { - const scrapeResult = await scrapeProfileUrl( - social.profile_url ?? null, - social.username ?? "" - ); - - return scrapeResult; - }) + const results = await scrapeProfileUrlBatch( + socials.map((social) => ({ + profileUrl: social.profile_url, + username: social.username, + })) ); - const results: ProfileScrapeResult[] = resultsWithNulls - .filter((result): result is ScrapeProfileResult => result !== null) - .map(({ runId, datasetId, error }) => ({ - runId, - datasetId, - error, - })); - res.json(results); return; } catch (error) { diff --git a/lib/apify/scrapeProfileUrlBatch.ts b/lib/apify/scrapeProfileUrlBatch.ts new file mode 100644 index 0000000..2ccbf4a --- /dev/null +++ b/lib/apify/scrapeProfileUrlBatch.ts @@ -0,0 +1,28 @@ +import { + ProfileScrapeResult, + ScrapeProfileResult, + scrapeProfileUrl, +} from "./scrapeProfileUrl"; + +type ScrapeProfileUrlBatchInput = { + profileUrl: string | null | undefined; + username: string | null | undefined; +}; + +export const scrapeProfileUrlBatch = async ( + inputs: ScrapeProfileUrlBatchInput[] +): Promise => { + const results = await Promise.all( + inputs.map(({ profileUrl, username }) => + scrapeProfileUrl(profileUrl ?? null, username ?? "") + ) + ); + + return results + .filter((result): result is ScrapeProfileResult => result !== null) + .map(({ runId, datasetId, error }) => ({ + runId, + datasetId, + error, + })); +};