Spaces:
Sleeping
Sleeping
Update server.js
Browse files
server.js
CHANGED
|
@@ -9,9 +9,12 @@ const streamifier = require('streamifier');
|
|
| 9 |
const app = express();
|
| 10 |
const upload = multer({ storage: multer.memoryStorage() });
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
app.set('trust proxy', 1);
|
| 13 |
|
| 14 |
-
// Configure Cloudinary
|
| 15 |
cloudinary.config({
|
| 16 |
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
| 17 |
api_key: process.env.CLOUDINARY_API_KEY,
|
|
@@ -21,9 +24,8 @@ cloudinary.config({
|
|
| 21 |
const DB_FILE = 'megapin_master_db.json';
|
| 22 |
const MAX_TRENDING = 100;
|
| 23 |
|
| 24 |
-
|
| 25 |
-
const
|
| 26 |
-
const ADMIN_PASS = process.env.ADMIN_PASS; // Set this in your .env
|
| 27 |
const DEFAULT_AVATAR = "https://cdn-icons-png.flaticon.com/512/149/149071.png";
|
| 28 |
|
| 29 |
let localDB = null;
|
|
@@ -32,14 +34,17 @@ app.use(express.urlencoded({ extended: true }));
|
|
| 32 |
app.use(express.json());
|
| 33 |
app.set('view engine', 'ejs');
|
| 34 |
|
|
|
|
| 35 |
app.use(cookieSession({
|
| 36 |
name: '__Secure-session',
|
| 37 |
-
keys: [process.env.SESSION_SECRET || '
|
| 38 |
maxAge: 24 * 60 * 60 * 1000,
|
| 39 |
-
secure
|
| 40 |
-
|
|
|
|
|
|
|
| 41 |
httpOnly: true,
|
| 42 |
-
partitioned:
|
| 43 |
}));
|
| 44 |
|
| 45 |
// --- DB ENGINE ---
|
|
@@ -52,6 +57,7 @@ async function initDB() {
|
|
| 52 |
if (!localDB.users) localDB.users = {};
|
| 53 |
if (!localDB.pins) localDB.pins = [];
|
| 54 |
} catch (e) {
|
|
|
|
| 55 |
localDB = { users: {}, pins: [] };
|
| 56 |
}
|
| 57 |
return localDB;
|
|
@@ -84,8 +90,8 @@ function addNotification(toUser, fromUser, type, pinId = null, preview = "") {
|
|
| 84 |
|
| 85 |
localDB.users[toUser].notifications.unshift({
|
| 86 |
from: fromUser,
|
| 87 |
-
fromAvatar: localDB.users[fromUser].avatar,
|
| 88 |
-
type: type,
|
| 89 |
pinId: pinId,
|
| 90 |
preview: preview,
|
| 91 |
timestamp: Date.now(),
|
|
@@ -100,7 +106,12 @@ async function checkBan(req, res, next) {
|
|
| 100 |
if (!req.session.user) return next();
|
| 101 |
await initDB();
|
| 102 |
const user = localDB.users[req.session.user];
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
req.session = null;
|
| 105 |
return res.send("Account Suspended.");
|
| 106 |
}
|
|
@@ -114,15 +125,16 @@ function checkAdmin(req, res, next) {
|
|
| 114 |
|
| 115 |
// --- ROUTES ---
|
| 116 |
|
| 117 |
-
// Helper to format pins for view
|
| 118 |
function formatPins(pins, username) {
|
| 119 |
return pins.map(pin => {
|
| 120 |
-
|
|
|
|
| 121 |
return {
|
| 122 |
...pin,
|
| 123 |
authorDisplayName: authorData.displayName || pin.author,
|
| 124 |
-
authorAvatar: authorData.avatar,
|
| 125 |
-
badge: authorData.badge,
|
| 126 |
likeCount: pin.likes ? pin.likes.length : 0,
|
| 127 |
hasLiked: pin.likes && username ? pin.likes.includes(username) : false,
|
| 128 |
comments: pin.comments || []
|
|
@@ -186,7 +198,8 @@ app.get('/u/:username', async (req, res) => {
|
|
| 186 |
postCount: userPins.length,
|
| 187 |
followerCount: targetUser.followers ? targetUser.followers.length : 0,
|
| 188 |
followingCount: targetUser.following ? targetUser.following.length : 0,
|
| 189 |
-
isFollowing: targetUser.followers && targetUser.followers.includes(username)
|
|
|
|
| 190 |
};
|
| 191 |
|
| 192 |
let currentUser = null;
|
|
@@ -208,6 +221,7 @@ app.get('/api/pin/:id', async (req, res) => {
|
|
| 208 |
await initDB();
|
| 209 |
const pin = localDB.pins.find(p => p.id === parseInt(req.params.id));
|
| 210 |
if(!pin) return res.json({error: "Not found"});
|
|
|
|
| 211 |
const formatted = formatPins([pin], req.session.user)[0];
|
| 212 |
res.json(formatted);
|
| 213 |
});
|
|
@@ -218,8 +232,8 @@ app.get('/api/search-users', async (req, res) => {
|
|
| 218 |
const users = Object.keys(localDB.users).filter(u => u.toLowerCase().includes(q));
|
| 219 |
const results = users.map(u => ({
|
| 220 |
username: u,
|
| 221 |
-
avatar: localDB.users[u].avatar,
|
| 222 |
-
badge: localDB.users[u].badge
|
| 223 |
}));
|
| 224 |
res.json(results);
|
| 225 |
});
|
|
@@ -276,8 +290,10 @@ app.post('/api/comment', checkBan, async (req, res) => {
|
|
| 276 |
if (!req.session.user) return res.status(401).json({error: "Login required"});
|
| 277 |
await initDB();
|
| 278 |
const { pinId, text } = req.body;
|
|
|
|
|
|
|
| 279 |
const pin = localDB.pins.find(p => p.id === parseInt(pinId));
|
| 280 |
-
if (pin
|
| 281 |
if (!pin.comments) pin.comments = [];
|
| 282 |
const comment = {
|
| 283 |
id: Date.now(),
|
|
@@ -334,7 +350,13 @@ app.post('/api/admin/delete-user', checkAdmin, async (req, res) => {
|
|
| 334 |
const { targetUser } = req.body;
|
| 335 |
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
|
| 336 |
delete localDB.users[targetUser];
|
|
|
|
| 337 |
localDB.pins = localDB.pins.filter(p => p.author !== targetUser);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
await saveDB();
|
| 339 |
res.json({success: true});
|
| 340 |
} else {
|
|
@@ -347,9 +369,16 @@ app.post('/api/admin/delete-user', checkAdmin, async (req, res) => {
|
|
| 347 |
app.post('/signup', async (req, res) => {
|
| 348 |
const { username, password, email } = req.body;
|
| 349 |
await initDB();
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
hash: bcrypt.hashSync(password, 8),
|
| 354 |
email: email,
|
| 355 |
avatar: DEFAULT_AVATAR,
|
|
@@ -359,13 +388,17 @@ app.post('/signup', async (req, res) => {
|
|
| 359 |
banned: false
|
| 360 |
};
|
| 361 |
await saveDB();
|
| 362 |
-
req.session.user =
|
| 363 |
res.redirect('/');
|
| 364 |
});
|
| 365 |
|
| 366 |
app.post('/login', async (req, res) => {
|
| 367 |
const { username, password } = req.body;
|
| 368 |
await initDB();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
// Admin Login Check
|
| 370 |
if (username === ADMIN_USER && password === ADMIN_PASS) {
|
| 371 |
req.session.user = username;
|
|
@@ -429,4 +462,4 @@ app.post('/delete', checkBan, async (req, res) => {
|
|
| 429 |
app.get('/logout', (req, res) => { req.session = null; res.redirect('/'); });
|
| 430 |
|
| 431 |
initDB();
|
| 432 |
-
app.listen(7860, () => console.log("Megapin Complete Active"));
|
|
|
|
| 9 |
const app = express();
|
| 10 |
const upload = multer({ storage: multer.memoryStorage() });
|
| 11 |
|
| 12 |
+
// Detect if we are in production (HTTPS) or development
|
| 13 |
+
const IS_PROD = process.env.NODE_ENV === 'production';
|
| 14 |
+
|
| 15 |
app.set('trust proxy', 1);
|
| 16 |
|
| 17 |
+
// Configure Cloudinary
|
| 18 |
cloudinary.config({
|
| 19 |
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
| 20 |
api_key: process.env.CLOUDINARY_API_KEY,
|
|
|
|
| 24 |
const DB_FILE = 'megapin_master_db.json';
|
| 25 |
const MAX_TRENDING = 100;
|
| 26 |
|
| 27 |
+
const ADMIN_USER = process.env.ADMIN_USER;
|
| 28 |
+
const ADMIN_PASS = process.env.ADMIN_PASS;
|
|
|
|
| 29 |
const DEFAULT_AVATAR = "https://cdn-icons-png.flaticon.com/512/149/149071.png";
|
| 30 |
|
| 31 |
let localDB = null;
|
|
|
|
| 34 |
app.use(express.json());
|
| 35 |
app.set('view engine', 'ejs');
|
| 36 |
|
| 37 |
+
// --- FIXED SESSION CONFIG ---
|
| 38 |
app.use(cookieSession({
|
| 39 |
name: '__Secure-session',
|
| 40 |
+
keys: [process.env.SESSION_SECRET || 'secret_key_change_me'],
|
| 41 |
maxAge: 24 * 60 * 60 * 1000,
|
| 42 |
+
// Only require secure (HTTPS) if we are definitely in production.
|
| 43 |
+
// This fixes "Signup broken" issues on localhost or non-https spaces.
|
| 44 |
+
secure: IS_PROD,
|
| 45 |
+
sameSite: IS_PROD ? 'none' : 'lax',
|
| 46 |
httpOnly: true,
|
| 47 |
+
partitioned: IS_PROD
|
| 48 |
}));
|
| 49 |
|
| 50 |
// --- DB ENGINE ---
|
|
|
|
| 57 |
if (!localDB.users) localDB.users = {};
|
| 58 |
if (!localDB.pins) localDB.pins = [];
|
| 59 |
} catch (e) {
|
| 60 |
+
// If file doesn't exist yet, create structure
|
| 61 |
localDB = { users: {}, pins: [] };
|
| 62 |
}
|
| 63 |
return localDB;
|
|
|
|
| 90 |
|
| 91 |
localDB.users[toUser].notifications.unshift({
|
| 92 |
from: fromUser,
|
| 93 |
+
fromAvatar: localDB.users[fromUser].avatar || DEFAULT_AVATAR,
|
| 94 |
+
type: type,
|
| 95 |
pinId: pinId,
|
| 96 |
preview: preview,
|
| 97 |
timestamp: Date.now(),
|
|
|
|
| 106 |
if (!req.session.user) return next();
|
| 107 |
await initDB();
|
| 108 |
const user = localDB.users[req.session.user];
|
| 109 |
+
// If user deleted but session exists, clear session
|
| 110 |
+
if (!user) {
|
| 111 |
+
req.session = null;
|
| 112 |
+
return res.redirect('/');
|
| 113 |
+
}
|
| 114 |
+
if (user.banned) {
|
| 115 |
req.session = null;
|
| 116 |
return res.send("Account Suspended.");
|
| 117 |
}
|
|
|
|
| 125 |
|
| 126 |
// --- ROUTES ---
|
| 127 |
|
| 128 |
+
// Helper to format pins for view safely
|
| 129 |
function formatPins(pins, username) {
|
| 130 |
return pins.map(pin => {
|
| 131 |
+
// Handle case where user was deleted but pin exists
|
| 132 |
+
const authorData = localDB.users[pin.author] || { avatar: DEFAULT_AVATAR, displayName: pin.author + " (Deleted)", badge: '' };
|
| 133 |
return {
|
| 134 |
...pin,
|
| 135 |
authorDisplayName: authorData.displayName || pin.author,
|
| 136 |
+
authorAvatar: authorData.avatar || DEFAULT_AVATAR,
|
| 137 |
+
badge: authorData.badge || '',
|
| 138 |
likeCount: pin.likes ? pin.likes.length : 0,
|
| 139 |
hasLiked: pin.likes && username ? pin.likes.includes(username) : false,
|
| 140 |
comments: pin.comments || []
|
|
|
|
| 198 |
postCount: userPins.length,
|
| 199 |
followerCount: targetUser.followers ? targetUser.followers.length : 0,
|
| 200 |
followingCount: targetUser.following ? targetUser.following.length : 0,
|
| 201 |
+
isFollowing: targetUser.followers && targetUser.followers.includes(username),
|
| 202 |
+
badge: targetUser.badge || ''
|
| 203 |
};
|
| 204 |
|
| 205 |
let currentUser = null;
|
|
|
|
| 221 |
await initDB();
|
| 222 |
const pin = localDB.pins.find(p => p.id === parseInt(req.params.id));
|
| 223 |
if(!pin) return res.json({error: "Not found"});
|
| 224 |
+
// Use req.session.user to determine "hasLiked" state in the modal
|
| 225 |
const formatted = formatPins([pin], req.session.user)[0];
|
| 226 |
res.json(formatted);
|
| 227 |
});
|
|
|
|
| 232 |
const users = Object.keys(localDB.users).filter(u => u.toLowerCase().includes(q));
|
| 233 |
const results = users.map(u => ({
|
| 234 |
username: u,
|
| 235 |
+
avatar: localDB.users[u].avatar || DEFAULT_AVATAR,
|
| 236 |
+
badge: localDB.users[u].badge || ''
|
| 237 |
}));
|
| 238 |
res.json(results);
|
| 239 |
});
|
|
|
|
| 290 |
if (!req.session.user) return res.status(401).json({error: "Login required"});
|
| 291 |
await initDB();
|
| 292 |
const { pinId, text } = req.body;
|
| 293 |
+
if(!text || !text.trim()) return res.json({success:false});
|
| 294 |
+
|
| 295 |
const pin = localDB.pins.find(p => p.id === parseInt(pinId));
|
| 296 |
+
if (pin) {
|
| 297 |
if (!pin.comments) pin.comments = [];
|
| 298 |
const comment = {
|
| 299 |
id: Date.now(),
|
|
|
|
| 350 |
const { targetUser } = req.body;
|
| 351 |
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
|
| 352 |
delete localDB.users[targetUser];
|
| 353 |
+
// Remove their pins
|
| 354 |
localDB.pins = localDB.pins.filter(p => p.author !== targetUser);
|
| 355 |
+
// Clean up follows (optional but good)
|
| 356 |
+
Object.values(localDB.users).forEach(u => {
|
| 357 |
+
if(u.followers) u.followers = u.followers.filter(f => f !== targetUser);
|
| 358 |
+
if(u.following) u.following = u.following.filter(f => f !== targetUser);
|
| 359 |
+
});
|
| 360 |
await saveDB();
|
| 361 |
res.json({success: true});
|
| 362 |
} else {
|
|
|
|
| 369 |
app.post('/signup', async (req, res) => {
|
| 370 |
const { username, password, email } = req.body;
|
| 371 |
await initDB();
|
| 372 |
+
|
| 373 |
+
// VALIDATION FIX: Check empty fields
|
| 374 |
+
if(!username || !password) return res.send("Username and Password are required.");
|
| 375 |
+
|
| 376 |
+
// Sanitize username
|
| 377 |
+
const safeUser = username.trim();
|
| 378 |
+
if (localDB.users[safeUser]) return res.send("Username taken.");
|
| 379 |
+
|
| 380 |
+
localDB.users[safeUser] = {
|
| 381 |
+
displayName: safeUser,
|
| 382 |
hash: bcrypt.hashSync(password, 8),
|
| 383 |
email: email,
|
| 384 |
avatar: DEFAULT_AVATAR,
|
|
|
|
| 388 |
banned: false
|
| 389 |
};
|
| 390 |
await saveDB();
|
| 391 |
+
req.session.user = safeUser;
|
| 392 |
res.redirect('/');
|
| 393 |
});
|
| 394 |
|
| 395 |
app.post('/login', async (req, res) => {
|
| 396 |
const { username, password } = req.body;
|
| 397 |
await initDB();
|
| 398 |
+
|
| 399 |
+
// VALIDATION FIX
|
| 400 |
+
if(!username || !password) return res.send("Missing credentials.");
|
| 401 |
+
|
| 402 |
// Admin Login Check
|
| 403 |
if (username === ADMIN_USER && password === ADMIN_PASS) {
|
| 404 |
req.session.user = username;
|
|
|
|
| 462 |
app.get('/logout', (req, res) => { req.session = null; res.redirect('/'); });
|
| 463 |
|
| 464 |
initDB();
|
| 465 |
+
app.listen(7860, () => console.log("Megapin Complete Active"));
|