Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2db0fb0
Merge pull request #2086 from OneCommunityGlobal/development
one-community Mar 6, 2026
5d28df2
Merge pull request #2099 from OneCommunityGlobal/development
one-community Mar 12, 2026
cf56d64
Merge pull request #2120 from OneCommunityGlobal/development
one-community Mar 25, 2026
b751912
Merge pull request #2122 from OneCommunityGlobal/development
one-community Mar 25, 2026
fb43e1b
Merge pull request #2124 from OneCommunityGlobal/development
one-community Mar 25, 2026
c0eb448
build: pushed changes in order to pull Masterdon
marcusyi1 Mar 27, 2026
f7b938e
Merge branch 'main' of https://github.com/OneCommunityGlobal/HGNRest …
marcusyi1 Mar 27, 2026
8fe1b7b
Merge branch 'development' of https://github.com/OneCommunityGlobal/H…
marcusyi1 Mar 27, 2026
35ae577
add: package update
marcusyi1 Mar 27, 2026
798eaa4
build: changes based on new regulations
marcusyi1 Mar 28, 2026
19682b5
build: changes based on new regulations
marcusyi1 Mar 28, 2026
4832cec
fix: Initial commit for PR
marcusyi1 Apr 3, 2026
8766eba
fix: package-lock merge conflict
marcusyi1 Apr 3, 2026
e0c46af
resolve: accept package-lock.json from development branch
marcusyi1 Apr 3, 2026
0f9f3bc
fix: threshold
marcusyi1 Apr 3, 2026
044b13e
fix: CLI test
marcusyi1 Apr 3, 2026
341d963
Merge branch 'development' into Marcus-finishes-x-auto-poster
marcusyi1 May 7, 2026
5b06740
fix: merge conflicts
marcusyi1 May 21, 2026
db3e769
fix: merge conflicts
marcusyi1 May 21, 2026
df18f09
fix: package dependencies
marcusyi1 May 21, 2026
e9c7b48
fix: lint error
marcusyi1 May 21, 2026
37d7fe8
Merge branch 'development' into Marcus-finishes-x-auto-poster
marcusyi1 May 24, 2026
c7b7a22
fix: code duplication
marcusyi1 May 25, 2026
63f39b6
fix: merge branch
marcusyi1 May 25, 2026
6f3c0ac
Merge branch 'development' into Marcus-finishes-x-auto-poster
marcusyi1 Jun 11, 2026
c40f563
chore: Add unit tests for X 280-character content validation
marcusyi1 Jun 11, 2026
e551499
fix: remove duplicate email-helper from userHelper.js
marcusyi1 Jun 14, 2026
19f3121
Merge branch 'development' into Marcus-finishes-x-auto-poster
marcusyi1 Jun 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 81 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
"node-cache": "^5.1.2",
"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",
Expand All @@ -144,5 +143,6 @@
"watch": [
"/src/**/*"
]
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
2 changes: 0 additions & 2 deletions src/controllers/timeZoneAPIController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const fetch = require('node-fetch');
const dotenv = require('dotenv');

dotenv.config();
Expand Down
5 changes: 2 additions & 3 deletions src/controllers/timeZoneAPIController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ jest.mock('../utilities/permissions', () => ({
hasPermission: jest.fn(), // Mocking the hasPermission function
}));

jest.mock('node-fetch');
// eslint-disable-next-line import/no-extraneous-dependencies
const fetch = require('node-fetch');
global.fetch = jest.fn();
const { fetch } = global;

const originalPremiumKey = process.env.TIMEZONE_PREMIUM_KEY;
process.env.TIMEZONE_PREMIUM_KEY = 'mockPremiumKey';
Expand Down
2 changes: 0 additions & 2 deletions src/controllers/userProfileController.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
const moment = require('moment-timezone');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
// eslint-disable-next-line import/no-extraneous-dependencies
const fetch = require('node-fetch');
const moment_ = require('moment');
const jwt = require('jsonwebtoken');
const userHelper = require('../helpers/userHelper')();
Expand Down
106 changes: 106 additions & 0 deletions src/controllers/xPostController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const XScheduledPost = require('../models/xScheduledPost');
const {
asyncRoute,
ValidationError,
NotFoundError,
validateContent,
findPostOr404,
applyUpdates,
X_MAX_CONTENT_LENGTH,
} = require('../helpers/xPostHelpers');

