Skip to content
Rishad Rahman
All work

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
The UniSphere home page: a blue-to-teal-to-orange gradient hero reading "UniSphere, where campus life connects" with Browse Marketplace and Get Started Free buttons, above feature cards for Campus Marketplace, Easy Transactions and Trusted Community.

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.

AreaWhat's implemented
ProductsListings with image upload, cart, checkout, order history, seller order management
ServicesService listings, weekly or date-specific time slots, booking requests: requested → approved / rejected → completed
PaymentsSSLCommerz payment-session integration for orders
CommunityConversations between users, notifications, reviews and a computed reputation score
Trust & safetyDisputes on orders or bookings, flagged content, monitoring alerts, activity log
AdminDashboard, 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

React + TypeScript
34 pages · React Router · Tailwind CSS
Context providers
Auth · Cart · Notifications (polling) · ProtectedRoute

API · Express 5

15 route modules
auth, products, cart, orders, services, bookings, time slots, payments, messaging, disputes, admin…
verifyToken → authorizeRoles(…)
Rejects deactivated or restricted accounts
controllers + lib/
reputation.js, notify.js · Multer uploads

Data & services

MongoDB
13 models: users, products, orders, services, bookings, disputes…
SSLCommerz
Payment sessions for checkout
Request path for a protected API call, top to bottom.

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. SameSite switches to none in production for the cross-origin client.
  • verifyToken middleware 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.

backend/lib/reputation.jsSource (opens GitHub in a new tab)
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:

backend/controllers/admin.controller.jsSource (opens GitHub in a new tab)
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.

↑ ↓ to move · Enter to open