Web · Full-stack platform
UniSphere
A campus marketplace where students buy and sell products, book each other's services, and settle problems through moderated disputes.
Stack
- TypeScript
- React
- Vite
- Tailwind CSS
- Node.js
- Express
- MongoDB
- Mongoose
- JWT
- SSLCommerz
- client pages and dashboards
- 34
- client pages and dashboards
- REST route modules
- 15
- REST route modules
- Mongoose data models
- 13
- Mongoose data models
- roles: buyer, seller, admin
- 3
- roles: buyer, seller, admin

What it does
UniSphere is two marketplaces sharing one community. Students can list products and sell them through a cart → checkout → order flow, or offer services (tutoring, for example) with their own availability, which other students book online or in person. Around those two flows sit the things that make a marketplace usable: messaging, notifications, reviews, and a way to complain when something goes wrong.
| Area | What's implemented |
|---|---|
| Products | Listings with image upload, cart, checkout, order history, seller order management |
| Services | Service listings, weekly or date-specific time slots, booking requests: requested → approved / rejected → completed |
| Payments | SSLCommerz payment-session integration for orders |
| Community | Conversations between users, notifications, reviews and a computed reputation score |
| Trust & safety | Disputes on orders or bookings, flagged content, monitoring alerts, activity log |
| Admin | Dashboard, user management, flagged items, monitoring and activity pages |
Architecture
A React + TypeScript single-page app built with Vite talks to an Express 5 REST API over Axios. Each API request passes through route → middleware → controller, and controllers use Mongoose models to read and write MongoDB.
Client · Vite SPA
API · Express 5
Data & services
On the client, React context providers hold auth, cart and notification state, a ProtectedRoute component guards authenticated pages, and notifications are kept fresh by polling. The client is configured to deploy on Vercel as a static build, pointing at a separately hosted API.
Authentication and roles
- Sessions are JWTs stored in an httpOnly cookie, so page scripts can't read them.
SameSiteswitches tononein production for the cross-origin client. verifyTokenmiddleware loads the user on every protected request and refuses deactivated or restricted accounts, so a restriction takes effect immediately rather than when a token expires.authorizeRoles(...)gates routes to buyers, sellers or admins.- Passwords are salted and hashed with PBKDF2-SHA512 (10,000 iterations) using Node's built-in
crypto.
A reputation score that's hard to game
A seller's reputation is recomputed from their history rather than stored as a running average. Rating quality counts for half; transaction volume grows logarithmically, so the hundredth sale matters less than the tenth; and consistency rewards sellers whose ratings don't swing wildly.
const calculateConsistencyScore = (ratings) => { if (!ratings.length) return 0; if (ratings.length === 1) return 65; const avg = ratings.reduce((sum, value) => sum + value, 0) / ratings.length; const variance = ratings.reduce((sum, value) => sum + (value - avg) ** 2, 0) / ratings.length; const stdDev = Math.sqrt(variance); // Lower standard deviation means higher consistency. const normalized = 100 - stdDev * 30; return clamp(round2(normalized), 0, 100);}; // …// Weighted dynamic score:// 1) Rating quality (50%), 2) completed transaction volume (25%),// 3) performance consistency (25%).const ratingComponent = (avgRating / 5) * 50;const transactionComponent = clamp(Math.log10(completedTransactions + 1) * 25, 0, 25);const consistencyComponent = (consistencyScore / 100) * 25;"Completed transactions" counts both delivered product orders and completed service bookings, fetched in parallel with Promise.all.
Disputes and monitoring
A user can open a dispute against another user over an order or a booking. Disputes move through Submitted → Under Review → Resolved / Rejected, and an admin resolution records an action: none, a warning, a refund, or an account restriction. Admins can restrict or deactivate accounts from user management, and the auth middleware enforces that on the user's very next request.
Monitoring alerts are deduplicated, so repeated triggers within a week don't flood the admin queue:
const createAlert = async ({ user, alertType, description, severity, metadata = {} }) => { const sevenDaysAgo = getDateRangeForDays(7); const existing = await MonitoringAlert.findOne({ user, alertType, status: { $in: ["open", "under_review"] }, createdAt: { $gte: sevenDaysAgo }, }); if (existing) return existing; return MonitoringAlert.create({ user, alertType, description, severity, metadata, status: "open" });};What's next
The project README lists the planned improvements: real-time chat (messaging currently works over regular requests), recommendations, university verification for sign-ups, a mobile app, and a richer analytics dashboard.