main
Some checks failed
Backend Deploy 🚀 / build-and-deploy (push) Has been cancelled

This commit is contained in:
Harun CAN
2026-03-14 14:01:01 +03:00
parent 0fefbe6859
commit dee6e29cfd
37 changed files with 1925 additions and 157 deletions

56
debug.log Normal file
View File

@@ -0,0 +1,56 @@
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
[ERROR] DeepResearchService failed: {"error":{"code":404,"message":"models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
ApiError: {"error":{"code":404,"message":"models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
at throwErrorIfNotOK (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:12045:30)
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
at async /Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:11765:13
at async Models.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:13095:24)
at async GeminiService.generateText (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:135:24)
at async GeminiService.generateJSON (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:241:22)
at async DeepResearchService.research (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/services/deep-research.service.ts:101:30)
at async ContentGenerationService.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.service.ts:99:24)
at async ContentGenerationController.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.controller.ts:38:24)
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash-001
[ERROR] DeepResearchService failed: {"error":{"code":404,"message":"models/gemini-1.5-flash-001 is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
ApiError: {"error":{"code":404,"message":"models/gemini-1.5-flash-001 is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
at throwErrorIfNotOK (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:12045:30)
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
at async /Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:11765:13
at async Models.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:13095:24)
at async GeminiService.generateText (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:135:24)
at async GeminiService.generateJSON (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:241:22)
at async DeepResearchService.research (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/services/deep-research.service.ts:101:30)
at async ContentGenerationService.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.service.ts:99:24)
at async ContentGenerationController.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.controller.ts:38:24)
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash-001
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-lite-001
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-lite-001
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Text generation completed
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
[DEBUG] GeminiService: Text generation completed

View File

261
generation_debug.log Normal file
View File

@@ -0,0 +1,261 @@
[2026-02-14T18:12:11.328Z] REQUEST RECEIVED: {"topic":"Sustainable Living","description":"Tips for eco-friendly living","keywords":["eco","green"],"niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-14T18:12:22.241Z] Generating for twitter with topic: Sustainable Living
[2026-02-14T18:12:23.557Z] Generated 315 chars for twitter
[2026-02-14T18:12:23.558Z] Content object created for twitter
[2026-02-15T17:23:19.053Z] REQUEST RECEIVED: {"topic":"Sustainable Living","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:23:31.138Z] Generating for twitter with topic: Sustainable Living
[2026-02-15T17:23:32.630Z] Generated 292 chars for twitter
[2026-02-15T17:23:32.630Z] Content object created for twitter
[2026-02-15T17:30:05.129Z] REQUEST RECEIVED: {"topic":"Impact of AI on Modern Web Development","niche":"ai-tech","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:30:14.875Z] Generating for twitter with topic: Impact of AI on Modern Web Development
[2026-02-15T17:30:16.506Z] Generated 429 chars for twitter
[2026-02-15T17:30:16.507Z] Content object created for twitter
[2026-02-15T17:33:10.527Z] REQUEST RECEIVED: {"topic":"Sustainable LivingSustainable Living","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:33:20.381Z] Generating for twitter with topic: Sustainable LivingSustainable Living
[2026-02-15T17:33:21.601Z] Generated 243 chars for twitter
[2026-02-15T17:33:21.601Z] Content object created for twitter
[2026-02-15T17:35:10.370Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:35:22.303Z] Generating for twitter with topic: Future of Coding
[2026-02-15T17:35:23.850Z] Generated 316 chars for twitter
[2026-02-15T17:35:23.850Z] Content object created for twitter
[2026-02-15T17:36:44.677Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"","platforms":["twitter","medium"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:36:56.298Z] Generating for twitter with topic: Future of Coding
[2026-02-15T17:36:58.147Z] Generated 518 chars for twitter
[2026-02-15T17:36:58.147Z] Content object created for twitter
[2026-02-15T17:36:58.147Z] Generating for medium with topic: Future of Coding
[2026-02-15T17:37:06.263Z] Generated 3653 chars for medium
[2026-02-15T17:37:06.263Z] Content object created for medium
[2026-02-15T17:38:28.720Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"ai-tech","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:38:38.744Z] Generating for twitter with topic: Future of Coding
[2026-02-15T17:38:41.101Z] Generated 514 chars for twitter
[2026-02-15T17:38:41.101Z] Content object created for twitter
[2026-02-15T17:40:02.990Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"ai-tech","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:40:13.277Z] Generating for twitter with topic: Future of Coding
[2026-02-15T17:40:14.767Z] Generated 304 chars for twitter
[2026-02-15T17:40:14.767Z] Content object created for twitter
[2026-02-15T17:42:27.534Z] REQUEST RECEIVED: {"topic":"Remote Work Trends","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:42:39.827Z] Generating for twitter with topic: Remote Work Trends
[2026-02-15T17:42:41.128Z] Generated 368 chars for twitter
[2026-02-15T17:42:41.128Z] Content object created for twitter
[2026-02-15T17:43:38.423Z] REQUEST RECEIVED: {"topic":"Remote Work TrendsRemote Work Trends","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:43:49.195Z] Generating for twitter with topic: Remote Work TrendsRemote Work Trends
[2026-02-15T17:43:50.709Z] Generated 327 chars for twitter
[2026-02-15T17:43:50.709Z] Content object created for twitter
[2026-02-15T17:44:37.651Z] REQUEST RECEIVED: {"topic":"Remote Work TrendsRemote Work Trends","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-15T17:44:48.516Z] Generating for twitter with topic: Remote Work TrendsRemote Work Trends
[2026-02-15T17:44:49.846Z] Generated 302 chars for twitter
[2026-02-15T17:44:49.846Z] Content object created for twitter
[2026-02-17T07:28:49.107Z] REQUEST RECEIVED: {"topic":"Artificial Intelligence Trends 2025Artificial Intelligence Trends 2025","niche":"ai-tech","platforms":["twitter","instagram"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T07:29:02.055Z] Generating for twitter with topic: Artificial Intelligence Trends 2025Artificial Intelligence Trends 2025
[2026-02-17T07:29:04.615Z] Generated 558 chars for twitter
[2026-02-17T07:29:04.615Z] Content object created for twitter
[2026-02-17T07:29:04.616Z] Generating for instagram with topic: Artificial Intelligence Trends 2025Artificial Intelligence Trends 2025
[2026-02-17T07:29:09.494Z] Generated 1561 chars for instagram
[2026-02-17T07:29:09.494Z] Content object created for instagram
[2026-02-17T07:33:18.818Z] REQUEST RECEIVED: {"topic":"Artificial Intelligence Trends 2025","niche":"ai-tech","platforms":["twitter","instagram","medium"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T07:33:33.613Z] Generating for twitter with topic: Artificial Intelligence Trends 2025
[2026-02-17T07:33:34.738Z] Generated 297 chars for twitter
[2026-02-17T07:33:34.738Z] Content object created for twitter
[2026-02-17T07:33:34.739Z] Generating for instagram with topic: Artificial Intelligence Trends 2025
[2026-02-17T07:33:41.526Z] Generated 2082 chars for instagram
[2026-02-17T07:33:41.526Z] Content object created for instagram
[2026-02-17T07:33:41.526Z] Generating for medium with topic: Artificial Intelligence Trends 2025
[2026-02-17T07:33:50.321Z] Generated 3083 chars for medium
[2026-02-17T07:33:50.321Z] Content object created for medium
[2026-02-17T07:54:28.812Z] REQUEST RECEIVED: {"topic":"14-year-old Miles Wu folded origami pattern that holds 10k times its own weight","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","14yearold","miles","folded","origami","pattern","that","holds","times"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T07:54:37.876Z] Generating for medium with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
[2026-02-17T07:54:41.553Z] Generated 1728 chars for medium
[2026-02-17T07:54:41.553Z] Content object created for medium
[2026-02-17T07:54:41.554Z] Generating for instagram with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
[2026-02-17T07:54:44.523Z] Generated 1374 chars for instagram
[2026-02-17T07:54:44.524Z] Content object created for instagram
[2026-02-17T07:54:44.524Z] Generating for linkedin with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
[2026-02-17T07:54:47.724Z] Generated 1132 chars for linkedin
[2026-02-17T07:54:47.724Z] Content object created for linkedin
[2026-02-17T07:54:47.725Z] Generating for twitter with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
[2026-02-17T07:54:48.998Z] Generated 333 chars for twitter
[2026-02-17T07:54:48.998Z] Content object created for twitter
[2026-02-17T08:10:50.574Z] REQUEST RECEIVED: {"topic":"AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","devlerine","türk","usulü","meydan","okuma","cosmos","sahneye","çıktı"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T08:10:58.281Z] Generating for medium with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
[2026-02-17T08:11:03.440Z] Generated 1755 chars for medium
[2026-02-17T08:11:03.441Z] Content object created for medium
[2026-02-17T08:11:03.441Z] Generating for instagram with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
[2026-02-17T08:11:08.653Z] Generated 1562 chars for instagram
[2026-02-17T08:11:08.654Z] Content object created for instagram
[2026-02-17T08:11:08.654Z] Generating for linkedin with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
[2026-02-17T08:11:11.604Z] Generated 1015 chars for linkedin
[2026-02-17T08:11:11.604Z] Content object created for linkedin
[2026-02-17T08:11:11.605Z] Generating for twitter with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
[2026-02-17T08:11:12.676Z] Generated 258 chars for twitter
[2026-02-17T08:11:12.676Z] Content object created for twitter
[2026-02-17T08:35:00.457Z] REQUEST RECEIVED: {"topic":"HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","hddlerde","etkisi","kapasitesinin","tükendiğini","açıkladı","donanımhaber"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T08:35:06.245Z] Generating for medium with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
[2026-02-17T08:35:11.472Z] Generated 2160 chars for medium
[2026-02-17T08:35:11.473Z] Content object created for medium
[2026-02-17T08:35:11.475Z] Generating for instagram with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
[2026-02-17T08:35:14.834Z] Generated 1210 chars for instagram
[2026-02-17T08:35:14.834Z] Content object created for instagram
[2026-02-17T08:35:14.835Z] Generating for linkedin with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
[2026-02-17T08:35:17.610Z] Generated 1241 chars for linkedin
[2026-02-17T08:35:17.610Z] Content object created for linkedin
[2026-02-17T08:35:17.610Z] Generating for twitter with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
[2026-02-17T08:35:19.131Z] Generated 375 chars for twitter
[2026-02-17T08:35:19.131Z] Content object created for twitter
[2026-02-17T08:55:10.195Z] REQUEST RECEIVED: {"topic":"India's Adani to invest $100 billion in AI data centers over the next decade - CNBC","description":"Trending across 3 source(s): NEWSAPI, NEWSAPI, NEWSAPI","keywords":["ai","indias","adani","invest","billion","data","centers","over","next","aiready"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T08:55:16.172Z] Generating for medium with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
[2026-02-17T08:55:21.052Z] Generated 2379 chars for medium
[2026-02-17T08:55:21.052Z] Content object created for medium
[2026-02-17T08:55:21.054Z] Generating for instagram with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
[2026-02-17T08:55:24.986Z] Generated 1425 chars for instagram
[2026-02-17T08:55:24.987Z] Content object created for instagram
[2026-02-17T08:55:24.987Z] Generating for linkedin with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
[2026-02-17T08:55:27.636Z] Generated 1229 chars for linkedin
[2026-02-17T08:55:27.636Z] Content object created for linkedin
[2026-02-17T08:55:27.637Z] Generating for twitter with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
[2026-02-17T08:55:28.808Z] Generated 370 chars for twitter
[2026-02-17T08:55:28.809Z] Content object created for twitter
[2026-02-17T09:13:15.586Z] REQUEST RECEIVED: {"topic":"This doctor is training AI to do her job. And its a booming business - CNN","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","this","doctor","training","booming","business"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T09:13:24.584Z] Generating for medium with topic: This doctor is training AI to do her job. And its a booming business - CNN
[2026-02-17T09:13:30.177Z] Generated 2831 chars for medium
[2026-02-17T09:13:30.178Z] Content object created for medium
[2026-02-17T09:13:30.179Z] Generating for instagram with topic: This doctor is training AI to do her job. And its a booming business - CNN
[2026-02-17T09:13:35.824Z] Generated 1656 chars for instagram
[2026-02-17T09:13:35.825Z] Content object created for instagram
[2026-02-17T09:13:35.826Z] Generating for linkedin with topic: This doctor is training AI to do her job. And its a booming business - CNN
[2026-02-17T09:13:39.085Z] Generated 1275 chars for linkedin
[2026-02-17T09:13:39.085Z] Content object created for linkedin
[2026-02-17T09:13:39.086Z] Generating for twitter with topic: This doctor is training AI to do her job. And its a booming business - CNN
[2026-02-17T09:13:40.440Z] Generated 326 chars for twitter
[2026-02-17T09:13:40.440Z] Content object created for twitter
[2026-02-17T09:26:31.432Z] REQUEST RECEIVED: {"topic":"Black Shark tabletler Türkiye'de: EVOFONE ile güçlü bir başlangıç - hibya.com","description":"Trending across 1 source(s): NEWSAPI","keywords":["gaming","black","shark","tabletler","türkiyede","evofone","güçlü","başlangıç","hibyacom"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T09:26:36.351Z] Generating for medium with topic: Black Shark tabletler Türkiye'de: EVOFONE ile güçlü bir başlangıç - hibya.com
[2026-02-17T09:26:39.872Z] Generated 1644 chars for medium
[2026-02-17T09:26:39.873Z] Content object created for medium
[2026-02-17T09:26:39.874Z] Generating for instagram with topic: Black Shark tabletler Türkiye'de: EVOFONE ile güçlü bir başlangıç - hibya.com
[2026-02-17T09:26:44.063Z] Generated 1327 chars for instagram
[2026-02-17T09:26:44.063Z] Content object created for instagram
[2026-02-17T09:26:44.064Z] Generating for linkedin with topic: Black Shark tabletler Türkiye'de: EVOFONE ile güçlü bir başlangıç - hibya.com
[2026-02-17T09:26:46.592Z] Generated 762 chars for linkedin
[2026-02-17T09:26:46.593Z] Content object created for linkedin
[2026-02-17T09:26:46.593Z] Generating for twitter with topic: Black Shark tabletler Türkiye'de: EVOFONE ile güçlü bir başlangıç - hibya.com
[2026-02-17T09:26:48.050Z] Generated 377 chars for twitter
[2026-02-17T09:26:48.051Z] Content object created for twitter
[2026-02-17T09:37:56.521Z] REQUEST RECEIVED: {"topic":"12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir","description":"Trending across 1 source(s): NEWSAPI","keywords":["gaming","ramli","fiyat","performans","canavarı","tablet","türkiyede","tamindir"],"niche":"","platforms":["twitter","linkedin","instagram","medium"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T09:38:02.369Z] Generating for twitter with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:38:04.105Z] Generated 314 chars for twitter
[2026-02-17T09:38:04.105Z] Content object created for twitter
[2026-02-17T09:38:04.105Z] Generating for linkedin with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:38:06.793Z] Generated 954 chars for linkedin
[2026-02-17T09:38:06.794Z] Content object created for linkedin
[2026-02-17T09:38:06.794Z] Generating for instagram with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:38:11.834Z] Generated 1562 chars for instagram
[2026-02-17T09:38:11.834Z] Content object created for instagram
[2026-02-17T09:38:11.835Z] Generating for medium with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:38:18.338Z] Generated 2357 chars for medium
[2026-02-17T09:38:18.338Z] Content object created for medium
[2026-02-17T09:41:36.516Z] REQUEST RECEIVED: {"topic":"12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir","description":"Trending across 1 source(s): NEWSAPI","keywords":["gaming","ramli","fiyat","performans","canavarı","tablet","türkiyede","tamindir"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T09:41:42.411Z] Generating for medium with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:41:47.382Z] Generated 2568 chars for medium
[2026-02-17T09:41:47.382Z] Content object created for medium
[2026-02-17T09:41:47.382Z] Generating for instagram with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:41:51.390Z] Generated 1513 chars for instagram
[2026-02-17T09:41:51.390Z] Content object created for instagram
[2026-02-17T09:41:51.391Z] Generating for linkedin with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:41:54.888Z] Generated 1318 chars for linkedin
[2026-02-17T09:41:54.888Z] Content object created for linkedin
[2026-02-17T09:41:54.889Z] Generating for twitter with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:41:56.159Z] Generated 270 chars for twitter
[2026-02-17T09:41:56.159Z] Content object created for twitter
[2026-02-17T09:48:09.972Z] REQUEST RECEIVED: {"topic":"12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir","description":"Trending across 1 source(s): NEWSAPI","keywords":["gaming","ramli","fiyat","performans","canavarı","tablet","türkiyede","tamindir"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-17T09:48:17.529Z] Generating for medium with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:48:23.685Z] Generated 2614 chars for medium
[2026-02-17T09:48:23.686Z] Content object created for medium
[2026-02-17T09:48:23.686Z] Generating for instagram with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:48:27.477Z] Generated 1673 chars for instagram
[2026-02-17T09:48:27.477Z] Content object created for instagram
[2026-02-17T09:48:27.478Z] Generating for linkedin with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:48:30.566Z] Generated 1004 chars for linkedin
[2026-02-17T09:48:30.567Z] Content object created for linkedin
[2026-02-17T09:48:30.567Z] Generating for twitter with topic: 12 GB RAM'li Fiyat Performans Canavarı Tablet Türkiye'de! - Tamindir
[2026-02-17T09:48:31.973Z] Generated 289 chars for twitter
[2026-02-17T09:48:31.973Z] Content object created for twitter
[2026-02-19T21:14:30.212Z] REQUEST RECEIVED: {"topic":"Gemini 3.1 Pro","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","gemini"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-19T21:14:37.246Z] Generating for medium with topic: Gemini 3.1 Pro
[2026-02-19T21:14:42.987Z] Generated 2670 chars for medium
[2026-02-19T21:14:42.987Z] Content object created for medium
[2026-02-19T21:14:42.988Z] Generating for instagram with topic: Gemini 3.1 Pro
[2026-02-19T21:14:48.145Z] Generated 1478 chars for instagram
[2026-02-19T21:14:48.145Z] Content object created for instagram
[2026-02-19T21:14:48.146Z] Generating for linkedin with topic: Gemini 3.1 Pro
[2026-02-19T21:14:50.694Z] Generated 1177 chars for linkedin
[2026-02-19T21:14:50.694Z] Content object created for linkedin
[2026-02-19T21:14:50.695Z] Generating for twitter with topic: Gemini 3.1 Pro
[2026-02-19T21:14:51.869Z] Generated 295 chars for twitter
[2026-02-19T21:14:51.870Z] Content object created for twitter
[2026-02-19T21:37:43.593Z] REQUEST RECEIVED: {"topic":"Gemini 3.1 Pro","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","gemini"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-19T21:37:51.572Z] Generating for medium with topic: Gemini 3.1 Pro
[2026-02-19T21:37:57.772Z] Generated 3217 chars for medium
[2026-02-19T21:37:57.772Z] Content object created for medium
[2026-02-19T21:37:57.773Z] Generating for instagram with topic: Gemini 3.1 Pro
[2026-02-19T21:38:03.610Z] Generated 2014 chars for instagram
[2026-02-19T21:38:03.610Z] Content object created for instagram
[2026-02-19T21:38:03.610Z] Generating for linkedin with topic: Gemini 3.1 Pro
[2026-02-19T21:38:06.728Z] Generated 1493 chars for linkedin
[2026-02-19T21:38:06.728Z] Content object created for linkedin
[2026-02-19T21:38:06.728Z] Generating for twitter with topic: Gemini 3.1 Pro
[2026-02-19T21:38:08.105Z] Generated 312 chars for twitter
[2026-02-19T21:38:08.106Z] Content object created for twitter
[2026-02-19T21:45:27.673Z] REQUEST RECEIVED: {"topic":"Gemini 3.1 Pro","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","gemini"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-19T21:45:36.370Z] Generating for medium with topic: Gemini 3.1 Pro
[2026-02-19T21:45:41.866Z] Generated 2569 chars for medium
[2026-02-19T21:45:41.866Z] Content object created for medium
[2026-02-19T21:45:41.867Z] Generating for instagram with topic: Gemini 3.1 Pro
[2026-02-19T21:45:46.953Z] Generated 1860 chars for instagram
[2026-02-19T21:45:46.954Z] Content object created for instagram
[2026-02-19T21:45:46.954Z] Generating for linkedin with topic: Gemini 3.1 Pro
[2026-02-19T21:45:50.494Z] Generated 1424 chars for linkedin
[2026-02-19T21:45:50.494Z] Content object created for linkedin
[2026-02-19T21:45:50.494Z] Generating for twitter with topic: Gemini 3.1 Pro
[2026-02-19T21:45:53.019Z] Generated 675 chars for twitter
[2026-02-19T21:45:53.019Z] Content object created for twitter
[2026-02-19T21:55:27.875Z] REQUEST RECEIVED: {"topic":"Gemini 3.1 Pro","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","gemini"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-02-19T21:55:37.975Z] Generating for medium with topic: Gemini 3.1 Pro
[2026-02-19T21:55:43.266Z] Generated 2332 chars for medium
[2026-02-19T21:55:43.267Z] Content object created for medium
[2026-02-19T21:55:43.267Z] Generating for instagram with topic: Gemini 3.1 Pro
[2026-02-19T21:55:47.252Z] Generated 1517 chars for instagram
[2026-02-19T21:55:47.252Z] Content object created for instagram
[2026-02-19T21:55:47.252Z] Generating for linkedin with topic: Gemini 3.1 Pro
[2026-02-19T21:55:50.496Z] Generated 1192 chars for linkedin
[2026-02-19T21:55:50.496Z] Content object created for linkedin
[2026-02-19T21:55:50.496Z] Generating for twitter with topic: Gemini 3.1 Pro
[2026-02-19T21:55:52.192Z] Generated 391 chars for twitter
[2026-02-19T21:55:52.192Z] Content object created for twitter
[2026-03-07T09:42:50.669Z] REQUEST RECEIVED: {"topic":"Eşref Rüyanın yeni fragmanı sadece Yandex AIda! Yeni özellikler ile dizi dünyasını keşfedin… - Hürriyet","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","eşref","rüyanın","yeni","fragmanı","sadece","yandex","aida"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
[2026-03-07T09:42:57.247Z] Generating for medium with topic: Eşref Rüyanın yeni fragmanı sadece Yandex AIda! Yeni özellikler ile dizi dünyasını keşfedin… - Hürriyet
[2026-03-07T09:43:01.551Z] Generated 1604 chars for medium
[2026-03-07T09:43:01.552Z] Content object created for medium
[2026-03-07T09:43:01.552Z] Generating for instagram with topic: Eşref Rüyanın yeni fragmanı sadece Yandex AIda! Yeni özellikler ile dizi dünyasını keşfedin… - Hürriyet
[2026-03-07T09:43:05.065Z] Generated 1438 chars for instagram
[2026-03-07T09:43:05.065Z] Content object created for instagram
[2026-03-07T09:43:05.066Z] Generating for linkedin with topic: Eşref Rüyanın yeni fragmanı sadece Yandex AIda! Yeni özellikler ile dizi dünyasını keşfedin… - Hürriyet
[2026-03-07T09:43:07.268Z] Generated 977 chars for linkedin
[2026-03-07T09:43:07.268Z] Content object created for linkedin
[2026-03-07T09:43:07.269Z] Generating for twitter with topic: Eşref Rüyanın yeni fragmanı sadece Yandex AIda! Yeni özellikler ile dizi dünyasını keşfedin… - Hürriyet
[2026-03-07T09:43:08.477Z] Generated 314 chars for twitter
[2026-03-07T09:43:08.477Z] Content object created for twitter
[2026-03-07T09:47:14.271Z] REQUEST RECEIVED: {"topic":"Testing generation","platforms":["twitter"]}
[2026-03-07T09:47:26.827Z] Generating for twitter with topic: Testing generation
[2026-03-07T09:47:28.074Z] Generated 456 chars for twitter
[2026-03-07T09:47:28.074Z] Content object created for twitter
[2026-03-07T09:47:36.616Z] REQUEST RECEIVED: {"topic":"Testing","platforms":["twitter"]}
[2026-03-07T09:47:49.418Z] Generating for twitter with topic: Testing
[2026-03-07T09:47:51.311Z] Generated 478 chars for twitter
[2026-03-07T09:47:51.311Z] Content object created for twitter
[2026-03-07T09:51:00.334Z] REQUEST RECEIVED: {"topic":"Next.js 14 Server Actions","platforms":["twitter","medium"]}
[2026-03-07T09:51:08.732Z] Generating for twitter with topic: Next.js 14 Server Actions
[2026-03-07T09:51:10.376Z] Generated 361 chars for twitter
[2026-03-07T09:51:10.376Z] Content object created for twitter
[2026-03-07T09:51:10.376Z] Generating for medium with topic: Next.js 14 Server Actions
[2026-03-07T09:51:16.327Z] Generated 2734 chars for medium
[2026-03-07T09:51:16.328Z] Content object created for medium

35
list_models.js Normal file
View File

@@ -0,0 +1,35 @@
const { GoogleGenAI } = require('@google/genai');
async function listModels() {
const apiKey = 'AIzaSyBnrLw5W4NidP4w-70x59fcPolz0izMVfU'; // Hardcoded from .env
console.log('Initializing with key:', apiKey.substring(0, 10) + '...');
try {
const client = new GoogleGenAI({ apiKey });
console.log('Client initialized.');
// Try to list models. In new SDK it might be different.
// Checking documentation or guessing.
// Based on stack trace: client.models.list() is likely.
const response = await client.models.list();
console.log('Models response:', response);
if (response && response.models) {
response.models.forEach(m => console.log(m.name));
} else {
// It might be an async iterable
for await (const model of response) {
console.log(model.name);
}
}
} catch (error) {
console.error('Error listing models:', error);
if (error.response) {
console.error('Response data:', error.response.data);
}
}
}
listModels();

707
package-lock.json generated
View File

@@ -30,6 +30,7 @@
"cache-manager-redis-yet": "^5.1.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"g-i-s": "^2.1.7",
"google-trends-api": "^4.9.2",
"helmet": "^8.1.0",
"ioredis": "^5.9.0",
@@ -5655,7 +5656,6 @@
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -5829,11 +5829,28 @@
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"dev": true
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/atomic-sleep": {
"version": "1.0.0",
@@ -5843,6 +5860,21 @@
"node": ">=8.0.0"
}
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
"license": "Apache-2.0",
"engines": {
"node": "*"
}
},
"node_modules/aws4": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
"license": "MIT"
},
"node_modules/babel-jest": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz",
@@ -5988,6 +6020,15 @@
"node": ">= 18"
}
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"license": "BSD-3-Clause",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/bignumber.js": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
@@ -6041,6 +6082,12 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/bowser": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz",
@@ -6399,6 +6446,12 @@
}
]
},
"node_modules/caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
"license": "Apache-2.0"
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -6437,6 +6490,48 @@
"node": ">=16"
}
},
"node_modules/cheerio": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
"integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
"license": "MIT",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"encoding-sniffer": "^0.2.1",
"htmlparser2": "^10.1.0",
"parse5": "^7.3.0",
"parse5-htmlparser2-tree-adapter": "^7.1.0",
"parse5-parser-stream": "^7.1.2",
"undici": "^7.19.0",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
"node": ">=20.18.1"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -6646,7 +6741,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -6836,6 +6930,46 @@
"node": ">= 8"
}
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@@ -6914,7 +7048,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@@ -6972,6 +7105,61 @@
"node": ">=0.3.1"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
@@ -7015,6 +7203,16 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"license": "MIT",
"dependencies": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -7059,6 +7257,31 @@
"node": ">= 0.8"
}
},
"node_modules/encoding-sniffer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.3",
"whatwg-encoding": "^3.1.1"
},
"funding": {
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
}
},
"node_modules/encoding-sniffer/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -7147,6 +7370,18 @@
"node": ">=10.13.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
@@ -7559,6 +7794,15 @@
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
"engines": [
"node >=0.6.0"
],
"license": "MIT"
},
"node_modules/fast-copy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz",
@@ -7568,8 +7812,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-diff": {
"version": "1.3.0",
@@ -7580,8 +7823,7 @@
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
@@ -7794,6 +8036,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
"license": "Apache-2.0",
"engines": {
"node": "*"
}
},
"node_modules/fork-ts-checker-webpack-plugin": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz",
@@ -7949,6 +8200,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/g-i-s": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/g-i-s/-/g-i-s-2.1.7.tgz",
"integrity": "sha512-tbCOBNwkt6ITGxeCPCWjil9QLTGiQ8jm+zElL1pTzqzmYf8i7asy5/on9nGvqTvG3EJhpr15Vl96pMHNi5zNiw==",
"license": "MIT",
"dependencies": {
"cheerio": "^1.0.0-rc.12",
"lodash.flatten": "^4.4.0",
"request": "^2.88.2"
}
},
"node_modules/gaxios": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz",
@@ -8057,6 +8319,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0"
}
},
"node_modules/glob": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
@@ -8209,6 +8480,29 @@
"node": ">=0.10.0"
}
},
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
"license": "ISC",
"engines": {
"node": ">=4"
}
},
"node_modules/har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"deprecated": "this library is no longer supported",
"license": "MIT",
"dependencies": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -8290,6 +8584,37 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"entities": "^7.0.1"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -8309,6 +8634,21 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
},
"engines": {
"node": ">=0.8",
"npm": ">=1.3.7"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
@@ -8551,6 +8891,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"license": "MIT"
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@@ -8568,6 +8914,12 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
"license": "MIT"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@@ -9386,6 +9738,12 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"license": "MIT"
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -9418,11 +9776,16 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
"node_modules/json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"license": "(AFL-2.1 OR BSD-3-Clause)"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@@ -9430,6 +9793,12 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC"
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -9481,6 +9850,21 @@
"npm": ">=6"
}
},
"node_modules/jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"license": "MIT",
"dependencies": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.4.0",
"verror": "1.10.0"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
@@ -9602,6 +9986,12 @@
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"node_modules/lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -10236,6 +10626,27 @@
"node": ">=8"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"license": "Apache-2.0",
"engines": {
"node": "*"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -10420,6 +10831,55 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-parser-stream": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
"license": "MIT",
"dependencies": {
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -10536,6 +10996,12 @@
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -10841,6 +11307,18 @@
"node": ">= 0.10"
}
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"funding": {
"url": "https://github.com/sponsors/lupomontero"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -10855,7 +11333,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -11003,6 +11480,82 @@
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
},
"node_modules/request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
"license": "Apache-2.0",
"dependencies": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/request/node_modules/form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/request/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/request/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/request/node_modules/qs": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz",
"integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.6"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -11536,6 +12089,31 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"node_modules/sshpk": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
"license": "MIT",
"dependencies": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
"bin": {
"sshpk-conv": "bin/sshpk-conv",
"sshpk-sign": "bin/sshpk-sign",
"sshpk-verify": "bin/sshpk-verify"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
@@ -12070,6 +12648,19 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/ts-api-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
@@ -12252,6 +12843,24 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"license": "Unlicense"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -12374,6 +12983,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -12464,7 +13082,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
@@ -12482,6 +13099,16 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"license": "MIT",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -12518,6 +13145,26 @@
"node": ">= 0.8"
}
},
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"engines": [
"node >=0.6.0"
],
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"node_modules/verror/node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"license": "MIT"
},
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
@@ -12746,6 +13393,40 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -40,6 +40,7 @@
"cache-manager-redis-yet": "^5.1.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"g-i-s": "^2.1.7",
"google-trends-api": "^4.9.2",
"helmet": "^8.1.0",
"ioredis": "^5.9.0",

