Skip to content

Conversation

nickventon
Copy link
Collaborator

@nickventon nickventon commented Sep 16, 2025

Description:

  • Added a cron job using node-cron to automatically generate and log a title for the first PostGroup every 6 hours.
  • Implemented a utility function to aggregate all post contents in a group and generate a concise title using OpenAI.
  • Retained manual execution support: running the file directly (e.g., with npx tsx src/postGroup.ts) will generate and print the title immediately for testing.
  • Both scheduled and manual logs are clearly labeled for easy monitoring and debugging.

Summary by CodeRabbit

  • New Features

    • Grouped posts now get AI-generated titles created from combined post content.
    • Titles refresh automatically on a periodic schedule to keep groups current.
    • Improved aggregation of posts from multiple social sources with richer categorization.
  • Chores

    • Added a scheduling dependency to support periodic background updates.
    • Included sample post groups for demonstration and testing.

Copy link

coderabbitai bot commented Sep 16, 2025

Walkthrough

Added node-cron dependency and a new src/postGroup.ts module that defines Post/PostGroup types, provides sample data, implements generateTitleForPostGroup (concatenates post contents, calls existing generateTitle), schedules a 6-hour cron job to generate and log titles for all post groups, and adds a main-guard for direct execution logging.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Added dependency: node-cron@^4.2.1.
Post grouping module & scheduler
src/postGroup.ts
New module exporting types Post, PostGroup; generateTitleForPostGroup(postGroup): Promise<string>; exported `logTitlesForAllPostGroups(context?: 'CRON'

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Timer as node-cron (every 6h)
  participant Mod as postGroup.ts
  participant Gen as generateTitle
  participant Log as Console

  Timer->>Mod: Trigger scheduled task
  Mod->>Mod: Iterate postGroups
  alt postGroups non-empty
    loop for each PostGroup
      Mod->>Gen: generateTitle(concatenated post contents)
      Gen-->>Mod: Title string
      Mod->>Mod: assign group.title
      Mod->>Log: Log generated title (context: CRON)
    end
  else no groups
    Mod->>Log: Log "No PostGroup found"
  end
  Note over Mod,Gen: Errors are caught and logged
Loading
sequenceDiagram
  autonumber
  actor CLI as Node (direct run)
  participant Mod as postGroup.ts
  participant Gen as generateTitle
  participant Log as Console

  CLI->>Mod: Execute (require.main === module)
  Mod->>Mod: Iterate postGroups
  alt postGroups non-empty
    loop for each PostGroup
      Mod->>Gen: generateTitle(concatenated post contents)
      Gen-->>Mod: Title string
      Mod->>Mod: assign group.title
      Mod->>Log: Log generated title (context: MANUAL)
    end
  else no groups
    Mod->>Log: Log "No PostGroup found"
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • tasin2610

Poem

Thump-thump, I wake the cron at dawn,
Every six hours I hop along—
I nibble posts, I stitch their tune,
A tidy title by the moon.
Rabbit grins: new headlines bloom 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the primary change: adding both scheduled (cron) and manual title generation for PostGroup; it is specific, concise, and uses a conventional "feat:" prefix appropriate for a feature change.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-title-gen-and-cronjob-for-postgroup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
package.json (1)

19-19: Verify node-cron interop and pin if needed.

  • Ensure tsconfig has esModuleInterop or allowSyntheticDefaultImports enabled; otherwise import cron from 'node-cron' in src/postGroup.ts will fail at compile time.
  • Consider pinning to an exact version to avoid scheduler behavior drifting on prod.

Add a convenience script for manual runs:

   "scripts": {
     "classify": "tsx run-classifier.ts",
+    "postgroup:title": "tsx src/postGroup.ts",
     "classify:complete": "tsx run-classifier.ts complete",
src/postGroup.ts (3)

1-2: Confirm default import shape for node-cron.

import cron from 'node-cron' assumes default export + TS interop. If your tsconfig lacks esModuleInterop/allowSyntheticDefaultImports, switch to named import (import { schedule } from 'node-cron') and call schedule(...).


22-26: Guard empty groups, cap payload size, and avoid logging raw content on failures.

  • Empty posts yields an empty prompt to OpenAI.
  • Large groups can blow token limits.
  • The referenced generateTitleForPost logs the full input on errors; passing the aggregated content risks leaking lots of text to logs.

Apply:

 export async function generateTitleForPostGroup(postGroup: PostGroup): Promise<string> {
-    const combinedContent = postGroup.posts.map(post => post.content).join('\n\n');
-    return await generateTitleForPost(combinedContent);
+    if (!postGroup?.posts?.length) {
+        throw new Error(`PostGroup ${postGroup?.id ?? 'unknown'} has no posts`);
+    }
+    const MAX_CHARS = 3500;
+    let combinedContent = postGroup.posts.map(p => p.content.trim()).join('\n\n');
+    if (combinedContent.length > MAX_CHARS) {
+        combinedContent = combinedContent.slice(0, MAX_CHARS) + '…';
+    }
+    try {
+        return await generateTitleForPost(combinedContent);
+    } catch (e) {
+        // Avoid logging raw combined content; include only metadata
+        throw new Error(`Title generation failed for PostGroup ${postGroup.id} (posts=${postGroup.posts.length}): ${String(e)}`);
+    }
 }

28-101: Seed data nits: drop link: undefined and consider fixed timestamps.

  • Explicit link: undefined is noisy; omit the key.
  • If this data is ever snapshot-tested, new Date().toISOString() at import time creates nondeterminism.
-                link: undefined,
+                // link intentionally omitted
-                createdAt: new Date().toISOString(),
-                updatedAt: new Date().toISOString(),
+                createdAt: new Date().toISOString(),
+                updatedAt: new Date().toISOString(),

If determinism is desired, replace with fixed ISO strings in fixtures.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5c5ad6 and cc9b622.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json (1 hunks)
  • src/postGroup.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/postGroup.ts (1)
src/generateTitle.ts (1)
  • generateTitleForPost (18-52)

src/postGroup.ts Outdated
Comment on lines 106 to 119
// Schedule a cron job to generate and log the title for the first PostGroup every 6 hours
cron.schedule('0 */6 * * *', async () => {
const group = postGroups[0];
if (!group) {
console.log('No PostGroup found.');
return;
}
try {
const title = await generateTitleForPostGroup(group);
console.log(`[CRON] Generated Title for PostGroup at ${new Date().toISOString()}:`, title);
} catch (e) {
console.error('[CRON] Error generating title:', e);
}
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid side effects on import; add timezone and idempotency.

Scheduling at module top-level starts a job whenever the module is imported (tests, scripts, REPL). Hot-reload can create duplicate jobs. Also, no timezone is specified.

Apply:

-// Schedule a cron job to generate and log the title for the first PostGroup every 6 hours
-cron.schedule('0 */6 * * *', async () => {
-    const group = postGroups[0];
-    if (!group) {
-        console.log('No PostGroup found.');
-        return;
-    }
-    try {
-        const title = await generateTitleForPostGroup(group);
-        console.log(`[CRON] Generated Title for PostGroup at ${new Date().toISOString()}:`, title);
-    } catch (e) {
-        console.error('[CRON] Error generating title:', e);
-    }
-});
+// Startable cron to generate and log the title for the first PostGroup every 6 hours
+export function startPostGroupTitleCron(tz = process.env.TZ || 'UTC') {
+    return cron.schedule('0 */6 * * *', async () => {
+        const group = postGroups[0];
+        if (!group) {
+            console.log('[CRON] No PostGroup found.');
+            return;
+        }
+        try {
+            const title = await generateTitleForPostGroup(group);
+            group.title = title; // persist locally if desired
+            console.log(`[CRON] Generated Title @ ${new Date().toISOString()}:`, title);
+        } catch (e) {
+            console.error('[CRON] Error generating title:', e);
+        }
+    }, { timezone: tz });
+}

Usage: call startPostGroupTitleCron() from your app bootstrap or behind a feature flag/env.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Schedule a cron job to generate and log the title for the first PostGroup every 6 hours
cron.schedule('0 */6 * * *', async () => {
const group = postGroups[0];
if (!group) {
console.log('No PostGroup found.');
return;
}
try {
const title = await generateTitleForPostGroup(group);
console.log(`[CRON] Generated Title for PostGroup at ${new Date().toISOString()}:`, title);
} catch (e) {
console.error('[CRON] Error generating title:', e);
}
});
// Startable cron to generate and log the title for the first PostGroup every 6 hours
export function startPostGroupTitleCron(tz = process.env.TZ || 'UTC') {
return cron.schedule('0 */6 * * *', async () => {
const group = postGroups[0];
if (!group) {
console.log('[CRON] No PostGroup found.');
return;
}
try {
const title = await generateTitleForPostGroup(group);
group.title = title; // persist locally if desired
console.log(`[CRON] Generated Title @ ${new Date().toISOString()}:`, title);
} catch (e) {
console.error('[CRON] Error generating title:', e);
}
}, { timezone: tz });
}
🤖 Prompt for AI Agents
In src/postGroup.ts around lines 106 to 119, the cron is scheduled at module
top-level causing side effects on import, no timezone is set, and repeated
imports/hot-reloads can register duplicate jobs; refactor by moving the cron
setup into an exported startPostGroupTitleCron() function that (1) returns or
stores a reference to the scheduled job so it can be canceled, (2) guards
against multiple starts by checking/clearing an existing job before scheduling
(idempotency), and (3) passes a timezone option (e.g., from config or
process.env) to cron.schedule; call startPostGroupTitleCron() from your app
bootstrap or behind a feature-flag/env rather than importing the module to
schedule automatically.

src/postGroup.ts Outdated
Comment on lines 121 to 136
// If this file is run directly, generate and log the title for the first PostGroup
if (require.main === module) {
(async () => {
const group = postGroups[0];
if (!group) {
console.log('No PostGroup found.');
return;
}
try {
const title = await generateTitleForPostGroup(group);
console.log('Title:', title);
} catch (e) {
console.error('Error generating title:', e);
}
})();
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Main-guard: make it safe and explicit, and start the cron only here.

As written, the block assumes require exists. Make it resilient and ensure the cron starts only on direct execution.

-// If this file is run directly, generate and log the title for the first PostGroup
-if (require.main === module) {
+// If this file is run directly, generate and log the title for the first PostGroup
+const isDirectRun = typeof require !== 'undefined' && require.main === module;
+if (isDirectRun) {
+    // optional: start the cron when run directly
+    const task = startPostGroupTitleCron();
     (async () => {
         const group = postGroups[0];
         if (!group) {
             console.log('No PostGroup found.');
             return;
         }
         try {
             const title = await generateTitleForPostGroup(group);
-            console.log('Title:', title);
+            group.title = title;
+            console.log('[MANUAL] Title:', title);
         } catch (e) {
             console.error('Error generating title:', e);
         }
     })();
+    // keep process alive if cron is running
+    task.start();
 }

If you later switch the repo to ESM ("type": "module"), replace the guard with an import.meta.url check.

🤖 Prompt for AI Agents
In src/postGroup.ts around lines 121 to 136, the current main-guard assumes
`require` exists and may break under ESM; make the guard resilient by checking
`typeof require !== 'undefined' && require.main === module` before running the
IIFE, and move/ensure the cron/job startup code is only invoked from inside this
same guarded block so the cron starts only on direct execution; also add a
comment noting that if the repo becomes ESM the guard should be replaced with an
`import.meta.url` check.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/postGroup.ts (2)

100-102: Avoid side effects on import; add timezone and idempotency.

Scheduling at module top-level starts a job whenever the module is imported (tests, scripts, REPL). Hot-reload can create duplicate jobs. Also, no timezone is specified.

Apply:

-// Schedule a cron job to generate and log the title for all PostGroups every 6 hours
-cron.schedule('0 */6 * * *', async () => {
-    await logTitlesForAllPostGroups('CRON');
-});
+// Startable cron to generate and log the title for all PostGroups every 6 hours
+export function startPostGroupTitleCron(tz = process.env.TZ || 'UTC') {
+    return cron.schedule('0 */6 * * *', async () => {
+        await logTitlesForAllPostGroups('CRON');
+    }, { 
+        timezone: tz,
+        scheduled: false // Don't start automatically
+    });
+}

Usage: call startPostGroupTitleCron().start() from your app bootstrap or behind a feature flag/env.


105-107: Main-guard: make it safe and explicit, and start the cron only here.

As written, the block assumes require exists. Make it resilient and ensure the cron starts only on direct execution.

-// If this file is run directly, generate and log the title for all PostGroups
-if (require.main === module) {
-    logTitlesForAllPostGroups('MANUAL');
-}
+// If this file is run directly, generate and log the title for all PostGroups
+const isDirectRun = typeof require !== 'undefined' && require.main === module;
+if (isDirectRun) {
+    // Start the cron when run directly and run manual execution
+    const task = startPostGroupTitleCron();
+    task.start();
+    logTitlesForAllPostGroups('MANUAL');
+}

If you later switch the repo to ESM ("type": "module"), replace the guard with an import.meta.url check.

🧹 Nitpick comments (1)
src/postGroup.ts (1)

29-68: Consider extracting sample data to a separate file.

The hardcoded sample data makes the module less reusable and mixes concerns. Consider moving this to a dedicated test data file or configuration.

// Create src/data/samplePostGroups.ts
export const samplePostGroups: PostGroup[] = [
  // ... current sample data
];

Then import it when needed:

-const postGroups: PostGroup[] = [
-    // ... sample data
-];
+import { samplePostGroups } from './data/samplePostGroups';
+
+const postGroups: PostGroup[] = samplePostGroups;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 074c667 and b1c8437.

📒 Files selected for processing (1)
  • src/postGroup.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/postGroup.ts (1)
src/generateTitle.ts (1)
  • generateTitleForPost (18-52)
🔇 Additional comments (4)
src/postGroup.ts (4)

1-2: LGTM!

Clean imports with appropriate dependencies for cron scheduling and title generation functionality.


4-20: Well-structured type definitions.

The Post and PostGroup types are comprehensive and properly typed with appropriate union types for sentiment and source enums.


23-26: LGTM!

The function correctly aggregates post content and delegates to the existing title generation utility. The implementation is clean and follows the single responsibility principle.


75-97: LGTM!

Good implementation with proper error handling, context-aware logging, and mutation of the group's title property for persistence.

Copy link
Contributor

@tasin2610 tasin2610 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is good but we need another pr for upstash integration.

@tasin2610 tasin2610 merged commit 49facc1 into main Sep 16, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants