Skip to content

Commit 8591572

Browse files
authored
Merge pull request #12 from Sentiopulse/feat/add-upstash-int
feat: Integrate Upstash Redis for Dynamic PostGroup Storage and Retrieval
2 parents 49facc1 + 211bbdc commit 8591572

File tree

6 files changed

+281
-48
lines changed

6 files changed

+281
-48
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
"classify:title": "tsx run-classifier.ts title",
1212
"dev": "tsx --watch run-classifier.ts",
1313
"build": "tsc",
14-
"start": "node dist/run-classifier.js"
14+
"start": "node dist/run-classifier.js",
15+
"seed": "tsx src/seed.ts",
16+
"seed:clear": "tsx src/seed.ts clear",
17+
"seed:verify": "tsx src/seed.ts verify"
1518
},
1619
"dependencies": {
1720
"@prisma/client": "^6.15.0",

src/postGroup.ts

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
12
import cron from 'node-cron';
23
import { generateTitleForPost } from './generateTitle';
4+
import { initRedis, getRedisClient } from './redisClient';
35

46
export type Post = {
57
id: string;
@@ -25,62 +27,40 @@ export async function generateTitleForPostGroup(postGroup: PostGroup): Promise<s
2527
return await generateTitleForPost(combinedContent);
2628
}
2729

28-
//One group with several posts
29-
const postGroups: PostGroup[] = [
30-
{
31-
id: "group1",
32-
posts: [
33-
{
34-
id: "post1",
35-
content: `Empery Digital\n@EMPD_BTC\n·\n11m\nBitcoin Firsts that changed everything:\n- $4B Pizza\n- A nation bets on BTC\n- Wall Street embraces it\n- The Trillion-Dollar Club\nFrom a pizza order to reshaping global finance.\n#Bitcoin #BTC #Blockchain #EmperyDigital`,
36-
sentiment: "BULLISH",
37-
source: "TWITTER",
38-
categories: ["Cryptocurrency", "Market Analysis"],
39-
subcategories: ["Bitcoin", "Milestones", "Adoption"],
40-
link: undefined,
41-
createdAt: new Date().toISOString(),
42-
updatedAt: new Date().toISOString(),
43-
},
44-
{
45-
id: "post2",
46-
content: `Empery Digital\n@EMPD_BTC\n·\n11m\nSome notable events in Bitcoin's history include:\n- The purchase of pizza with Bitcoin\n- A country adopting BTC\n- Increased interest from Wall Street\n- Joining the Trillion-Dollar Club\nThese milestones reflect Bitcoin's evolving role in finance.\n#Bitcoin #BTC #Blockchain #EmperyDigital`,
47-
sentiment: "NEUTRAL",
48-
source: "TWITTER",
49-
categories: ["Cryptocurrency", "Market Analysis"],
50-
subcategories: ["Bitcoin", "Milestones", "Adoption"],
51-
link: undefined,
52-
createdAt: new Date().toISOString(),
53-
updatedAt: new Date().toISOString(),
54-
},
55-
{
56-
id: "post3",
57-
content: `Empery Digital\n@EMPD_BTC\n·\n11m\nRecent events in Bitcoin's history have raised concerns:\n- The infamous $4B pizza purchase\n- A nation risking its economy on BTC\n- Wall Street's speculative involvement\n- Entering the Trillion-Dollar Club amid volatility\nFrom a simple transaction to ongoing financial uncertainty.\n#Bitcoin #BTC #Blockchain #EmperyDigital`,
58-
sentiment: "BEARISH",
59-
source: "TWITTER",
60-
categories: ["Cryptocurrency", "Market Analysis"],
61-
subcategories: ["Bitcoin", "Milestones", "Risks"],
62-
link: undefined,
63-
createdAt: new Date().toISOString(),
64-
updatedAt: new Date().toISOString(),
65-
}
66-
]
67-
}
68-
];
6930

31+
// Fetch all PostGroups from Redis (expects a key 'PostGroup' with a JSON array, or adapt as needed)
32+
export async function fetchPostGroupsFromRedis(): Promise<PostGroup[]> {
33+
await initRedis();
34+
const redis = getRedisClient();
35+
// Adjust the key if you use a different one
36+
const data = await redis.get('post-groups');
37+
if (!data) return [];
38+
try {
39+
return JSON.parse(data);
40+
} catch (e) {
41+
console.error('Failed to parse post-groups data from Redis:', e);
42+
return [];
43+
}
44+
}
7045

71-
export default postGroups;
7246

7347

74-
// Reusable function to generate and log the title for the first PostGroup
48+
// Reusable function to generate and log the title for all PostGroups from Redis
7549
export async function logTitlesForAllPostGroups(context: 'CRON' | 'MANUAL' = 'MANUAL') {
50+
const postGroups = await fetchPostGroupsFromRedis();
7651
if (!postGroups.length) {
77-
console.log('No PostGroups found.');
52+
console.log('No PostGroups found in Redis.');
7853
return;
7954
}
55+
let updated = false;
56+
const postGroupsWithOrderedKeys = [];
8057
for (const group of postGroups) {
58+
let title = group.title;
8159
try {
82-
const title = await generateTitleForPostGroup(group);
83-
group.title = title;
60+
title = await generateTitleForPostGroup(group);
61+
if (group.title !== title) {
62+
updated = true;
63+
}
8464
if (context === 'CRON') {
8565
console.log(`[CRON] Generated Title for PostGroup (id: ${group.id}) at ${new Date().toISOString()}:`, title);
8666
} else {
@@ -93,6 +73,22 @@ export async function logTitlesForAllPostGroups(context: 'CRON' | 'MANUAL' = 'MA
9373
console.error(`Error generating title for PostGroup (id: ${group.id}):`, e);
9474
}
9575
}
76+
postGroupsWithOrderedKeys.push({
77+
id: group.id,
78+
title,
79+
posts: group.posts
80+
});
81+
}
82+
// Save updated PostGroups with titles back to Redis
83+
if (updated) {
84+
await initRedis();
85+
const redis = getRedisClient();
86+
await redis.set('post-groups', JSON.stringify(postGroupsWithOrderedKeys));
87+
if (context === 'CRON') {
88+
console.log('[CRON] Updated post-groups with titles saved to Redis.');
89+
} else {
90+
console.log('Updated post-groups with titles saved to Redis.');
91+
}
9692
}
9793
}
9894

src/redisClient.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ export async function initRedis(): Promise<RedisClientType> {
5151
await newClient.connect();
5252
// assign to module client (cast to satisfy TS) and return the concrete client
5353
client = newClient as unknown as RedisClientType;
54-
console.log("Redis connected:", safeRedisUrlForLog(REDIS_URL));
5554
// Clear connecting before returning so subsequent callers don't wait
5655
connecting = null;
5756
return newClient as unknown as RedisClientType;

src/seed.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env node
2+
3+
import { seedDatabase, clearDatabase, verifySeeding } from './seedDatabase';
4+
5+
/**
6+
* Command-line script to seed the Upstash Redis database with mock data.
7+
*
8+
* Usage:
9+
* npx tsx src/seed.ts # Seed the database
10+
* npx tsx src/seed.ts clear # Clear all PostGroup data
11+
* npx tsx src/seed.ts verify # Verify seeded data
12+
* npx tsx src/seed.ts --help # Show help
13+
*/
14+
15+
async function main() {
16+
const args = process.argv.slice(2);
17+
const command = args[0]?.toLowerCase();
18+
19+
try {
20+
switch (command) {
21+
case 'clear':
22+
await clearDatabase();
23+
break;
24+
25+
case 'verify':
26+
await verifySeeding();
27+
break;
28+
29+
case '--help':
30+
case '-h':
31+
case 'help':
32+
showHelp();
33+
break;
34+
35+
case undefined:
36+
// Default action: seed the database
37+
await seedDatabase();
38+
await verifySeeding(); // Verify after seeding
39+
break;
40+
41+
default:
42+
console.error(`❌ Unknown command: ${command}`);
43+
showHelp();
44+
process.exit(1);
45+
}
46+
47+
console.log('✨ Operation completed successfully!');
48+
process.exit(0);
49+
50+
} catch (error) {
51+
console.error('💥 Operation failed:', error);
52+
process.exit(1);
53+
}
54+
}
55+
56+
function showHelp() {
57+
console.log(`
58+
🌱 Database Seeding Script
59+
60+
Usage:
61+
npx tsx src/seed.ts # Seed the database with mock data
62+
npx tsx src/seed.ts clear # Clear all PostGroup data (use with caution!)
63+
npx tsx src/seed.ts verify # Verify that data was seeded correctly
64+
npx tsx src/seed.ts --help # Show this help message
65+
66+
Examples:
67+
# Seed the database with mock post groups and posts
68+
npx tsx src/seed.ts
69+
70+
# Clear all data before seeding fresh data
71+
npx tsx src/seed.ts clear && npx tsx src/seed.ts
72+
73+
# Just verify what's currently in the database
74+
npx tsx src/seed.ts verify
75+
76+
Environment Requirements:
77+
- REDIS_URL or Upstash environment variables must be configured
78+
- See .env file for configuration details
79+
`);
80+
}
81+
82+
// Run the script
83+
if (require.main === module) {
84+
main();
85+
}

src/seedData.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { PostGroup } from './postGroup';
2+
3+
export const seedData: PostGroup[] = [
4+
{
5+
"id": "group1",
6+
"posts": [
7+
{
8+
"id": "post1",
9+
"content": "Empery Digital\n@EMPD_BTC\n·\n11m\nBitcoin Firsts that changed everything:\n- $4B Pizza\n- A nation bets on BTC\n- Wall Street embraces it\n- The Trillion-Dollar Club\nFrom a pizza order to reshaping global finance.\n#Bitcoin #BTC #Blockchain #EmperyDigital",
10+
"sentiment": "BULLISH",
11+
"source": "TWITTER",
12+
"categories": [
13+
"Cryptocurrency",
14+
"Market Analysis"
15+
],
16+
"subcategories": [
17+
"Bitcoin",
18+
"Milestones",
19+
"Adoption"
20+
],
21+
"createdAt": "2025-09-16T12:00:00.000Z",
22+
"updatedAt": "2025-09-16T12:00:00.000Z"
23+
},
24+
{
25+
"id": "post2",
26+
"content": "Empery Digital\n@EMPD_BTC\n·\n11m\nSome notable events in Bitcoin's history include:\n- The purchase of pizza with Bitcoin\n- A country adopting BTC\n- Increased interest from Wall Street\n- Joining the Trillion-Dollar Club\nThese milestones reflect Bitcoin's evolving role in finance.\n#Bitcoin #BTC #Blockchain #EmperyDigital",
27+
"sentiment": "NEUTRAL",
28+
"source": "TWITTER",
29+
"categories": [
30+
"Cryptocurrency",
31+
"Market Analysis"
32+
],
33+
"subcategories": [
34+
"Bitcoin",
35+
"Milestones",
36+
"Adoption"
37+
],
38+
"createdAt": "2025-09-16T12:00:00.000Z",
39+
"updatedAt": "2025-09-16T12:00:00.000Z"
40+
},
41+
{
42+
"id": "post3",
43+
"content": "Empery Digital\n@EMPD_BTC\n·\n11m\nRecent events in Bitcoin's history have raised concerns:\n- The infamous $4B pizza purchase\n- A nation risking its economy on BTC\n- Wall Street's speculative involvement\n- Entering the Trillion-Dollar Club amid volatility\nFrom a simple transaction to ongoing financial uncertainty.\n#Bitcoin #BTC #Blockchain #EmperyDigital",
44+
"sentiment": "BEARISH",
45+
"source": "TWITTER",
46+
"categories": [
47+
"Cryptocurrency",
48+
"Market Analysis"
49+
],
50+
"subcategories": [
51+
"Bitcoin",
52+
"Milestones",
53+
"Risks"
54+
],
55+
"createdAt": "2025-09-16T12:00:00.000Z",
56+
"updatedAt": "2025-09-16T12:00:00.000Z"
57+
}
58+
]
59+
}
60+
];

src/seedDatabase.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { initRedis, getRedisClient } from './redisClient';
2+
import { seedData } from './seedData';
3+
import { PostGroup } from './postGroup';
4+
5+
/**
6+
* Seeds the Upstash Redis database with mock data for PostGroups.
7+
* This function will clear existing PostGroup data and populate it with the seed data.
8+
*/
9+
export async function seedDatabase(): Promise<void> {
10+
try {
11+
console.log('🌱 Starting database seeding...');
12+
13+
// Initialize Redis connection
14+
await initRedis();
15+
const redis = getRedisClient();
16+
17+
console.log('✅ Connected to Redis');
18+
19+
// Store the seed data as post-groups data in Redis
20+
// Using the new key structure as required
21+
await redis.set('post-groups', JSON.stringify(seedData));
22+
23+
console.log(`✅ Successfully seeded ${seedData.length} post group(s) to Redis`);
24+
console.log('📊 Seed data summary:');
25+
26+
seedData.forEach((group: PostGroup, index: number) => {
27+
console.log(` Group ${index + 1}: ${group.id} (${group.posts.length} posts)`);
28+
group.posts.forEach((post, postIndex) => {
29+
console.log(` Post ${postIndex + 1}: ${post.id} - ${post.sentiment} (${post.source})`);
30+
});
31+
});
32+
33+
console.log('🎉 Database seeding completed successfully!');
34+
35+
} catch (error) {
36+
console.error('❌ Error seeding database:', error);
37+
throw error;
38+
}
39+
}
40+
41+
/**
42+
* Clears all PostGroup data from the database.
43+
* Use with caution - this will delete all existing post groups!
44+
*/
45+
export async function clearDatabase(): Promise<void> {
46+
try {
47+
console.log('🧹 Clearing PostGroup data from database...');
48+
49+
await initRedis();
50+
const redis = getRedisClient();
51+
52+
await redis.del('post-groups');
53+
54+
console.log('✅ Database cleared successfully');
55+
56+
} catch (error) {
57+
console.error('❌ Error clearing database:', error);
58+
throw error;
59+
}
60+
}
61+
62+
/**
63+
* Verifies that the seed data was properly stored by retrieving and logging it.
64+
*/
65+
export async function verifySeeding(): Promise<void> {
66+
try {
67+
console.log('🔍 Verifying seeded data...');
68+
69+
await initRedis();
70+
const redis = getRedisClient();
71+
72+
const data = await redis.get('post-groups');
73+
74+
if (!data) {
75+
console.log('❌ No data found in database');
76+
return;
77+
}
78+
79+
const postGroups: PostGroup[] = JSON.parse(data);
80+
81+
console.log(`✅ Found ${postGroups.length} post group(s) in database:`);
82+
postGroups.forEach((group, index) => {
83+
console.log(` Group ${index + 1}: ${group.id} (${group.posts.length} posts)`);
84+
});
85+
86+
} catch (error) {
87+
console.error('❌ Error verifying seeded data:', error);
88+
throw error;
89+
}
90+
}

0 commit comments

Comments
 (0)