Skip to content

Commit f2ca99c

Browse files
committed
feat: Implement payment provider abstraction, Drizzle ORM database service, and core application utilities and stores.
1 parent 7164ffe commit f2ca99c

File tree

12 files changed

+478
-149
lines changed

12 files changed

+478
-149
lines changed

package-lock.json

Lines changed: 292 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"@sentry/nextjs": "^10.27.0",
5555
"@stripe/stripe-js": "^8.0.0",
5656
"@tabler/icons-react": "^3.34.1",
57+
"@upstash/ratelimit": "^2.0.5",
5758
"@vercel/kv": "^3.0.0",
5859
"aceternity-ui": "^0.2.2",
5960
"ai": "^5.0.28",
@@ -75,6 +76,8 @@
7576
"next": "15.5.2",
7677
"next-themes": "^0.4.6",
7778
"openai": "^5.16.0",
79+
"pino": "^10.1.0",
80+
"pino-pretty": "^13.1.3",
7881
"razorpay": "^2.9.6",
7982
"react": "19.1.0",
8083
"react-dom": "19.1.0",
@@ -101,6 +104,9 @@
101104
"eslint": "^9",
102105
"eslint-config-next": "15.5.2",
103106
"tailwindcss": "^4.1.13",
104-
"typescript": "^5"
107+
"typescript": "^5",
108+
"vitest": "^2.1.8",
109+
"@testing-library/react": "^16.0.1",
110+
"@testing-library/jest-dom": "^6.6.3"
105111
}
106112
}

