Spaces:
Running
Running
Update server.js
Browse files
server.js
CHANGED
|
@@ -20,7 +20,6 @@ cloudinary.config({
|
|
| 20 |
const DB_FILE = 'megapin_master_db.json';
|
| 21 |
const MAX_TRENDING = 100;
|
| 22 |
const ADMIN_USER = process.env.ADMIN_USER;
|
| 23 |
-
// Admin Pass is checked in /login via process.env.ADMIN_PASS
|
| 24 |
const DEFAULT_AVATAR = "https://cdn-icons-png.flaticon.com/512/149/149071.png";
|
| 25 |
|
| 26 |
let localDB = null;
|
|
@@ -28,7 +27,6 @@ let localDB = null;
|
|
| 28 |
app.use(express.urlencoded({ extended: true }));
|
| 29 |
app.use(express.json());
|
| 30 |
app.set('view engine', 'ejs');
|
| 31 |
-
app.use(express.static('public')); // ADDED: To serve your external script/css
|
| 32 |
|
| 33 |
app.use(cookieSession({
|
| 34 |
name: '__Secure-session',
|
|
@@ -73,19 +71,22 @@ const uploadImage = (buffer) => {
|
|
| 73 |
});
|
| 74 |
};
|
| 75 |
|
|
|
|
| 76 |
function addNotification(toUser, fromUser, type, pinId = null, preview = "") {
|
| 77 |
if (toUser === fromUser) return;
|
| 78 |
if (!localDB.users[toUser]) return;
|
| 79 |
if (!localDB.users[toUser].notifications) localDB.users[toUser].notifications = [];
|
|
|
|
| 80 |
localDB.users[toUser].notifications.unshift({
|
| 81 |
from: fromUser,
|
| 82 |
fromAvatar: localDB.users[fromUser].avatar,
|
| 83 |
-
type: type,
|
| 84 |
pinId: pinId,
|
| 85 |
preview: preview,
|
| 86 |
timestamp: Date.now(),
|
| 87 |
read: false
|
| 88 |
});
|
|
|
|
| 89 |
if (localDB.users[toUser].notifications.length > 50) localDB.users[toUser].notifications.pop();
|
| 90 |
}
|
| 91 |
|
|
@@ -108,7 +109,7 @@ function checkAdmin(req, res, next) {
|
|
| 108 |
|
| 109 |
// --- ROUTES ---
|
| 110 |
|
| 111 |
-
// Helper
|
| 112 |
function formatPins(pins, username) {
|
| 113 |
return pins.map(pin => {
|
| 114 |
const authorData = localDB.users[pin.author] || { avatar: DEFAULT_AVATAR, displayName: pin.author };
|
|
@@ -124,6 +125,7 @@ function formatPins(pins, username) {
|
|
| 124 |
});
|
| 125 |
}
|
| 126 |
|
|
|
|
| 127 |
app.get('/', async (req, res) => {
|
| 128 |
await initDB();
|
| 129 |
const username = req.session.user;
|
|
@@ -138,23 +140,32 @@ app.get('/', async (req, res) => {
|
|
| 138 |
if (currentUser.notifications) notifications = currentUser.notifications;
|
| 139 |
}
|
| 140 |
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
res.render('index', {
|
| 145 |
-
pins:
|
| 146 |
user: currentUser,
|
| 147 |
notifications,
|
| 148 |
isAdmin: (username === ADMIN_USER),
|
|
|
|
| 149 |
pageType: 'home',
|
| 150 |
profileUser: null
|
| 151 |
});
|
| 152 |
});
|
| 153 |
|
| 154 |
-
//
|
| 155 |
app.get('/u/:username', async (req, res) => {
|
| 156 |
await initDB();
|
| 157 |
-
const username = req.session.user;
|
| 158 |
const targetUsername = req.params.username;
|
| 159 |
|
| 160 |
if (!localDB.users[targetUsername]) return res.redirect('/');
|
|
@@ -163,7 +174,6 @@ app.get('/u/:username', async (req, res) => {
|
|
| 163 |
const userPins = localDB.pins.filter(p => p.author === targetUsername);
|
| 164 |
const formattedPins = formatPins(userPins, username);
|
| 165 |
|
| 166 |
-
// Construct Profile Object
|
| 167 |
const profileUser = {
|
| 168 |
username: targetUsername,
|
| 169 |
...targetUser,
|
|
@@ -188,7 +198,14 @@ app.get('/u/:username', async (req, res) => {
|
|
| 188 |
|
| 189 |
// --- API ACTIONS ---
|
| 190 |
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
app.get('/api/search-users', async (req, res) => {
|
| 193 |
await initDB();
|
| 194 |
const q = req.query.q ? req.query.q.toLowerCase() : '';
|
|
@@ -201,58 +218,6 @@ app.get('/api/search-users', async (req, res) => {
|
|
| 201 |
res.json(results);
|
| 202 |
});
|
| 203 |
|
| 204 |
-
// Get Single Pin Details (For Modal)
|
| 205 |
-
app.get('/api/pin/:id', async (req, res) => {
|
| 206 |
-
await initDB();
|
| 207 |
-
const pin = localDB.pins.find(p => p.id === parseInt(req.params.id));
|
| 208 |
-
if(!pin) return res.json({error: "Not found"});
|
| 209 |
-
const formatted = formatPins([pin], req.session.user)[0];
|
| 210 |
-
res.json(formatted);
|
| 211 |
-
});
|
| 212 |
-
|
| 213 |
-
// ADMIN ROUTES
|
| 214 |
-
app.post('/api/admin/badge', checkAdmin, async (req, res) => {
|
| 215 |
-
await initDB();
|
| 216 |
-
const { targetUser, badge } = req.body; // badge: 'blue', 'green', or ''
|
| 217 |
-
if(localDB.users[targetUser]) {
|
| 218 |
-
localDB.users[targetUser].badge = badge;
|
| 219 |
-
await saveDB();
|
| 220 |
-
res.json({success: true});
|
| 221 |
-
} else {
|
| 222 |
-
res.json({success: false});
|
| 223 |
-
}
|
| 224 |
-
});
|
| 225 |
-
|
| 226 |
-
app.post('/api/admin/ban', checkAdmin, async (req, res) => {
|
| 227 |
-
await initDB();
|
| 228 |
-
const { targetUser, banned } = req.body;
|
| 229 |
-
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
|
| 230 |
-
localDB.users[targetUser].banned = banned; // true or false
|
| 231 |
-
await saveDB();
|
| 232 |
-
res.json({success: true});
|
| 233 |
-
} else {
|
| 234 |
-
res.json({success: false});
|
| 235 |
-
}
|
| 236 |
-
});
|
| 237 |
-
|
| 238 |
-
app.post('/api/admin/delete-user', checkAdmin, async (req, res) => {
|
| 239 |
-
await initDB();
|
| 240 |
-
const { targetUser } = req.body;
|
| 241 |
-
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
|
| 242 |
-
delete localDB.users[targetUser];
|
| 243 |
-
// Clean up their pins
|
| 244 |
-
localDB.pins = localDB.pins.filter(p => p.author !== targetUser);
|
| 245 |
-
await saveDB();
|
| 246 |
-
res.json({success: true});
|
| 247 |
-
} else {
|
| 248 |
-
res.json({success: false});
|
| 249 |
-
}
|
| 250 |
-
});
|
| 251 |
-
|
| 252 |
-
// (Keep existing API routes: like, follow, comment, clear-notifications, signup, login, etc.)
|
| 253 |
-
// ... [INSERT YOUR EXISTING /api/like, /api/follow routes HERE] ...
|
| 254 |
-
// Re-pasting essential ones for context:
|
| 255 |
-
|
| 256 |
app.post('/api/like', checkBan, async (req, res) => {
|
| 257 |
if (!req.session.user) return res.status(401).json({error: "Login required"});
|
| 258 |
await initDB();
|
|
@@ -281,12 +246,16 @@ app.post('/api/follow', checkBan, async (req, res) => {
|
|
| 281 |
const { targetUser } = req.body;
|
| 282 |
const me = req.session.user;
|
| 283 |
if (!localDB.users[targetUser] || targetUser === me) return res.json({ success: false });
|
|
|
|
| 284 |
const targetData = localDB.users[targetUser];
|
| 285 |
const myData = localDB.users[me];
|
|
|
|
| 286 |
if (!targetData.followers) targetData.followers = [];
|
| 287 |
if (!myData.following) myData.following = [];
|
|
|
|
| 288 |
let isFollowing = false;
|
| 289 |
const idx = targetData.followers.indexOf(me);
|
|
|
|
| 290 |
if (idx === -1) {
|
| 291 |
targetData.followers.push(me);
|
| 292 |
myData.following.push(targetUser);
|
|
@@ -323,6 +292,55 @@ app.post('/api/comment', checkBan, async (req, res) => {
|
|
| 323 |
res.json({ success: false });
|
| 324 |
});
|
| 325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
app.post('/signup', async (req, res) => {
|
| 327 |
const { username, password, email } = req.body;
|
| 328 |
await initDB();
|
|
@@ -345,14 +363,13 @@ app.post('/signup', async (req, res) => {
|
|
| 345 |
app.post('/login', async (req, res) => {
|
| 346 |
const { username, password } = req.body;
|
| 347 |
await initDB();
|
| 348 |
-
// Admin Check
|
| 349 |
if (username === ADMIN_USER && password === process.env.ADMIN_PASS) {
|
| 350 |
req.session.user = username;
|
| 351 |
return res.redirect('/');
|
| 352 |
}
|
| 353 |
const user = localDB.users[username];
|
| 354 |
if (user && bcrypt.compareSync(password, user.hash)) {
|
| 355 |
-
if(user.banned) return res.send("
|
| 356 |
req.session.user = username;
|
| 357 |
res.redirect('/');
|
| 358 |
} else {
|
|
@@ -389,7 +406,6 @@ app.post('/update-profile', upload.single('avatar'), checkBan, async (req, res)
|
|
| 389 |
user.avatar = result.secure_url;
|
| 390 |
}
|
| 391 |
await saveDB();
|
| 392 |
-
// Redirect back to profile page
|
| 393 |
res.redirect('/u/' + req.session.user);
|
| 394 |
});
|
| 395 |
|
|
@@ -408,4 +424,4 @@ app.post('/delete', checkBan, async (req, res) => {
|
|
| 408 |
app.get('/logout', (req, res) => { req.session = null; res.redirect('/'); });
|
| 409 |
|
| 410 |
initDB();
|
| 411 |
-
app.listen(7860, () => console.log("Megapin
|
|
|
|
| 20 |
const DB_FILE = 'megapin_master_db.json';
|
| 21 |
const MAX_TRENDING = 100;
|
| 22 |
const ADMIN_USER = process.env.ADMIN_USER;
|
|
|
|
| 23 |
const DEFAULT_AVATAR = "https://cdn-icons-png.flaticon.com/512/149/149071.png";
|
| 24 |
|
| 25 |
let localDB = null;
|
|
|
|
| 27 |
app.use(express.urlencoded({ extended: true }));
|
| 28 |
app.use(express.json());
|
| 29 |
app.set('view engine', 'ejs');
|
|
|
|
| 30 |
|
| 31 |
app.use(cookieSession({
|
| 32 |
name: '__Secure-session',
|
|
|
|
| 71 |
});
|
| 72 |
};
|
| 73 |
|
| 74 |
+
// --- LOGIC HELPERS ---
|
| 75 |
function addNotification(toUser, fromUser, type, pinId = null, preview = "") {
|
| 76 |
if (toUser === fromUser) return;
|
| 77 |
if (!localDB.users[toUser]) return;
|
| 78 |
if (!localDB.users[toUser].notifications) localDB.users[toUser].notifications = [];
|
| 79 |
+
|
| 80 |
localDB.users[toUser].notifications.unshift({
|
| 81 |
from: fromUser,
|
| 82 |
fromAvatar: localDB.users[fromUser].avatar,
|
| 83 |
+
type: type,
|
| 84 |
pinId: pinId,
|
| 85 |
preview: preview,
|
| 86 |
timestamp: Date.now(),
|
| 87 |
read: false
|
| 88 |
});
|
| 89 |
+
|
| 90 |
if (localDB.users[toUser].notifications.length > 50) localDB.users[toUser].notifications.pop();
|
| 91 |
}
|
| 92 |
|
|
|
|
| 109 |
|
| 110 |
// --- ROUTES ---
|
| 111 |
|
| 112 |
+
// Helper: Format pins for view
|
| 113 |
function formatPins(pins, username) {
|
| 114 |
return pins.map(pin => {
|
| 115 |
const authorData = localDB.users[pin.author] || { avatar: DEFAULT_AVATAR, displayName: pin.author };
|
|
|
|
| 125 |
});
|
| 126 |
}
|
| 127 |
|
| 128 |
+
// HOME PAGE
|
| 129 |
app.get('/', async (req, res) => {
|
| 130 |
await initDB();
|
| 131 |
const username = req.session.user;
|
|
|
|
| 140 |
if (currentUser.notifications) notifications = currentUser.notifications;
|
| 141 |
}
|
| 142 |
|
| 143 |
+
let viewPins = localDB.pins;
|
| 144 |
+
if (req.query.q) {
|
| 145 |
+
const q = req.query.q.toLowerCase();
|
| 146 |
+
viewPins = localDB.pins.filter(p =>
|
| 147 |
+
p.author.toLowerCase().includes(q) ||
|
| 148 |
+
(p.caption && p.caption.toLowerCase().includes(q))
|
| 149 |
+
);
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
const formattedPins = formatPins(viewPins, username);
|
| 153 |
|
| 154 |
res.render('index', {
|
| 155 |
+
pins: formattedPins,
|
| 156 |
user: currentUser,
|
| 157 |
notifications,
|
| 158 |
isAdmin: (username === ADMIN_USER),
|
| 159 |
+
searchQuery: req.query.q || '',
|
| 160 |
pageType: 'home',
|
| 161 |
profileUser: null
|
| 162 |
});
|
| 163 |
});
|
| 164 |
|
| 165 |
+
// PROFILE PAGE
|
| 166 |
app.get('/u/:username', async (req, res) => {
|
| 167 |
await initDB();
|
| 168 |
+
const username = req.session.user;
|
| 169 |
const targetUsername = req.params.username;
|
| 170 |
|
| 171 |
if (!localDB.users[targetUsername]) return res.redirect('/');
|
|
|
|
| 174 |
const userPins = localDB.pins.filter(p => p.author === targetUsername);
|
| 175 |
const formattedPins = formatPins(userPins, username);
|
| 176 |
|
|
|
|
| 177 |
const profileUser = {
|
| 178 |
username: targetUsername,
|
| 179 |
...targetUser,
|
|
|
|
| 198 |
|
| 199 |
// --- API ACTIONS ---
|
| 200 |
|
| 201 |
+
app.get('/api/pin/:id', async (req, res) => {
|
| 202 |
+
await initDB();
|
| 203 |
+
const pin = localDB.pins.find(p => p.id === parseInt(req.params.id));
|
| 204 |
+
if(!pin) return res.json({error: "Not found"});
|
| 205 |
+
const formatted = formatPins([pin], req.session.user)[0];
|
| 206 |
+
res.json(formatted);
|
| 207 |
+
});
|
| 208 |
+
|
| 209 |
app.get('/api/search-users', async (req, res) => {
|
| 210 |
await initDB();
|
| 211 |
const q = req.query.q ? req.query.q.toLowerCase() : '';
|
|
|
|
| 218 |
res.json(results);
|
| 219 |
});
|
| 220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
app.post('/api/like', checkBan, async (req, res) => {
|
| 222 |
if (!req.session.user) return res.status(401).json({error: "Login required"});
|
| 223 |
await initDB();
|
|
|
|
| 246 |
const { targetUser } = req.body;
|
| 247 |
const me = req.session.user;
|
| 248 |
if (!localDB.users[targetUser] || targetUser === me) return res.json({ success: false });
|
| 249 |
+
|
| 250 |
const targetData = localDB.users[targetUser];
|
| 251 |
const myData = localDB.users[me];
|
| 252 |
+
|
| 253 |
if (!targetData.followers) targetData.followers = [];
|
| 254 |
if (!myData.following) myData.following = [];
|
| 255 |
+
|
| 256 |
let isFollowing = false;
|
| 257 |
const idx = targetData.followers.indexOf(me);
|
| 258 |
+
|
| 259 |
if (idx === -1) {
|
| 260 |
targetData.followers.push(me);
|
| 261 |
myData.following.push(targetUser);
|
|
|
|
| 292 |
res.json({ success: false });
|
| 293 |
});
|
| 294 |
|
| 295 |
+
app.post('/api/clear-notifications', async (req, res) => {
|
| 296 |
+
if (!req.session.user) return;
|
| 297 |
+
await initDB();
|
| 298 |
+
if(localDB.users[req.session.user]) {
|
| 299 |
+
localDB.users[req.session.user].notifications = [];
|
| 300 |
+
await saveDB();
|
| 301 |
+
}
|
| 302 |
+
res.json({success: true});
|
| 303 |
+
});
|
| 304 |
+
|
| 305 |
+
// --- ADMIN API ---
|
| 306 |
+
app.post('/api/admin/badge', checkAdmin, async (req, res) => {
|
| 307 |
+
await initDB();
|
| 308 |
+
const { targetUser, badge } = req.body;
|
| 309 |
+
if(localDB.users[targetUser]) {
|
| 310 |
+
localDB.users[targetUser].badge = badge;
|
| 311 |
+
await saveDB();
|
| 312 |
+
res.json({success: true});
|
| 313 |
+
} else {
|
| 314 |
+
res.json({success: false});
|
| 315 |
+
}
|
| 316 |
+
});
|
| 317 |
+
|
| 318 |
+
app.post('/api/admin/ban', checkAdmin, async (req, res) => {
|
| 319 |
+
await initDB();
|
| 320 |
+
const { targetUser, banned } = req.body;
|
| 321 |
+
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
|
| 322 |
+
localDB.users[targetUser].banned = banned;
|
| 323 |
+
await saveDB();
|
| 324 |
+
res.json({success: true});
|
| 325 |
+
} else {
|
| 326 |
+
res.json({success: false});
|
| 327 |
+
}
|
| 328 |
+
});
|
| 329 |
+
|
| 330 |
+
app.post('/api/admin/delete-user', checkAdmin, async (req, res) => {
|
| 331 |
+
await initDB();
|
| 332 |
+
const { targetUser } = req.body;
|
| 333 |
+
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
|
| 334 |
+
delete localDB.users[targetUser];
|
| 335 |
+
localDB.pins = localDB.pins.filter(p => p.author !== targetUser);
|
| 336 |
+
await saveDB();
|
| 337 |
+
res.json({success: true});
|
| 338 |
+
} else {
|
| 339 |
+
res.json({success: false});
|
| 340 |
+
}
|
| 341 |
+
});
|
| 342 |
+
|
| 343 |
+
// --- STANDARD FORMS ---
|
| 344 |
app.post('/signup', async (req, res) => {
|
| 345 |
const { username, password, email } = req.body;
|
| 346 |
await initDB();
|
|
|
|
| 363 |
app.post('/login', async (req, res) => {
|
| 364 |
const { username, password } = req.body;
|
| 365 |
await initDB();
|
|
|
|
| 366 |
if (username === ADMIN_USER && password === process.env.ADMIN_PASS) {
|
| 367 |
req.session.user = username;
|
| 368 |
return res.redirect('/');
|
| 369 |
}
|
| 370 |
const user = localDB.users[username];
|
| 371 |
if (user && bcrypt.compareSync(password, user.hash)) {
|
| 372 |
+
if(user.banned) return res.send("Banned.");
|
| 373 |
req.session.user = username;
|
| 374 |
res.redirect('/');
|
| 375 |
} else {
|
|
|
|
| 406 |
user.avatar = result.secure_url;
|
| 407 |
}
|
| 408 |
await saveDB();
|
|
|
|
| 409 |
res.redirect('/u/' + req.session.user);
|
| 410 |
});
|
| 411 |
|
|
|
|
| 424 |
app.get('/logout', (req, res) => { req.session = null; res.redirect('/'); });
|
| 425 |
|
| 426 |
initDB();
|
| 427 |
+
app.listen(7860, () => console.log("Megapin Redux Active"));
|