← back to SmokeShop

lib/scheduler.js

87 lines

/**
 * Scheduler — node-cron based posting scheduler
 * Checks every 15 minutes for due posts and publishes them
 */

const cron = require('node-cron');
const path = require('path');
const ig = require('./instagram-api');

let db = null;
let cronJob = null;

function init(database) {
  db = database;
}

function getSetting(key) {
  const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key);
  return row ? row.value : null;
}

async function processDuePosts() {
  const now = new Date().toISOString();
  const duePosts = db.prepare(
    "SELECT * FROM posts WHERE status = 'scheduled' AND scheduled_at <= ? ORDER BY scheduled_at ASC"
  ).all(now);

  if (duePosts.length === 0) return;

  const accessToken = getSetting('ig_access_token');
  const igAccountId = getSetting('ig_business_account_id');
  const appUrl = getSetting('app_url') || process.env.APP_URL || 'http://45.61.58.125:8401';

  if (!accessToken || !igAccountId) {
    console.log(`[Scheduler] ${duePosts.length} posts due but Instagram not connected`);
    return;
  }

  for (const post of duePosts) {
    try {
      // The image must be publicly accessible for Meta Graph API
      const imageUrl = `${appUrl}/generated/${path.basename(post.image_path || '')}`;

      if (!post.image_path) {
        console.log(`[Scheduler] Post ${post.id} has no image, skipping`);
        db.prepare("UPDATE posts SET status = 'failed' WHERE id = ?").run(post.id);
        continue;
      }

      const result = await ig.publishPost(igAccountId, accessToken, imageUrl, post.caption);

      db.prepare(
        "UPDATE posts SET status = 'posted', posted_at = ?, ig_media_id = ? WHERE id = ?"
      ).run(new Date().toISOString(), result.mediaId, post.id);

      console.log(`[Scheduler] Posted ${post.id}: "${post.title}" → IG media ${result.mediaId}`);
    } catch (err) {
      console.error(`[Scheduler] Failed to post ${post.id}:`, err.message);
      db.prepare("UPDATE posts SET status = 'failed' WHERE id = ?").run(post.id);
    }
  }
}

function start() {
  if (cronJob) return;
  // Run every 15 minutes
  cronJob = cron.schedule('*/15 * * * *', async () => {
    console.log('[Scheduler] Checking for due posts...');
    try {
      await processDuePosts();
    } catch (err) {
      console.error('[Scheduler] Error:', err.message);
    }
  });
  console.log('[Scheduler] Started — checking every 15 minutes');
}

function stop() {
  if (cronJob) {
    cronJob.stop();
    cronJob = null;
    console.log('[Scheduler] Stopped');
  }
}

module.exports = { init, start, stop, processDuePosts };