src/lib/image-session-store.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,17 @@ export function cleanupExpiredImages(): number {
127127
/**
128128
* Get session info (without the image data)
129129
*/
130-
export function getSessionInfo(chatId: string): Omit<ImageSession, 'imageBase64'> | null {
130+
export function getSessionInfo(
131+
chatId: string,
132+
): Omit<ImageSession, 'imageBase64'> | null {
131133
const session = imageSessionStore.get(chatId);
132134

133135
if (!session) {
134136
return null;
135137
}
136138

137139
const { imageBase64, ...info } = session;
140+
void imageBase64;
138141
return info;
139142
}
140143

@@ -143,7 +146,7 @@ export function getSessionInfo(chatId: string): Omit<ImageSession, 'imageBase64'
143146
*/
144147
export function hasSessionImage(chatId: string, userId: string): boolean {
145148
const session = imageSessionStore.get(chatId);
146-
return session !== null && session.userId === userId;
149+
return !!session && session.userId === userId;
147150
}
148151

149152
// Run cleanup every 15 minutes

src/lib/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const logger = pino({
77
asObject: true,
88
},
99
formatters: {
10-
level: (label) => {
10+
level: (label: string) => {
1111
return { level: label };
1212
},
1313
},

src/lib/mem0-client.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function getMemoryClient(): MemoryClient {
3636
export async function addMemory(
3737
messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,
3838
userId: string,
39-
chatId?: string
39+
chatId?: string,
4040
) {
4141
try {
4242
const client = getMemoryClient();
@@ -64,16 +64,25 @@ export async function addMemory(
6464
// Add timestamp for debugging
6565
metadata.timestamp = new Date().toISOString();
6666

67-
console.log(`📝 [MEM0] Adding ${messages.length} messages for user ${userId}, chat ${chatId || 'N/A'}`);
67+
console.log(
68+
`📝 [MEM0] Adding ${messages.length} messages for user ${userId}, chat ${
69+
chatId || 'N/A'
70+
}`,
71+
);
6872
console.log(`📝 [MEM0] Metadata:`, JSON.stringify(metadata));
6973

7074
// Log message summary for debugging
7175
messages.forEach((msg, idx) => {
7276
console.log(` ${idx + 1}. ${msg.role}: ${msg.content.slice(0, 50)}...`);
7377
});
7478

79+
// Filter out system messages as they might not be supported or needed for memory
80+
const validMessages = messages.filter(
81+
(msg) => msg.role !== 'system',
82+
) as Array<{ role: 'user' | 'assistant'; content: string }>;
83+
7584
// Call Mem0 API
76-
const result = await client.add(messages, metadata);
85+
const result = await client.add(validMessages, metadata);
7786

7887
console.log('✅ [MEM0] Memory stored successfully');
7988
console.log('📊 [MEM0] Result:', JSON.stringify(result, null, 2));
@@ -105,7 +114,7 @@ export async function searchMemory(
105114
query: string,
106115
userId: string,
107116
chatId?: string,
108-
limit: number = 10
117+
limit: number = 10,
109118
) {
110119
try {
111120
const client = getMemoryClient();
@@ -118,16 +127,12 @@ export async function searchMemory(
118127

119128
// Build filters for searching memories
120129
const filters: any = {
121-
OR: [
122-
{ user_id: userId }
123-
]
130+
OR: [{ user_id: userId }],
124131
};
125132

126133
// Optionally filter by specific chat
127134
if (chatId) {
128-
filters.AND = [
129-
{ chat_id: chatId }
130-
];
135+
filters.AND = [{ chat_id: chatId }];
131136
}
132137

133138
console.log(`🔍 [MEM0] Searching memory...`);
@@ -140,10 +145,12 @@ export async function searchMemory(
140145
const results = await client.search(query, {
141146
api_version: 'v2',
142147
filters: filters,
143-
limit: limit
148+
limit: limit,
144149
});
145150

146-
console.log(`✅ [MEM0] Search completed: found ${results?.length || 0} memories`);
151+
console.log(
152+
`✅ [MEM0] Search completed: found ${results?.length || 0} memories`,
153+
);
147154

148155
if (results && results.length > 0) {
149156
console.log('📋 [MEM0] Sample results:');
@@ -176,10 +183,7 @@ export async function getChatMemories(userId: string, chatId: string) {
176183
const client = getMemoryClient();
177184

178185
const filters = {
179-
AND: [
180-
{ user_id: userId },
181-
{ chat_id: chatId }
182-
]
186+
AND: [{ user_id: userId }, { chat_id: chatId }],
183187
};
184188

185189
console.log(`📚 [MEM0] Retrieving all memories for chat ${chatId}`);
@@ -188,7 +192,7 @@ export async function getChatMemories(userId: string, chatId: string) {
188192
const results = await client.search('', {
189193
api_version: 'v2',
190194
filters: filters,
191-
limit: 50 // Get more memories for full context
195+
limit: 50, // Get more memories for full context
192196
});
193197

194198
console.log(`✅ [MEM0] Retrieved ${results?.length || 0} chat memories`);
@@ -205,13 +209,15 @@ export async function getChatMemories(userId: string, chatId: string) {
205209
*/
206210
export async function deleteUserMemories(userId: string) {
207211
try {
208-
const client = getMemoryClient();
212+
getMemoryClient(); // Ensure client is initialized
209213

210214
console.log(`🗑️ [MEM0] Deleting memories for user ${userId}`);
211215

212216
// Note: The actual delete implementation depends on mem0ai API
213217
// You may need to adjust this based on the latest API
214-
console.warn('[MEM0] Delete functionality needs to be implemented based on mem0ai API');
218+
console.warn(
219+
'[MEM0] Delete functionality needs to be implemented based on mem0ai API',
220+
);
215221

216222
return { success: true, message: 'Delete not yet implemented' };
217223
} catch (error) {

src/lib/nano-banana.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { nanoBananaConfig, IMAGE_GENERATION_CONFIG } from '@/config/ai';
1+
import { nanoBananaConfig } from '@/config/ai';
22

33
interface NanoBananaResponse {
44
success: boolean;
@@ -46,7 +46,7 @@ export class NanoBananaService {
4646
*/
4747
async generateThumbnail(
4848
prompt: string,
49-
options: GenerationOptions = {}
49+
options: GenerationOptions = {},
5050
): Promise<NanoBananaResponse> {
5151
const startTime = Date.now();
5252

@@ -63,15 +63,17 @@ export class NanoBananaService {
6363
const response = await fetch(`${this.baseUrl}/images/generations`, {
6464
method: 'POST',
6565
headers: {
66-
'Authorization': `Bearer ${this.apiKey}`,
66+
Authorization: `Bearer ${this.apiKey}`,
6767
'Content-Type': 'application/json',
6868
},
6969
body: JSON.stringify(requestBody),
7070
});
7171

7272
if (!response.ok) {
7373
const errorData = await response.text();
74-
throw new Error(`Nano Banana API error: ${response.status} - ${errorData}`);
74+
throw new Error(
75+
`Nano Banana API error: ${response.status} - ${errorData}`,
76+
);
7577
}
7678

7779
const data = await response.json();
@@ -96,7 +98,8 @@ export class NanoBananaService {
9698

9799
return {
98100
success: false,
99-
error: error instanceof Error ? error.message : 'Unknown error occurred',
101+
error:
102+
error instanceof Error ? error.message : 'Unknown error occurred',
100103
metadata: {
101104
model: options.model || nanoBananaConfig.models.flash,
102105
prompt: prompt,
@@ -113,13 +116,13 @@ export class NanoBananaService {
113116
async generateThumbnailVariations(
114117
prompt: string,
115118
count: number = 2,
116-
options: GenerationOptions = {}
119+
options: GenerationOptions = {},
117120
): Promise<NanoBananaResponse[]> {
118121
const promises = Array.from({ length: count }, (_, index) =>
119122
this.generateThumbnail(prompt, {
120123
...options,
121124
seed: options.seed ? options.seed + index : undefined,
122-
})
125+
}),
123126
);
124127

125128
return Promise.all(promises);
@@ -140,7 +143,9 @@ export class NanoBananaService {
140143
];
141144

142145
// Check if prompt already contains thumbnail-specific terms
143-
const hasThumbTerms = /thumbnail|youtube|clickbait|title|preview/i.test(prompt);
146+
const hasThumbTerms = /thumbnail|youtube|clickbait|title|preview/i.test(
147+
prompt,
148+
);
144149

145150
if (!hasThumbTerms) {
146151
prompt += ', thumbnail style';
@@ -149,7 +154,7 @@ export class NanoBananaService {
149154
// Add enhancements if not already present
150155
const lowercasePrompt = prompt.toLowerCase();
151156
const missingEnhancements = enhancements.filter(
152-
enhancement => !lowercasePrompt.includes(enhancement)
157+
(enhancement) => !lowercasePrompt.includes(enhancement),
153158
);
154159

155160
if (missingEnhancements.length > 0) {
@@ -166,7 +171,7 @@ export class NanoBananaService {
166171
try {
167172
const response = await fetch(`${this.baseUrl}/models`, {
168173
headers: {
169-
'Authorization': `Bearer ${this.apiKey}`,
174+
Authorization: `Bearer ${this.apiKey}`,
170175
},
171176
});
172177

@@ -184,7 +189,7 @@ export class NanoBananaService {
184189
try {
185190
const response = await fetch(`${this.baseUrl}/models`, {
186191
headers: {
187-
'Authorization': `Bearer ${this.apiKey}`,
192+
Authorization: `Bearer ${this.apiKey}`,
188193
},
189194
});
190195

@@ -193,12 +198,16 @@ export class NanoBananaService {
193198
}
194199

195200
const data = await response.json();
196-
return data.data?.map((model: any) => model.id) || [nanoBananaConfig.models.flash];
201+
return (
202+
data.data?.map((model: any) => model.id) || [
203+
nanoBananaConfig.models.flash,
204+
]
205+
);
197206
} catch (error) {
198207
console.error('Error fetching available models:', error);
199208
return [nanoBananaConfig.models.flash]; // Default fallback
200209
}
201210
}
202211
}
203212

204-
export default NanoBananaService;
213+
export default NanoBananaService;

src/lib/payment/provider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import Stripe from 'stripe';
99
import Razorpay from 'razorpay';
1010
import { razorpayConfig } from '@/config/payment';
11+
import crypto from 'crypto';
1112

1213
// Payment provider types
1314
export type PaymentProvider = 'stripe' | 'razorpay';
@@ -239,7 +240,7 @@ export function verifyWebhookSignature(
239240
}
240241

241242
if (PROVIDER === 'razorpay') {
242-
const crypto = require('crypto');
243+
243244
const expectedSignature = crypto
244245
.createHmac('sha256', secret)
245246
.update(payload)

0 commit comments

Comments
 (0)