From dee6e29cfdf80a514bad8814768163ea3d0072c8 Mon Sep 17 00:00:00 2001 From: Harun CAN Date: Sat, 14 Mar 2026 14:01:01 +0300 Subject: [PATCH] main --- debug.log | 56 ++ gemini_diagnostics.log | 0 generation_debug.log | 261 +++++++ list_models.js | 35 + package-lock.json | 707 +++++++++++++++++- package.json | 1 + src/app.controller.ts | 8 +- src/app.module.ts | 2 +- .../content-generation.controller.ts | 62 +- .../content-generation.service.ts | 281 ++++++- .../services/deep-research.service.ts | 17 +- .../services/platform-generator.service.ts | 95 ++- .../services/variation.service.ts | 91 ++- src/modules/content/content.controller.ts | 3 +- src/modules/content/content.service.ts | 2 +- src/modules/gemini/gemini.service.ts | 106 ++- .../neuro-marketing.service.ts | 17 + src/modules/seo/seo.service.ts | 52 +- .../services/content-optimization.service.ts | 30 +- .../seo/services/keyword-research.service.ts | 14 + src/modules/trends/trends.service.ts | 4 +- .../services/gemini-image.service.ts | 4 +- .../services/google-image-search.service.ts | 28 + .../services/veo-video.service.ts | 2 +- .../visual-generation.controller.ts | 7 + .../visual-generation.module.ts | 2 + .../visual-generation.service.ts | 6 + test-anon.ts | 22 + test-api-master.ts | 41 + test-api.js | 3 + test-api.ts | 19 + test-db-medium.ts | 18 + test-find-user.ts | 18 + test-recent-master.ts | 25 + test-script.js | 12 + test-script.ts | 12 + test-script2.ts | 19 + 37 files changed, 1925 insertions(+), 157 deletions(-) create mode 100644 debug.log delete mode 100644 gemini_diagnostics.log create mode 100644 generation_debug.log create mode 100644 list_models.js create mode 100644 src/modules/visual-generation/services/google-image-search.service.ts create mode 100644 test-anon.ts create mode 100644 test-api-master.ts create mode 100644 test-api.js create mode 100644 test-api.ts create mode 100644 test-db-medium.ts create mode 100644 test-find-user.ts create mode 100644 test-recent-master.ts create mode 100644 test-script.js create mode 100644 test-script.ts create mode 100644 test-script2.ts diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..f6bc5f4 --- /dev/null +++ b/debug.log @@ -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 diff --git a/gemini_diagnostics.log b/gemini_diagnostics.log deleted file mode 100644 index e69de29..0000000 diff --git a/generation_debug.log b/generation_debug.log new file mode 100644 index 0000000..84012ec --- /dev/null +++ b/generation_debug.log @@ -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 it’s 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 it’s 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 it’s 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 it’s 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 it’s 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üya’nın yeni fragmanı sadece Yandex AI’da! 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üya’nın yeni fragmanı sadece Yandex AI’da! 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üya’nın yeni fragmanı sadece Yandex AI’da! 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üya’nın yeni fragmanı sadece Yandex AI’da! 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üya’nın yeni fragmanı sadece Yandex AI’da! 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 diff --git a/list_models.js b/list_models.js new file mode 100644 index 0000000..aed89b3 --- /dev/null +++ b/list_models.js @@ -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(); diff --git a/package-lock.json b/package-lock.json index c4f6e35..718d4ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 24a4d6c..6a4d7b7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app.controller.ts b/src/app.controller.ts index cce879e..0e31e2f 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -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) {} + constructor(private readonly appService: AppService) { } + @Public() @Get() - getHello(): string { - return this.appService.getHello(); + getHello(): any { + return { status: 'running', message: this.appService.getHello() }; } } diff --git a/src/app.module.ts b/src/app.module.ts index 834457e..c8f25d0 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -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), }, }), diff --git a/src/modules/content-generation/content-generation.controller.ts b/src/modules/content-generation/content-generation.controller.ts index 359bd37..5c636a5 100644 --- a/src/modules/content-generation/content-generation.controller.ts +++ b/src/modules/content-generation/content-generation.controller.ts @@ -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') diff --git a/src/modules/content-generation/content-generation.service.ts b/src/modules/content-generation/content-generation.service.ts index 13e0db6..9106e9d 100644 --- a/src/modules/content-generation/content-generation.service.ts +++ b/src/modules/content-generation/content-generation.service.ts @@ -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); } + // 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); + } + } + 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 + this.logger.error(`Failed to generate for ${platform}`, error); + // Continue to next platform even if one fails } } - console.log(`[ContentGenerationService] Generated content for ${platformContent.length} platforms`); + this.logger.log(`Generated content for ${platformContent.length} platforms`); // Generate variations for primary platform const variations: VariationSet[] = []; @@ -164,29 +203,56 @@ 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) { - const primaryContent = platformContent[0].content; - const seoScore = this.seoService.quickScore(primaryContent, 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'], - meta: { - title: `${topic} | Content Hunter`, - description: research?.summary?.slice(0, 160) || `Learn about ${topic}`, - }, - }; + try { + const primaryContent = platformContent[0].content; + 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, + 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 @@ -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; + } } diff --git a/src/modules/content-generation/services/deep-research.service.ts b/src/modules/content-generation/services/deep-research.service.ts index 7929804..792279c 100644 --- a/src/modules/content-generation/services/deep-research.service.ts +++ b/src/modules/content-generation/services/deep-research.service.ts @@ -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.`; + } /** diff --git a/src/modules/content-generation/services/platform-generator.service.ts b/src/modules/content-generation/services/platform-generator.service.ts index e15008d..0e942c9 100644 --- a/src/modules/content-generation/services/platform-generator.service.ts +++ b/src/modules/content-generation/services/platform-generator.service.ts @@ -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 = { twitter: { @@ -420,14 +464,32 @@ export class PlatformGeneratorService { platform: Platform, format: string, language: string = 'tr', + writingStyle?: string, + ctaType?: string, ): Promise { 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·\n·\n\n💡 Save this for later!\n\nWant more ${topic} tips? Follow @yourhandle`; + content = language === 'tr' + ? `${mainMessage}\n\n·\n·\n·\n\n💡 Bunu kaydet!\n\nDaha fazla ${topic} ipucu için takip et` + : `${mainMessage}\n\n·\n·\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!`; diff --git a/src/modules/content-generation/services/variation.service.ts b/src/modules/content-generation/services/variation.service.ts index a97e808..a52c5fc 100644 --- a/src/modules/content-generation/services/variation.service.ts +++ b/src/modules/content-generation/services/variation.service.ts @@ -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> = { + // 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> = { 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> = { + 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, }); diff --git a/src/modules/content/content.controller.ts b/src/modules/content/content.controller.ts index 45fa28f..761090f 100644 --- a/src/modules/content/content.controller.ts +++ b/src/modules/content/content.controller.ts @@ -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, }); } diff --git a/src/modules/content/content.service.ts b/src/modules/content/content.service.ts index 59b34f0..651de67 100644 --- a/src/modules/content/content.service.ts +++ b/src/modules/content/content.service.ts @@ -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, diff --git a/src/modules/gemini/gemini.service.ts b/src/modules/gemini/gemini.service.ts index 2e36d15..9d8ac3d 100644 --- a/src/modules/gemini/gemini.service.ts +++ b/src/modules/gemini/gemini.service.ts @@ -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('gemini.enabled', false); this.defaultModel = this.configService.get( '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() : ''; + } } diff --git a/src/modules/neuro-marketing/neuro-marketing.service.ts b/src/modules/neuro-marketing/neuro-marketing.service.ts index 2df2acb..6e2080e 100644 --- a/src/modules/neuro-marketing/neuro-marketing.service.ts +++ b/src/modules/neuro-marketing/neuro-marketing.service.ts @@ -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, diff --git a/src/modules/seo/seo.service.ts b/src/modules/seo/seo.service.ts index 82bdc04..1ebbd48 100644 --- a/src/modules/seo/seo.service.ts +++ b/src/modules/seo/seo.service.ts @@ -43,6 +43,7 @@ export class SeoService { metaDescription?: string; url?: string; competitorDomains?: string[]; + language?: 'en' | 'tr' | 'de' | 'es' | 'fr'; }, ): Promise { // 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); @@ -133,21 +137,39 @@ export class SeoService { headings = blueprint.suggestedHeadings; differentiators = blueprint.differentiators; } else { - headings = [ - `What is ${keyword}?`, - `Why ${keyword} is Important`, - `How to Use ${keyword}`, - `${keyword} Best Practices`, - `Common ${keyword} Mistakes`, - `${keyword} Tools & Resources`, - `FAQs about ${keyword}`, - ]; - differentiators = [ - 'Include original research or data', - 'Add expert insights', - 'Provide actionable steps', - 'Include real examples', - ]; + 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}?`, + `Why ${keyword} is Important`, + `How to Use ${keyword}`, + `${keyword} Best Practices`, + `Common ${keyword} Mistakes`, + `${keyword} Tools & Resources`, + `FAQs about ${keyword}`, + ]; + differentiators = [ + 'Include original research or data', + 'Add expert insights', + 'Provide actionable steps', + 'Include real examples', + ]; + } } return { diff --git a/src/modules/seo/services/content-optimization.service.ts b/src/modules/seo/services/content-optimization.service.ts index 6725870..085f26a 100644 --- a/src/modules/seo/services/content-optimization.service.ts +++ b/src/modules/seo/services/content-optimization.service.ts @@ -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', }); } diff --git a/src/modules/seo/services/keyword-research.service.ts b/src/modules/seo/services/keyword-research.service.ts index 67c6abd..3ca5be6 100644 --- a/src/modules/seo/services/keyword-research.service.ts +++ b/src/modules/seo/services/keyword-research.service.ts @@ -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', ], }; diff --git a/src/modules/trends/trends.service.ts b/src/modules/trends/trends.service.ts index 7b005c6..d02a40a 100644 --- a/src/modules/trends/trends.service.ts +++ b/src/modules/trends/trends.service.ts @@ -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 diff --git a/src/modules/visual-generation/services/gemini-image.service.ts b/src/modules/visual-generation/services/gemini-image.service.ts index ec915b4..a97c575 100644 --- a/src/modules/visual-generation/services/gemini-image.service.ts +++ b/src/modules/visual-generation/services/gemini-image.service.ts @@ -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, diff --git a/src/modules/visual-generation/services/google-image-search.service.ts b/src/modules/visual-generation/services/google-image-search.service.ts new file mode 100644 index 0000000..0d2ed4d --- /dev/null +++ b/src/modules/visual-generation/services/google-image-search.service.ts @@ -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 { + 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 + } + }); + }); + } +} diff --git a/src/modules/visual-generation/services/veo-video.service.ts b/src/modules/visual-generation/services/veo-video.service.ts index 7d37473..293d771 100644 --- a/src/modules/visual-generation/services/veo-video.service.ts +++ b/src/modules/visual-generation/services/veo-video.service.ts @@ -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, diff --git a/src/modules/visual-generation/visual-generation.controller.ts b/src/modules/visual-generation/visual-generation.controller.ts index cd77dbd..6fc61ce 100644 --- a/src/modules/visual-generation/visual-generation.controller.ts +++ b/src/modules/visual-generation/visual-generation.controller.ts @@ -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); + } } diff --git a/src/modules/visual-generation/visual-generation.module.ts b/src/modules/visual-generation/visual-generation.module.ts index 3a59dba..9f9e18d 100644 --- a/src/modules/visual-generation/visual-generation.module.ts +++ b/src/modules/visual-generation/visual-generation.module.ts @@ -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], diff --git a/src/modules/visual-generation/visual-generation.service.ts b/src/modules/visual-generation/visual-generation.service.ts index 6057f7b..35814e4 100644 --- a/src/modules/visual-generation/visual-generation.service.ts +++ b/src/modules/visual-generation/visual-generation.service.ts @@ -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 { + return this.searchService.search(query); + } + // ========== UNIFIED GENERATION ========== /** diff --git a/test-anon.ts b/test-anon.ts new file mode 100644 index 0000000..cce7c3e --- /dev/null +++ b/test-anon.ts @@ -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(); diff --git a/test-api-master.ts b/test-api-master.ts new file mode 100644 index 0000000..ff27387 --- /dev/null +++ b/test-api-master.ts @@ -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); diff --git a/test-api.js b/test-api.js new file mode 100644 index 0000000..31de364 --- /dev/null +++ b/test-api.js @@ -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. diff --git a/test-api.ts b/test-api.ts new file mode 100644 index 0000000..8c6185c --- /dev/null +++ b/test-api.ts @@ -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(); diff --git a/test-db-medium.ts b/test-db-medium.ts new file mode 100644 index 0000000..130197c --- /dev/null +++ b/test-db-medium.ts @@ -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()); diff --git a/test-find-user.ts b/test-find-user.ts new file mode 100644 index 0000000..40a1b9f --- /dev/null +++ b/test-find-user.ts @@ -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()); diff --git a/test-recent-master.ts b/test-recent-master.ts new file mode 100644 index 0000000..cae57af --- /dev/null +++ b/test-recent-master.ts @@ -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()); diff --git a/test-script.js b/test-script.js new file mode 100644 index 0000000..15b2d76 --- /dev/null +++ b/test-script.js @@ -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()); diff --git a/test-script.ts b/test-script.ts new file mode 100644 index 0000000..1df75b2 --- /dev/null +++ b/test-script.ts @@ -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(); }); diff --git a/test-script2.ts b/test-script2.ts new file mode 100644 index 0000000..59078ca --- /dev/null +++ b/test-script2.ts @@ -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(); });