const requireFutureDate = (value, msg = 'scheduledAt must be in the future') => {
if (new Date(value) <= new Date()) throw new ValidationError(msg);
};

exports.createPost = asyncRoute(async (req, res) => {
const { content } = req.body;
validateContent(content);
const now = new Date();
const doc = await XScheduledPost.create({
content,
scheduledAt: now,
status: 'posted',
postedAt: now,
createdBy: req.user?._id,
});
return res.status(201).json({
message: 'Post staged successfully',
postId: doc._id,
intentUrl: `https://x.com/intent/tweet?text=${encodeURIComponent(content)}`,
});
});

exports.schedulePost = asyncRoute(async (req, res) => {
const { content, scheduledAt, mediaBase64, altText } = req.body;
validateContent(content);
if (!scheduledAt) throw new ValidationError('scheduledAt is required');
requireFutureDate(scheduledAt);
const doc = await XScheduledPost.create({
content,
scheduledAt: new Date(scheduledAt),
mediaBase64: mediaBase64 || null,
altText: altText || '',
createdBy: req.user?._id,
});
return res.status(201).json({ message: 'Post scheduled', post: doc });
});

exports.getScheduled = asyncRoute(async (req, res) => {
const posts = await XScheduledPost.find({ status: { $in: ['pending', 'ready'] } })
.sort({ scheduledAt: 1 })
.lean();
return res.json(posts);
});

exports.deleteScheduled = asyncRoute(async (req, res) => {
const doc = await XScheduledPost.findByIdAndDelete(req.params.id);
if (!doc) throw new NotFoundError('Scheduled post not found');
return res.json({ message: 'Scheduled post cancelled' });
});

exports.getHistory = asyncRoute(async (req, res) => {
const posts = await XScheduledPost.find({ status: 'posted' }).sort({ postedAt: -1 }).lean();
return res.json(posts);
});

exports.markAsPosted = asyncRoute(async (req, res) => {
const doc = await XScheduledPost.findByIdAndUpdate(
req.params.id,
{ status: 'posted', postedAt: new Date() },
{ new: true },
);
if (!doc) throw new NotFoundError('Scheduled post not found');
return res.json(doc);
});

exports.updateScheduledPost = asyncRoute(async (req, res) => {
const doc = await findPostOr404(XScheduledPost, req.params.id);
if (doc.status !== 'pending' && doc.status !== 'ready') {
throw new ValidationError(`Cannot edit a post with status: ${doc.status}`);
}
const { content, scheduledAt } = req.body;
if (
content !== undefined &&
(typeof content !== 'string' || content.length > X_MAX_CONTENT_LENGTH)
) {
throw new ValidationError('Content must be a string of 280 characters or fewer');
}
if (scheduledAt !== undefined) {
requireFutureDate(scheduledAt);
req.body.scheduledAt = new Date(scheduledAt);
}
applyUpdates(doc, req.body, ['content', 'scheduledAt', 'mediaBase64', 'altText']);
await doc.save();
return res.json(doc);
});

exports.skipPost = asyncRoute(async (req, res) => {
const doc = await XScheduledPost.findByIdAndUpdate(
req.params.id,
{ status: 'skipped' },
{ new: true },
);
if (!doc) throw new NotFoundError('Scheduled post not found');
return res.json(doc);
});
22 changes: 22 additions & 0 deletions src/cronjobs/xScheduleJob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const cron = require('node-cron');
const XScheduledPost = require('../models/xScheduledPost');

const runScheduler = async () => {
try {
const result = await XScheduledPost.updateMany(
{ scheduledAt: { $lte: new Date() }, status: 'pending' },
{ $set: { status: 'ready' } },
);
if (result.modifiedCount > 0) {
// eslint-disable-next-line no-console
console.log(`[xScheduler] Promoted ${result.modifiedCount} post(s) to ready`);
}
} catch (err) {
// eslint-disable-next-line no-console
console.error('[xScheduler] Error promoting posts:', err.message);
}
};

const start = () => cron.schedule('* * * * *', runScheduler);

module.exports = { start, runScheduler };
2 changes: 0 additions & 2 deletions src/helpers/githubPRHelper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const fetch = require('node-fetch');
const { RateLimitedError } = require('../utilities/errorHandling/customError');

function createGitHubClient({ owner, repo }) {
Expand Down
Loading
Loading