View File

@@ -1,12 +1,14 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { Public } from './common/decorators';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) { }
@Public()
@Get()
getHello(): string {
return this.appService.getHello();
getHello(): any {
return { status: 'running', message: this.appService.getHello() };
}
}

View File

@@ -109,7 +109,7 @@ import {
useFactory: (configService: ConfigService) => ({
fallbackLanguage: configService.get('i18n.fallbackLanguage', 'en'),
loaderOptions: {
path: path.join(__dirname, '/i18n/'),
path: path.join(__dirname, '../i18n/'),
watch: configService.get('app.isDevelopment', true),
},
}),

View File

@@ -9,56 +9,82 @@ import {
Body,
Param,
Query,
Logger,
} from '@nestjs/common';
import { ContentGenerationService } from './content-generation.service';
import type { ContentGenerationRequest } from './content-generation.service';
import type { Platform } from './services/platform-generator.service';
import { WRITING_STYLES, CTA_TYPES } from './services/platform-generator.service';
import type { VariationType } from './services/variation.service';
import type { BrandVoice } from './services/brand-voice.service';
import { Public, CurrentUser } from '../../common/decorators';
import type { User } from '@prisma/client';
@Controller('content-generation')
export class ContentGenerationController {
private readonly logger = new Logger(ContentGenerationController.name);
constructor(private readonly service: ContentGenerationService) { }
// ========== FULL GENERATION ==========
@Public()
@Post('generate')
async generateContent(
@Body() body: ContentGenerationRequest,
@CurrentUser() user: any,
) {
console.log('[ContentGenerationController] Received generation request:', JSON.stringify(body));
console.log('[ContentGenerationController] User context:', user?.id || 'NO_USER');
if (!user) {
console.warn('[ContentGenerationController] No user found in context, proceeding without save or with mock user');
}
this.logger.log(`[DEBUG] Received generate request from user ${user?.id}: ${JSON.stringify(body)}`);
const bundle = await this.service.generateContent(body);
console.log('[ContentGenerationController] Bundle generated, platforms count:', bundle.platforms.length);
let masterContentId = 'not-saved';
if (user?.id) {
const saved = await this.service.saveGeneratedBundle(user.id, bundle);
masterContentId = saved.masterContentId;
console.log('[ContentGenerationController] Bundle saved, masterContentId:', masterContentId);
}
// Always save content — service handles anonymous users automatically
const saved = await this.service.saveGeneratedBundle(user?.id || null, bundle);
return {
...bundle,
masterContentId,
masterContentId: saved.masterContentId,
};
}
// ========== WRITING STYLES & CTA ==========
@Public()
@Get('writing-styles')
getWritingStyles() {
return WRITING_STYLES;
}
@Public()
@Get('cta-types')
getCtaTypes() {
return CTA_TYPES;
}
// ========== NEURO REGENERATION ==========
@Public()
@Post('regenerate-neuro')
async regenerateForNeuro(
@Body() body: {
content: string;
platform: string;
currentScore: number;
improvements: string[];
},
@CurrentUser() user: any,
) {
this.logger.log(`[DEBUG] Neuro regeneration request for platform: ${body.platform}, current score: ${body.currentScore}`);
return this.service.regenerateForNeuro(body);
}
// ========== NICHES ==========
@Get('niches')
getNiches() {
return this.service.getNiches();
this.logger.log('[DEBUG] Fetching all niches');
const niches = this.service.getNiches();
this.logger.log(`[DEBUG] Found ${niches.length} niches`);
return niches;
}
@Get('niches/:id')

View File

@@ -13,6 +13,7 @@ import { VariationService, VariationSet, VariationOptions } from './services/var
import { SeoService, FullSeoAnalysis as SeoDTO } from '../seo/seo.service';
import { NeuroMarketingService } from '../neuro-marketing/neuro-marketing.service';
import { StorageService } from '../visual-generation/services/storage.service';
import { VisualGenerationService } from '../visual-generation/visual-generation.service';
import { ContentType as PrismaContentType, ContentStatus as PrismaContentStatus, MasterContentType as PrismaMasterContentType } from '@prisma/client';
@@ -24,11 +25,15 @@ export interface ContentGenerationRequest {
includeHashtags?: boolean;
brandVoiceId?: string;
variationCount?: number;
writingStyle?: string;
ctaType?: string;
}
export interface SeoAnalysisResult {
score: number;
keywords: string[];
questions: string[];
longTail: { keyword: string; estimatedVolume: number; competitionLevel: string }[];
suggestions: string[];
meta: { title: string; description: string; };
}
@@ -56,6 +61,9 @@ export interface GeneratedContentBundle {
export class ContentGenerationService {
private readonly logger = new Logger(ContentGenerationService.name);
// Visual platforms that should get auto-generated images
private readonly VISUAL_PLATFORMS: Platform[] = ['instagram', 'twitter', 'medium'];
constructor(
private readonly prisma: PrismaService,
private readonly nicheService: NicheService,
@@ -67,6 +75,7 @@ export class ContentGenerationService {
private readonly seoService: SeoService,
private readonly neuroService: NeuroMarketingService,
private readonly storageService: StorageService,
private readonly visualService: VisualGenerationService,
) { }
@@ -84,7 +93,10 @@ export class ContentGenerationService {
includeHashtags = true,
brandVoiceId,
variationCount = 3,
writingStyle,
ctaType,
} = request;
console.log(`[ContentGenerationService] Starting generation for topic: ${topic}, platforms: ${platforms.join(', ')}`);
// Analyze niche if provided
@@ -108,19 +120,29 @@ export class ContentGenerationService {
const platformContent: GeneratedContent[] = [];
for (const platform of platforms) {
try {
console.log(`[ContentGenerationService] Generating for platform: ${platform}`);
this.logger.log(`Generating for platform: ${platform}`);
// Use AI generation when available
// Sanitize research summary to remove source names/URLs
const sanitizedSummary = this.sanitizeResearchSummary(
research?.summary || `Everything you need to know about ${topic}`
);
// Normalize platform to lowercase for consistency
const normalizedPlatform = platform.toLowerCase();
const aiContent = await this.platformService.generateAIContent(
topic,
research?.summary || `Everything you need to know about ${topic}`,
platform,
sanitizedSummary,
normalizedPlatform as any, // Cast to any/Platform to resolve type mismatch if Platform is strict union
'standard',
'tr',
writingStyle,
ctaType,
);
console.log(`[ContentGenerationService] AI content length for ${platform}: ${aiContent?.length || 0}`);
this.logger.log(`AI content length for ${platform}: ${aiContent?.length || 0}`);
if (!aiContent || aiContent.trim().length === 0) {
console.warn(`[ContentGenerationService] AI Content is empty for ${platform}`);
this.logger.warn(`AI Content is empty for ${platform}`);
}
const config = this.platformService.getPlatformConfig(platform);
@@ -147,15 +169,32 @@ export class ContentGenerationService {
content.hashtags = hashtagSet.hashtags.map((h) => h.hashtag);
}
platformContent.push(content);
console.log(`[ContentGenerationService] Successfully pushed content for ${platform}`);
} catch (error) {
this.logger.error(`Failed to generate content for platform ${platform}: ${error.message}`);
// Continue to next platform
// Generate image for visual platforms
if (this.VISUAL_PLATFORMS.includes(platform)) {
try {
this.logger.log(`Generating image for visual platform: ${platform}`);
const platformKey = platform === 'instagram' ? 'instagram_feed' : platform;
const imagePrompt = `${topic} - Professional, high-quality ${platform} visual. Detailed, engaging, and relevant to the topic: ${topic}.`;
const image = await this.visualService.generateImage({
prompt: imagePrompt,
platform: platformKey,
enhancePrompt: true,
});
content.imageUrl = image.url;
this.logger.log(`Image generated for ${platform}: ${image.url}`);
} catch (imgError) {
this.logger.warn(`Image generation failed for ${platform}, continuing without image`, imgError);
}
}
console.log(`[ContentGenerationService] Generated content for ${platformContent.length} platforms`);
platformContent.push(content);
} catch (error) {
this.logger.error(`Failed to generate for ${platform}`, error);
// Continue to next platform even if one fails
}
}
this.logger.log(`Generated content for ${platformContent.length} platforms`);
// Generate variations for primary platform
const variations: VariationSet[] = [];
@@ -164,30 +203,57 @@ export class ContentGenerationService {
const variationSet = this.variationService.generateVariations(primaryContent, {
count: variationCount,
variationType: 'complete',
language: 'tr',
});
variations.push(variationSet);
}
// SEO Analysis
// SEO Analysis (Full)
let seoResult: SeoAnalysisResult | undefined;
if (platformContent.length > 0) {
try {
const primaryContent = platformContent[0].content;
const seoScore = this.seoService.quickScore(primaryContent, topic);
const fullSeo = await this.seoService.analyzeFull(primaryContent, topic, { language: 'tr' });
const keywordTerms = fullSeo.keywords.related.map(k => k.term);
const questions = fullSeo.keywords.main.questions || [];
const longTail = fullSeo.keywords.longTail.map(lt => ({
keyword: lt.keyword,
estimatedVolume: lt.estimatedVolume,
competitionLevel: lt.competitionLevel,
}));
seoResult = {
score: fullSeo.content.score.overall,
keywords: [fullSeo.keywords.main.term, ...keywordTerms].slice(0, 15),
questions,
longTail: longTail.slice(0, 10),
suggestions: fullSeo.content.score.overall < 70 ? [
'Add more keyword density',
'Include long-tail keywords',
'Add meta description',
'Improve content structure with headings',
] : ['SEO is well optimized', 'Content structure is strong'],
meta: {
title: fullSeo.content.meta.title || `${topic} | Content Hunter`,
description: fullSeo.content.meta.description || research?.summary?.slice(0, 160) || `Learn about ${topic}`,
},
};
} catch (seoError) {
this.logger.warn(`Full SEO analysis failed, falling back to basic`, seoError);
const seoScore = this.seoService.quickScore(platformContent[0].content, topic);
const lsiKeywords = this.seoService.getLSIKeywords(topic, 10);
seoResult = {
score: seoScore,
keywords: lsiKeywords,
suggestions: seoScore < 70 ? [
'Add more keyword density',
'Include long-tail keywords',
'Add meta description',
] : ['SEO is optimized'],
questions: [],
longTail: [],
suggestions: seoScore < 70 ? ['Add more keyword density', 'Include long-tail keywords'] : ['SEO is optimized'],
meta: {
title: `${topic} | Content Hunter`,
description: research?.summary?.slice(0, 160) || `Learn about ${topic}`,
},
};
}
}
// Neuro Marketing Analysis
let neuroResult: NeuroAnalysisResult | undefined;
@@ -220,8 +286,30 @@ export class ContentGenerationService {
/**
* Persist generated content bundle to database
*/
async saveGeneratedBundle(userId: string, bundle: GeneratedContentBundle): Promise<{ masterContentId: string }> {
console.log(`[ContentGenerationService] Saving bundle for user ${userId}, topic: ${bundle.topic}`);
async saveGeneratedBundle(userId: string | null, bundle: GeneratedContentBundle): Promise<{ masterContentId: string }> {
// If no userId, try to find or create a system anonymous user
let effectiveUserId = userId;
if (!effectiveUserId) {
try {
const anonUser = await this.prisma.user.findFirst({ where: { email: 'anonymous@contenthunter.system' } });
if (anonUser) {
effectiveUserId = anonUser.id;
} else {
const newAnon = await this.prisma.user.create({
data: {
email: 'anonymous@contenthunter.system',
password: 'system-anonymous-no-login',
firstName: 'Anonymous',
},
});
effectiveUserId = newAnon.id;
}
} catch (anonError) {
this.logger.warn(`Could not create anonymous user, content will not be saved: ${anonError.message}`);
return { masterContentId: 'not-saved' };
}
}
console.log(`[ContentGenerationService] Saving bundle for user ${effectiveUserId}, topic: ${bundle.topic}`);
try {
return await this.prisma.$transaction(async (tx) => {
// 1. Create DeepResearch if it exists in bundle
@@ -229,9 +317,9 @@ export class ContentGenerationService {
if (bundle.research) {
const research = await tx.deepResearch.create({
data: {
userId,
userId: effectiveUserId!,
topic: bundle.topic,
query: bundle.topic, // Simplified for now
query: bundle.topic,
summary: bundle.research.summary,
sources: JSON.parse(JSON.stringify(bundle.research.sources)),
keyFindings: JSON.parse(JSON.stringify(bundle.research.keyFindings)),
@@ -245,10 +333,10 @@ export class ContentGenerationService {
// 2. Create MasterContent
const masterContent = await tx.masterContent.create({
data: {
userId,
userId: effectiveUserId!,
title: bundle.topic,
body: bundle.platforms[0]?.content || '', // Use first platform as master body for now
type: PrismaMasterContentType.BLOG, // Default
body: bundle.platforms[0]?.content || '',
type: PrismaMasterContentType.BLOG,
status: PrismaContentStatus.DRAFT,
researchId,
summary: bundle.research?.summary,
@@ -267,7 +355,7 @@ export class ContentGenerationService {
const content = await tx.content.create({
data: {
userId,
userId: effectiveUserId!,
masterContentId: masterContent.id,
type: contentType,
title: `${bundle.topic} - ${platformContent.platform}`,
@@ -302,7 +390,19 @@ export class ContentGenerationService {
}
}
console.log(`[ContentGenerationService] Bundle saved successfully. MasterContentId: ${masterContent.id}`);
// Robust Verification
const savedCount = await tx.content.count({
where: { masterContentId: masterContent.id }
});
if (savedCount !== bundle.platforms.length) {
this.logger.error(`[CRITICAL] Save mismatch! Expected ${bundle.platforms.length} items, found ${savedCount}. MasterID: ${masterContent.id}`);
// Ensure we at least have the master content
}
return { masterContentId: masterContent.id };
});
} catch (error) {
@@ -411,4 +511,117 @@ export class ContentGenerationService {
createABTest(content: string) {
return this.variationService.createABTest(content);
}
// ========== NEURO REGENERATION ==========
async regenerateForNeuro(request: {
content: string;
platform: string;
currentScore: number;
improvements: string[];
}): Promise<{ content: string; score: number; improvements: string[] }> {
const { content, platform, currentScore, improvements } = request;
// Use platform service to regenerate with neuro optimization
const platformEnum = platform as Platform;
const improvementList = improvements.join('\n- ');
const neuroPrompt = `Sen nöro-pazarlama uzmanı bir sosyal medya içerik yazarısın.
MEVCUT İÇERİK:
${content}
MEVCUT NÖRO SKORU: ${currentScore}/100
İYİLEŞTİRME ÖNERİLERİ:
- ${improvementList}
GÖREV: Yukarıdaki içeriği nöro-pazarlama ilkeleri kullanarak yeniden yaz.
Mevcut mesajı koru ama psikolojik etkiyi artır.
KURALLAR:
1. Güçlü bir hook ile başla (merak, şok, soru)
2. Duygusal tetikleyiciler kullan (korku, heyecan, aidiyet)
3. Sosyal kanıt ekle
4. Aciliyet hissi yarat
5. Güçlü bir CTA ile bitir
6. Karakter limitini koru
7. Platformun tonuna uygun yaz
8. SADECE yayınlanacak metni yaz
SADECE yeniden yazılmış metni döndür, açıklama ekleme.`;
try {
const response = await this.platformService.generateAIContent(
neuroPrompt,
content,
platformEnum,
'standard',
'tr',
);
// Re-analyze with neuro service
const analysis = this.neuroService.analyze(response, platformEnum);
return {
content: response,
score: analysis.prediction.overallScore,
improvements: analysis.prediction.improvements,
};
} catch (error) {
this.logger.error(`Neuro regeneration failed: ${error.message}`);
return {
content,
score: currentScore,
improvements: ['Regeneration failed, try again'],
};
}
}
/**
* Strip source names, URLs, and attribution phrases from research summary
* to prevent them from leaking into generated content.
*/
private sanitizeResearchSummary(summary: string): string {
let sanitized = summary;
// Remove URLs
sanitized = sanitized.replace(/https?:\/\/[^\s]+/gi, '');
sanitized = sanitized.replace(/www\.[^\s]+/gi, '');
// Remove common Turkish attribution phrases
const attributionPatterns = [
/\b\w+\.com(\.tr)?\b/gi,
/\b\w+\.org(\.tr)?\b/gi,
/\b\w+\.net(\.tr)?\b/gi,
/\bkaynağına göre\b/gi,
/\b'e göre\b/gi,
/\baccording to [^,.]+/gi,
/\bsource:\s*[^,.]+/gi,
/\breferans:\s*[^,.]+/gi,
/\bkaynak:\s*[^,.]+/gi,
];
// Common Turkish tech/news source brands to strip
const sourceNames = [
'donanımhaber', 'technopat', 'webtekno', 'shiftdelete',
'chip online', 'log.com', 'mediatrend', 'bbc', 'cnn',
'reuters', 'anadolu ajansı', 'hürriyet', 'milliyet',
'sabah', 'forbes', 'bloomberg', 'techcrunch',
];
for (const pattern of attributionPatterns) {
sanitized = sanitized.replace(pattern, '');
}
for (const source of sourceNames) {
const regex = new RegExp(`\\b${source}\\b`, 'gi');
sanitized = sanitized.replace(regex, '');
}
// Clean up multiple spaces and trailing commas
sanitized = sanitized.replace(/\s{2,}/g, ' ').replace(/,\s*,/g, ',').trim();
return sanitized;
}
}

View File

@@ -78,7 +78,9 @@ export class DeepResearchService {
this.logger.log(`Performing ${depth} research on: ${topic}`);
if (!this.gemini.isAvailable()) {
const available = this.gemini.isAvailable();
this.logger.log(`Checking Gemini availability: ${available}`);
if (!available) {
this.logger.warn('Gemini not available, returning basic research');
return this.getFallbackResearch(topic);
}
@@ -135,6 +137,8 @@ export class DeepResearchService {
generatedAt: new Date(),
};
} catch (error) {
const fs = require('fs');
try { fs.appendFileSync('debug.log', `[ERROR] DeepResearchService failed: ${error.message}\n${error.stack}\n`); } catch (e) { }
this.logger.error(`Research failed: ${error.message}`);
return this.getFallbackResearch(topic);
}
@@ -169,11 +173,18 @@ ${includeStats ? '- Include relevant statistics with context and year' : ''}
${includeQuotes ? '- Include quotes from industry experts or thought leaders' : ''}
- Suggest related topics for further exploration
- Provide unique content angles for creating engaging content
- List credible sources (real URLs when possible)
- Provide unique content angles for creating engaging content
- List credible sources (use real URLs if known, otherwise use valid homepage URLs or Google Search links. DO NOT invent 404 paths)
LANGUAGE: Respond in ${language === 'tr' ? 'Turkish' : 'English'}
Be factual, avoid speculation, and cite sources where possible.`;
Be factual, avoid speculation, and cite sources where possible.
IMPORTANT: Your output MUST be valid JSON.
- String values MUST NOT contain raw newline characters.
- Use '\\n' for newlines inside strings.
- Only output the JSON object, NO markdown code blocks.`;
}
/**

View File

@@ -45,6 +45,7 @@ export interface GeneratedContent {
characterCount: number;
isWithinLimit: boolean;
variations?: string[];
imageUrl?: string;
}
export interface MultiPlatformContent {
@@ -55,12 +56,55 @@ export interface MultiPlatformContent {
platforms: GeneratedContent[];
}
export interface WritingStyle {
id: string;
name: string;
description: string;
promptInstruction: string;
}
export const WRITING_STYLES: WritingStyle[] = [
{ id: 'professional', name: 'Professional', description: 'Corporate, polished, and authoritative tone', promptInstruction: 'Kurumsal, cilalı ve otoriter bir ton kullan. Profesyonel ve güvenilir bir ses ile yaz.' },
{ id: 'conversational', name: 'Conversational', description: 'Friendly, casual, like talking to a friend', promptInstruction: 'Samimi ve sıcak bir ton kullan. Bir arkadaşınla sohbet eder gibi yaz, doğal ol.' },
{ id: 'humorous', name: 'Humorous', description: 'Witty, fun, with clever humor', promptInstruction: 'Esprili ve eğlenceli yaz. Zekice şakalar ve kelime oyunları kullan ama içeriğin değerini koru.' },
{ id: 'storytelling', name: 'Storytelling', description: 'Narrative-driven, engaging stories', promptInstruction: 'Hikaye anlatma tekniği kullan. Bir olay örgüsü kur, karakterler ve sahneler ekle. Okuyucuyu hikayenin içine çek.' },
{ id: 'educational', name: 'Educational', description: 'Informative, teacher-like, step-by-step', promptInstruction: 'Eğitici ve öğretici bir ton kullan. Adım adım açıkla, karmaşık konuları basitleştir. Öğretmen gibi yaz.' },
{ id: 'inspirational', name: 'Inspirational', description: 'Motivating, uplifting, empowering', promptInstruction: 'İlham verici ve motive edici yaz. Güçlü duygusal ifadeler kullan. Okuyucuyu harekete geçir ve cesaretlendir.' },
{ id: 'provocative', name: 'Provocative', description: 'Bold, thought-provoking, challenging norms', promptInstruction: 'Cesur ve düşündürücü yaz. Kalıpları kır, soru sor, normları sorgula. Tartışma başlatacak bir ton kullan.' },
{ id: 'minimalist', name: 'Minimalist', description: 'Clean, concise, every word counts', promptInstruction: 'Minimalist yaz. Her kelime önemli, gereksiz kelime kullanma. Kısa, net ve öz cümleler kur.' },
{ id: 'data-driven', name: 'Data-Driven', description: 'Stats-heavy, research-backed, analytical', promptInstruction: 'Veri odaklı yaz. İstatistikler, yüzdeler ve araştırma sonuçları kullan. Analitik ve kanıta dayalı bir ton benimse.' },
{ id: 'emotional', name: 'Emotional', description: 'Heart-touching, empathetic, deeply personal', promptInstruction: 'Duygusal ve empatik yaz. Kişisel ve samimi bir ton kullan. Okuyucunun duygularına hitap et, yürekten yaz.' },
{ id: 'luxury', name: 'Luxury', description: 'Elegant, sophisticated, premium feel', promptInstruction: 'Lüks ve sofistike bir ton kullan. Zarif kelimeler seç, premium bir his oluştur. Ayrıcalıklı ve seçkin bir ses kullan.' },
{ id: 'casual-gen-z', name: 'Gen-Z Casual', description: 'Trendy, meme-aware, internet culture', promptInstruction: 'Gen-Z diline uygun yaz. Internet kültürünü, trend kelimeleri ve güncel jargonu kullan. Rahat ve cool bir ses benimse.' },
{ id: 'journalistic', name: 'Journalistic', description: 'News-style, factual, objective reporting', promptInstruction: 'Gazetecilik tarzında yaz. Nesnel, gerçeklere dayalı ve haber formatında ol. 5N1K kuralını uygula.' },
{ id: 'poetic', name: 'Poetic', description: 'Lyrical, metaphor-rich, artistic', promptInstruction: 'Şiirsel ve sanatsal yaz. Metaforlar, benzetmeler ve imgeler kullan. Dilin müziğini hissettir.' },
{ id: 'sales-copy', name: 'Sales Copy', description: 'Persuasive, benefit-focused, conversion-oriented', promptInstruction: 'Satış odaklı yaz. Faydaları vurgula, ikna edici bir dil kullan. AIDA formülünü uygula: Dikkat, İlgi, Arzu, Aksiyon.' },
];
export const CTA_TYPES = [
{ id: 'follow', name: 'Follow', description: 'Takip et', promptInstruction: 'Hesabı takip etmeye davet eden bir CTA ekle.' },
{ id: 'link', name: 'Click Link', description: 'Linke tıkla', promptInstruction: 'Bio\'daki linke veya belirtilen linke tıklamaya davet eden bir CTA ekle.' },
{ id: 'comment', name: 'Comment', description: 'Yorum yap', promptInstruction: 'Yorum yapmaya ve düşünce paylaşmaya davet eden bir CTA ekle.' },
{ id: 'share', name: 'Share', description: 'Paylaş', promptInstruction: 'İçeriği paylaşmaya davet eden bir CTA ekle.' },
{ id: 'buy', name: 'Buy Now', description: 'Hemen al', promptInstruction: 'Ürünü satın almaya teşvik eden bir CTA ekle.' },
{ id: 'subscribe', name: 'Subscribe', description: 'Abone ol', promptInstruction: 'Abone olmaya veya listeye katılmaya davet eden bir CTA ekle.' },
{ id: 'learn-more', name: 'Learn More', description: 'Daha fazla öğren', promptInstruction: 'Daha fazla bilgi edinmeye yönlendiren bir CTA ekle.' },
{ id: 'custom', name: 'Custom', description: 'Özel CTA', promptInstruction: 'Etkili ve ikna edici bir CTA ekle.' },
];
@Injectable()
export class PlatformGeneratorService {
private readonly logger = new Logger(PlatformGeneratorService.name);
constructor(private readonly gemini: GeminiService) { }
/**
* Get available writing styles
*/
getWritingStyles(): WritingStyle[] {
return WRITING_STYLES;
}
// Platform configurations
private readonly platforms: Record<Platform, PlatformConfig> = {
twitter: {
@@ -420,14 +464,32 @@ export class PlatformGeneratorService {
platform: Platform,
format: string,
language: string = 'tr',
writingStyle?: string,
ctaType?: string,
): Promise<string> {
const config = this.platforms[platform];
if (!config) {
throw new Error(`Invalid platform: ${platform}`);
}
if (!this.gemini.isAvailable()) {
this.logger.warn('Gemini not available, using template fallback');
return this.generateTemplateContent(topic, mainMessage, config);
}
// Resolve writing style instruction
const style = WRITING_STYLES.find(s => s.id === writingStyle);
const styleInstruction = style
? `\nYAZIM STİLİ: ${style.name}\nSTİL TALİMATI: ${style.promptInstruction}`
: '';
// Resolve CTA instruction
const cta = CTA_TYPES.find(c => c.id === ctaType);
const ctaInstruction = cta
? `\nCTA TİPİ: ${cta.name}\nCTA TALİMATI: ${cta.promptInstruction}`
: '\nCTA TALİMATI: İçeriğin sonunda etkili bir CTA (harekete geçirici mesaj) ekle.';
const prompt = `Sen profesyonel bir sosyal medya içerik uzmanısın.
PLATFORM: ${config.name}
@@ -436,7 +498,7 @@ ANA MESAJ: ${mainMessage}
FORMAT: ${format}
KARAKTER LİMİTİ: ${config.maxCharacters}
MAX HASHTAG: ${config.maxHashtags}
TON: ${config.tone}
TON: ${config.tone}${styleInstruction}${ctaInstruction}
Bu platform için özgün, ilgi çekici ve viral potansiyeli yüksek bir içerik oluştur.
@@ -444,11 +506,19 @@ KURALLAR:
1. Karakter limitine uy
2. Platformun tonuna uygun yaz
3. Hook (dikkat çeken giriş) ile başla
4. CTA (harekete geçirici) ile bitir
4. CTA ile bitir (yukarıdaki CTA talimatına göre)
5. Emoji kullan ama aşırıya kaçma
6. ${language === 'tr' ? 'Türkçe' : 'İngilizce'} yaz
7. ASLA resim URL'i, medya linki veya [görsel] gibi yer tutucular ekleme
8. Görsel betimlemeleri metnin içine YAZMA
9. İçerik %100 özgün olmalı - asla kaynak kopyası yapma
10. Kaynak linklerini, URL'leri veya atıfları ASLA ekleme
11. Mevcut içeriklerden alıntı yapma, tamamen yeni ve orijinal yaz
12. Bilgiyi kendi cümlelerinle ifade et, paraphrase bile yapma
13. Araştırma kaynaklarının isimlerini (web siteleri, haber siteleri, markalar, gazeteler) ASLA metinde kullanma veya referans verme
14. "...göre", "...kaynağına göre", "according to" gibi atıf ifadeleri ASLA kullanma
SADECE içeriği yaz, açıklama veya başlık ekleme.`;
SADECE yayınlanacak metni yaz, açıklama veya başlık ekleme.`;
try {
const response = await this.gemini.generateText(prompt, {
@@ -458,7 +528,7 @@ SADECE içeriği yaz, açıklama veya başlık ekleme.`;
return response.text;
} catch (error) {
this.logger.error(`AI content generation failed: ${error.message}`);
return this.generateTemplateContent(topic, mainMessage, config);
return this.generateTemplateContent(topic, mainMessage, config, language);
}
}
@@ -466,21 +536,30 @@ SADECE içeriği yaz, açıklama veya başlık ekleme.`;
topic: string,
mainMessage: string,
config: PlatformConfig,
language: string = 'tr',
): string {
let content = '';
switch (config.platform) {
case 'twitter':
content = `${mainMessage}\n\nLearn more about ${topic} 👇`;
content = language === 'tr'
? `${mainMessage}\n\n${topic} hakkında daha fazlası 👇`
: `${mainMessage}\n\nLearn more about ${topic} 👇`;
break;
case 'instagram':
content = `${mainMessage}\n\\\\n\n💡 Save this for later!\n\nWant more ${topic} tips? Follow @yourhandle`;
content = language === 'tr'
? `${mainMessage}\n\\\\n\n💡 Bunu kaydet!\n\nDaha fazla ${topic} ipucu için takip et`
: `${mainMessage}\n\\\\n\n💡 Save this!\n\nFollow for more ${topic} tips`;
break;
case 'linkedin':
content = `${mainMessage}\n\n↓\n\nHere's what I've learned about ${topic}:\n\n1. Start small\n2. Be consistent\n3. Learn from feedback\n\nWhat's your experience with ${topic}?\n\nShare in the comments 👇`;
content = language === 'tr'
? `${mainMessage}\n\n↓\n\n${topic} hakkında öğrendiklerim:\n\n1. Küçük başla\n2. Tutarlı ol\n3. Geri bildirimlerden öğren\n\n${topic} deneyiminiz nedir?\n\nYorumlarınızı paylaşın 👇`
: `${mainMessage}\n\n↓\n\nHere's what I've learned about ${topic}:\n\n1. Start small\n2. Be consistent\n3. Learn from feedback\n\nWhat's your experience with ${topic}?\n\nShare in the comments 👇`;
break;
case 'tiktok':
content = `POV: You finally understand ${topic}\n\n${mainMessage}\n\nFollow for more tips! 🎯`;
content = language === 'tr'
? `POV: Sonunda ${topic} konusunu anladın\n\n${mainMessage}\n\nDaha fazla ipucu için takip et! 🎯`
: `POV: You finally understand ${topic}\n\n${mainMessage}\n\nFollow for more tips! 🎯`;
break;
case 'youtube':
content = `📺 ${topic.toUpperCase()}\n\n${mainMessage}\n\n⏱ Timestamps:\n0:00 Intro\n1:00 Main content\n\n🔔 Subscribe for more!`;

View File

@@ -9,6 +9,7 @@ export interface VariationOptions {
preserveCore?: boolean;
targetLength?: 'shorter' | 'same' | 'longer';
toneShift?: string;
language?: 'tr' | 'en';
}
export type VariationType =
@@ -38,8 +39,8 @@ export interface VariationSet {
export class VariationService {
private readonly logger = new Logger(VariationService.name);
// Hook variations
private readonly hookTemplates = {
// Hook variations (English)
private readonly hookTemplatesEn = {
question: (topic: string) => `Ever wondered why ${topic}?`,
bold: (topic: string) => `Here's the truth about ${topic}:`,
story: (topic: string) => `Last year, I learned something about ${topic} that changed everything.`,
@@ -50,8 +51,24 @@ export class VariationService {
curiosity: (topic: string) => `The ${topic} secret nobody talks about:`,
};
// Tone modifiers
private readonly toneModifiers: Record<string, (text: string) => string> = {
// Hook variations (Turkish)
private readonly hookTemplatesTr = {
question: (topic: string) => `${topic} hakkında hiç merak ettiniz mi?`,
bold: (topic: string) => `İşte ${topic} hakkındaki gerçek:`,
story: (topic: string) => `Geçen yıl ${topic} hakkında her şeyi değiştiren bir şey öğrendim.`,
statistic: (topic: string) => `İnsanların %73'ü ${topic} konusunda yanılıyor. İşte keşfettiğim:`,
contrarian: (topic: string) => `${topic} hakkında size söylenen her şey yanlış.`,
pain: (topic: string) => `${topic} konusunda zorlanıyor musunuz? Yalnız değilsiniz.`,
promise: (topic: string) => `Ya ${topic} konusunda sadece 30 günde uzmanlaşabilseydiniz?`,
curiosity: (topic: string) => `Kimsenin bahsetmediği ${topic} sırrı:`,
};
private getHookTemplates(language: 'tr' | 'en' = 'en') {
return language === 'tr' ? this.hookTemplatesTr : this.hookTemplatesEn;
}
// Tone modifiers (English)
private readonly toneModifiersEn: Record<string, (text: string) => string> = {
professional: (text) => text.replace(/!/g, '.').replace(/lol|haha|😂/gi, ''),
casual: (text) => text.replace(/Furthermore,/g, 'Also,').replace(/However,/g, 'But'),
enthusiastic: (text) => text.replace(/\./g, '!').replace(/good/gi, 'amazing'),
@@ -60,6 +77,20 @@ export class VariationService {
humorous: (text) => text + ' (And yes, I learned this the hard way 😅)',
};
// Tone modifiers (Turkish)
private readonly toneModifiersTr: Record<string, (text: string) => string> = {
professional: (text) => text.replace(/!/g, '.').replace(/haha|😂/gi, ''),
casual: (text) => text.replace(/Ayrıca,/g, 'Bunun yanı sıra,').replace(/Ancak,/g, 'Ama'),
enthusiastic: (text) => text.replace(/\./g, '!').replace(/iyi/gi, 'muhteşem'),
empathetic: (text) => `Bunun ne kadar zor olduğunu anlıyorum. ${text}`,
urgent: (text) => `Beklemeyin. ${text} Zaman daralıyor.`,
humorous: (text) => text + ' (Evet, bunu zor yoldan öğrendim 😅)',
};
private getToneModifiers(language: 'tr' | 'en' = 'en') {
return language === 'tr' ? this.toneModifiersTr : this.toneModifiersEn;
}
/**
* Generate variations of content
*/
@@ -67,18 +98,18 @@ export class VariationService {
content: string,
options: VariationOptions = {},
): VariationSet {
const { count = 3, variationType = 'complete', preserveCore = true } = options;
const { count = 3, variationType = 'complete', preserveCore = true, language = 'en' } = options;
const variations: ContentVariation[] = [];
switch (variationType) {
case 'hook':
variations.push(...this.generateHookVariations(content, count));
variations.push(...this.generateHookVariations(content, count, language));
break;
case 'angle':
variations.push(...this.generateAngleVariations(content, count));
variations.push(...this.generateAngleVariations(content, count, language));
break;
case 'tone':
variations.push(...this.generateToneVariations(content, count, options.toneShift));
variations.push(...this.generateToneVariations(content, count, options.toneShift, language));
break;
case 'format':
variations.push(...this.generateFormatVariations(content, count));
@@ -88,7 +119,7 @@ export class VariationService {
break;
case 'complete':
default:
variations.push(...this.generateCompleteVariations(content, count, preserveCore));
variations.push(...this.generateCompleteVariations(content, count, preserveCore, language));
}
return {
@@ -127,14 +158,15 @@ export class VariationService {
/**
* Generate hook-only variations
*/
private generateHookVariations(content: string, count: number): ContentVariation[] {
private generateHookVariations(content: string, count: number, language: 'tr' | 'en' = 'en'): ContentVariation[] {
const topic = this.extractTopic(content);
const hookTypes = Object.keys(this.hookTemplates);
const hookTemplates = this.getHookTemplates(language);
const hookTypes = Object.keys(hookTemplates);
const variations: ContentVariation[] = [];
for (let i = 0; i < Math.min(count, hookTypes.length); i++) {
const hookType = hookTypes[i] as keyof typeof this.hookTemplates;
const newHook = this.hookTemplates[hookType](topic);
const hookType = hookTypes[i] as keyof typeof hookTemplates;
const newHook = hookTemplates[hookType](topic);
// Replace first line with new hook
const lines = content.split('\n');
@@ -155,14 +187,22 @@ export class VariationService {
/**
* Generate angle variations
*/
private generateAngleVariations(content: string, count: number): ContentVariation[] {
const angles = [
private generateAngleVariations(content: string, count: number, language: 'tr' | 'en' = 'en'): ContentVariation[] {
const anglesEn = [
{ name: 'how-to', prefix: 'Here\'s how to', focus: 'actionable steps' },
{ name: 'why', prefix: 'This is why', focus: 'reasoning and benefits' },
{ name: 'what-if', prefix: 'What if you could', focus: 'possibilities' },
{ name: 'mistake', prefix: 'The biggest mistake people make with', focus: 'what not to do' },
{ name: 'story', prefix: 'When I first started with', focus: 'personal experience' },
];
const anglesTr = [
{ name: 'nasıl', prefix: 'İşte nasıl', focus: 'uygulanabilir adımlar' },
{ name: 'neden', prefix: 'İşte bu yüzden', focus: 'nedenler ve faydalar' },
{ name: 'ya-olsaydı', prefix: 'Ya şunu yapabilseydiniz:', focus: 'olasılıklar' },
{ name: 'hata', prefix: 'İnsanların en büyük hatası:', focus: 'ne yapılmamalı' },
{ name: 'hikaye', prefix: 'İlk başladığımda', focus: 'kişisel deneyim' },
];
const angles = language === 'tr' ? anglesTr : anglesEn;
const topic = this.extractTopic(content);
const variations: ContentVariation[] = [];
@@ -188,13 +228,15 @@ export class VariationService {
content: string,
count: number,
specificTone?: string,
language: 'tr' | 'en' = 'en',
): ContentVariation[] {
const toneModifiers = this.getToneModifiers(language);
const tones = specificTone
? [specificTone]
: Object.keys(this.toneModifiers).slice(0, count);
: Object.keys(toneModifiers).slice(0, count);
return tones.map((tone, i) => {
const modifier = this.toneModifiers[tone];
const modifier = toneModifiers[tone];
const modified = modifier ? modifier(content) : content;
return {
@@ -289,16 +331,21 @@ export class VariationService {
content: string,
count: number,
preserveCore: boolean,
language: 'tr' | 'en' = 'en',
): ContentVariation[] {
const variations: ContentVariation[] = [];
const topic = this.extractTopic(content);
const core = this.keepCore(content);
const isTr = language === 'tr';
// Variation 1: Different angle + hook
variations.push({
id: 'complete-1',
type: 'complete',
content: `The truth about ${topic}:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nSave this for later.`,
content: isTr
? `İşte ${topic} hakkındaki gerçek:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nBunu kaydet, lazım olacak.`
: `The truth about ${topic}:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nSave this for later.`,
changes: ['New hook', 'New CTA', preserveCore ? 'Preserved core' : 'Rewrote core'],
similarity: preserveCore ? 60 : 40,
});
@@ -308,7 +355,9 @@ export class VariationService {
variations.push({
id: 'complete-2',
type: 'complete',
content: `I wish someone told me this about ${topic} earlier:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nShare if this helped.`,
content: isTr
? `Keşke biri bana ${topic} hakkında daha önce bunu söyleseydi:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nFaydalı bulduysan paylaş.`
: `I wish someone told me this about ${topic} earlier:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nShare if this helped.`,
changes: ['Story-based approach', 'Personal angle'],
similarity: preserveCore ? 55 : 35,
});
@@ -319,7 +368,9 @@ export class VariationService {
variations.push({
id: 'complete-3',
type: 'complete',
content: `Stop what you're doing.\n\nThis ${topic} insight is too important:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nYou're welcome.`,
content: isTr
? `Dur bir dakika.\n\nBu ${topic} bilgisi çok önemli:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nRica ederim.`
: `Stop what you're doing.\n\nThis ${topic} insight is too important:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nYou're welcome.`,
changes: ['Bold/direct style', 'Urgency added'],
similarity: preserveCore ? 50 : 30,
});

View File

@@ -142,11 +142,12 @@ export class ContentController {
@ApiOperation({ summary: 'Update content' })
async updateContent(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: { body?: string; status?: ContentStatus; scheduledAt?: string },
@Body() dto: { body?: string; status?: ContentStatus; scheduledAt?: string; imageUrl?: string },
) {
return this.contentService.update(id, {
...dto,
scheduledAt: dto.scheduledAt ? new Date(dto.scheduledAt) : undefined,
imageUrl: dto.imageUrl,
});
}

View File

@@ -117,7 +117,7 @@ export class ContentService {
/**
* Update content
*/
async update(id: string, data: { body?: string; status?: ContentStatus; scheduledAt?: Date }) {
async update(id: string, data: { body?: string; status?: ContentStatus; scheduledAt?: Date; imageUrl?: string }) {
return this.prisma.content.update({
where: { id },
data,

View File

@@ -1,20 +1,13 @@
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { GoogleGenAI } from '@google/genai';
import * as fs from 'fs';
import * as path from 'path';
const LOG_FILE = path.join(process.cwd(), 'gemini_diagnostics.log');
function logToFile(message: string) {
const timestamp = new Date().toISOString();
fs.appendFileSync(LOG_FILE, `[${timestamp}] ${message}\n`);
}
export interface GeminiGenerateOptions {
model?: string;
systemPrompt?: string;
temperature?: number;
maxTokens?: number;
responseMimeType?: string;
}
export interface GeminiChatMessage {
@@ -59,12 +52,12 @@ export class GeminiService implements OnModuleInit {
this.isEnabled = this.configService.get<boolean>('gemini.enabled', false);
this.defaultModel = this.configService.get<string>(
'gemini.defaultModel',
'gemini-1.5-flash',
'gemini-2.0-flash-001',
);
}
onModuleInit() {
this.logger.log(`Initializing GeminiService. isEnabled: ${this.isEnabled}`);
this.logger.log(`Initializing GeminiService. isEnabled: ${this.isEnabled} with model: ${this.defaultModel}`);
if (!this.isEnabled) {
this.logger.log(
@@ -96,7 +89,6 @@ export class GeminiService implements OnModuleInit {
*/
isAvailable(): boolean {
const available = this.isEnabled && !!this.client;
logToFile(`[GeminiService] isAvailable: ${available} (isEnabled: ${this.isEnabled}, hasClient: ${!!this.client})`);
return available;
}
@@ -137,19 +129,25 @@ export class GeminiService implements OnModuleInit {
parts: [{ text: prompt }],
});
logToFile(`[GeminiService] Calling generateContent with model: ${model}`);
const response = await this.client!.models.generateContent({
model,
contents,
config: {
temperature: options.temperature,
maxOutputTokens: options.maxTokens,
responseMimeType: options.responseMimeType,
},
});
logToFile(`[GeminiService] Response: ${JSON.stringify(response).substring(0, 1000)}`);
} as any);
// Diagnostic logging for development
if (process.env.NODE_ENV === 'development') {
this.logger.debug('Raw Gemini Response:', JSON.stringify(response, null, 2));
}
const text = this.extractText(response);
return {
text: (response.text || '').trim(),
text,
usage: response.usageMetadata,
};
} catch (error) {
@@ -204,8 +202,10 @@ export class GeminiService implements OnModuleInit {
},
});
const text = this.extractText(response);
return {
text: (response.text || '').trim(),
text,
usage: response.usageMetadata,
};
} catch (error) {
@@ -234,23 +234,83 @@ ${schema}
IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
const response = await this.generateText(fullPrompt, options);
const isPro = (options?.model || this.defaultModel).includes('pro');
// Add instruction to prompt if using Pro model (since we removed responseMimeType)
const finalPrompt = isPro ? `${fullPrompt}\n\nIMPORTANT: Output strictly valid JSON.` : fullPrompt;
const response = await this.generateText(finalPrompt, {
...options,
model: options?.model || this.defaultModel,
// Only set responseMimeType for Flash models that support it reliably
responseMimeType: isPro ? undefined : 'application/json',
});
try {
// Try to extract JSON from the response
let jsonStr = response.text;
const jsonStr = response.text || '';
// Remove potential markdown code blocks (handle both complete and truncated)
let cleanedStr = jsonStr.trim();
// Remove potential markdown code blocks
const jsonMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
if (jsonMatch) {
jsonStr = jsonMatch[1].trim();
// Try to find the first '{' and last '}'
const firstBrace = cleanedStr.indexOf('{');
const lastBrace = cleanedStr.lastIndexOf('}');
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
cleanedStr = cleanedStr.substring(firstBrace, lastBrace + 1);
} else if (firstBrace !== -1) {
// Truncated JSON - starts but doesn't end?
cleanedStr = cleanedStr.substring(firstBrace);
}
const data = JSON.parse(jsonStr) as T;
// Fix raw newlines inside string values (not between keys)
// This is a common issue where AI produces literal newlines in JSON strings
cleanedStr = cleanedStr.replace(/"([^"]*)"/g, (match, p1) => {
return '"' + p1.replace(/\n/g, '\\n') + '"';
});
const data = JSON.parse(cleanedStr) as T;
return { data, usage: response.usage };
} catch (error) {
this.logger.error('Failed to parse JSON response', error);
throw new Error('Failed to parse AI response as JSON');
}
}
/**
* Extract text from Gemini response, handling various SDK versions/structures
*/
private extractText(response: any): string {
// Check for safety filters or other finish reasons
if (response.candidates && response.candidates.length > 0) {
const candidate = response.candidates[0];
if (candidate.finishReason && candidate.finishReason !== 'STOP') {
this.logger.warn(`Gemini generation finished with reason: ${candidate.finishReason}`);
if (candidate.finishReason === 'SAFETY') {
this.logger.warn('Content was blocked by safety filters');
}
}
}
let text = '';
try {
// In some SDK versions text() is a method, in others it's a property
if (typeof response.text === 'function') {
text = response.text();
} else if (typeof response.text === 'string') {
text = response.text;
}
} catch (e) {
this.logger.error('Error calling response.text():', e);
}
if (!text && response.candidates && response.candidates.length > 0) {
const candidate = response.candidates[0];
if (candidate.content && candidate.content.parts && candidate.content.parts.length > 0) {
text = candidate.content.parts[0].text || '';
}
}
return text ? text.trim() : '';
}
}

View File

@@ -54,6 +54,23 @@ export class NeuroMarketingService {
const triggerAnalysis = this.triggersService.analyzeContent(content);
const urgencyAnalysis = this.urgencyService.analyzeUrgency(content);
// Enhance prediction improvements with more actionable data
if (prediction.overallScore < 70) {
prediction.improvements = [
...prediction.improvements,
'Kanca (Hook) cümlesini daha merak uyandırıcı hale getirin.',
'Duygusal tetikleyicileri (korku, arzu, merak) daha net kullanın.',
'Okuyucuya "bunun içinde benim için ne var?" sorusunun cevabını hemen verin.',
'Harekete geçirici mesajınızı (CTA) daha net ve tekil hale getirin.'
];
} else {
prediction.improvements = [
'Harika bir duygusal ton yakaladınız.',
'Hikaye anlatımınız oldukça etkili.',
'Kitle etkileşimi için güçlü bir yapı var.'
];
}
return {
prediction,
triggerAnalysis,

View File

@@ -43,6 +43,7 @@ export class SeoService {
metaDescription?: string;
url?: string;
competitorDomains?: string[];
language?: 'en' | 'tr' | 'de' | 'es' | 'fr';
},
): Promise<FullSeoAnalysis> {
// Analyze content
@@ -51,6 +52,7 @@ export class SeoService {
title: options?.title,
metaDescription: options?.metaDescription,
url: options?.url,
language: options?.language || 'en',
});
// Generate optimized meta
@@ -107,6 +109,7 @@ export class SeoService {
options?: {
competitorDomains?: string[];
contentType?: string;
language?: 'en' | 'tr' | 'de' | 'es' | 'fr';
},
): Promise<{
title: string;
@@ -118,6 +121,7 @@ export class SeoService {
}> {
// Get keyword data
const keywordData = await this.keywordService.suggestKeywords(keyword);
const language = options?.language || 'en';
// Get title variations
const titles = this.optimizationService.generateTitleVariations(keyword, 1);
@@ -132,6 +136,23 @@ export class SeoService {
const blueprint = this.competitorService.generateContentBlueprint(keyword, competitorContent);
headings = blueprint.suggestedHeadings;
differentiators = blueprint.differentiators;
} else {
if (language === 'tr') {
headings = [
`${keyword} Nedir?`,
`Neden ${keyword} Önemlidir?`,
`${keyword} Nasıl Kullanılır?`,
`${keyword} En İyi Uygulamalar`,
`Sık Yapılan ${keyword} Hataları`,
`${keyword} Araçları ve Kaynakları`,
`${keyword} Hakkında SSS`,
];
differentiators = [
'Orijinal araştırma veya veri ekleyin',
'Uzman görüşlerine yer verin',
'Uygulanabilir adımlar sunun',
'Gerçek örnekler ekleyin',
];
} else {
headings = [
`What is ${keyword}?`,
@@ -149,6 +170,7 @@ export class SeoService {
'Include real examples',
];
}
}
return {
title: titles[0] || `Complete Guide to ${keyword}`,

View File

@@ -70,6 +70,7 @@ export class ContentOptimizationService {
title?: string;
metaDescription?: string;
url?: string;
language?: 'en' | 'tr' | 'de' | 'es' | 'fr';
}): SeoScore {
const breakdown = {
titleOptimization: this.scoreTitleOptimization(options?.title, options?.targetKeyword),
@@ -342,15 +343,17 @@ export class ContentOptimizationService {
targetKeyword?: string;
title?: string;
metaDescription?: string;
language?: 'en' | 'tr' | 'de' | 'es' | 'fr';
}): SeoIssue[] {
const issues: SeoIssue[] = [];
const isTr = options?.language === 'tr';
if (!options?.title) {
issues.push({
type: 'error',
category: 'title',
message: 'Missing title tag',
fix: 'Add a compelling title with your target keyword',
message: isTr ? 'Başlık etiketi eksik' : 'Missing title tag',
fix: isTr ? 'Hedef anahtar kelimenizi içeren etkileyici bir başlık ekleyin' : 'Add a compelling title with your target keyword',
});
}
@@ -358,8 +361,8 @@ export class ContentOptimizationService {
issues.push({
type: 'error',
category: 'meta',
message: 'Missing meta description',
fix: 'Add a meta description between 150-160 characters',
message: isTr ? 'Meta açıklaması eksik' : 'Missing meta description',
fix: isTr ? '150-160 karakter arasında bir meta açıklaması ekleyin' : 'Add a meta description between 150-160 characters',
});
}
@@ -368,8 +371,8 @@ export class ContentOptimizationService {
issues.push({
type: 'warning',
category: 'content',
message: 'Content is too short',
fix: 'Aim for at least 1000 words for better SEO',
message: isTr ? 'İçerik çok kısa' : 'Content is too short',
fix: isTr ? 'Daha iyi SEO için en az 1000 kelime hedefleyin' : 'Aim for at least 1000 words for better SEO',
});
}
@@ -378,16 +381,17 @@ export class ContentOptimizationService {
private generateSuggestions(
breakdown: SeoScore['breakdown'],
options?: { targetKeyword?: string },
options?: { targetKeyword?: string; language?: 'en' | 'tr' | 'de' | 'es' | 'fr' },
): SeoSuggestion[] {
const suggestions: SeoSuggestion[] = [];
const isTr = options?.language === 'tr';
if (breakdown.keywordDensity < 70 && options?.targetKeyword) {
suggestions.push({
priority: 'high',
category: 'keywords',
suggestion: `Increase usage of "${options.targetKeyword}" in your content`,
impact: 'Better keyword relevance signals to search engines',
suggestion: isTr ? `İçeriğinizde "${options.targetKeyword}" kullanımını artırın` : `Increase usage of "${options.targetKeyword}" in your content`,
impact: isTr ? 'Arama motorlarına daha iyi anahtar kelime sinyalleri' : 'Better keyword relevance signals to search engines',
});
}
@@ -395,8 +399,8 @@ export class ContentOptimizationService {
suggestions.push({
priority: 'medium',
category: 'content',
suggestion: 'Expand your content with more detailed information',
impact: 'Longer, comprehensive content typically ranks better',
suggestion: isTr ? 'İçeriğinizi daha detaylı bilgilerle genişletin' : 'Expand your content with more detailed information',
impact: isTr ? 'Uzun ve kapsamlı içerikler genellikle daha iyi sıralanır' : 'Longer, comprehensive content typically ranks better',
});
}
@@ -404,8 +408,8 @@ export class ContentOptimizationService {
suggestions.push({
priority: 'medium',
category: 'links',
suggestion: 'Add more internal links to related content',
impact: 'Improves site structure and helps with crawling',
suggestion: isTr ? 'İlgili içeriklere daha fazla iç bağlantı ekleyin' : 'Add more internal links to related content',
impact: isTr ? 'Site yapısını iyileştirir ve taranabilirliğe yardımcı olur' : 'Improves site structure and helps with crawling',
});
}

View File

@@ -41,17 +41,31 @@ export class KeywordResearchService {
'best', 'top', 'free', 'cheap', 'affordable', 'premium',
'easy', 'quick', 'simple', 'ultimate', 'complete', 'beginner',
'advanced', 'professional', 'expert', 'step by step',
// TR
'nasıl', 'nedir', 'neden', 'ne zaman', 'nerede', 'kim',
'en iyi', 'ücretsiz', 'ucuz', 'uygun fiyatlı', 'premium',
'kolay', 'hızlı', 'basit', 'tam', 'başlangıç', 'ileri seviye',
'uzman', 'adım adım',
],
suffix: [
'guide', 'tutorial', 'tips', 'tricks', 'examples', 'templates',
'tools', 'software', 'apps', 'services', 'strategies', 'techniques',
'for beginners', 'for experts', 'in 2024', 'vs', 'alternatives',
'review', 'comparison', 'checklist', 'resources', 'ideas',
// TR
'kılavuzu', 'dersi', 'ipuçları', 'taktikleri', 'örnekleri', 'şablonları',
'araçları', 'yazılımı', 'uygulamaları', 'hizmetleri', 'stratejileri', 'teknikleri',
'yeni başlayanlar için', 'uzmanlar için', '2024', 'vs', 'alternatifleri',
'incelemesi', 'karşılaştırması', 'kontrol listesi', 'kaynakları', 'fikirleri',
],
questions: [
'what is', 'how to', 'why should', 'when to use', 'where can I find',
'who needs', 'which is best', 'can you', 'should I', 'does',
'is it worth', 'how much does', 'how long does', 'what are the benefits',
// TR
'nedir', 'nasıl yapılır', 'neden', 'ne zaman kullanılır', 'nerede bulunur',
'kime lazım', 'hangisi en iyi', 'yapabilir misin', 'yapmalı mıyım',
'değer mi', 'ne kadar', 'ne kadar sürer', 'faydaları nelerdir',
],
};

View File

@@ -178,7 +178,9 @@ export class TrendsService {
Keep the tone professional and maintain any technical terms or proper nouns correctly.
Text to translate:
"${text}"`;
"${text}"
IMPORTANT: Return ONLY the translated text without any conversational fillers, options, quotes, or explanations.`;
const response = await this.gemini.generateText(prompt, {
temperature: 0.3, // Lower temperature for more accurate translation

View File

@@ -153,8 +153,8 @@ export class GeminiImageService {
id: `img-${Date.now()}`,
prompt,
enhancedPrompt: enhanced,
url: `https://storage.example.com/generated/${Date.now()}.png`,
thumbnailUrl: `https://storage.example.com/generated/${Date.now()}_thumb.png`,
url: `https://placehold.co/${dimensions.width}x${dimensions.height}/png?text=${encodeURIComponent(prompt.slice(0, 20))}`,
thumbnailUrl: `https://placehold.co/${Math.floor(dimensions.width / 2)}x${Math.floor(dimensions.height / 2)}/png?text=${encodeURIComponent(prompt.slice(0, 20))}`,
style: finalStyle,
aspectRatio: finalRatio,
width: dimensions.width,

View File

@@ -0,0 +1,28 @@
import { Injectable, Logger } from '@nestjs/common';
const gis = require('g-i-s');
@Injectable()
export class GoogleImageSearchService {
private readonly logger = new Logger(GoogleImageSearchService.name);
async search(query: string): Promise<string[]> {
this.logger.log(`Searching images for query: ${query}`);
return new Promise((resolve, reject) => {
gis(query, (error: any, results: any[]) => {
if (error) {
this.logger.error('Google Image Search failed', error);
// Return empty array instead of failing completely?
// No, let user know search failed.
reject(error);
} else {
// results is an array of objects with url, width, height
// Filter for reasonable size images if possible, but g-i-s results are simple.
// Just map to URLs.
const urls = results.map(r => r.url);
this.logger.log(`Found ${urls.length} images`);
resolve(urls.slice(0, 30)); // Return top 30
}
});
});
}
}

View File

@@ -173,7 +173,7 @@ export class VeoVideoService {
const video: GeneratedVideo = {
id: `vid-${Date.now()}`,
prompt,
url: `https://storage.example.com/videos/${Date.now()}.mp4`,
url: `https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_1MB.mp4`,
thumbnailUrl: `https://storage.example.com/videos/${Date.now()}_thumb.jpg`,
duration: finalDuration,
aspectRatio: finalRatio,

View File

@@ -246,4 +246,11 @@ export class VisualGenerationController {
) {
return this.service.createCollection(body);
}
// ========== SEARCH ==========
@Get('search')
searchImages(@Query('q') query: string) {
return this.service.searchImages(query);
}
}

View File

@@ -11,6 +11,7 @@ import { NeuroVisualService } from './services/neuro-visual.service';
import { TemplateEditorService } from './services/template-editor.service';
import { AssetLibraryService } from './services/asset-library.service';
import { StorageService } from './services/storage.service';
import { GoogleImageSearchService } from './services/google-image-search.service';
@Module({
@@ -23,6 +24,7 @@ import { StorageService } from './services/storage.service';
TemplateEditorService,
AssetLibraryService,
StorageService,
GoogleImageSearchService,
],
controllers: [VisualGenerationController],
exports: [VisualGenerationService, StorageService],

View File

@@ -8,6 +8,7 @@ import { VeoVideoService, VideoGenerationRequest, GeneratedVideo, VideoStyle } f
import { NeuroVisualService, NeuroOptimizedVisual } from './services/neuro-visual.service';
import { TemplateEditorService, Template, TemplateType, RenderedTemplate } from './services/template-editor.service';
import { AssetLibraryService, Asset, AssetType, AssetCollection } from './services/asset-library.service';
import { GoogleImageSearchService } from './services/google-image-search.service';
export interface VisualContentRequest {
type: 'image' | 'video' | 'template';
@@ -39,8 +40,13 @@ export class VisualGenerationService {
private readonly neuroService: NeuroVisualService,
private readonly templateService: TemplateEditorService,
private readonly assetService: AssetLibraryService,
private readonly searchService: GoogleImageSearchService,
) { }
async searchImages(query: string): Promise<string[]> {
return this.searchService.search(query);
}
// ========== UNIFIED GENERATION ==========
/**

22
test-anon.ts Normal file
View File

@@ -0,0 +1,22 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function test() {
try {
const newAnon = await prisma.user.create({
data: {
email: 'anonymous@contenthunter.system' + Date.now(),
password: 'system-anonymous-no-login',
firstName: 'Anonymous',
},
});
console.log("Success:", newAnon);
} catch (e: any) {
console.error("Prisma Error:", e);
} finally {
await prisma.$disconnect();
}
}
test();

41
test-api-master.ts Normal file
View File

@@ -0,0 +1,41 @@
import fetch from 'node-fetch';
async function main() {
const loginRes = await fetch('http://localhost:3000/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'testuser@example.com', password: 'password123' })
});
if (!loginRes.ok) {
console.log("LOGIN FAILED:", await loginRes.text());
return;
}
const loginData: any = await loginRes.json();
const token = loginData.data?.accessToken;
if (!token) {
console.log("No token in response:", loginData);
return;
}
const masterId = "dabae8f3-4223-4e3e-8876-44c6d31562e3";
console.log(`Fetching master: ${masterId}`);
const masterRes = await fetch(`http://localhost:3000/api/content/master/${masterId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data: any = await masterRes.json();
console.log("Master response structure:", Object.keys(data));
console.log("Data keys:", data.data ? Object.keys(data.data) : "No data object");
const contents = data.contents || data.data?.contents;
if (!contents) {
console.log("NO CONTENTS ARRAY FOUND!");
} else {
console.log(`Found ${contents.length} items in contents array`);
console.log(contents.map((c: any) => ({ id: c.id, type: c.type })));
}
}
main().catch(console.error);

3
test-api.js Normal file
View File

@@ -0,0 +1,3 @@
const http = require('http');
// Assume backend is running on a port, maybe 3001 or 3000
// Wait, I can just use Prisma again to verify `masterContent.contents`. I already did that.

19
test-api.ts Normal file
View File

@@ -0,0 +1,19 @@
// test-api.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './src/app.module';
import { MasterContentService } from './src/modules/content/services/master-content.service';
async function bootstrap() {
const app = await NestFactory.createApplicationContext(AppModule);
const masterContentService = app.get(MasterContentService);
// Fetch a known master ID from the DB
const id = '80c71d39-4614-426c-9d9a-e5b2301cdebd';
const data = await masterContentService.getById(id);
console.log("Returned data type:", typeof data);
console.log("Has contents?", Array.isArray(data?.contents));
console.log("Contents array:", data?.contents?.map(c => ({ id: c.id, type: c.type, title: c.title })));
await app.close();
}
bootstrap();

18
test-db-medium.ts Normal file
View File

@@ -0,0 +1,18 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const contents = await prisma.content.findMany({
where: {
OR: [
{ title: { contains: 'medium' } },
{ type: 'BLOG' }
]
},
orderBy: { createdAt: 'desc' },
take: 5
});
console.log(JSON.stringify(contents, null, 2));
}
main().catch(console.error).finally(() => prisma.$disconnect());

18
test-find-user.ts Normal file
View File

@@ -0,0 +1,18 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.findFirst();
if (!user) {
console.log("No user found.");
return;
}
console.log(`User email: ${user.email}`);
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());

25
test-recent-master.ts Normal file
View File

@@ -0,0 +1,25 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const latestMaster = await prisma.masterContent.findFirst({
orderBy: { createdAt: 'desc' },
include: { contents: true }
});
if (!latestMaster) {
console.log("No master content found.");
return;
}
console.log(`Latest Master ID: ${latestMaster.id}`);
console.log(`Number of contents: ${latestMaster.contents.length}`);
for (const c of latestMaster.contents) {
console.log(` - ID: ${c.id}, Type: ${c.type}, Title: ${c.title}`);
}
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());

12
test-script.js Normal file
View File

@@ -0,0 +1,12 @@
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function main() {
const master = await prisma.masterContent.findFirst({
orderBy: { createdAt: 'desc' },
include: { contents: true }
});
console.log(JSON.stringify(master, null, 2));
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());

12
test-script.ts Normal file
View File

@@ -0,0 +1,12 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const master = await prisma.masterContent.findFirst({
orderBy: { createdAt: 'desc' },
include: { contents: true }
});
console.log(JSON.stringify(master, null, 2));
}
main()
.catch(console.error)
.finally(async () => { await prisma.$disconnect(); });

19
test-script2.ts Normal file
View File

@@ -0,0 +1,19 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const masters = await prisma.masterContent.findMany({
orderBy: { createdAt: 'desc' },
take: 10,
include: { contents: true }
});
for (const master of masters) {
console.log(`Master: ${master.id}, Topic: ${master.title}`);
for (const c of master.contents) {
console.log(` - ${c.type} -> title: ${c.title}`);
}
}
}
main()
.catch(console.error)
.finally(async () => { await prisma.$disconnect(); });