@zodic/shared 0.0.400 → 0.0.402
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/index.ts +0 -99
- package/app/base/AppContext.ts +0 -41
- package/app/services/ConceptService.ts +172 -244
- package/app/services/PaymentService.ts +61 -2
- package/db/migrations/{0000_little_sleeper.sql → 0000_workable_the_hand.sql} +197 -24
- package/db/migrations/meta/0000_snapshot.json +1663 -451
- package/db/migrations/meta/_journal.json +2 -156
- package/db/schema.ts +0 -31
- package/drizzle.config.ts +2 -2
- package/package.json +10 -5
- package/types/scopes/cloudflare.ts +2 -118
- package/types/scopes/generic.ts +1 -1
- package/utils/buildMessages.ts +1 -32
- package/wrangler.toml +24 -3
- package/app/durable/ConceptNameDO.ts +0 -199
- package/app/durable/index.ts +0 -1
- package/app/workflow/old/ArchetypeWorkflow.ts +0 -156
- package/db/migrations/0001_mysterious_mystique.sql +0 -47
- package/db/migrations/0002_reflective_firelord.sql +0 -13
- package/db/migrations/0003_thin_valeria_richards.sql +0 -28
- package/db/migrations/0004_loose_iron_monger.sql +0 -15
- package/db/migrations/0005_famous_cammi.sql +0 -26
- package/db/migrations/0006_fine_manta.sql +0 -1
- package/db/migrations/0007_typical_grim_reaper.sql +0 -1
- package/db/migrations/0008_fine_betty_brant.sql +0 -1
- package/db/migrations/0009_spooky_doctor_spectrum.sql +0 -20
- package/db/migrations/0010_tricky_lord_hawal.sql +0 -23
- package/db/migrations/0011_hard_king_bedlam.sql +0 -1
- package/db/migrations/0012_sudden_doctor_spectrum.sql +0 -27
- package/db/migrations/0013_lean_frightful_four.sql +0 -7
- package/db/migrations/0014_green_marvel_apes.sql +0 -10
- package/db/migrations/0015_zippy_sersi.sql +0 -10
- package/db/migrations/0016_awesome_squadron_sinister.sql +0 -1
- package/db/migrations/0017_vengeful_electro.sql +0 -1
- package/db/migrations/0018_wooden_sersi.sql +0 -16
- package/db/migrations/0019_abandoned_orphan.sql +0 -59
- package/db/migrations/0020_smiling_blob.sql +0 -1
- package/db/migrations/0021_flawless_wallflower.sql +0 -1
- package/db/migrations/0022_pale_marvex.sql +0 -1
- package/db/migrations/meta/0001_snapshot.json +0 -2200
- package/db/migrations/meta/0002_snapshot.json +0 -2284
- package/db/migrations/meta/0003_snapshot.json +0 -2417
- package/db/migrations/meta/0004_snapshot.json +0 -2417
- package/db/migrations/meta/0005_snapshot.json +0 -2417
- package/db/migrations/meta/0006_snapshot.json +0 -2425
- package/db/migrations/meta/0007_snapshot.json +0 -2433
- package/db/migrations/meta/0008_snapshot.json +0 -2425
- package/db/migrations/meta/0009_snapshot.json +0 -2425
- package/db/migrations/meta/0010_snapshot.json +0 -2594
- package/db/migrations/meta/0011_snapshot.json +0 -2602
- package/db/migrations/meta/0012_snapshot.json +0 -2602
- package/db/migrations/meta/0013_snapshot.json +0 -2649
- package/db/migrations/meta/0014_snapshot.json +0 -2723
- package/db/migrations/meta/0015_snapshot.json +0 -2711
- package/db/migrations/meta/0016_snapshot.json +0 -2715
- package/db/migrations/meta/0017_snapshot.json +0 -2723
- package/db/migrations/meta/0018_snapshot.json +0 -2834
- package/db/migrations/meta/0019_snapshot.json +0 -3244
- package/db/migrations/meta/0020_snapshot.json +0 -3252
- package/db/migrations/meta/0021_snapshot.json +0 -3254
- package/db/migrations/meta/0022_snapshot.json +0 -3260
package/app/api/index.ts
CHANGED
|
@@ -448,105 +448,6 @@ export const Api = (env: BackendBindings) => ({
|
|
|
448
448
|
}
|
|
449
449
|
},
|
|
450
450
|
},
|
|
451
|
-
callImageDescriber: async (imageUrl: string): Promise<string> => {
|
|
452
|
-
const mimeType = 'image/png';
|
|
453
|
-
const endpoint =
|
|
454
|
-
'https://us-central1-describepicture.cloudfunctions.net/describe_picture_api';
|
|
455
|
-
const hexKey = env.DESCRIBER_API_KEY;
|
|
456
|
-
if (!hexKey) throw new Error('Describer API Key not found');
|
|
457
|
-
|
|
458
|
-
const appId = env.DESCRIBER_APP_ID;
|
|
459
|
-
if (!appId) throw new Error('Describer APP ID not found');
|
|
460
|
-
|
|
461
|
-
const data = JSON.stringify({
|
|
462
|
-
imageUrl,
|
|
463
|
-
prompt: env.PROMPT_IMAGE_DESCRIBER,
|
|
464
|
-
mimeType,
|
|
465
|
-
appId,
|
|
466
|
-
imageBase64: '',
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
const encryptData = async (
|
|
470
|
-
data: string,
|
|
471
|
-
hexKey: string
|
|
472
|
-
): Promise<{ iv: string; encryptedData: string }> => {
|
|
473
|
-
const keyBytes = Uint8Array.from(
|
|
474
|
-
hexKey.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) || []
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
const key = await crypto.subtle.importKey(
|
|
478
|
-
'raw',
|
|
479
|
-
keyBytes,
|
|
480
|
-
{ name: 'AES-GCM' },
|
|
481
|
-
false,
|
|
482
|
-
['encrypt']
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
486
|
-
const encodedData = new TextEncoder().encode(data);
|
|
487
|
-
|
|
488
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
489
|
-
{ name: 'AES-GCM', iv },
|
|
490
|
-
key,
|
|
491
|
-
encodedData
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
return {
|
|
495
|
-
iv: btoa(String.fromCharCode(...iv)),
|
|
496
|
-
encryptedData: btoa(String.fromCharCode(...new Uint8Array(encrypted))),
|
|
497
|
-
};
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const { iv, encryptedData } = await encryptData(data, hexKey);
|
|
501
|
-
|
|
502
|
-
const payload = { iv, encryptedData };
|
|
503
|
-
|
|
504
|
-
try {
|
|
505
|
-
const response = await fetch(endpoint, {
|
|
506
|
-
method: 'POST',
|
|
507
|
-
headers: { 'Content-Type': 'application/json' },
|
|
508
|
-
body: JSON.stringify(payload),
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
if (!response.ok) {
|
|
512
|
-
const error = await response.json();
|
|
513
|
-
console.error('Error from Image Describer API:', error);
|
|
514
|
-
throw new Error(`Image Describer API Error: ${response.status}`);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const result = (await response.json()) as string;
|
|
518
|
-
return result;
|
|
519
|
-
} catch (err: any) {
|
|
520
|
-
console.error('Error calling Image Describer API:', err.message);
|
|
521
|
-
throw err;
|
|
522
|
-
}
|
|
523
|
-
},
|
|
524
|
-
|
|
525
|
-
callUForm: async (imageUrl: string): Promise<string> => {
|
|
526
|
-
try {
|
|
527
|
-
const res = await fetch(imageUrl);
|
|
528
|
-
console.log('IMAGE URL -> ', imageUrl);
|
|
529
|
-
const blob = await res.arrayBuffer();
|
|
530
|
-
|
|
531
|
-
const input = {
|
|
532
|
-
image: [...new Uint8Array(blob)],
|
|
533
|
-
prompt: env.PROMPT_IMAGE_DESCRIBER,
|
|
534
|
-
max_tokens: 512,
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
const response = await env.AI.run('@cf/llava-hf/llava-1.5-7b-hf', input);
|
|
538
|
-
|
|
539
|
-
if (!response) {
|
|
540
|
-
throw new Error('UForm model did not return a response.');
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
console.log('UForm Model Response ->', response.description);
|
|
544
|
-
return response.description;
|
|
545
|
-
} catch (err: any) {
|
|
546
|
-
console.error('Error using UForm model:', err.message);
|
|
547
|
-
throw err;
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
451
|
|
|
551
452
|
callAstrology: {
|
|
552
453
|
getNatalChartInterpretation: async ({
|
package/app/base/AppContext.ts
CHANGED
|
@@ -13,31 +13,6 @@ export class AppContext {
|
|
|
13
13
|
return buildLLMMessages(this.env);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
kvCosmicMirrorArchetypesStore() {
|
|
17
|
-
if (!this.env.KV_COSMIC_MIRROR_ARCHETYPES) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
'KV_COSMIC_MIRROR_ARCHETYPES is not defined in the environment.'
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
return this.env.KV_COSMIC_MIRROR_ARCHETYPES;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
kvCosmicMirrorManagementStore() {
|
|
26
|
-
if (!this.env.KV_COSMIC_MIRROR_MANAGEMENT) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
'KV_COSMIC_MIRROR_MANAGEMENT is not defined in the environment.'
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
return this.env.KV_COSMIC_MIRROR_MANAGEMENT;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
kvConceptsStore() {
|
|
35
|
-
if (!this.env.KV_CONCEPTS) {
|
|
36
|
-
throw new Error('KV_CONCEPTS is not defined in the environment.');
|
|
37
|
-
}
|
|
38
|
-
return this.env.KV_CONCEPTS;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
16
|
kvConceptFailuresStore() {
|
|
42
17
|
if (!this.env.KV_CONCEPT_FAILURES) {
|
|
43
18
|
throw new Error('KV_CONCEPT_FAILURES is not defined in the environment.');
|
|
@@ -45,22 +20,6 @@ export class AppContext {
|
|
|
45
20
|
return this.env.KV_CONCEPT_FAILURES;
|
|
46
21
|
}
|
|
47
22
|
|
|
48
|
-
kvConceptNamesStore() {
|
|
49
|
-
if (!this.env.KV_CONCEPT_NAMES) {
|
|
50
|
-
throw new Error('KV_CONCEPT_NAMES is not defined in the environment.');
|
|
51
|
-
}
|
|
52
|
-
return this.env.KV_CONCEPT_NAMES;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
kvConceptsManagementStore() {
|
|
56
|
-
if (!this.env.KV_CONCEPTS_MANAGEMENT) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
'KV_CONCEPTS_MANAGEMENT is not defined in the environment.'
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
return this.env.KV_CONCEPTS_MANAGEMENT;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
23
|
kvAstroStore() {
|
|
65
24
|
if (!this.env.KV_ASTRO) {
|
|
66
25
|
throw new Error('KV_ASTRO is not defined in the environment.');
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
KVNamespaceListKey,
|
|
3
3
|
KVNamespaceListResult,
|
|
4
4
|
} from '@cloudflare/workers-types';
|
|
5
|
-
import { and, eq, inArray, isNull } from 'drizzle-orm';
|
|
5
|
+
import { and, eq, inArray, isNull, sql } from 'drizzle-orm';
|
|
6
6
|
import { drizzle } from 'drizzle-orm/d1';
|
|
7
7
|
import { inject, injectable } from 'inversify';
|
|
8
8
|
import 'reflect-metadata';
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
concepts,
|
|
17
17
|
conceptsData,
|
|
18
18
|
houseReports,
|
|
19
|
+
users,
|
|
19
20
|
} from '../../db/schema';
|
|
20
21
|
import {
|
|
21
22
|
AstrologicalReport,
|
|
@@ -44,6 +45,7 @@ export class ConceptService {
|
|
|
44
45
|
const drizzle = this.context.drizzle();
|
|
45
46
|
const conceptsList = await drizzle
|
|
46
47
|
.select({
|
|
48
|
+
id: concepts.id,
|
|
47
49
|
slug: concepts.slug,
|
|
48
50
|
planet1: concepts.planet1,
|
|
49
51
|
planet2: concepts.planet2,
|
|
@@ -58,156 +60,6 @@ export class ConceptService {
|
|
|
58
60
|
/**
|
|
59
61
|
* Generate basic info for a concept: name, description, and poem.
|
|
60
62
|
*/
|
|
61
|
-
async generateBasicInfo(
|
|
62
|
-
conceptSlug: Concept,
|
|
63
|
-
combinationString: string,
|
|
64
|
-
override: boolean = false
|
|
65
|
-
): Promise<void> {
|
|
66
|
-
console.log(
|
|
67
|
-
`🚀 Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const kvStore = this.context.kvConceptsStore();
|
|
71
|
-
const kvFailuresStore = this.context.kvConceptFailuresStore();
|
|
72
|
-
const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
|
|
73
|
-
const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
|
|
74
|
-
|
|
75
|
-
// ✅ Use Durable Object stub
|
|
76
|
-
const id = this.context.env.CONCEPT_NAMES_DO.idFromName(conceptSlug);
|
|
77
|
-
const stub = this.context.env.CONCEPT_NAMES_DO.get(id);
|
|
78
|
-
|
|
79
|
-
console.log(`📡 Fetching existing KV data for ${conceptSlug}...`);
|
|
80
|
-
const existingEN = await this.getKVConcept(kvKeyEN);
|
|
81
|
-
const existingPT = await this.getKVConcept(kvKeyPT);
|
|
82
|
-
|
|
83
|
-
if (!override && existingEN.name && existingPT.name) {
|
|
84
|
-
console.log(`⚡ Basic info already exists for ${conceptSlug}, skipping.`);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let attempts = 0;
|
|
89
|
-
const maxAttempts = 3;
|
|
90
|
-
const MAX_NAME_LENGTH = 44;
|
|
91
|
-
|
|
92
|
-
while (attempts < maxAttempts) {
|
|
93
|
-
let phase = 'generation';
|
|
94
|
-
try {
|
|
95
|
-
attempts++;
|
|
96
|
-
console.log(
|
|
97
|
-
`🔄 Attempt ${attempts} to generate basic info for ${conceptSlug}...`
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
let allNamesEN: string[] = [];
|
|
101
|
-
let allNamesPT: string[] = [];
|
|
102
|
-
const response = await stub.fetch(`https://internal/names`);
|
|
103
|
-
|
|
104
|
-
if (response.ok) {
|
|
105
|
-
const data = (await response.json()) as {
|
|
106
|
-
'en-us': string[];
|
|
107
|
-
'pt-br': string[];
|
|
108
|
-
};
|
|
109
|
-
allNamesEN = data['en-us'] || [];
|
|
110
|
-
allNamesPT = data['pt-br'] || [];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
console.log(`✏️ Generating new name...`);
|
|
114
|
-
const messages = this.context
|
|
115
|
-
.buildLLMMessages()
|
|
116
|
-
.generateConceptBasicInfo({
|
|
117
|
-
combination: combinationString,
|
|
118
|
-
conceptSlug,
|
|
119
|
-
existingNames:
|
|
120
|
-
allNamesEN.length > 100 ? allNamesEN.slice(-100) : allNamesEN,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
let aiResponse = await this.context
|
|
124
|
-
.api()
|
|
125
|
-
.callTogether.single(messages, {});
|
|
126
|
-
if (!aiResponse) throw new Error(`❌ AI returned an empty response`);
|
|
127
|
-
|
|
128
|
-
phase = 'cleaning';
|
|
129
|
-
aiResponse = this.cleanAIResponse(aiResponse);
|
|
130
|
-
|
|
131
|
-
console.log('!-- aiResponse -> ', aiResponse);
|
|
132
|
-
console.log('!-- aiResponse length -> ', aiResponse.length);
|
|
133
|
-
|
|
134
|
-
phase = 'parsing';
|
|
135
|
-
let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
|
|
136
|
-
this.parseBasicInfoResponse(aiResponse, conceptSlug);
|
|
137
|
-
|
|
138
|
-
console.log(`🎭 Generated names: EN - "${nameEN}", PT - "${namePT}"`);
|
|
139
|
-
|
|
140
|
-
// ✅ Forcefully trim the name to exactly 5 words if it's too long
|
|
141
|
-
const enforceWordLimit = (name: string): string => {
|
|
142
|
-
const words = name.split(/\s+/);
|
|
143
|
-
if (words.length > 6) {
|
|
144
|
-
return words.slice(0, 6).join(' ');
|
|
145
|
-
}
|
|
146
|
-
return name;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
nameEN = enforceWordLimit(nameEN);
|
|
150
|
-
namePT = enforceWordLimit(namePT);
|
|
151
|
-
|
|
152
|
-
console.log(`✂️ Trimmed names: EN - "${nameEN}", PT - "${namePT}"`);
|
|
153
|
-
|
|
154
|
-
// ✅ Check uniqueness before storing
|
|
155
|
-
if (allNamesEN.includes(nameEN) || allNamesPT.includes(namePT)) {
|
|
156
|
-
console.warn(
|
|
157
|
-
`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`
|
|
158
|
-
);
|
|
159
|
-
if (attempts >= maxAttempts) {
|
|
160
|
-
console.log(
|
|
161
|
-
`🚨 Max attempts reached. Storing name despite duplicate.`
|
|
162
|
-
);
|
|
163
|
-
await kvFailuresStore.put(
|
|
164
|
-
`failures:duplicates:${conceptSlug}:${combinationString}`,
|
|
165
|
-
JSON.stringify({
|
|
166
|
-
nameEN,
|
|
167
|
-
namePT,
|
|
168
|
-
attempts,
|
|
169
|
-
conceptSlug,
|
|
170
|
-
combinationString,
|
|
171
|
-
timestamp: new Date().toISOString(),
|
|
172
|
-
})
|
|
173
|
-
);
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
console.log(`📝 Storing names in Durable Object...`);
|
|
180
|
-
// await stub.fetch(`https://internal/add-name`, { method: 'POST', body: JSON.stringify({ language: 'en-us', name: nameEN }), headers: { 'Content-Type': 'application/json' } });
|
|
181
|
-
// await stub.fetch(`https://internal/add-name`, { method: 'POST', body: JSON.stringify({ language: 'pt-br', name: namePT }), headers: { 'Content-Type': 'application/json' } });
|
|
182
|
-
|
|
183
|
-
Object.assign(existingEN, {
|
|
184
|
-
name: nameEN,
|
|
185
|
-
description: descriptionEN,
|
|
186
|
-
poem: poemEN,
|
|
187
|
-
status: 'idle',
|
|
188
|
-
});
|
|
189
|
-
await kvStore.put(kvKeyEN, JSON.stringify(existingEN));
|
|
190
|
-
|
|
191
|
-
Object.assign(existingPT, {
|
|
192
|
-
name: namePT,
|
|
193
|
-
description: descriptionPT,
|
|
194
|
-
poem: poemPT,
|
|
195
|
-
status: 'idle',
|
|
196
|
-
});
|
|
197
|
-
await kvStore.put(kvKeyPT, JSON.stringify(existingPT));
|
|
198
|
-
|
|
199
|
-
console.log(
|
|
200
|
-
`✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`
|
|
201
|
-
);
|
|
202
|
-
return;
|
|
203
|
-
} catch (error) {
|
|
204
|
-
console.error(
|
|
205
|
-
`❌ Attempt ${attempts} failed at phase: ${phase}`,
|
|
206
|
-
error
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
63
|
|
|
212
64
|
private cleanAIResponse(response: string): string {
|
|
213
65
|
console.log('🧼 Cleaning AI Response...');
|
|
@@ -1049,99 +901,7 @@ export class ConceptService {
|
|
|
1049
901
|
/**
|
|
1050
902
|
* Fetch and initialize a KVConcept object if not present.
|
|
1051
903
|
*/
|
|
1052
|
-
private async getKVConcept(kvKey: string): Promise<KVConcept> {
|
|
1053
|
-
const existingData = await this.context
|
|
1054
|
-
.kvConceptsStore()
|
|
1055
|
-
.get<KVConcept>(kvKey, 'json');
|
|
1056
|
-
|
|
1057
|
-
if (existingData) {
|
|
1058
|
-
return existingData;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
console.log(
|
|
1062
|
-
`No existing KV data found. Creating default entry for key: ${kvKey}`
|
|
1063
|
-
);
|
|
1064
|
-
return {
|
|
1065
|
-
name: '',
|
|
1066
|
-
description: '',
|
|
1067
|
-
content: [],
|
|
1068
|
-
poem: [],
|
|
1069
|
-
leonardoPrompt: '',
|
|
1070
|
-
postImages: [],
|
|
1071
|
-
reelImages: [null, null, null],
|
|
1072
|
-
status: 'idle',
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
private async findDuplicateNamesAndUsedNames(limit?: number) {
|
|
1077
|
-
console.log(`🔍 Scanning KV for duplicate names...`);
|
|
1078
|
-
|
|
1079
|
-
const kvStore = this.context.env.KV_CONCEPTS;
|
|
1080
|
-
let cursor: string | undefined = undefined;
|
|
1081
|
-
let nameUsageMapEN: Record<string, string[]> = {}; // { enName: [combination1, combination2] }
|
|
1082
|
-
let usedNamesEN = new Set<string>(); // ✅ All used EN names
|
|
1083
|
-
let usedNamesPT = new Set<string>(); // ✅ All used PT names
|
|
1084
|
-
let totalKeysScanned = 0;
|
|
1085
|
-
|
|
1086
|
-
do {
|
|
1087
|
-
const response: KVNamespaceListResult<KVConcept> = (await kvStore.list({
|
|
1088
|
-
prefix: 'concepts:',
|
|
1089
|
-
cursor,
|
|
1090
|
-
})) as KVNamespaceListResult<KVConcept, string>;
|
|
1091
|
-
|
|
1092
|
-
const {
|
|
1093
|
-
keys,
|
|
1094
|
-
cursor: newCursor,
|
|
1095
|
-
}: { keys: KVNamespaceListKey<KVConcept>[]; cursor?: string } = response;
|
|
1096
|
-
|
|
1097
|
-
totalKeysScanned += keys.length;
|
|
1098
|
-
|
|
1099
|
-
for (const key of keys) {
|
|
1100
|
-
const kvData = (await kvStore.get(key.name, 'json')) as {
|
|
1101
|
-
name: string;
|
|
1102
|
-
ptName?: string;
|
|
1103
|
-
};
|
|
1104
|
-
|
|
1105
|
-
if (kvData?.name) {
|
|
1106
|
-
usedNamesEN.add(kvData.name); // ✅ Store all used EN names
|
|
1107
|
-
|
|
1108
|
-
if (!nameUsageMapEN[kvData.name]) {
|
|
1109
|
-
nameUsageMapEN[kvData.name] = [];
|
|
1110
|
-
}
|
|
1111
|
-
nameUsageMapEN[kvData.name].push(key.name); // Store the key for that name
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
if (kvData?.ptName) {
|
|
1115
|
-
usedNamesPT.add(kvData.ptName); // ✅ Store all used PT names
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
904
|
|
|
1119
|
-
cursor = newCursor;
|
|
1120
|
-
} while (cursor && (!limit || totalKeysScanned < limit));
|
|
1121
|
-
|
|
1122
|
-
// ✅ Find names used more than once (EN)
|
|
1123
|
-
let duplicateENNames = Object.entries(nameUsageMapEN)
|
|
1124
|
-
.filter(([_, combinations]) => combinations.length > 1)
|
|
1125
|
-
.map(([name, combinations]) => ({
|
|
1126
|
-
name,
|
|
1127
|
-
combinations,
|
|
1128
|
-
}));
|
|
1129
|
-
|
|
1130
|
-
// ✅ If limit is set, trim duplicates list
|
|
1131
|
-
if (limit && duplicateENNames.length > limit) {
|
|
1132
|
-
duplicateENNames = duplicateENNames.slice(0, limit);
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
console.log(
|
|
1136
|
-
`✅ Found ${duplicateENNames.length} duplicate EN names out of ${totalKeysScanned} entries.`
|
|
1137
|
-
);
|
|
1138
|
-
|
|
1139
|
-
return {
|
|
1140
|
-
duplicateEntries: duplicateENNames, // ✅ List of EN names that need regeneration
|
|
1141
|
-
usedNamesEN, // ✅ Set of all unique EN names
|
|
1142
|
-
usedNamesPT, // ✅ Set of all unique PT names
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
905
|
|
|
1146
906
|
async generateAstroReportContent(
|
|
1147
907
|
params:
|
|
@@ -1831,7 +1591,7 @@ export class ConceptService {
|
|
|
1831
1591
|
);
|
|
1832
1592
|
}
|
|
1833
1593
|
|
|
1834
|
-
// src/services/
|
|
1594
|
+
// src/services/ts
|
|
1835
1595
|
async checkConceptCompletion(
|
|
1836
1596
|
conceptSlug: Concept,
|
|
1837
1597
|
combinationString: string,
|
|
@@ -1866,4 +1626,172 @@ export class ConceptService {
|
|
|
1866
1626
|
|
|
1867
1627
|
return hasPrompt && !!hasPostImages;
|
|
1868
1628
|
}
|
|
1629
|
+
|
|
1630
|
+
async getLang(userId: string): Promise<'pt-br' | 'en-us'> {
|
|
1631
|
+
try {
|
|
1632
|
+
console.log(
|
|
1633
|
+
`🌍 [LANG QUERY] Fetching language preference for user: ${userId}`
|
|
1634
|
+
);
|
|
1635
|
+
|
|
1636
|
+
const drizzle = this.context.drizzle();
|
|
1637
|
+
const user = await drizzle
|
|
1638
|
+
.select({ language: users.language })
|
|
1639
|
+
.from(users)
|
|
1640
|
+
.where(eq(users.id, userId))
|
|
1641
|
+
.get();
|
|
1642
|
+
|
|
1643
|
+
if (!user || !user.language) {
|
|
1644
|
+
console.warn(
|
|
1645
|
+
`⚠️ [LANG WARNING] No language preference found for user: ${userId}, defaulting to 'pt-br'`
|
|
1646
|
+
);
|
|
1647
|
+
return 'pt-br';
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
console.log(
|
|
1651
|
+
`✅ [LANG SUCCESS] Retrieved language for user: ${userId} -> ${user.language}`
|
|
1652
|
+
);
|
|
1653
|
+
return user.language as 'pt-br' | 'en-us';
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
console.error(
|
|
1656
|
+
`❌ [ERROR] Failed to fetch language for user ${userId}:`,
|
|
1657
|
+
error
|
|
1658
|
+
);
|
|
1659
|
+
throw new Error('Failed to fetch user language');
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
async queueUserConcepts(userId: string): Promise<void> {
|
|
1664
|
+
console.log(
|
|
1665
|
+
`[${new Date().toISOString()}] 🔧 Starting queueing of user concepts for user ${userId}`
|
|
1666
|
+
);
|
|
1667
|
+
|
|
1668
|
+
const lang = await this.getLang(userId);
|
|
1669
|
+
console.log(
|
|
1670
|
+
`[${new Date().toISOString()}] 📝 Retrieved language for user ${userId}: ${lang}`
|
|
1671
|
+
);
|
|
1672
|
+
|
|
1673
|
+
const db = this.context.drizzle();
|
|
1674
|
+
const { userConcepts } = schema;
|
|
1675
|
+
|
|
1676
|
+
// Fetch all available concepts
|
|
1677
|
+
const concepts = await this.getAllConcepts();
|
|
1678
|
+
if (!concepts || concepts.length === 0) {
|
|
1679
|
+
console.log(
|
|
1680
|
+
`[${new Date().toISOString()}] ⚠️ No concepts found to queue for user ${userId}`
|
|
1681
|
+
);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
console.log(
|
|
1685
|
+
`[${new Date().toISOString()}] ✅ Retrieved ${
|
|
1686
|
+
concepts.length
|
|
1687
|
+
} concepts for user ${userId}`
|
|
1688
|
+
);
|
|
1689
|
+
|
|
1690
|
+
for (const concept of concepts) {
|
|
1691
|
+
const {
|
|
1692
|
+
id: conceptId,
|
|
1693
|
+
slug: conceptSlug,
|
|
1694
|
+
planet1,
|
|
1695
|
+
planet2,
|
|
1696
|
+
planet3,
|
|
1697
|
+
} = concept;
|
|
1698
|
+
|
|
1699
|
+
console.log(
|
|
1700
|
+
`[${new Date().toISOString()}] 🔧 Processing concept ${conceptSlug} for user ${userId}`
|
|
1701
|
+
);
|
|
1702
|
+
|
|
1703
|
+
const planets = [planet1, planet2, planet3];
|
|
1704
|
+
const userSigns = await this.getUserSigns(userId, planets);
|
|
1705
|
+
if (userSigns.length !== 3) {
|
|
1706
|
+
console.log(
|
|
1707
|
+
`[${new Date().toISOString()}] ⚠️ Missing astrological data for user ${userId} in concept ${conceptSlug}, found ${
|
|
1708
|
+
userSigns.length
|
|
1709
|
+
} signs`
|
|
1710
|
+
);
|
|
1711
|
+
continue; // Skip to the next concept if signs are missing
|
|
1712
|
+
}
|
|
1713
|
+
console.log(
|
|
1714
|
+
`[${new Date().toISOString()}] ✅ Retrieved user signs for ${conceptSlug}: ${userSigns
|
|
1715
|
+
.map((s) => `${s.name}: ${s.sign}`)
|
|
1716
|
+
.join(', ')}`
|
|
1717
|
+
);
|
|
1718
|
+
|
|
1719
|
+
const signMap = new Map(userSigns.map((s) => [s.name, s.sign]));
|
|
1720
|
+
const combinationString = [
|
|
1721
|
+
signMap.get(planet1),
|
|
1722
|
+
signMap.get(planet2),
|
|
1723
|
+
signMap.get(planet3),
|
|
1724
|
+
].join('-');
|
|
1725
|
+
console.log(
|
|
1726
|
+
`[${new Date().toISOString()}] 🔗 Generated combination string for ${conceptSlug}: ${combinationString}`
|
|
1727
|
+
);
|
|
1728
|
+
|
|
1729
|
+
// Create or retrieve conceptCombination
|
|
1730
|
+
const conceptCombinationId = await this.getOrCreateConceptCombination(
|
|
1731
|
+
conceptId,
|
|
1732
|
+
combinationString,
|
|
1733
|
+
planets,
|
|
1734
|
+
userSigns
|
|
1735
|
+
);
|
|
1736
|
+
console.log(
|
|
1737
|
+
`[${new Date().toISOString()}] ✅ Created/Retrieved concept combination ID for ${conceptSlug}: ${conceptCombinationId}`
|
|
1738
|
+
);
|
|
1739
|
+
|
|
1740
|
+
// Check if a userConcept exists; create if not
|
|
1741
|
+
const existingUserConcept = await db
|
|
1742
|
+
.select({ conceptCombinationId: userConcepts.conceptCombinationId })
|
|
1743
|
+
.from(userConcepts)
|
|
1744
|
+
.where(
|
|
1745
|
+
and(
|
|
1746
|
+
eq(userConcepts.userId, userId),
|
|
1747
|
+
eq(userConcepts.conceptId, conceptId)
|
|
1748
|
+
)
|
|
1749
|
+
)
|
|
1750
|
+
.limit(1)
|
|
1751
|
+
.execute();
|
|
1752
|
+
|
|
1753
|
+
if (!existingUserConcept.length) {
|
|
1754
|
+
const userConceptId = uuidv4();
|
|
1755
|
+
await db
|
|
1756
|
+
.insert(userConcepts)
|
|
1757
|
+
.values({
|
|
1758
|
+
id: userConceptId,
|
|
1759
|
+
userId,
|
|
1760
|
+
conceptId,
|
|
1761
|
+
conceptCombinationId,
|
|
1762
|
+
createdAt: sql`CURRENT_TIMESTAMP`,
|
|
1763
|
+
updatedAt: sql`CURRENT_TIMESTAMP`,
|
|
1764
|
+
})
|
|
1765
|
+
.execute();
|
|
1766
|
+
console.log(
|
|
1767
|
+
`[${new Date().toISOString()}] ✅ Inserted new user concept with ID: ${userConceptId} for ${conceptSlug}`
|
|
1768
|
+
);
|
|
1769
|
+
} else {
|
|
1770
|
+
console.log(
|
|
1771
|
+
`[${new Date().toISOString()}] 🔍 Found existing user concept for user ${userId}, concept ${conceptSlug}`
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// Queue the generation
|
|
1776
|
+
const queuePayload = {
|
|
1777
|
+
userId,
|
|
1778
|
+
conceptSlug,
|
|
1779
|
+
combinationString,
|
|
1780
|
+
lang,
|
|
1781
|
+
conceptCombinationId,
|
|
1782
|
+
};
|
|
1783
|
+
console.log(
|
|
1784
|
+
`[${new Date().toISOString()}] ✅ Queue Payload for ${conceptSlug}:`,
|
|
1785
|
+
queuePayload
|
|
1786
|
+
);
|
|
1787
|
+
await this.context.env.CONCEPT_GENERATION_QUEUE.send(queuePayload);
|
|
1788
|
+
console.log(
|
|
1789
|
+
`[${new Date().toISOString()}] ⏳ Queued concept generation for ${conceptSlug}:${combinationString}`
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
console.log(
|
|
1794
|
+
`[${new Date().toISOString()}] ✅ Completed queueing all concepts for user ${userId}`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1869
1797
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { eq } from 'drizzle-orm';
|
|
2
2
|
import { injectable } from 'inversify';
|
|
3
|
+
import { payments, products, userProducts } from '../../db/schema';
|
|
3
4
|
import { AppContext } from '../base';
|
|
4
|
-
import {
|
|
5
|
+
import { ConceptService } from './ConceptService';
|
|
5
6
|
|
|
6
7
|
@injectable()
|
|
7
8
|
export class PaymentService {
|
|
8
|
-
constructor(
|
|
9
|
+
constructor(
|
|
10
|
+
private context: AppContext,
|
|
11
|
+
private conceptService: ConceptService
|
|
12
|
+
) {}
|
|
9
13
|
|
|
10
14
|
async handleCheckoutEvent(payload: any): Promise<void> {
|
|
11
15
|
const { event, checkout } = payload;
|
|
@@ -57,6 +61,60 @@ export class PaymentService {
|
|
|
57
61
|
private async handleCheckoutPaid(checkout: any): Promise<void> {
|
|
58
62
|
const checkoutId = checkout.id;
|
|
59
63
|
const db = this.context.drizzle();
|
|
64
|
+
|
|
65
|
+
// Fetch the userProduct to get userId and productId
|
|
66
|
+
const userProduct = await db
|
|
67
|
+
.select({
|
|
68
|
+
userId: userProducts.userId,
|
|
69
|
+
productId: userProducts.productId,
|
|
70
|
+
})
|
|
71
|
+
.from(userProducts)
|
|
72
|
+
.where(eq(userProducts.checkoutId, checkoutId))
|
|
73
|
+
.limit(1)
|
|
74
|
+
.get();
|
|
75
|
+
|
|
76
|
+
if (!userProduct) {
|
|
77
|
+
console.log(
|
|
78
|
+
`[${new Date().toISOString()}] ⚠️ No user product found for checkoutId: ${checkoutId}`
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Fetch the product slug
|
|
84
|
+
const product = await db
|
|
85
|
+
.select({ slug: products.slug })
|
|
86
|
+
.from(products)
|
|
87
|
+
.where(eq(products.id, userProduct.productId))
|
|
88
|
+
.limit(1)
|
|
89
|
+
.get();
|
|
90
|
+
|
|
91
|
+
if (!product) {
|
|
92
|
+
console.log(
|
|
93
|
+
`[${new Date().toISOString()}] ⚠️ No product found for productId: ${
|
|
94
|
+
userProduct.productId
|
|
95
|
+
}`
|
|
96
|
+
);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Queue concept generations if the product slug is "concepts"
|
|
101
|
+
if (product.slug === 'concepts') {
|
|
102
|
+
try {
|
|
103
|
+
await this.conceptService.queueUserConcepts(userProduct.userId);
|
|
104
|
+
console.log(
|
|
105
|
+
`[${new Date().toISOString()}] ⏳ Queued concept generations for user ${
|
|
106
|
+
userProduct.userId
|
|
107
|
+
} after successful payment`
|
|
108
|
+
);
|
|
109
|
+
} catch {
|
|
110
|
+
console.log(
|
|
111
|
+
`[${new Date().toISOString()}] ⚠️ Error queuing concept generations for user ${
|
|
112
|
+
userProduct.userId
|
|
113
|
+
} after successful payment`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
60
118
|
// Update userProducts to 'unlocked'
|
|
61
119
|
await db
|
|
62
120
|
.update(userProducts)
|
|
@@ -78,6 +136,7 @@ export class PaymentService {
|
|
|
78
136
|
const checkoutId = checkout.id;
|
|
79
137
|
const db = this.context.drizzle();
|
|
80
138
|
// Update userProducts to 'locked'
|
|
139
|
+
|
|
81
140
|
await db
|
|
82
141
|
.update(userProducts)
|
|
83
142
|
.set({ status: 'locked', updatedAt: new Date() })
|