Mega_pin / server.js
Shinhati2023's picture
Update server.js
ab70e6b verified
const express = require('express');
const cloudinary = require('cloudinary').v2;
const multer = require('multer');
const cookieSession = require('cookie-session');
const bcrypt = require('bcryptjs');
const axios = require('axios');
const streamifier = require('streamifier');
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
// Essential for Hugging Face Spaces proxy
app.set('trust proxy', 1);
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET
});
const DB_FILE = 'megapin_master_db.json';
const MAX_TRENDING = 100;
// SECRETS
const ADMIN_USER = process.env.ADMIN_USER;
const ADMIN_PASS = process.env.ADMIN_PASS;
const DEFAULT_AVATAR = "https://cdn-icons-png.flaticon.com/512/149/149071.png";
let localDB = null;
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.set('view engine', 'ejs');
// --- SESSION CONFIG ---
app.use(cookieSession({
name: 'mp_session',
keys: [process.env.SESSION_SECRET || 'secret_key_change_me'],
maxAge: 24 * 60 * 60 * 1000,
secure: true,
sameSite: 'none',
httpOnly: true
}));
// --- DB ENGINE ---
async function initDB() {
if (localDB) return localDB;
try {
const url = cloudinary.url(DB_FILE, { resource_type: 'raw' }) + `?t=${Date.now()}`;
const res = await axios.get(url);
localDB = res.data;
if (!localDB.users) localDB.users = {};
if (!localDB.pins) localDB.pins = [];
} catch (e) {
localDB = { users: {}, pins: [] };
}
return localDB;
}
async function saveDB() {
const buffer = Buffer.from(JSON.stringify(localDB));
return new Promise((resolve, reject) => {
const stream = cloudinary.uploader.upload_stream(
{ resource_type: 'raw', public_id: DB_FILE, overwrite: true, invalidate: true },
(err, res) => {
if (err) { console.error("Backup failed:", err); reject(err); }
else resolve(res);
}
);
streamifier.createReadStream(buffer).pipe(stream);
});
}
const uploadImage = (buffer) => {
return new Promise((resolve, reject) => {
const stream = cloudinary.uploader.upload_stream({ folder: "megapin_assets" }, (err, res) => {
if (res) resolve(res); else reject(err);
});
streamifier.createReadStream(buffer).pipe(stream);
});
};
// --- MIDDLEWARE ---
function addNotification(toUser, fromUser, type, pinId = null, preview = "") {
if (toUser === fromUser) return;
if (!localDB.users[toUser]) return;
if (!localDB.users[toUser].notifications) localDB.users[toUser].notifications = [];
localDB.users[toUser].notifications.unshift({
from: fromUser,
fromAvatar: localDB.users[fromUser].avatar || DEFAULT_AVATAR,
type: type,
pinId: pinId,
preview: preview,
timestamp: Date.now(),
read: false
});
if (localDB.users[toUser].notifications.length > 50) localDB.users[toUser].notifications.pop();
}
async function checkBan(req, res, next) {
if (!req.session.user) return next();
await initDB();
const user = localDB.users[req.session.user];
if (!user) {
req.session = null;
return res.redirect('/');
}
if (user.banned) {
req.session = null;
return res.send("Account Suspended.");
}
next();
}
function checkAdmin(req, res, next) {
if (req.session.user === ADMIN_USER) return next();
res.status(403).json({ error: "Admin only" });
}
function formatPins(pins, username) {
return pins.map(pin => {
const authorData = localDB.users[pin.author] || { avatar: DEFAULT_AVATAR, displayName: pin.author + " (Deleted)", badge: '' };
return {
...pin,
authorDisplayName: authorData.displayName || pin.author,
authorAvatar: authorData.avatar || DEFAULT_AVATAR,
badge: authorData.badge || '',
likeCount: pin.likes ? pin.likes.length : 0,
hasLiked: pin.likes && username ? pin.likes.includes(username) : false,
comments: pin.comments || []
};
});
}
// --- ROUTES ---
app.get('/', async (req, res) => {
await initDB();
const username = req.session.user;
let currentUser = null;
let notifications = [];
if (username && localDB.users[username]) {
currentUser = { ...localDB.users[username], username };
if (!currentUser.notifications) currentUser.notifications = [];
notifications = currentUser.notifications;
}
let viewPins = localDB.pins;
if (req.query.q) {
const q = req.query.q.toLowerCase();
viewPins = localDB.pins.filter(p =>
p.author.toLowerCase().includes(q) ||
(p.caption && p.caption.toLowerCase().includes(q))
);
}
const formattedPins = formatPins(viewPins, username);
res.render('index', {
pins: formattedPins,
user: currentUser,
notifications,
isAdmin: (username === ADMIN_USER),
searchQuery: req.query.q || '',
pageType: 'home',
profileUser: null
});
});
app.get('/u/:username', async (req, res) => {
await initDB();
const username = req.session.user;
const targetUsername = req.params.username;
if (!localDB.users[targetUsername]) return res.redirect('/');
const targetUser = localDB.users[targetUsername];
const userPins = localDB.pins.filter(p => p.author === targetUsername);
const formattedPins = formatPins(userPins, username);
const profileUser = {
username: targetUsername,
...targetUser,
postCount: userPins.length,
followerCount: targetUser.followers ? targetUser.followers.length : 0,
followingCount: targetUser.following ? targetUser.following.length : 0,
isFollowing: targetUser.followers && targetUser.followers.includes(username),
badge: targetUser.badge || ''
};
let currentUser = null;
if (username && localDB.users[username]) currentUser = { ...localDB.users[username], username };
res.render('index', {
pins: formattedPins,
user: currentUser,
notifications: currentUser ? currentUser.notifications : [],
isAdmin: (username === ADMIN_USER),
pageType: 'profile',
profileUser: profileUser
});
});
// --- API ---
app.get('/api/pin/:id', async (req, res) => {
await initDB();
const pin = localDB.pins.find(p => p.id === parseInt(req.params.id));
if(!pin) return res.json({error: "Not found"});
const formatted = formatPins([pin], req.session.user)[0];
res.json(formatted);
});
app.get('/api/search-users', async (req, res) => {
await initDB();
const q = req.query.q ? req.query.q.toLowerCase() : '';
const users = Object.keys(localDB.users).filter(u => u.toLowerCase().includes(q));
const results = users.map(u => ({
username: u,
avatar: localDB.users[u].avatar || DEFAULT_AVATAR,
badge: localDB.users[u].badge || ''
}));
res.json(results);
});
app.post('/api/like', checkBan, async (req, res) => {
if (!req.session.user) return res.status(401).json({error: "Login required"});
await initDB();
const { pinId } = req.body;
const pin = localDB.pins.find(p => p.id === parseInt(pinId));
let liked = false;
if (pin) {
if (!pin.likes) pin.likes = [];
const index = pin.likes.indexOf(req.session.user);
if (index === -1) {
pin.likes.push(req.session.user);
liked = true;
addNotification(pin.author, req.session.user, 'like', pin.id);
} else {
pin.likes.splice(index, 1);
}
await saveDB();
return res.json({ success: true, liked, count: pin.likes.length });
}
res.json({ success: false });
});
app.post('/api/follow', checkBan, async (req, res) => {
if (!req.session.user) return res.status(401).json({error: "Login required"});
await initDB();
const { targetUser } = req.body;
const me = req.session.user;
if (!localDB.users[targetUser] || targetUser === me) return res.json({ success: false });
const targetData = localDB.users[targetUser];
const myData = localDB.users[me];
if (!targetData.followers) targetData.followers = [];
if (!myData.following) myData.following = [];
let isFollowing = false;
const idx = targetData.followers.indexOf(me);
if (idx === -1) {
targetData.followers.push(me);
myData.following.push(targetUser);
isFollowing = true;
addNotification(targetUser, me, 'follow');
} else {
targetData.followers.splice(idx, 1);
const myIdx = myData.following.indexOf(targetUser);
if (myIdx !== -1) myData.following.splice(myIdx, 1);
}
await saveDB();
res.json({ success: true, isFollowing, followersCount: targetData.followers.length });
});
app.post('/api/comment', checkBan, async (req, res) => {
if (!req.session.user) return res.status(401).json({error: "Login required"});
await initDB();
const { pinId, text } = req.body;
if(!text || !text.trim()) return res.json({success:false});
const pin = localDB.pins.find(p => p.id === parseInt(pinId));
if (pin) {
if (!pin.comments) pin.comments = [];
const comment = {
id: Date.now(),
user: req.session.user,
avatar: localDB.users[req.session.user].avatar,
text: text,
timestamp: Date.now()
};
pin.comments.push(comment);
addNotification(pin.author, req.session.user, 'comment', pin.id, text);
await saveDB();
return res.json({ success: true, comment });
}
res.json({ success: false });
});
app.post('/api/clear-notifications', async (req, res) => {
if (!req.session.user) return;
await initDB();
if(localDB.users[req.session.user]) {
localDB.users[req.session.user].notifications = [];
await saveDB();
}
res.json({success: true});
});
// --- ADMIN API ---
app.post('/api/admin/badge', checkAdmin, async (req, res) => {
await initDB();
const { targetUser, badge } = req.body;
if(localDB.users[targetUser]) {
localDB.users[targetUser].badge = badge;
await saveDB();
res.json({success: true});
} else {
res.json({success: false});
}
});
app.post('/api/admin/ban', checkAdmin, async (req, res) => {
await initDB();
const { targetUser, banned } = req.body;
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
localDB.users[targetUser].banned = banned;
await saveDB();
res.json({success: true});
} else {
res.json({success: false});
}
});
app.post('/api/admin/delete-user', checkAdmin, async (req, res) => {
await initDB();
const { targetUser } = req.body;
if(localDB.users[targetUser] && targetUser !== ADMIN_USER) {
delete localDB.users[targetUser];
localDB.pins = localDB.pins.filter(p => p.author !== targetUser);
await saveDB();
res.json({success: true});
} else {
res.json({success: false});
}
});
// --- FORMS ---
app.post('/signup', async (req, res) => {
const { username, password, email, gender } = req.body; // Added gender
await initDB();
if(!username || !password) return res.send("Username and Password are required.");
const safeUser = username.trim();
if (localDB.users[safeUser]) return res.send("Username taken.");
localDB.users[safeUser] = {
displayName: safeUser,
hash: bcrypt.hashSync(password, 8),
email: email || '',
gender: gender || 'Not Specified', // Save gender
avatar: DEFAULT_AVATAR,
followers: [],
following: [],
notifications: [],
banned: false
};
await saveDB();
req.session.user = safeUser;
res.redirect('/');
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
await initDB();
if(!username || !password) return res.send("Missing credentials.");
if (username === ADMIN_USER && password === ADMIN_PASS) {
req.session.user = username;
return res.redirect('/');
}
const user = localDB.users[username];
if (user && bcrypt.compareSync(password, user.hash)) {
if(user.banned) return res.send("Banned.");
req.session.user = username;
res.redirect('/');
} else {
res.send("Invalid credentials.");
}
});
app.post('/add', checkBan, upload.single('image'), async (req, res) => {
if (!req.session.user || !req.file) return res.redirect('/');
await initDB();
try {
const result = await uploadImage(req.file.buffer);
const finalUrl = result.secure_url;
localDB.pins.unshift({
id: Date.now(),
url: finalUrl,
caption: req.body.caption,
author: req.session.user,
likes: [],
comments: [],
timestamp: Date.now()
});
if (localDB.pins.length > MAX_TRENDING) localDB.pins = localDB.pins.slice(0, MAX_TRENDING);
await saveDB();
} catch(err) {
console.error("Upload Error", err);
}
res.redirect('/');
});
app.post('/update-profile', upload.single('avatar'), checkBan, async (req, res) => {
if (!req.session.user) return res.redirect('/');
await initDB();
const user = localDB.users[req.session.user];
if (req.body.bio) user.bio = req.body.bio;
if (req.body.display_name) user.displayName = req.body.display_name;
if (req.file) {
try {
const result = await uploadImage(req.file.buffer);
user.avatar = result.secure_url;
} catch(e) { console.error(e); }
}
await saveDB();
res.redirect('/u/' + req.session.user);
});
app.post('/delete', checkBan, async (req, res) => {
if (!req.session.user) return res.redirect('/');
await initDB();
const id = parseInt(req.body.id);
const pin = localDB.pins.find(p => p.id === id);
if (pin && (pin.author === req.session.user || req.session.user === ADMIN_USER)) {
localDB.pins = localDB.pins.filter(p => p.id !== id);
await saveDB();
}
res.redirect('/');
});
app.get('/logout', (req, res) => { req.session = null; res.redirect('/'); });
initDB();
app.listen(7860, () => console.log("Megapin Final Aesthetic Active"));