generated from fahricansecer/boilerplate-be
This commit is contained in:
56
debug.log
Normal file
56
debug.log
Normal file
@@ -0,0 +1,56 @@
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
|
||||
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
|
||||
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
|
||||
[ERROR] DeepResearchService failed: {"error":{"code":404,"message":"models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
|
||||
ApiError: {"error":{"code":404,"message":"models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
|
||||
at throwErrorIfNotOK (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:12045:30)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
|
||||
at async /Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:11765:13
|
||||
at async Models.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:13095:24)
|
||||
at async GeminiService.generateText (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:135:24)
|
||||
at async GeminiService.generateJSON (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:241:22)
|
||||
at async DeepResearchService.research (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/services/deep-research.service.ts:101:30)
|
||||
at async ContentGenerationService.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.service.ts:99:24)
|
||||
at async ContentGenerationController.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.controller.ts:38:24)
|
||||
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash
|
||||
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash-001
|
||||
[ERROR] DeepResearchService failed: {"error":{"code":404,"message":"models/gemini-1.5-flash-001 is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
|
||||
ApiError: {"error":{"code":404,"message":"models/gemini-1.5-flash-001 is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
|
||||
at throwErrorIfNotOK (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:12045:30)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
|
||||
at async /Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:11765:13
|
||||
at async Models.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/node_modules/@google/genai/dist/node/index.cjs:13095:24)
|
||||
at async GeminiService.generateText (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:135:24)
|
||||
at async GeminiService.generateJSON (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/gemini/gemini.service.ts:241:22)
|
||||
at async DeepResearchService.research (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/services/deep-research.service.ts:101:30)
|
||||
at async ContentGenerationService.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.service.ts:99:24)
|
||||
at async ContentGenerationController.generateContent (/Users/haruncan/Documents/GitHub/ContentHunter/CH_BE/src/modules/content-generation/content-generation.controller.ts:38:24)
|
||||
[DEBUG] GeminiService: Generating text with model gemini-1.5-flash-001
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-lite-001
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-lite-001
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-pro-latest
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
[DEBUG] GeminiService: Generating text with model gemini-2.0-flash-001
|
||||
[DEBUG] GeminiService: Text generation completed
|
||||
261
generation_debug.log
Normal file
261
generation_debug.log
Normal file
@@ -0,0 +1,261 @@
|
||||
[2026-02-14T18:12:11.328Z] REQUEST RECEIVED: {"topic":"Sustainable Living","description":"Tips for eco-friendly living","keywords":["eco","green"],"niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-14T18:12:22.241Z] Generating for twitter with topic: Sustainable Living
|
||||
[2026-02-14T18:12:23.557Z] Generated 315 chars for twitter
|
||||
[2026-02-14T18:12:23.558Z] Content object created for twitter
|
||||
[2026-02-15T17:23:19.053Z] REQUEST RECEIVED: {"topic":"Sustainable Living","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:23:31.138Z] Generating for twitter with topic: Sustainable Living
|
||||
[2026-02-15T17:23:32.630Z] Generated 292 chars for twitter
|
||||
[2026-02-15T17:23:32.630Z] Content object created for twitter
|
||||
[2026-02-15T17:30:05.129Z] REQUEST RECEIVED: {"topic":"Impact of AI on Modern Web Development","niche":"ai-tech","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:30:14.875Z] Generating for twitter with topic: Impact of AI on Modern Web Development
|
||||
[2026-02-15T17:30:16.506Z] Generated 429 chars for twitter
|
||||
[2026-02-15T17:30:16.507Z] Content object created for twitter
|
||||
[2026-02-15T17:33:10.527Z] REQUEST RECEIVED: {"topic":"Sustainable LivingSustainable Living","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:33:20.381Z] Generating for twitter with topic: Sustainable LivingSustainable Living
|
||||
[2026-02-15T17:33:21.601Z] Generated 243 chars for twitter
|
||||
[2026-02-15T17:33:21.601Z] Content object created for twitter
|
||||
[2026-02-15T17:35:10.370Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:35:22.303Z] Generating for twitter with topic: Future of Coding
|
||||
[2026-02-15T17:35:23.850Z] Generated 316 chars for twitter
|
||||
[2026-02-15T17:35:23.850Z] Content object created for twitter
|
||||
[2026-02-15T17:36:44.677Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"","platforms":["twitter","medium"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:36:56.298Z] Generating for twitter with topic: Future of Coding
|
||||
[2026-02-15T17:36:58.147Z] Generated 518 chars for twitter
|
||||
[2026-02-15T17:36:58.147Z] Content object created for twitter
|
||||
[2026-02-15T17:36:58.147Z] Generating for medium with topic: Future of Coding
|
||||
[2026-02-15T17:37:06.263Z] Generated 3653 chars for medium
|
||||
[2026-02-15T17:37:06.263Z] Content object created for medium
|
||||
[2026-02-15T17:38:28.720Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"ai-tech","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:38:38.744Z] Generating for twitter with topic: Future of Coding
|
||||
[2026-02-15T17:38:41.101Z] Generated 514 chars for twitter
|
||||
[2026-02-15T17:38:41.101Z] Content object created for twitter
|
||||
[2026-02-15T17:40:02.990Z] REQUEST RECEIVED: {"topic":"Future of Coding","niche":"ai-tech","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:40:13.277Z] Generating for twitter with topic: Future of Coding
|
||||
[2026-02-15T17:40:14.767Z] Generated 304 chars for twitter
|
||||
[2026-02-15T17:40:14.767Z] Content object created for twitter
|
||||
[2026-02-15T17:42:27.534Z] REQUEST RECEIVED: {"topic":"Remote Work Trends","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:42:39.827Z] Generating for twitter with topic: Remote Work Trends
|
||||
[2026-02-15T17:42:41.128Z] Generated 368 chars for twitter
|
||||
[2026-02-15T17:42:41.128Z] Content object created for twitter
|
||||
[2026-02-15T17:43:38.423Z] REQUEST RECEIVED: {"topic":"Remote Work TrendsRemote Work Trends","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:43:49.195Z] Generating for twitter with topic: Remote Work TrendsRemote Work Trends
|
||||
[2026-02-15T17:43:50.709Z] Generated 327 chars for twitter
|
||||
[2026-02-15T17:43:50.709Z] Content object created for twitter
|
||||
[2026-02-15T17:44:37.651Z] REQUEST RECEIVED: {"topic":"Remote Work TrendsRemote Work Trends","niche":"","platforms":["twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-15T17:44:48.516Z] Generating for twitter with topic: Remote Work TrendsRemote Work Trends
|
||||
[2026-02-15T17:44:49.846Z] Generated 302 chars for twitter
|
||||
[2026-02-15T17:44:49.846Z] Content object created for twitter
|
||||
[2026-02-17T07:28:49.107Z] REQUEST RECEIVED: {"topic":"Artificial Intelligence Trends 2025Artificial Intelligence Trends 2025","niche":"ai-tech","platforms":["twitter","instagram"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-17T07:29:02.055Z] Generating for twitter with topic: Artificial Intelligence Trends 2025Artificial Intelligence Trends 2025
|
||||
[2026-02-17T07:29:04.615Z] Generated 558 chars for twitter
|
||||
[2026-02-17T07:29:04.615Z] Content object created for twitter
|
||||
[2026-02-17T07:29:04.616Z] Generating for instagram with topic: Artificial Intelligence Trends 2025Artificial Intelligence Trends 2025
|
||||
[2026-02-17T07:29:09.494Z] Generated 1561 chars for instagram
|
||||
[2026-02-17T07:29:09.494Z] Content object created for instagram
|
||||
[2026-02-17T07:33:18.818Z] REQUEST RECEIVED: {"topic":"Artificial Intelligence Trends 2025","niche":"ai-tech","platforms":["twitter","instagram","medium"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-17T07:33:33.613Z] Generating for twitter with topic: Artificial Intelligence Trends 2025
|
||||
[2026-02-17T07:33:34.738Z] Generated 297 chars for twitter
|
||||
[2026-02-17T07:33:34.738Z] Content object created for twitter
|
||||
[2026-02-17T07:33:34.739Z] Generating for instagram with topic: Artificial Intelligence Trends 2025
|
||||
[2026-02-17T07:33:41.526Z] Generated 2082 chars for instagram
|
||||
[2026-02-17T07:33:41.526Z] Content object created for instagram
|
||||
[2026-02-17T07:33:41.526Z] Generating for medium with topic: Artificial Intelligence Trends 2025
|
||||
[2026-02-17T07:33:50.321Z] Generated 3083 chars for medium
|
||||
[2026-02-17T07:33:50.321Z] Content object created for medium
|
||||
[2026-02-17T07:54:28.812Z] REQUEST RECEIVED: {"topic":"14-year-old Miles Wu folded origami pattern that holds 10k times its own weight","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","14yearold","miles","folded","origami","pattern","that","holds","times"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-17T07:54:37.876Z] Generating for medium with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
|
||||
[2026-02-17T07:54:41.553Z] Generated 1728 chars for medium
|
||||
[2026-02-17T07:54:41.553Z] Content object created for medium
|
||||
[2026-02-17T07:54:41.554Z] Generating for instagram with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
|
||||
[2026-02-17T07:54:44.523Z] Generated 1374 chars for instagram
|
||||
[2026-02-17T07:54:44.524Z] Content object created for instagram
|
||||
[2026-02-17T07:54:44.524Z] Generating for linkedin with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
|
||||
[2026-02-17T07:54:47.724Z] Generated 1132 chars for linkedin
|
||||
[2026-02-17T07:54:47.724Z] Content object created for linkedin
|
||||
[2026-02-17T07:54:47.725Z] Generating for twitter with topic: 14-year-old Miles Wu folded origami pattern that holds 10k times its own weight
|
||||
[2026-02-17T07:54:48.998Z] Generated 333 chars for twitter
|
||||
[2026-02-17T07:54:48.998Z] Content object created for twitter
|
||||
[2026-02-17T08:10:50.574Z] REQUEST RECEIVED: {"topic":"AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","devlerine","türk","usulü","meydan","okuma","cosmos","sahneye","çıktı"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-17T08:10:58.281Z] Generating for medium with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
|
||||
[2026-02-17T08:11:03.440Z] Generated 1755 chars for medium
|
||||
[2026-02-17T08:11:03.441Z] Content object created for medium
|
||||
[2026-02-17T08:11:03.441Z] Generating for instagram with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
|
||||
[2026-02-17T08:11:08.653Z] Generated 1562 chars for instagram
|
||||
[2026-02-17T08:11:08.654Z] Content object created for instagram
|
||||
[2026-02-17T08:11:08.654Z] Generating for linkedin with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
|
||||
[2026-02-17T08:11:11.604Z] Generated 1015 chars for linkedin
|
||||
[2026-02-17T08:11:11.604Z] Content object created for linkedin
|
||||
[2026-02-17T08:11:11.605Z] Generating for twitter with topic: AI devlerine Türk usulü meydan okuma: Cosmos T1 sahneye çıktı - chip.com.tr
|
||||
[2026-02-17T08:11:12.676Z] Generated 258 chars for twitter
|
||||
[2026-02-17T08:11:12.676Z] Content object created for twitter
|
||||
[2026-02-17T08:35:00.457Z] REQUEST RECEIVED: {"topic":"HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber","description":"Trending across 1 source(s): NEWSAPI","keywords":["ai","hddlerde","etkisi","kapasitesinin","tükendiğini","açıkladı","donanımhaber"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-17T08:35:06.245Z] Generating for medium with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
|
||||
[2026-02-17T08:35:11.472Z] Generated 2160 chars for medium
|
||||
[2026-02-17T08:35:11.473Z] Content object created for medium
|
||||
[2026-02-17T08:35:11.475Z] Generating for instagram with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
|
||||
[2026-02-17T08:35:14.834Z] Generated 1210 chars for instagram
|
||||
[2026-02-17T08:35:14.834Z] Content object created for instagram
|
||||
[2026-02-17T08:35:14.835Z] Generating for linkedin with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
|
||||
[2026-02-17T08:35:17.610Z] Generated 1241 chars for linkedin
|
||||
[2026-02-17T08:35:17.610Z] Content object created for linkedin
|
||||
[2026-02-17T08:35:17.610Z] Generating for twitter with topic: HDD'lerde AI etkisi: WD HDD kapasitesinin tükendiğini açıkladı - DonanımHaber
|
||||
[2026-02-17T08:35:19.131Z] Generated 375 chars for twitter
|
||||
[2026-02-17T08:35:19.131Z] Content object created for twitter
|
||||
[2026-02-17T08:55:10.195Z] REQUEST RECEIVED: {"topic":"India's Adani to invest $100 billion in AI data centers over the next decade - CNBC","description":"Trending across 3 source(s): NEWSAPI, NEWSAPI, NEWSAPI","keywords":["ai","indias","adani","invest","billion","data","centers","over","next","aiready"],"niche":"","platforms":["medium","instagram","linkedin","twitter"],"includeResearch":true,"includeHashtags":true,"brandVoice":"friendly-expert","count":1}
|
||||
[2026-02-17T08:55:16.172Z] Generating for medium with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
|
||||
[2026-02-17T08:55:21.052Z] Generated 2379 chars for medium
|
||||
[2026-02-17T08:55:21.052Z] Content object created for medium
|
||||
[2026-02-17T08:55:21.054Z] Generating for instagram with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
|
||||
[2026-02-17T08:55:24.986Z] Generated 1425 chars for instagram
|
||||
[2026-02-17T08:55:24.987Z] Content object created for instagram
|
||||
[2026-02-17T08:55:24.987Z] Generating for linkedin with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
|
||||
[2026-02-17T08:55:27.636Z] Generated 1229 chars for linkedin
|
||||
[2026-02-17T08:55:27.636Z] Content object created for linkedin
|
||||
[2026-02-17T08:55:27.637Z] Generating for twitter with topic: India's Adani to invest $100 billion in AI data centers over the next decade - CNBC
|
||||
[2026-02-17T08:55:28.808Z] Generated 370 chars for twitter
|
||||
[2026-02-17T08:55:28.809Z] Content object created for twitter
|
||||
[2026-02-17T09:13:15.586Z] REQUEST RECEIVED: {"topic":"This doctor is training AI to do her job. And 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
|
||||
35
list_models.js
Normal file
35
list_models.js
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
const { GoogleGenAI } = require('@google/genai');
|
||||
|
||||
async function listModels() {
|
||||
const apiKey = 'AIzaSyBnrLw5W4NidP4w-70x59fcPolz0izMVfU'; // Hardcoded from .env
|
||||
console.log('Initializing with key:', apiKey.substring(0, 10) + '...');
|
||||
|
||||
try {
|
||||
const client = new GoogleGenAI({ apiKey });
|
||||
console.log('Client initialized.');
|
||||
// Try to list models. In new SDK it might be different.
|
||||
// Checking documentation or guessing.
|
||||
// Based on stack trace: client.models.list() is likely.
|
||||
|
||||
const response = await client.models.list();
|
||||
console.log('Models response:', response);
|
||||
|
||||
if (response && response.models) {
|
||||
response.models.forEach(m => console.log(m.name));
|
||||
} else {
|
||||
// It might be an async iterable
|
||||
for await (const model of response) {
|
||||
console.log(model.name);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error listing models:', error);
|
||||
if (error.response) {
|
||||
console.error('Response data:', error.response.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listModels();
|
||||
707
package-lock.json
generated
707
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { Public } from './common/decorators';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) { }
|
||||
|
||||
@Public()
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
getHello(): any {
|
||||
return { status: 'running', message: this.appService.getHello() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -13,6 +13,7 @@ import { VariationService, VariationSet, VariationOptions } from './services/var
|
||||
import { SeoService, FullSeoAnalysis as SeoDTO } from '../seo/seo.service';
|
||||
import { NeuroMarketingService } from '../neuro-marketing/neuro-marketing.service';
|
||||
import { StorageService } from '../visual-generation/services/storage.service';
|
||||
import { VisualGenerationService } from '../visual-generation/visual-generation.service';
|
||||
import { ContentType as PrismaContentType, ContentStatus as PrismaContentStatus, MasterContentType as PrismaMasterContentType } from '@prisma/client';
|
||||
|
||||
|
||||
@@ -24,11 +25,15 @@ export interface ContentGenerationRequest {
|
||||
includeHashtags?: boolean;
|
||||
brandVoiceId?: string;
|
||||
variationCount?: number;
|
||||
writingStyle?: string;
|
||||
ctaType?: string;
|
||||
}
|
||||
|
||||
export interface SeoAnalysisResult {
|
||||
score: number;
|
||||
keywords: string[];
|
||||
questions: string[];
|
||||
longTail: { keyword: string; estimatedVolume: number; competitionLevel: string }[];
|
||||
suggestions: string[];
|
||||
meta: { title: string; description: string; };
|
||||
}
|
||||
@@ -56,6 +61,9 @@ export interface GeneratedContentBundle {
|
||||
export class ContentGenerationService {
|
||||
private readonly logger = new Logger(ContentGenerationService.name);
|
||||
|
||||
// Visual platforms that should get auto-generated images
|
||||
private readonly VISUAL_PLATFORMS: Platform[] = ['instagram', 'twitter', 'medium'];
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly nicheService: NicheService,
|
||||
@@ -67,6 +75,7 @@ export class ContentGenerationService {
|
||||
private readonly seoService: SeoService,
|
||||
private readonly neuroService: NeuroMarketingService,
|
||||
private readonly storageService: StorageService,
|
||||
private readonly visualService: VisualGenerationService,
|
||||
) { }
|
||||
|
||||
|
||||
@@ -84,7 +93,10 @@ export class ContentGenerationService {
|
||||
includeHashtags = true,
|
||||
brandVoiceId,
|
||||
variationCount = 3,
|
||||
writingStyle,
|
||||
ctaType,
|
||||
} = request;
|
||||
|
||||
console.log(`[ContentGenerationService] Starting generation for topic: ${topic}, platforms: ${platforms.join(', ')}`);
|
||||
|
||||
// Analyze niche if provided
|
||||
@@ -108,19 +120,29 @@ export class ContentGenerationService {
|
||||
const platformContent: GeneratedContent[] = [];
|
||||
for (const platform of platforms) {
|
||||
try {
|
||||
console.log(`[ContentGenerationService] Generating for platform: ${platform}`);
|
||||
this.logger.log(`Generating for platform: ${platform}`);
|
||||
|
||||
// Use AI generation when available
|
||||
// Sanitize research summary to remove source names/URLs
|
||||
const sanitizedSummary = this.sanitizeResearchSummary(
|
||||
research?.summary || `Everything you need to know about ${topic}`
|
||||
);
|
||||
// Normalize platform to lowercase for consistency
|
||||
const normalizedPlatform = platform.toLowerCase();
|
||||
const aiContent = await this.platformService.generateAIContent(
|
||||
topic,
|
||||
research?.summary || `Everything you need to know about ${topic}`,
|
||||
platform,
|
||||
sanitizedSummary,
|
||||
normalizedPlatform as any, // Cast to any/Platform to resolve type mismatch if Platform is strict union
|
||||
'standard',
|
||||
'tr',
|
||||
writingStyle,
|
||||
ctaType,
|
||||
);
|
||||
console.log(`[ContentGenerationService] AI content length for ${platform}: ${aiContent?.length || 0}`);
|
||||
|
||||
this.logger.log(`AI content length for ${platform}: ${aiContent?.length || 0}`);
|
||||
|
||||
if (!aiContent || aiContent.trim().length === 0) {
|
||||
console.warn(`[ContentGenerationService] AI Content is empty for ${platform}`);
|
||||
this.logger.warn(`AI Content is empty for ${platform}`);
|
||||
}
|
||||
|
||||
const config = this.platformService.getPlatformConfig(platform);
|
||||
@@ -147,15 +169,32 @@ export class ContentGenerationService {
|
||||
content.hashtags = hashtagSet.hashtags.map((h) => h.hashtag);
|
||||
}
|
||||
|
||||
platformContent.push(content);
|
||||
console.log(`[ContentGenerationService] Successfully pushed content for ${platform}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to generate content for platform ${platform}: ${error.message}`);
|
||||
// Continue to next platform
|
||||
// Generate image for visual platforms
|
||||
if (this.VISUAL_PLATFORMS.includes(platform)) {
|
||||
try {
|
||||
this.logger.log(`Generating image for visual platform: ${platform}`);
|
||||
const platformKey = platform === 'instagram' ? 'instagram_feed' : platform;
|
||||
const imagePrompt = `${topic} - Professional, high-quality ${platform} visual. Detailed, engaging, and relevant to the topic: ${topic}.`;
|
||||
const image = await this.visualService.generateImage({
|
||||
prompt: imagePrompt,
|
||||
platform: platformKey,
|
||||
enhancePrompt: true,
|
||||
});
|
||||
content.imageUrl = image.url;
|
||||
this.logger.log(`Image generated for ${platform}: ${image.url}`);
|
||||
} catch (imgError) {
|
||||
this.logger.warn(`Image generation failed for ${platform}, continuing without image`, imgError);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[ContentGenerationService] Generated content for ${platformContent.length} platforms`);
|
||||
platformContent.push(content);
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to generate for ${platform}`, error);
|
||||
// Continue to next platform even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`Generated content for ${platformContent.length} platforms`);
|
||||
|
||||
// Generate variations for primary platform
|
||||
const variations: VariationSet[] = [];
|
||||
@@ -164,30 +203,57 @@ export class ContentGenerationService {
|
||||
const variationSet = this.variationService.generateVariations(primaryContent, {
|
||||
count: variationCount,
|
||||
variationType: 'complete',
|
||||
language: 'tr',
|
||||
});
|
||||
variations.push(variationSet);
|
||||
}
|
||||
|
||||
// SEO Analysis
|
||||
// SEO Analysis (Full)
|
||||
let seoResult: SeoAnalysisResult | undefined;
|
||||
if (platformContent.length > 0) {
|
||||
try {
|
||||
const primaryContent = platformContent[0].content;
|
||||
const seoScore = this.seoService.quickScore(primaryContent, topic);
|
||||
const fullSeo = await this.seoService.analyzeFull(primaryContent, topic, { language: 'tr' });
|
||||
const keywordTerms = fullSeo.keywords.related.map(k => k.term);
|
||||
const questions = fullSeo.keywords.main.questions || [];
|
||||
const longTail = fullSeo.keywords.longTail.map(lt => ({
|
||||
keyword: lt.keyword,
|
||||
estimatedVolume: lt.estimatedVolume,
|
||||
competitionLevel: lt.competitionLevel,
|
||||
}));
|
||||
seoResult = {
|
||||
score: fullSeo.content.score.overall,
|
||||
keywords: [fullSeo.keywords.main.term, ...keywordTerms].slice(0, 15),
|
||||
questions,
|
||||
longTail: longTail.slice(0, 10),
|
||||
suggestions: fullSeo.content.score.overall < 70 ? [
|
||||
'Add more keyword density',
|
||||
'Include long-tail keywords',
|
||||
'Add meta description',
|
||||
'Improve content structure with headings',
|
||||
] : ['SEO is well optimized', 'Content structure is strong'],
|
||||
meta: {
|
||||
title: fullSeo.content.meta.title || `${topic} | Content Hunter`,
|
||||
description: fullSeo.content.meta.description || research?.summary?.slice(0, 160) || `Learn about ${topic}`,
|
||||
},
|
||||
};
|
||||
} catch (seoError) {
|
||||
this.logger.warn(`Full SEO analysis failed, falling back to basic`, seoError);
|
||||
const seoScore = this.seoService.quickScore(platformContent[0].content, topic);
|
||||
const lsiKeywords = this.seoService.getLSIKeywords(topic, 10);
|
||||
seoResult = {
|
||||
score: seoScore,
|
||||
keywords: lsiKeywords,
|
||||
suggestions: seoScore < 70 ? [
|
||||
'Add more keyword density',
|
||||
'Include long-tail keywords',
|
||||
'Add meta description',
|
||||
] : ['SEO is optimized'],
|
||||
questions: [],
|
||||
longTail: [],
|
||||
suggestions: seoScore < 70 ? ['Add more keyword density', 'Include long-tail keywords'] : ['SEO is optimized'],
|
||||
meta: {
|
||||
title: `${topic} | Content Hunter`,
|
||||
description: research?.summary?.slice(0, 160) || `Learn about ${topic}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Neuro Marketing Analysis
|
||||
let neuroResult: NeuroAnalysisResult | undefined;
|
||||
@@ -220,8 +286,30 @@ export class ContentGenerationService {
|
||||
/**
|
||||
* Persist generated content bundle to database
|
||||
*/
|
||||
async saveGeneratedBundle(userId: string, bundle: GeneratedContentBundle): Promise<{ masterContentId: string }> {
|
||||
console.log(`[ContentGenerationService] Saving bundle for user ${userId}, topic: ${bundle.topic}`);
|
||||
async saveGeneratedBundle(userId: string | null, bundle: GeneratedContentBundle): Promise<{ masterContentId: string }> {
|
||||
// If no userId, try to find or create a system anonymous user
|
||||
let effectiveUserId = userId;
|
||||
if (!effectiveUserId) {
|
||||
try {
|
||||
const anonUser = await this.prisma.user.findFirst({ where: { email: 'anonymous@contenthunter.system' } });
|
||||
if (anonUser) {
|
||||
effectiveUserId = anonUser.id;
|
||||
} else {
|
||||
const newAnon = await this.prisma.user.create({
|
||||
data: {
|
||||
email: 'anonymous@contenthunter.system',
|
||||
password: 'system-anonymous-no-login',
|
||||
firstName: 'Anonymous',
|
||||
},
|
||||
});
|
||||
effectiveUserId = newAnon.id;
|
||||
}
|
||||
} catch (anonError) {
|
||||
this.logger.warn(`Could not create anonymous user, content will not be saved: ${anonError.message}`);
|
||||
return { masterContentId: 'not-saved' };
|
||||
}
|
||||
}
|
||||
console.log(`[ContentGenerationService] Saving bundle for user ${effectiveUserId}, topic: ${bundle.topic}`);
|
||||
try {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
// 1. Create DeepResearch if it exists in bundle
|
||||
@@ -229,9 +317,9 @@ export class ContentGenerationService {
|
||||
if (bundle.research) {
|
||||
const research = await tx.deepResearch.create({
|
||||
data: {
|
||||
userId,
|
||||
userId: effectiveUserId!,
|
||||
topic: bundle.topic,
|
||||
query: bundle.topic, // Simplified for now
|
||||
query: bundle.topic,
|
||||
summary: bundle.research.summary,
|
||||
sources: JSON.parse(JSON.stringify(bundle.research.sources)),
|
||||
keyFindings: JSON.parse(JSON.stringify(bundle.research.keyFindings)),
|
||||
@@ -245,10 +333,10 @@ export class ContentGenerationService {
|
||||
// 2. Create MasterContent
|
||||
const masterContent = await tx.masterContent.create({
|
||||
data: {
|
||||
userId,
|
||||
userId: effectiveUserId!,
|
||||
title: bundle.topic,
|
||||
body: bundle.platforms[0]?.content || '', // Use first platform as master body for now
|
||||
type: PrismaMasterContentType.BLOG, // Default
|
||||
body: bundle.platforms[0]?.content || '',
|
||||
type: PrismaMasterContentType.BLOG,
|
||||
status: PrismaContentStatus.DRAFT,
|
||||
researchId,
|
||||
summary: bundle.research?.summary,
|
||||
@@ -267,7 +355,7 @@ export class ContentGenerationService {
|
||||
|
||||
const content = await tx.content.create({
|
||||
data: {
|
||||
userId,
|
||||
userId: effectiveUserId!,
|
||||
masterContentId: masterContent.id,
|
||||
type: contentType,
|
||||
title: `${bundle.topic} - ${platformContent.platform}`,
|
||||
@@ -302,7 +390,19 @@ export class ContentGenerationService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`[ContentGenerationService] Bundle saved successfully. MasterContentId: ${masterContent.id}`);
|
||||
|
||||
// Robust Verification
|
||||
const savedCount = await tx.content.count({
|
||||
where: { masterContentId: masterContent.id }
|
||||
});
|
||||
|
||||
if (savedCount !== bundle.platforms.length) {
|
||||
this.logger.error(`[CRITICAL] Save mismatch! Expected ${bundle.platforms.length} items, found ${savedCount}. MasterID: ${masterContent.id}`);
|
||||
// Ensure we at least have the master content
|
||||
}
|
||||
|
||||
return { masterContentId: masterContent.id };
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -411,4 +511,117 @@ export class ContentGenerationService {
|
||||
createABTest(content: string) {
|
||||
return this.variationService.createABTest(content);
|
||||
}
|
||||
|
||||
// ========== NEURO REGENERATION ==========
|
||||
|
||||
async regenerateForNeuro(request: {
|
||||
content: string;
|
||||
platform: string;
|
||||
currentScore: number;
|
||||
improvements: string[];
|
||||
}): Promise<{ content: string; score: number; improvements: string[] }> {
|
||||
const { content, platform, currentScore, improvements } = request;
|
||||
|
||||
// Use platform service to regenerate with neuro optimization
|
||||
const platformEnum = platform as Platform;
|
||||
const improvementList = improvements.join('\n- ');
|
||||
|
||||
const neuroPrompt = `Sen nöro-pazarlama uzmanı bir sosyal medya içerik yazarısın.
|
||||
|
||||
MEVCUT İÇERİK:
|
||||
${content}
|
||||
|
||||
MEVCUT NÖRO SKORU: ${currentScore}/100
|
||||
|
||||
İYİLEŞTİRME ÖNERİLERİ:
|
||||
- ${improvementList}
|
||||
|
||||
GÖREV: Yukarıdaki içeriği nöro-pazarlama ilkeleri kullanarak yeniden yaz.
|
||||
Mevcut mesajı koru ama psikolojik etkiyi artır.
|
||||
|
||||
KURALLAR:
|
||||
1. Güçlü bir hook ile başla (merak, şok, soru)
|
||||
2. Duygusal tetikleyiciler kullan (korku, heyecan, aidiyet)
|
||||
3. Sosyal kanıt ekle
|
||||
4. Aciliyet hissi yarat
|
||||
5. Güçlü bir CTA ile bitir
|
||||
6. Karakter limitini koru
|
||||
7. Platformun tonuna uygun yaz
|
||||
8. SADECE yayınlanacak metni yaz
|
||||
|
||||
SADECE yeniden yazılmış metni döndür, açıklama ekleme.`;
|
||||
|
||||
try {
|
||||
const response = await this.platformService.generateAIContent(
|
||||
neuroPrompt,
|
||||
content,
|
||||
platformEnum,
|
||||
'standard',
|
||||
'tr',
|
||||
);
|
||||
|
||||
// Re-analyze with neuro service
|
||||
const analysis = this.neuroService.analyze(response, platformEnum);
|
||||
|
||||
return {
|
||||
content: response,
|
||||
score: analysis.prediction.overallScore,
|
||||
improvements: analysis.prediction.improvements,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`Neuro regeneration failed: ${error.message}`);
|
||||
return {
|
||||
content,
|
||||
score: currentScore,
|
||||
improvements: ['Regeneration failed, try again'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip source names, URLs, and attribution phrases from research summary
|
||||
* to prevent them from leaking into generated content.
|
||||
*/
|
||||
private sanitizeResearchSummary(summary: string): string {
|
||||
let sanitized = summary;
|
||||
|
||||
// Remove URLs
|
||||
sanitized = sanitized.replace(/https?:\/\/[^\s]+/gi, '');
|
||||
sanitized = sanitized.replace(/www\.[^\s]+/gi, '');
|
||||
|
||||
// Remove common Turkish attribution phrases
|
||||
const attributionPatterns = [
|
||||
/\b\w+\.com(\.tr)?\b/gi,
|
||||
/\b\w+\.org(\.tr)?\b/gi,
|
||||
/\b\w+\.net(\.tr)?\b/gi,
|
||||
/\bkaynağına göre\b/gi,
|
||||
/\b'e göre\b/gi,
|
||||
/\baccording to [^,.]+/gi,
|
||||
/\bsource:\s*[^,.]+/gi,
|
||||
/\breferans:\s*[^,.]+/gi,
|
||||
/\bkaynak:\s*[^,.]+/gi,
|
||||
];
|
||||
|
||||
// Common Turkish tech/news source brands to strip
|
||||
const sourceNames = [
|
||||
'donanımhaber', 'technopat', 'webtekno', 'shiftdelete',
|
||||
'chip online', 'log.com', 'mediatrend', 'bbc', 'cnn',
|
||||
'reuters', 'anadolu ajansı', 'hürriyet', 'milliyet',
|
||||
'sabah', 'forbes', 'bloomberg', 'techcrunch',
|
||||
];
|
||||
|
||||
for (const pattern of attributionPatterns) {
|
||||
sanitized = sanitized.replace(pattern, '');
|
||||
}
|
||||
|
||||
for (const source of sourceNames) {
|
||||
const regex = new RegExp(`\\b${source}\\b`, 'gi');
|
||||
sanitized = sanitized.replace(regex, '');
|
||||
}
|
||||
|
||||
// Clean up multiple spaces and trailing commas
|
||||
sanitized = sanitized.replace(/\s{2,}/g, ' ').replace(/,\s*,/g, ',').trim();
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.`;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface GeneratedContent {
|
||||
characterCount: number;
|
||||
isWithinLimit: boolean;
|
||||
variations?: string[];
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export interface MultiPlatformContent {
|
||||
@@ -55,12 +56,55 @@ export interface MultiPlatformContent {
|
||||
platforms: GeneratedContent[];
|
||||
}
|
||||
|
||||
export interface WritingStyle {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
promptInstruction: string;
|
||||
}
|
||||
|
||||
export const WRITING_STYLES: WritingStyle[] = [
|
||||
{ id: 'professional', name: 'Professional', description: 'Corporate, polished, and authoritative tone', promptInstruction: 'Kurumsal, cilalı ve otoriter bir ton kullan. Profesyonel ve güvenilir bir ses ile yaz.' },
|
||||
{ id: 'conversational', name: 'Conversational', description: 'Friendly, casual, like talking to a friend', promptInstruction: 'Samimi ve sıcak bir ton kullan. Bir arkadaşınla sohbet eder gibi yaz, doğal ol.' },
|
||||
{ id: 'humorous', name: 'Humorous', description: 'Witty, fun, with clever humor', promptInstruction: 'Esprili ve eğlenceli yaz. Zekice şakalar ve kelime oyunları kullan ama içeriğin değerini koru.' },
|
||||
{ id: 'storytelling', name: 'Storytelling', description: 'Narrative-driven, engaging stories', promptInstruction: 'Hikaye anlatma tekniği kullan. Bir olay örgüsü kur, karakterler ve sahneler ekle. Okuyucuyu hikayenin içine çek.' },
|
||||
{ id: 'educational', name: 'Educational', description: 'Informative, teacher-like, step-by-step', promptInstruction: 'Eğitici ve öğretici bir ton kullan. Adım adım açıkla, karmaşık konuları basitleştir. Öğretmen gibi yaz.' },
|
||||
{ id: 'inspirational', name: 'Inspirational', description: 'Motivating, uplifting, empowering', promptInstruction: 'İlham verici ve motive edici yaz. Güçlü duygusal ifadeler kullan. Okuyucuyu harekete geçir ve cesaretlendir.' },
|
||||
{ id: 'provocative', name: 'Provocative', description: 'Bold, thought-provoking, challenging norms', promptInstruction: 'Cesur ve düşündürücü yaz. Kalıpları kır, soru sor, normları sorgula. Tartışma başlatacak bir ton kullan.' },
|
||||
{ id: 'minimalist', name: 'Minimalist', description: 'Clean, concise, every word counts', promptInstruction: 'Minimalist yaz. Her kelime önemli, gereksiz kelime kullanma. Kısa, net ve öz cümleler kur.' },
|
||||
{ id: 'data-driven', name: 'Data-Driven', description: 'Stats-heavy, research-backed, analytical', promptInstruction: 'Veri odaklı yaz. İstatistikler, yüzdeler ve araştırma sonuçları kullan. Analitik ve kanıta dayalı bir ton benimse.' },
|
||||
{ id: 'emotional', name: 'Emotional', description: 'Heart-touching, empathetic, deeply personal', promptInstruction: 'Duygusal ve empatik yaz. Kişisel ve samimi bir ton kullan. Okuyucunun duygularına hitap et, yürekten yaz.' },
|
||||
{ id: 'luxury', name: 'Luxury', description: 'Elegant, sophisticated, premium feel', promptInstruction: 'Lüks ve sofistike bir ton kullan. Zarif kelimeler seç, premium bir his oluştur. Ayrıcalıklı ve seçkin bir ses kullan.' },
|
||||
{ id: 'casual-gen-z', name: 'Gen-Z Casual', description: 'Trendy, meme-aware, internet culture', promptInstruction: 'Gen-Z diline uygun yaz. Internet kültürünü, trend kelimeleri ve güncel jargonu kullan. Rahat ve cool bir ses benimse.' },
|
||||
{ id: 'journalistic', name: 'Journalistic', description: 'News-style, factual, objective reporting', promptInstruction: 'Gazetecilik tarzında yaz. Nesnel, gerçeklere dayalı ve haber formatında ol. 5N1K kuralını uygula.' },
|
||||
{ id: 'poetic', name: 'Poetic', description: 'Lyrical, metaphor-rich, artistic', promptInstruction: 'Şiirsel ve sanatsal yaz. Metaforlar, benzetmeler ve imgeler kullan. Dilin müziğini hissettir.' },
|
||||
{ id: 'sales-copy', name: 'Sales Copy', description: 'Persuasive, benefit-focused, conversion-oriented', promptInstruction: 'Satış odaklı yaz. Faydaları vurgula, ikna edici bir dil kullan. AIDA formülünü uygula: Dikkat, İlgi, Arzu, Aksiyon.' },
|
||||
];
|
||||
|
||||
export const CTA_TYPES = [
|
||||
{ id: 'follow', name: 'Follow', description: 'Takip et', promptInstruction: 'Hesabı takip etmeye davet eden bir CTA ekle.' },
|
||||
{ id: 'link', name: 'Click Link', description: 'Linke tıkla', promptInstruction: 'Bio\'daki linke veya belirtilen linke tıklamaya davet eden bir CTA ekle.' },
|
||||
{ id: 'comment', name: 'Comment', description: 'Yorum yap', promptInstruction: 'Yorum yapmaya ve düşünce paylaşmaya davet eden bir CTA ekle.' },
|
||||
{ id: 'share', name: 'Share', description: 'Paylaş', promptInstruction: 'İçeriği paylaşmaya davet eden bir CTA ekle.' },
|
||||
{ id: 'buy', name: 'Buy Now', description: 'Hemen al', promptInstruction: 'Ürünü satın almaya teşvik eden bir CTA ekle.' },
|
||||
{ id: 'subscribe', name: 'Subscribe', description: 'Abone ol', promptInstruction: 'Abone olmaya veya listeye katılmaya davet eden bir CTA ekle.' },
|
||||
{ id: 'learn-more', name: 'Learn More', description: 'Daha fazla öğren', promptInstruction: 'Daha fazla bilgi edinmeye yönlendiren bir CTA ekle.' },
|
||||
{ id: 'custom', name: 'Custom', description: 'Özel CTA', promptInstruction: 'Etkili ve ikna edici bir CTA ekle.' },
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class PlatformGeneratorService {
|
||||
private readonly logger = new Logger(PlatformGeneratorService.name);
|
||||
|
||||
constructor(private readonly gemini: GeminiService) { }
|
||||
|
||||
/**
|
||||
* Get available writing styles
|
||||
*/
|
||||
getWritingStyles(): WritingStyle[] {
|
||||
return WRITING_STYLES;
|
||||
}
|
||||
|
||||
// Platform configurations
|
||||
private readonly platforms: Record<Platform, PlatformConfig> = {
|
||||
twitter: {
|
||||
@@ -420,14 +464,32 @@ export class PlatformGeneratorService {
|
||||
platform: Platform,
|
||||
format: string,
|
||||
language: string = 'tr',
|
||||
writingStyle?: string,
|
||||
ctaType?: string,
|
||||
): Promise<string> {
|
||||
const config = this.platforms[platform];
|
||||
|
||||
if (!config) {
|
||||
throw new Error(`Invalid platform: ${platform}`);
|
||||
}
|
||||
|
||||
if (!this.gemini.isAvailable()) {
|
||||
this.logger.warn('Gemini not available, using template fallback');
|
||||
return this.generateTemplateContent(topic, mainMessage, config);
|
||||
}
|
||||
|
||||
// Resolve writing style instruction
|
||||
const style = WRITING_STYLES.find(s => s.id === writingStyle);
|
||||
const styleInstruction = style
|
||||
? `\nYAZIM STİLİ: ${style.name}\nSTİL TALİMATI: ${style.promptInstruction}`
|
||||
: '';
|
||||
|
||||
// Resolve CTA instruction
|
||||
const cta = CTA_TYPES.find(c => c.id === ctaType);
|
||||
const ctaInstruction = cta
|
||||
? `\nCTA TİPİ: ${cta.name}\nCTA TALİMATI: ${cta.promptInstruction}`
|
||||
: '\nCTA TALİMATI: İçeriğin sonunda etkili bir CTA (harekete geçirici mesaj) ekle.';
|
||||
|
||||
const prompt = `Sen profesyonel bir sosyal medya içerik uzmanısın.
|
||||
|
||||
PLATFORM: ${config.name}
|
||||
@@ -436,7 +498,7 @@ ANA MESAJ: ${mainMessage}
|
||||
FORMAT: ${format}
|
||||
KARAKTER LİMİTİ: ${config.maxCharacters}
|
||||
MAX HASHTAG: ${config.maxHashtags}
|
||||
TON: ${config.tone}
|
||||
TON: ${config.tone}${styleInstruction}${ctaInstruction}
|
||||
|
||||
Bu platform için özgün, ilgi çekici ve viral potansiyeli yüksek bir içerik oluştur.
|
||||
|
||||
@@ -444,11 +506,19 @@ KURALLAR:
|
||||
1. Karakter limitine uy
|
||||
2. Platformun tonuna uygun yaz
|
||||
3. Hook (dikkat çeken giriş) ile başla
|
||||
4. CTA (harekete geçirici) ile bitir
|
||||
4. CTA ile bitir (yukarıdaki CTA talimatına göre)
|
||||
5. Emoji kullan ama aşırıya kaçma
|
||||
6. ${language === 'tr' ? 'Türkçe' : 'İngilizce'} yaz
|
||||
7. ASLA resim URL'i, medya linki veya [görsel] gibi yer tutucular ekleme
|
||||
8. Görsel betimlemeleri metnin içine YAZMA
|
||||
9. İçerik %100 özgün olmalı - asla kaynak kopyası yapma
|
||||
10. Kaynak linklerini, URL'leri veya atıfları ASLA ekleme
|
||||
11. Mevcut içeriklerden alıntı yapma, tamamen yeni ve orijinal yaz
|
||||
12. Bilgiyi kendi cümlelerinle ifade et, paraphrase bile yapma
|
||||
13. Araştırma kaynaklarının isimlerini (web siteleri, haber siteleri, markalar, gazeteler) ASLA metinde kullanma veya referans verme
|
||||
14. "...göre", "...kaynağına göre", "according to" gibi atıf ifadeleri ASLA kullanma
|
||||
|
||||
SADECE içeriği yaz, açıklama veya başlık ekleme.`;
|
||||
SADECE yayınlanacak metni yaz, açıklama veya başlık ekleme.`;
|
||||
|
||||
try {
|
||||
const response = await this.gemini.generateText(prompt, {
|
||||
@@ -458,7 +528,7 @@ SADECE içeriği yaz, açıklama veya başlık ekleme.`;
|
||||
return response.text;
|
||||
} catch (error) {
|
||||
this.logger.error(`AI content generation failed: ${error.message}`);
|
||||
return this.generateTemplateContent(topic, mainMessage, config);
|
||||
return this.generateTemplateContent(topic, mainMessage, config, language);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,21 +536,30 @@ SADECE içeriği yaz, açıklama veya başlık ekleme.`;
|
||||
topic: string,
|
||||
mainMessage: string,
|
||||
config: PlatformConfig,
|
||||
language: string = 'tr',
|
||||
): string {
|
||||
let content = '';
|
||||
|
||||
switch (config.platform) {
|
||||
case 'twitter':
|
||||
content = `${mainMessage}\n\nLearn more about ${topic} 👇`;
|
||||
content = language === 'tr'
|
||||
? `${mainMessage}\n\n${topic} hakkında daha fazlası 👇`
|
||||
: `${mainMessage}\n\nLearn more about ${topic} 👇`;
|
||||
break;
|
||||
case 'instagram':
|
||||
content = `${mainMessage}\n\n·\n·\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!`;
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface VariationOptions {
|
||||
preserveCore?: boolean;
|
||||
targetLength?: 'shorter' | 'same' | 'longer';
|
||||
toneShift?: string;
|
||||
language?: 'tr' | 'en';
|
||||
}
|
||||
|
||||
export type VariationType =
|
||||
@@ -38,8 +39,8 @@ export interface VariationSet {
|
||||
export class VariationService {
|
||||
private readonly logger = new Logger(VariationService.name);
|
||||
|
||||
// Hook variations
|
||||
private readonly hookTemplates = {
|
||||
// Hook variations (English)
|
||||
private readonly hookTemplatesEn = {
|
||||
question: (topic: string) => `Ever wondered why ${topic}?`,
|
||||
bold: (topic: string) => `Here's the truth about ${topic}:`,
|
||||
story: (topic: string) => `Last year, I learned something about ${topic} that changed everything.`,
|
||||
@@ -50,8 +51,24 @@ export class VariationService {
|
||||
curiosity: (topic: string) => `The ${topic} secret nobody talks about:`,
|
||||
};
|
||||
|
||||
// Tone modifiers
|
||||
private readonly toneModifiers: Record<string, (text: string) => string> = {
|
||||
// Hook variations (Turkish)
|
||||
private readonly hookTemplatesTr = {
|
||||
question: (topic: string) => `${topic} hakkında hiç merak ettiniz mi?`,
|
||||
bold: (topic: string) => `İşte ${topic} hakkındaki gerçek:`,
|
||||
story: (topic: string) => `Geçen yıl ${topic} hakkında her şeyi değiştiren bir şey öğrendim.`,
|
||||
statistic: (topic: string) => `İnsanların %73'ü ${topic} konusunda yanılıyor. İşte keşfettiğim:`,
|
||||
contrarian: (topic: string) => `${topic} hakkında size söylenen her şey yanlış.`,
|
||||
pain: (topic: string) => `${topic} konusunda zorlanıyor musunuz? Yalnız değilsiniz.`,
|
||||
promise: (topic: string) => `Ya ${topic} konusunda sadece 30 günde uzmanlaşabilseydiniz?`,
|
||||
curiosity: (topic: string) => `Kimsenin bahsetmediği ${topic} sırrı:`,
|
||||
};
|
||||
|
||||
private getHookTemplates(language: 'tr' | 'en' = 'en') {
|
||||
return language === 'tr' ? this.hookTemplatesTr : this.hookTemplatesEn;
|
||||
}
|
||||
|
||||
// Tone modifiers (English)
|
||||
private readonly toneModifiersEn: Record<string, (text: string) => string> = {
|
||||
professional: (text) => text.replace(/!/g, '.').replace(/lol|haha|😂/gi, ''),
|
||||
casual: (text) => text.replace(/Furthermore,/g, 'Also,').replace(/However,/g, 'But'),
|
||||
enthusiastic: (text) => text.replace(/\./g, '!').replace(/good/gi, 'amazing'),
|
||||
@@ -60,6 +77,20 @@ export class VariationService {
|
||||
humorous: (text) => text + ' (And yes, I learned this the hard way 😅)',
|
||||
};
|
||||
|
||||
// Tone modifiers (Turkish)
|
||||
private readonly toneModifiersTr: Record<string, (text: string) => string> = {
|
||||
professional: (text) => text.replace(/!/g, '.').replace(/haha|😂/gi, ''),
|
||||
casual: (text) => text.replace(/Ayrıca,/g, 'Bunun yanı sıra,').replace(/Ancak,/g, 'Ama'),
|
||||
enthusiastic: (text) => text.replace(/\./g, '!').replace(/iyi/gi, 'muhteşem'),
|
||||
empathetic: (text) => `Bunun ne kadar zor olduğunu anlıyorum. ${text}`,
|
||||
urgent: (text) => `Beklemeyin. ${text} Zaman daralıyor.`,
|
||||
humorous: (text) => text + ' (Evet, bunu zor yoldan öğrendim 😅)',
|
||||
};
|
||||
|
||||
private getToneModifiers(language: 'tr' | 'en' = 'en') {
|
||||
return language === 'tr' ? this.toneModifiersTr : this.toneModifiersEn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate variations of content
|
||||
*/
|
||||
@@ -67,18 +98,18 @@ export class VariationService {
|
||||
content: string,
|
||||
options: VariationOptions = {},
|
||||
): VariationSet {
|
||||
const { count = 3, variationType = 'complete', preserveCore = true } = options;
|
||||
const { count = 3, variationType = 'complete', preserveCore = true, language = 'en' } = options;
|
||||
const variations: ContentVariation[] = [];
|
||||
|
||||
switch (variationType) {
|
||||
case 'hook':
|
||||
variations.push(...this.generateHookVariations(content, count));
|
||||
variations.push(...this.generateHookVariations(content, count, language));
|
||||
break;
|
||||
case 'angle':
|
||||
variations.push(...this.generateAngleVariations(content, count));
|
||||
variations.push(...this.generateAngleVariations(content, count, language));
|
||||
break;
|
||||
case 'tone':
|
||||
variations.push(...this.generateToneVariations(content, count, options.toneShift));
|
||||
variations.push(...this.generateToneVariations(content, count, options.toneShift, language));
|
||||
break;
|
||||
case 'format':
|
||||
variations.push(...this.generateFormatVariations(content, count));
|
||||
@@ -88,7 +119,7 @@ export class VariationService {
|
||||
break;
|
||||
case 'complete':
|
||||
default:
|
||||
variations.push(...this.generateCompleteVariations(content, count, preserveCore));
|
||||
variations.push(...this.generateCompleteVariations(content, count, preserveCore, language));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -127,14 +158,15 @@ export class VariationService {
|
||||
/**
|
||||
* Generate hook-only variations
|
||||
*/
|
||||
private generateHookVariations(content: string, count: number): ContentVariation[] {
|
||||
private generateHookVariations(content: string, count: number, language: 'tr' | 'en' = 'en'): ContentVariation[] {
|
||||
const topic = this.extractTopic(content);
|
||||
const hookTypes = Object.keys(this.hookTemplates);
|
||||
const hookTemplates = this.getHookTemplates(language);
|
||||
const hookTypes = Object.keys(hookTemplates);
|
||||
const variations: ContentVariation[] = [];
|
||||
|
||||
for (let i = 0; i < Math.min(count, hookTypes.length); i++) {
|
||||
const hookType = hookTypes[i] as keyof typeof this.hookTemplates;
|
||||
const newHook = this.hookTemplates[hookType](topic);
|
||||
const hookType = hookTypes[i] as keyof typeof hookTemplates;
|
||||
const newHook = hookTemplates[hookType](topic);
|
||||
|
||||
// Replace first line with new hook
|
||||
const lines = content.split('\n');
|
||||
@@ -155,14 +187,22 @@ export class VariationService {
|
||||
/**
|
||||
* Generate angle variations
|
||||
*/
|
||||
private generateAngleVariations(content: string, count: number): ContentVariation[] {
|
||||
const angles = [
|
||||
private generateAngleVariations(content: string, count: number, language: 'tr' | 'en' = 'en'): ContentVariation[] {
|
||||
const anglesEn = [
|
||||
{ name: 'how-to', prefix: 'Here\'s how to', focus: 'actionable steps' },
|
||||
{ name: 'why', prefix: 'This is why', focus: 'reasoning and benefits' },
|
||||
{ name: 'what-if', prefix: 'What if you could', focus: 'possibilities' },
|
||||
{ name: 'mistake', prefix: 'The biggest mistake people make with', focus: 'what not to do' },
|
||||
{ name: 'story', prefix: 'When I first started with', focus: 'personal experience' },
|
||||
];
|
||||
const anglesTr = [
|
||||
{ name: 'nasıl', prefix: 'İşte nasıl', focus: 'uygulanabilir adımlar' },
|
||||
{ name: 'neden', prefix: 'İşte bu yüzden', focus: 'nedenler ve faydalar' },
|
||||
{ name: 'ya-olsaydı', prefix: 'Ya şunu yapabilseydiniz:', focus: 'olasılıklar' },
|
||||
{ name: 'hata', prefix: 'İnsanların en büyük hatası:', focus: 'ne yapılmamalı' },
|
||||
{ name: 'hikaye', prefix: 'İlk başladığımda', focus: 'kişisel deneyim' },
|
||||
];
|
||||
const angles = language === 'tr' ? anglesTr : anglesEn;
|
||||
|
||||
const topic = this.extractTopic(content);
|
||||
const variations: ContentVariation[] = [];
|
||||
@@ -188,13 +228,15 @@ export class VariationService {
|
||||
content: string,
|
||||
count: number,
|
||||
specificTone?: string,
|
||||
language: 'tr' | 'en' = 'en',
|
||||
): ContentVariation[] {
|
||||
const toneModifiers = this.getToneModifiers(language);
|
||||
const tones = specificTone
|
||||
? [specificTone]
|
||||
: Object.keys(this.toneModifiers).slice(0, count);
|
||||
: Object.keys(toneModifiers).slice(0, count);
|
||||
|
||||
return tones.map((tone, i) => {
|
||||
const modifier = this.toneModifiers[tone];
|
||||
const modifier = toneModifiers[tone];
|
||||
const modified = modifier ? modifier(content) : content;
|
||||
|
||||
return {
|
||||
@@ -289,16 +331,21 @@ export class VariationService {
|
||||
content: string,
|
||||
count: number,
|
||||
preserveCore: boolean,
|
||||
language: 'tr' | 'en' = 'en',
|
||||
): ContentVariation[] {
|
||||
const variations: ContentVariation[] = [];
|
||||
const topic = this.extractTopic(content);
|
||||
const core = this.keepCore(content);
|
||||
|
||||
const isTr = language === 'tr';
|
||||
|
||||
// Variation 1: Different angle + hook
|
||||
variations.push({
|
||||
id: 'complete-1',
|
||||
type: 'complete',
|
||||
content: `The truth about ${topic}:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nSave this for later.`,
|
||||
content: isTr
|
||||
? `İşte ${topic} hakkındaki gerçek:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nBunu kaydet, lazım olacak.`
|
||||
: `The truth about ${topic}:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nSave this for later.`,
|
||||
changes: ['New hook', 'New CTA', preserveCore ? 'Preserved core' : 'Rewrote core'],
|
||||
similarity: preserveCore ? 60 : 40,
|
||||
});
|
||||
@@ -308,7 +355,9 @@ export class VariationService {
|
||||
variations.push({
|
||||
id: 'complete-2',
|
||||
type: 'complete',
|
||||
content: `I wish someone told me this about ${topic} earlier:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nShare if this helped.`,
|
||||
content: isTr
|
||||
? `Keşke biri bana ${topic} hakkında daha önce bunu söyleseydi:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nFaydalı bulduysan paylaş.`
|
||||
: `I wish someone told me this about ${topic} earlier:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nShare if this helped.`,
|
||||
changes: ['Story-based approach', 'Personal angle'],
|
||||
similarity: preserveCore ? 55 : 35,
|
||||
});
|
||||
@@ -319,7 +368,9 @@ export class VariationService {
|
||||
variations.push({
|
||||
id: 'complete-3',
|
||||
type: 'complete',
|
||||
content: `Stop what you're doing.\n\nThis ${topic} insight is too important:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nYou're welcome.`,
|
||||
content: isTr
|
||||
? `Dur bir dakika.\n\nBu ${topic} bilgisi çok önemli:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nRica ederim.`
|
||||
: `Stop what you're doing.\n\nThis ${topic} insight is too important:\n\n${preserveCore ? core : this.rewriteCore(core)}\n\nYou're welcome.`,
|
||||
changes: ['Bold/direct style', 'Urgency added'],
|
||||
similarity: preserveCore ? 50 : 30,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { GoogleGenAI } from '@google/genai';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const LOG_FILE = path.join(process.cwd(), 'gemini_diagnostics.log');
|
||||
function logToFile(message: string) {
|
||||
const timestamp = new Date().toISOString();
|
||||
fs.appendFileSync(LOG_FILE, `[${timestamp}] ${message}\n`);
|
||||
}
|
||||
|
||||
export interface GeminiGenerateOptions {
|
||||
model?: string;
|
||||
systemPrompt?: string;
|
||||
temperature?: number;
|
||||
maxTokens?: number;
|
||||
responseMimeType?: string;
|
||||
}
|
||||
|
||||
export interface GeminiChatMessage {
|
||||
@@ -59,12 +52,12 @@ export class GeminiService implements OnModuleInit {
|
||||
this.isEnabled = this.configService.get<boolean>('gemini.enabled', false);
|
||||
this.defaultModel = this.configService.get<string>(
|
||||
'gemini.defaultModel',
|
||||
'gemini-1.5-flash',
|
||||
'gemini-2.0-flash-001',
|
||||
);
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
this.logger.log(`Initializing GeminiService. isEnabled: ${this.isEnabled}`);
|
||||
this.logger.log(`Initializing GeminiService. isEnabled: ${this.isEnabled} with model: ${this.defaultModel}`);
|
||||
|
||||
if (!this.isEnabled) {
|
||||
this.logger.log(
|
||||
@@ -96,7 +89,6 @@ export class GeminiService implements OnModuleInit {
|
||||
*/
|
||||
isAvailable(): boolean {
|
||||
const available = this.isEnabled && !!this.client;
|
||||
logToFile(`[GeminiService] isAvailable: ${available} (isEnabled: ${this.isEnabled}, hasClient: ${!!this.client})`);
|
||||
return available;
|
||||
}
|
||||
|
||||
@@ -137,19 +129,25 @@ export class GeminiService implements OnModuleInit {
|
||||
parts: [{ text: prompt }],
|
||||
});
|
||||
|
||||
logToFile(`[GeminiService] Calling generateContent with model: ${model}`);
|
||||
const response = await this.client!.models.generateContent({
|
||||
model,
|
||||
contents,
|
||||
config: {
|
||||
temperature: options.temperature,
|
||||
maxOutputTokens: options.maxTokens,
|
||||
responseMimeType: options.responseMimeType,
|
||||
},
|
||||
});
|
||||
logToFile(`[GeminiService] Response: ${JSON.stringify(response).substring(0, 1000)}`);
|
||||
} as any);
|
||||
|
||||
// Diagnostic logging for development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this.logger.debug('Raw Gemini Response:', JSON.stringify(response, null, 2));
|
||||
}
|
||||
|
||||
const text = this.extractText(response);
|
||||
|
||||
return {
|
||||
text: (response.text || '').trim(),
|
||||
text,
|
||||
usage: response.usageMetadata,
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -204,8 +202,10 @@ export class GeminiService implements OnModuleInit {
|
||||
},
|
||||
});
|
||||
|
||||
const text = this.extractText(response);
|
||||
|
||||
return {
|
||||
text: (response.text || '').trim(),
|
||||
text,
|
||||
usage: response.usageMetadata,
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -234,23 +234,83 @@ ${schema}
|
||||
|
||||
IMPORTANT: Only output valid JSON, no markdown code blocks or other text.`;
|
||||
|
||||
const response = await this.generateText(fullPrompt, options);
|
||||
const isPro = (options?.model || this.defaultModel).includes('pro');
|
||||
|
||||
// Add instruction to prompt if using Pro model (since we removed responseMimeType)
|
||||
const finalPrompt = isPro ? `${fullPrompt}\n\nIMPORTANT: Output strictly valid JSON.` : fullPrompt;
|
||||
|
||||
const response = await this.generateText(finalPrompt, {
|
||||
...options,
|
||||
model: options?.model || this.defaultModel,
|
||||
// Only set responseMimeType for Flash models that support it reliably
|
||||
responseMimeType: isPro ? undefined : 'application/json',
|
||||
});
|
||||
try {
|
||||
// Try to extract JSON from the response
|
||||
let jsonStr = response.text;
|
||||
const jsonStr = response.text || '';
|
||||
// Remove potential markdown code blocks (handle both complete and truncated)
|
||||
let cleanedStr = jsonStr.trim();
|
||||
|
||||
// Remove potential markdown code blocks
|
||||
const jsonMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||
if (jsonMatch) {
|
||||
jsonStr = jsonMatch[1].trim();
|
||||
// Try to find the first '{' and last '}'
|
||||
const firstBrace = cleanedStr.indexOf('{');
|
||||
const lastBrace = cleanedStr.lastIndexOf('}');
|
||||
|
||||
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
||||
cleanedStr = cleanedStr.substring(firstBrace, lastBrace + 1);
|
||||
} else if (firstBrace !== -1) {
|
||||
// Truncated JSON - starts but doesn't end?
|
||||
cleanedStr = cleanedStr.substring(firstBrace);
|
||||
}
|
||||
|
||||
const data = JSON.parse(jsonStr) as T;
|
||||
// Fix raw newlines inside string values (not between keys)
|
||||
// This is a common issue where AI produces literal newlines in JSON strings
|
||||
cleanedStr = cleanedStr.replace(/"([^"]*)"/g, (match, p1) => {
|
||||
return '"' + p1.replace(/\n/g, '\\n') + '"';
|
||||
});
|
||||
|
||||
const data = JSON.parse(cleanedStr) as T;
|
||||
return { data, usage: response.usage };
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to parse JSON response', error);
|
||||
throw new Error('Failed to parse AI response as JSON');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract text from Gemini response, handling various SDK versions/structures
|
||||
*/
|
||||
private extractText(response: any): string {
|
||||
// Check for safety filters or other finish reasons
|
||||
if (response.candidates && response.candidates.length > 0) {
|
||||
const candidate = response.candidates[0];
|
||||
if (candidate.finishReason && candidate.finishReason !== 'STOP') {
|
||||
this.logger.warn(`Gemini generation finished with reason: ${candidate.finishReason}`);
|
||||
if (candidate.finishReason === 'SAFETY') {
|
||||
this.logger.warn('Content was blocked by safety filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let text = '';
|
||||
|
||||
try {
|
||||
// In some SDK versions text() is a method, in others it's a property
|
||||
if (typeof response.text === 'function') {
|
||||
text = response.text();
|
||||
} else if (typeof response.text === 'string') {
|
||||
text = response.text;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error('Error calling response.text():', e);
|
||||
}
|
||||
|
||||
if (!text && response.candidates && response.candidates.length > 0) {
|
||||
const candidate = response.candidates[0];
|
||||
if (candidate.content && candidate.content.parts && candidate.content.parts.length > 0) {
|
||||
text = candidate.content.parts[0].text || '';
|
||||
}
|
||||
}
|
||||
|
||||
return text ? text.trim() : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,6 +43,7 @@ export class SeoService {
|
||||
metaDescription?: string;
|
||||
url?: string;
|
||||
competitorDomains?: string[];
|
||||
language?: 'en' | 'tr' | 'de' | 'es' | 'fr';
|
||||
},
|
||||
): Promise<FullSeoAnalysis> {
|
||||
// Analyze content
|
||||
@@ -51,6 +52,7 @@ export class SeoService {
|
||||
title: options?.title,
|
||||
metaDescription: options?.metaDescription,
|
||||
url: options?.url,
|
||||
language: options?.language || 'en',
|
||||
});
|
||||
|
||||
// Generate optimized meta
|
||||
@@ -107,6 +109,7 @@ export class SeoService {
|
||||
options?: {
|
||||
competitorDomains?: string[];
|
||||
contentType?: string;
|
||||
language?: 'en' | 'tr' | 'de' | 'es' | 'fr';
|
||||
},
|
||||
): Promise<{
|
||||
title: string;
|
||||
@@ -118,6 +121,7 @@ export class SeoService {
|
||||
}> {
|
||||
// Get keyword data
|
||||
const keywordData = await this.keywordService.suggestKeywords(keyword);
|
||||
const language = options?.language || 'en';
|
||||
|
||||
// Get title variations
|
||||
const titles = this.optimizationService.generateTitleVariations(keyword, 1);
|
||||
@@ -132,6 +136,23 @@ export class SeoService {
|
||||
const blueprint = this.competitorService.generateContentBlueprint(keyword, competitorContent);
|
||||
headings = blueprint.suggestedHeadings;
|
||||
differentiators = blueprint.differentiators;
|
||||
} else {
|
||||
if (language === 'tr') {
|
||||
headings = [
|
||||
`${keyword} Nedir?`,
|
||||
`Neden ${keyword} Önemlidir?`,
|
||||
`${keyword} Nasıl Kullanılır?`,
|
||||
`${keyword} En İyi Uygulamalar`,
|
||||
`Sık Yapılan ${keyword} Hataları`,
|
||||
`${keyword} Araçları ve Kaynakları`,
|
||||
`${keyword} Hakkında SSS`,
|
||||
];
|
||||
differentiators = [
|
||||
'Orijinal araştırma veya veri ekleyin',
|
||||
'Uzman görüşlerine yer verin',
|
||||
'Uygulanabilir adımlar sunun',
|
||||
'Gerçek örnekler ekleyin',
|
||||
];
|
||||
} else {
|
||||
headings = [
|
||||
`What is ${keyword}?`,
|
||||
@@ -149,6 +170,7 @@ export class SeoService {
|
||||
'Include real examples',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: titles[0] || `Complete Guide to ${keyword}`,
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
const gis = require('g-i-s');
|
||||
|
||||
@Injectable()
|
||||
export class GoogleImageSearchService {
|
||||
private readonly logger = new Logger(GoogleImageSearchService.name);
|
||||
|
||||
async search(query: string): Promise<string[]> {
|
||||
this.logger.log(`Searching images for query: ${query}`);
|
||||
return new Promise((resolve, reject) => {
|
||||
gis(query, (error: any, results: any[]) => {
|
||||
if (error) {
|
||||
this.logger.error('Google Image Search failed', error);
|
||||
// Return empty array instead of failing completely?
|
||||
// No, let user know search failed.
|
||||
reject(error);
|
||||
} else {
|
||||
// results is an array of objects with url, width, height
|
||||
// Filter for reasonable size images if possible, but g-i-s results are simple.
|
||||
// Just map to URLs.
|
||||
const urls = results.map(r => r.url);
|
||||
this.logger.log(`Found ${urls.length} images`);
|
||||
resolve(urls.slice(0, 30)); // Return top 30
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -8,6 +8,7 @@ import { VeoVideoService, VideoGenerationRequest, GeneratedVideo, VideoStyle } f
|
||||
import { NeuroVisualService, NeuroOptimizedVisual } from './services/neuro-visual.service';
|
||||
import { TemplateEditorService, Template, TemplateType, RenderedTemplate } from './services/template-editor.service';
|
||||
import { AssetLibraryService, Asset, AssetType, AssetCollection } from './services/asset-library.service';
|
||||
import { GoogleImageSearchService } from './services/google-image-search.service';
|
||||
|
||||
export interface VisualContentRequest {
|
||||
type: 'image' | 'video' | 'template';
|
||||
@@ -39,8 +40,13 @@ export class VisualGenerationService {
|
||||
private readonly neuroService: NeuroVisualService,
|
||||
private readonly templateService: TemplateEditorService,
|
||||
private readonly assetService: AssetLibraryService,
|
||||
private readonly searchService: GoogleImageSearchService,
|
||||
) { }
|
||||
|
||||
async searchImages(query: string): Promise<string[]> {
|
||||
return this.searchService.search(query);
|
||||
}
|
||||
|
||||
// ========== UNIFIED GENERATION ==========
|
||||
|
||||
/**
|
||||
|
||||
22
test-anon.ts
Normal file
22
test-anon.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function test() {
|
||||
try {
|
||||
const newAnon = await prisma.user.create({
|
||||
data: {
|
||||
email: 'anonymous@contenthunter.system' + Date.now(),
|
||||
password: 'system-anonymous-no-login',
|
||||
firstName: 'Anonymous',
|
||||
},
|
||||
});
|
||||
console.log("Success:", newAnon);
|
||||
} catch (e: any) {
|
||||
console.error("Prisma Error:", e);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
41
test-api-master.ts
Normal file
41
test-api-master.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
async function main() {
|
||||
const loginRes = await fetch('http://localhost:3000/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: 'testuser@example.com', password: 'password123' })
|
||||
});
|
||||
|
||||
if (!loginRes.ok) {
|
||||
console.log("LOGIN FAILED:", await loginRes.text());
|
||||
return;
|
||||
}
|
||||
const loginData: any = await loginRes.json();
|
||||
const token = loginData.data?.accessToken;
|
||||
if (!token) {
|
||||
console.log("No token in response:", loginData);
|
||||
return;
|
||||
}
|
||||
|
||||
const masterId = "dabae8f3-4223-4e3e-8876-44c6d31562e3";
|
||||
console.log(`Fetching master: ${masterId}`);
|
||||
|
||||
const masterRes = await fetch(`http://localhost:3000/api/content/master/${masterId}`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const data: any = await masterRes.json();
|
||||
console.log("Master response structure:", Object.keys(data));
|
||||
console.log("Data keys:", data.data ? Object.keys(data.data) : "No data object");
|
||||
|
||||
const contents = data.contents || data.data?.contents;
|
||||
if (!contents) {
|
||||
console.log("NO CONTENTS ARRAY FOUND!");
|
||||
} else {
|
||||
console.log(`Found ${contents.length} items in contents array`);
|
||||
console.log(contents.map((c: any) => ({ id: c.id, type: c.type })));
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
3
test-api.js
Normal file
3
test-api.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const http = require('http');
|
||||
// Assume backend is running on a port, maybe 3001 or 3000
|
||||
// Wait, I can just use Prisma again to verify `masterContent.contents`. I already did that.
|
||||
19
test-api.ts
Normal file
19
test-api.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// test-api.ts
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './src/app.module';
|
||||
import { MasterContentService } from './src/modules/content/services/master-content.service';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.createApplicationContext(AppModule);
|
||||
const masterContentService = app.get(MasterContentService);
|
||||
|
||||
// Fetch a known master ID from the DB
|
||||
const id = '80c71d39-4614-426c-9d9a-e5b2301cdebd';
|
||||
const data = await masterContentService.getById(id);
|
||||
console.log("Returned data type:", typeof data);
|
||||
console.log("Has contents?", Array.isArray(data?.contents));
|
||||
console.log("Contents array:", data?.contents?.map(c => ({ id: c.id, type: c.type, title: c.title })));
|
||||
|
||||
await app.close();
|
||||
}
|
||||
bootstrap();
|
||||
18
test-db-medium.ts
Normal file
18
test-db-medium.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const contents = await prisma.content.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ title: { contains: 'medium' } },
|
||||
{ type: 'BLOG' }
|
||||
]
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 5
|
||||
});
|
||||
console.log(JSON.stringify(contents, null, 2));
|
||||
}
|
||||
|
||||
main().catch(console.error).finally(() => prisma.$disconnect());
|
||||
18
test-find-user.ts
Normal file
18
test-find-user.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const user = await prisma.user.findFirst();
|
||||
|
||||
if (!user) {
|
||||
console.log("No user found.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`User email: ${user.email}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(() => prisma.$disconnect());
|
||||
25
test-recent-master.ts
Normal file
25
test-recent-master.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const latestMaster = await prisma.masterContent.findFirst({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { contents: true }
|
||||
});
|
||||
|
||||
if (!latestMaster) {
|
||||
console.log("No master content found.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Latest Master ID: ${latestMaster.id}`);
|
||||
console.log(`Number of contents: ${latestMaster.contents.length}`);
|
||||
for (const c of latestMaster.contents) {
|
||||
console.log(` - ID: ${c.id}, Type: ${c.type}, Title: ${c.title}`);
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(() => prisma.$disconnect());
|
||||
12
test-script.js
Normal file
12
test-script.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
async function main() {
|
||||
const master = await prisma.masterContent.findFirst({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { contents: true }
|
||||
});
|
||||
console.log(JSON.stringify(master, null, 2));
|
||||
}
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(() => prisma.$disconnect());
|
||||
12
test-script.ts
Normal file
12
test-script.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
async function main() {
|
||||
const master = await prisma.masterContent.findFirst({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { contents: true }
|
||||
});
|
||||
console.log(JSON.stringify(master, null, 2));
|
||||
}
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(async () => { await prisma.$disconnect(); });
|
||||
19
test-script2.ts
Normal file
19
test-script2.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
async function main() {
|
||||
const masters = await prisma.masterContent.findMany({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 10,
|
||||
include: { contents: true }
|
||||
});
|
||||
|
||||
for (const master of masters) {
|
||||
console.log(`Master: ${master.id}, Topic: ${master.title}`);
|
||||
for (const c of master.contents) {
|
||||
console.log(` - ${c.type} -> title: ${c.title}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
main()
|
||||
.catch(console.error)
|
||||
.finally(async () => { await prisma.$disconnect(); });
|
||||
Reference in New Issue
Block a user