diff --git a/package-lock.json b/package-lock.json
index f541cbff0..b89639251 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -30,6 +30,7 @@
"body-parser": "^1.18.3",
"card-validator": "^10.0.2",
"cheerio": "^0.22.0",
+ "cloudinary": "^2.8.0",
"compression": "^1.8.0",
"cors": "^2.8.4",
"cron": "^1.8.2",
@@ -54,6 +55,7 @@
"node-cron": "^3.0.3",
"node-datetime": "^2.0.3",
"node-fetch": "^2.6.7",
+ "node-schedule": "^2.1.1",
"nodemailer": "^7.0.11",
"parse-link-header": "^2.0.0",
"redis": "^4.2.0",
@@ -61,11 +63,13 @@
"sanitize-html": "^2.16.0",
"sharp": "^0.34.5",
"socket.io": "^4.8.1",
+ "streamifier": "^0.1.1",
"supertest": "^6.3.4",
"telesignsdk": "^3.0.3",
"twilio": "^5.5.2",
"uuid": "^3.4.0",
- "ws": "^8.17.1"
+ "ws": "^8.17.1",
+ "xmlrpc": "^1.3.2"
},
"devDependencies": {
"@babel/eslint-parser": "^7.15.0",
@@ -1273,7 +1277,6 @@
"node_modules/@babel/core": {
"version": "7.28.5",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -3906,7 +3909,6 @@
"node_modules/@redis/client": {
"version": "1.6.1",
"license": "MIT",
- "peer": true,
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
@@ -4853,7 +4855,6 @@
"node_modules/@types/node-fetch": {
"version": "2.6.13",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.4"
@@ -4988,7 +4989,6 @@
"version": "8.15.0",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5766,7 +5766,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -6124,6 +6123,19 @@
"node": ">=6"
}
},
+ "node_modules/cloudinary": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.8.0.tgz",
+ "integrity": "sha512-s7frvR0HnQXeJsQSIsbLa/I09IMb1lOnVLEDH5b5E53WTiCYgrNNOBGV/i/nLHwrcEOUkqjfSwP1+enXWNYmdw==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.21",
+ "q": "^1.5.1"
+ },
+ "engines": {
+ "node": ">=9"
+ }
+ },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"license": "Apache-2.0",
@@ -6355,6 +6367,18 @@
"moment-timezone": "^0.5.x"
}
},
+ "node_modules/cron-parser": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
+ "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "luxon": "^3.2.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"dev": true,
@@ -6987,7 +7011,6 @@
"version": "8.57.1",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -7133,7 +7156,6 @@
"version": "2.32.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -7185,7 +7207,6 @@
"version": "6.10.2",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"aria-query": "^5.3.2",
"array-includes": "^3.1.8",
@@ -7214,7 +7235,6 @@
"version": "7.37.5",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"array-includes": "^3.1.8",
"array.prototype.findlast": "^1.2.5",
@@ -7246,7 +7266,6 @@
"version": "4.6.2",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10"
},
@@ -10481,6 +10500,12 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/long-timeout": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
+ "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==",
+ "license": "MIT"
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"dev": true,
@@ -10499,6 +10524,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/luxon": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
+ "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/make-dir": {
"version": "2.1.0",
"license": "MIT",
@@ -10997,6 +11031,8 @@
},
"node_modules/multer": {
"version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
+ "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
@@ -11167,6 +11203,20 @@
"version": "2.0.27",
"license": "MIT"
},
+ "node_modules/node-schedule": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz",
+ "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cron-parser": "^4.2.0",
+ "long-timeout": "0.1.1",
+ "sorted-array-functions": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/nodemailer": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz",
@@ -11966,6 +12016,17 @@
],
"license": "MIT"
},
+ "node_modules/q": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
+ "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.0",
+ "teleport": ">=0.2.0"
+ }
+ },
"node_modules/qs": {
"version": "6.13.0",
"license": "BSD-3-Clause",
@@ -12917,6 +12978,12 @@
}
}
},
+ "node_modules/sorted-array-functions": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
+ "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==",
+ "license": "MIT"
+ },
"node_modules/source-map": {
"version": "0.6.1",
"license": "BSD-3-Clause",
@@ -12988,6 +13055,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/streamifier": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz",
+ "integrity": "sha512-zDgl+muIlWzXNsXeyUfOk9dChMjlpkq0DRsxujtYPgyJ676yQ8jEm6zzaaWHFDg5BNcLuif0eD2MTyJdZqXpdg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/streamsearch": {
"version": "1.1.0",
"engines": {
@@ -14005,6 +14081,29 @@
"node": ">=6.0"
}
},
+ "node_modules/xmlrpc": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz",
+ "integrity": "sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": "1.2.x",
+ "xmlbuilder": "8.2.x"
+ },
+ "engines": {
+ "node": ">=0.8",
+ "npm": ">=1.0.0"
+ }
+ },
+ "node_modules/xmlrpc/node_modules/xmlbuilder": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz",
+ "integrity": "sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/xtend": {
"version": "4.0.2",
"license": "MIT",
diff --git a/package.json b/package.json
index e5c4e9c28..8dfac0916 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
"body-parser": "^1.18.3",
"card-validator": "^10.0.2",
"cheerio": "^0.22.0",
+ "cloudinary": "^2.8.0",
"compression": "^1.8.0",
"cors": "^2.8.4",
"cron": "^1.8.2",
@@ -104,6 +105,7 @@
"node-cron": "^3.0.3",
"node-datetime": "^2.0.3",
"node-fetch": "^2.6.7",
+ "node-schedule": "^2.1.1",
"nodemailer": "^7.0.11",
"parse-link-header": "^2.0.0",
"redis": "^4.2.0",
@@ -111,11 +113,13 @@
"sanitize-html": "^2.16.0",
"sharp": "^0.34.5",
"socket.io": "^4.8.1",
+ "streamifier": "^0.1.1",
"supertest": "^6.3.4",
"telesignsdk": "^3.0.3",
"twilio": "^5.5.2",
"uuid": "^3.4.0",
- "ws": "^8.17.1"
+ "ws": "^8.17.1",
+ "xmlrpc": "^1.3.2"
},
"nodemonConfig": {
"watch": [
diff --git a/src/controllers/liveJournalPostController.js b/src/controllers/liveJournalPostController.js
new file mode 100644
index 000000000..95f6b1922
--- /dev/null
+++ b/src/controllers/liveJournalPostController.js
@@ -0,0 +1,228 @@
+/* eslint-disable no-console */
+const crypto = require('crypto');
+const xmlrpc = require('xmlrpc');
+const mongoose = require('mongoose');
+// eslint-disable-next-line import/no-extraneous-dependencies
+const cloudinary = require('cloudinary').v2;
+// eslint-disable-next-line import/no-extraneous-dependencies
+const streamifier = require('streamifier');
+const LiveJournalPost = require('../models/liveJournalPost');
+
+const LJ_API_HOST = 'www.livejournal.com';
+const LJ_API_PATH = '/interface/xmlrpc';
+const HISTORY_LIMIT = 50;
+
+cloudinary.config({
+ cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
+ api_key: process.env.CLOUDINARY_API_KEY,
+ api_secret: process.env.CLOUDINARY_API_SECRET,
+});
+
+const CUSTOM_HEADERS = {
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
+ 'Accept': 'text/xml',
+ 'Content-Type': 'text/xml',
+ 'Connection': 'keep-alive',
+};
+
+const uploadToCloudinary = (fileBuffer) => new Promise((resolve, reject) => {
+ const uploadStream = cloudinary.uploader.upload_stream(
+ { folder: 'livejournal_posts' },
+ (error, result) => {
+ if (error) reject(error);
+ else resolve(result.secure_url);
+ },
+ );
+ streamifier.createReadStream(fileBuffer).pipe(uploadStream);
+});
+
+const getChallenge = () => new Promise((resolve, reject) => {
+ const client = xmlrpc.createSecureClient({
+ host: LJ_API_HOST,
+ port: 443,
+ path: LJ_API_PATH,
+ headers: CUSTOM_HEADERS,
+ });
+
+ client.methodCall('LJ.XMLRPC.getchallenge', [], (error, value) => {
+ if (error) {
+ if (error.body) console.error('LJ Raw Error Body:', error.body);
+ reject(error);
+ } else {
+ resolve(value);
+ }
+ });
+});
+
+const generateAuthResponse = (challenge, password) => {
+ const hash = crypto.createHash('md5');
+ hash.update(challenge + crypto.createHash('md5').update(password).digest('hex'));
+ return hash.digest('hex');
+};
+
+const postToLiveJournal = async (postData) => {
+ const challengeData = await getChallenge();
+ const authResponse = generateAuthResponse(challengeData.challenge, postData.password);
+ const now = new Date();
+
+ // FIX: Removed 'title' attribute to prevent XML-RPC crash
+ let finalContent = postData.content;
+ if (postData.imageUrl) {
+ const alt = postData.altText ? postData.altText.replace(/"/g, '"') : '';
+ finalContent = `
${finalContent}`;
+ }
+
+ const ljParams = {
+ username: postData.username,
+ auth_method: 'challenge',
+ auth_challenge: challengeData.challenge,
+ auth_response: authResponse,
+ ver: 1,
+ event: finalContent,
+ subject: postData.subject || '',
+ lineendings: 'unix',
+ year: now.getFullYear(),
+ mon: now.getMonth() + 1,
+ day: now.getDate(),
+ hour: now.getHours(),
+ min: now.getMinutes(),
+ };
+
+ if (postData.security === 'private') ljParams.security = 'private';
+ else if (postData.security === 'friends') { ljParams.security = 'usemask'; ljParams.allowmask = 1; }
+ else ljParams.security = 'public';
+
+ if (postData.tags && postData.tags.trim()) ljParams.props = { taglist: postData.tags.trim() };
+
+ const client = xmlrpc.createSecureClient({
+ host: LJ_API_HOST,
+ port: 443,
+ path: LJ_API_PATH,
+ headers: CUSTOM_HEADERS,
+ });
+
+ return new Promise((resolve, reject) => {
+ client.methodCall('LJ.XMLRPC.postevent', [ljParams], (error, value) => {
+ if (error) {
+ console.error('LiveJournal API Error:', error);
+ reject(error);
+ } else {
+ console.log('LiveJournal Success:', value);
+ resolve(value);
+ }
+ });
+ });
+};
+
+exports.createPost = async (req, res) => {
+ try {
+ const { username, password, subject, content, security, tags, altText } = req.body;
+ let imageUrl = null;
+ if (req.file) imageUrl = await uploadToCloudinary(req.file.buffer);
+
+ const userId = req.user ? req.user._id : new mongoose.Types.ObjectId();
+ if (!username || !password) return res.status(400).json({ success: false, message: 'Username and password are required' });
+
+ const result = await postToLiveJournal({
+ username, password, subject, content, security: security || 'public', tags, imageUrl, altText,
+ });
+
+ const post = new LiveJournalPost({
+ userId, username, subject: subject || 'Untitled', content, security: security || 'public', tags, status: 'posted', ljItemId: result.itemid, ljUrl: result.url, postedAt: new Date(),
+ });
+ await post.save();
+ res.json({ success: true, message: 'Posted successfully', post: { id: post._id, itemId: result.itemid, url: result.url } });
+ } catch (error) {
+ console.error('Error posting:', error);
+ res.status(500).json({ success: false, message: error.message || 'Failed to post' });
+ }
+};
+
+exports.schedulePost = async (req, res) => {
+ try {
+ const { username, password, subject, content, security, tags, scheduledDateTime, altText } = req.body;
+ let imageUrl = null;
+ if (req.file) imageUrl = await uploadToCloudinary(req.file.buffer);
+
+ const userId = req.user ? req.user._id : new mongoose.Types.ObjectId();
+ const scheduledDate = new Date(scheduledDateTime);
+
+ // FIX: Removed 'title' attribute here as well
+ let finalContent = content;
+ if (imageUrl) {
+ const alt = altText ? altText.replace(/"/g, '"') : '';
+ finalContent = `
${content}`;
+ }
+
+ const scheduledPost = new LiveJournalPost({
+ userId, username, password, subject: subject || 'Untitled', content: finalContent, security: security || 'public', tags, status: 'scheduled', scheduledFor: scheduledDate,
+ });
+ await scheduledPost.save();
+ res.json({ success: true, message: 'Post scheduled successfully', post: { id: scheduledPost._id, scheduledFor: scheduledDate } });
+ } catch (error) {
+ console.error('Error scheduling:', error);
+ res.status(500).json({ success: false, message: error.message });
+ }
+};
+
+exports.getScheduledPosts = async (req, res) => {
+ try {
+ const userId = req.user ? req.user._id : null;
+ const scheduledPosts = await LiveJournalPost.find({
+ userId: userId || { $exists: true }, status: 'scheduled', scheduledFor: { $gte: new Date() },
+ }).sort({ scheduledFor: 1 });
+ res.json({ success: true, posts: scheduledPosts });
+ } catch (error) { res.status(500).json({ success: false, message: 'Failed to fetch scheduled posts' }); }
+};
+
+exports.deleteScheduledPost = async (req, res) => {
+ try {
+ const { id } = req.params;
+ await LiveJournalPost.deleteOne({ _id: id });
+ res.json({ success: true, message: 'Scheduled post deleted successfully' });
+ } catch (error) { res.status(500).json({ success: false, message: 'Failed to delete scheduled post' }); }
+};
+
+exports.updateScheduledPost = async (req, res) => {
+ try {
+ const { id } = req.params;
+ const { subject, content, security, tags, scheduledDateTime } = req.body;
+ const post = await LiveJournalPost.findOne({ _id: id });
+ if (!post) return res.status(404).json({ success: false, message: 'Post not found' });
+
+ if (subject !== undefined) post.subject = subject;
+ if (content !== undefined) post.content = content;
+ if (security !== undefined) post.security = security;
+ if (tags !== undefined) post.tags = tags;
+ if (scheduledDateTime !== undefined) post.scheduledFor = new Date(scheduledDateTime);
+
+ await post.save();
+ res.json({ success: true, message: 'Scheduled post updated successfully', post });
+ } catch (error) { res.status(500).json({ success: false, message: 'Failed to update' }); }
+};
+
+exports.postScheduledNow = async (req, res) => {
+ try {
+ const { id } = req.params;
+ const post = await LiveJournalPost.findOne({ _id: id }).select('+password');
+ if (!post) return res.status(404).json({ success: false, message: 'Post not found' });
+
+ const result = await postToLiveJournal({
+ username: post.username, password: post.password, subject: post.subject, content: post.content, security: post.security, tags: post.tags,
+ });
+
+ post.status = 'posted'; post.ljItemId = result.itemid; post.ljUrl = result.url; post.postedAt = new Date(); post.scheduledFor = undefined;
+ await post.save();
+ res.json({ success: true, message: 'Posted successfully', post: { id: post._id, itemId: result.itemid, url: result.url } });
+ } catch (error) { res.status(500).json({ success: false, message: error.message || 'Failed to post' }); }
+};
+
+exports.getPostHistory = async (req, res) => {
+ try {
+ const userId = req.user ? req.user._id : null;
+ const posts = await LiveJournalPost.find({
+ userId: userId || { $exists: true }, status: { $in: ['posted', 'failed'] },
+ }).sort({ createdAt: -1 }).limit(HISTORY_LIMIT);
+ res.json({ success: true, posts });
+ } catch (error) { res.status(500).json({ success: false, message: 'Failed to fetch history' }); }
+};
\ No newline at end of file
diff --git a/src/models/liveJournalPost.js b/src/models/liveJournalPost.js
new file mode 100644
index 000000000..3ddd95893
--- /dev/null
+++ b/src/models/liveJournalPost.js
@@ -0,0 +1,66 @@
+const mongoose = require('mongoose');
+
+const liveJournalPostSchema = new mongoose.Schema({
+ userId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'UserProfile',
+ required: true,
+ index: true
+ },
+ username: {
+ type: String,
+ required: true
+ },
+ password: {
+ type: String,
+ select: false
+ },
+ subject: {
+ type: String,
+ maxlength: 255,
+ default: 'Untitled'
+ },
+ content: {
+ type: String,
+ required: true,
+ maxlength: 16777216
+ },
+ security: {
+ type: String,
+ enum: ['public', 'private', 'friends'],
+ default: 'public'
+ },
+ tags: {
+ type: String,
+ maxlength: 1000
+ },
+ status: {
+ type: String,
+ enum: ['posted', 'scheduled', 'failed'],
+ default: 'scheduled',
+ index: true
+ },
+ scheduledFor: {
+ type: Date,
+ index: true
+ },
+ postedAt: {
+ type: Date
+ },
+ ljItemId: {
+ type: String
+ },
+ ljUrl: {
+ type: String
+ },
+ errorMessage: {
+ type: String
+ }
+}, {
+ timestamps: true
+});
+
+liveJournalPostSchema.index({ status: 1, scheduledFor: 1 });
+liveJournalPostSchema.index({ userId: 1, createdAt: -1 });
+
+module.exports = mongoose.model('LiveJournalPost', liveJournalPostSchema, 'livejournalposts');
\ No newline at end of file
diff --git a/src/routes/liveJournalRoutes.js b/src/routes/liveJournalRoutes.js
new file mode 100644
index 000000000..2baf1245d
--- /dev/null
+++ b/src/routes/liveJournalRoutes.js
@@ -0,0 +1,33 @@
+import { Router } from 'express';
+import multer from 'multer';
+import {
+ createPost,
+ schedulePost,
+ getScheduledPosts,
+ updateScheduledPost,
+ deleteScheduledPost,
+ postScheduledNow,
+ getPostHistory
+} from '../controllers/liveJournalPostController';
+
+const router = Router();
+
+// Configure Multer to store file in memory
+const storage = multer.memoryStorage();
+const upload = multer({
+ storage: storage,
+ limits: { fileSize: 5 * 1024 * 1024 } // Limit to 5MB
+});
+
+// Update POST routes to accept a single file named 'image'
+router.post('/post', upload.single('image'), createPost);
+router.post('/schedule', upload.single('image'), schedulePost);
+
+// These routes remain unchanged
+router.get('/scheduled', getScheduledPosts);
+router.put('/schedule/:id', updateScheduledPost);
+router.delete('/schedule/:id', deleteScheduledPost);
+router.post('/post-scheduled/:id', postScheduledNow);
+router.get('/history', getPostHistory);
+
+export default router;
\ No newline at end of file
diff --git a/src/server.js b/src/server.js
index c803aa67e..407e18dda 100644
--- a/src/server.js
+++ b/src/server.js
@@ -6,13 +6,18 @@ const { app, logger } = require('./app');
const TimerWebsockets = require('./websockets').default;
const MessagingWebSocket = require('./websockets/lbMessaging/messagingSocket').default;
require('./startup/db')();
+// const { initializeLiveJournalScheduler } = require('./utilities/liveJournalScheduler');
+// initializeLiveJournalScheduler();
+const liveJournalRoutes = require('./routes/liveJournalRoutes').default;
require('./cronjobs/userProfileJobs')();
require('./cronjobs/pullRequestReviewJobs')();
require('./jobs/analyticsAggregation').scheduleDaily();
require('./cronjobs/bidWinnerJobs')();
+// eslint-disable-next-line import/order
const websocketRouter = require('./websockets/webSocketRouter');
-const port = process.env.PORT || 4500;
+const DEFAULT_PORT = 4500;
+const port = process.env.PORT || DEFAULT_PORT;
// Create HTTP server for both Express and Socket.IO
const server = http.createServer(app);
@@ -32,7 +37,7 @@ server.listen(port, () => {
const timerService = TimerWebsockets();
const messagingService = MessagingWebSocket();
-
+app.use('/api/livejournal', liveJournalRoutes);
websocketRouter(server, [timerService, messagingService]);
-module.exports = server;
+module.exports = server;
\ No newline at end of file
diff --git a/src/startup/middleware.js b/src/startup/middleware.js
index c4815fd79..6b5231fcd 100644
--- a/src/startup/middleware.js
+++ b/src/startup/middleware.js
@@ -1,5 +1,8 @@
+/* eslint-disable complexity */
+/* eslint-disable no-magic-numbers */
const jwt = require('jsonwebtoken');
const moment = require('moment');
+const express = require('express');
const config = require('../config');
const webhookController = require('../controllers/lbdashboard/webhookController'); // your new controller
const { Bids } = require('../models/lbdashboard/bids'); // or wherever you're getting Bids
@@ -25,6 +28,10 @@ function socketMiddleware(socket, next) {
}
*/
module.exports = function (app) {
+ // Increase request size limit for image uploads
+ app.use(express.json({ limit: '50mb' }));
+ app.use(express.urlencoded({ limit: '50mb', extended: true }));
+
app.all('*', (req, res, next) => {
const openPaths = ['/api/lb/myWebhooks'];
@@ -118,4 +125,4 @@ module.exports = function (app) {
});
// Apply PayPal middleware only to specific route
app.post('/api/lb/myWebhooks/', paypalAuthMiddleware, webhookTest);
-};
+};
\ No newline at end of file
diff --git a/src/startup/routes.js b/src/startup/routes.js
index 46a84eeb7..83ee0a809 100644
--- a/src/startup/routes.js
+++ b/src/startup/routes.js
@@ -116,6 +116,7 @@ const teamRouter = require('../routes/teamRouter')(team);
const jobsRouter = require('../routes/jobsRouter');
const laborCostRouter = require('../routes/laborCostRouter');
const jobAnalyticsRouter = require('../routes/jobAnalyticsRouter');
+const liveJournalPost = require('../models/liveJournalPost');
// const actionItemRouter = require('../routes/actionItemRouter')(actionItem);
// const actionItemRouter = require('../routes/actionItemRouter')(actionItem);
@@ -279,7 +280,7 @@ const dropboxRouter = require('../routes/automation/dropboxRouter');
const githubRouter = require('../routes/automation/githubRouter');
const sentryRouter = require('../routes/automation/sentryRouter');
const slackRouter = require('../routes/automation/slackRouter');
-
+const liveJournalRoutes = require('../routes/liveJournalRoutes').default;
//lbdashboard_bidoverview
const bidPropertyRouter = require('../routes/lbdashboard/bidPropertyRouter')(bidoverview_Listing);
@@ -440,6 +441,7 @@ module.exports = function (app) {
app.use('/api/github', githubRouter);
app.use('/api/sentry', sentryRouter);
app.use('/api/slack', slackRouter);
+ app.use('/api/livejournal', liveJournalRoutes);
app.use('/api/accessManagement', appAccessRouter);
app.use('/api/bm', bmExternalTeam);
app.use('/api', bmProjectRiskProfileRouter);
diff --git a/src/utilities/liveJournalScheduler.js b/src/utilities/liveJournalScheduler.js
new file mode 100644
index 000000000..0ab6461f0
--- /dev/null
+++ b/src/utilities/liveJournalScheduler.js
@@ -0,0 +1,22 @@
+const schedule = require('node-schedule');
+const liveJournalPostController = require('../controllers/liveJournalPostController')();
+
+const initializeLiveJournalScheduler = () => {
+ const job = schedule.scheduleJob('* * * * *', async () => {
+ try {
+ const result = await liveJournalPostController.processScheduledPosts();
+
+ if (result.processed > 0) {
+ console.log(`[LiveJournal Scheduler] Processed ${result.processed} posts: ${result.successful} successful, ${result.failed} failed`);
+ }
+ } catch (error) {
+ console.error('[LiveJournal Scheduler] Error processing scheduled posts:', error);
+ }
+ });
+
+ console.log('[LiveJournal Scheduler] Initialized - will check for scheduled posts every minute');
+
+ return job;
+};
+
+module.exports = { initializeLiveJournalScheduler };
\ No newline at end of file