Portfolio
Loading Shiham's portfolio
Preparing the latest projects, skills, and contact paths with a quiet production-ready polish.
Initializing portfolio
Projects · Skills · Contact
Portfolio
Preparing the latest projects, skills, and contact paths with a quiet production-ready polish.
Initializing portfolio
Projects · Skills · Contact
A marketplace for verified Sri Lankan homestays, supporting guest bookings, host onboarding, admin approval, and test-mode payments.
GedaraStay was developed as a five-member academic group project to explore how a digital marketplace could connect travelers with Sri Lankan homestay owners while addressing trust, property verification, reservation control, and administrative oversight. The system serves guests, room owners, and administrators through distinct role-based experiences.
The platform supports approved-stay discovery, wishlists, request-to-book reservations, host acceptance or rejection, test-mode Stripe checkout, reviews, in-app notifications, and structured support cases. Room owners complete staged onboarding and manage listings, while administrators review identity, property, tourism-compliance, stay, booking, payment, refund, payout, and safety-related operations.
The application uses a React single-page frontend with domain-focused Zustand stores and an Express REST API backed by MongoDB through Mongoose. Backend middleware enforces JWT authentication and role permissions, while Stripe, Cloudinary, and Google Maps provide payment, media, and location capabilities. The repository is positioned as a completed academic portfolio/demo build rather than a production marketplace.
Related projects
Jan 2026 – Jun 2026
A multi-vendor commerce platform connecting buyers, sellers, and administrators through dedicated storefronts, dashboards, payments, chat, and personalized discovery.
The system is a React single-page application that communicates with an Express REST API through credentialed Axios requests. MongoDB stores marketplace, verification, booking, payment, ledger, notification, review, wishlist, and support data, while Stripe, Cloudinary, and Google Maps provide external services.
Sri Lankan homestay & cultural-stay booking platform — connecting travelers with verified local hosts for authentic, community-rooted stays.
gedaraStay is a full-stack marketplace for booking verified Sri Lankan homestays, local homes, and cultural-immersion stays (village homestays, heritage homes, tea/spice farm stays, eco-nature stays). It supports three roles — guests, hosts (room owners), and admins — with a request-to-book reservation flow, Stripe test-mode payments, an internal settlement ledger, a multi-stage host/property verification pipeline, an in-app notification system, and a support ticketing system. The UI is available in English, Sinhala, and Tamil.
This project is currently packaged as a portfolio/demo application, not a live production marketplace (see Known Limitations).
Peer-to-peer short-term rentals face a trust gap on both sides: guests can't easily verify a listing is real and safe, and platforms/hosts need a way to prove property ownership, identity, and local tourism-authority compliance before accepting money. gedaraStay addresses this by:
checkout.session.completed, checkout.session.expired, payment_intent.payment_failed)Frontend
Backend
jsonwebtoken) + bcryptjsexpress-rate-limitDatabase: MongoDB (designed for MongoDB Atlas, works with any Mongo instance)
Auth/Security: httpOnly JWT cookies, bcrypt hashing, RBAC middleware (verifyToken, optionalVerifyToken, adminRoute, roomOwnerRoute, adminOrRoomOwnerRoute), startup rejection of placeholder JWT secrets, sensitive-data redaction in error responses and logs, masked document numbers in API output
Integrations: Stripe (payments), Cloudinary (media), Google Maps Places API (location)
Testing: Not currently configured (see Testing)
DevOps: No Dockerfile or CI pipeline is included; deployment is manual (see Known Limitations)
Request lifecycle example (booking → payment):
Booking created with status pending_host_confirmation, hostResponseDueAt set 48h out.awaiting_guest_payment, paymentDueAt set (PAYMENT_DEADLINE_MINUTES, default 24h).Payment record tracks it.PaymentEvent, booking flips to confirmed, a LedgerEntry is posted, both parties are notified in-app.bookingLifecycle.js) automatically expire the booking and release the dates.gedara-stay-main/
├── backend/
│ ├── controllers/ # Route handlers (auth, stay, booking, payment, host,
│ │ # admin verification, admin operations, ledger,
│ │ # notification, support, review, wishlist, user)
│ ├── models/ # Mongoose schemas (User, Stay, Booking, Payment,
│ │ # PaymentEvent, LedgerEntry, Review, Wishlist,
│ │ # Notification, SupportCase, VerificationDecision)
│ ├── routes/ # Express routers, one per resource
│ ├── middleware/ # JWT auth, RBAC guards, error handler, request logger
│ ├── services/ # notification.service.js, support.service.js
│ ├── utils/ # booking lifecycle, quote calc, stay approval gates,
│ │ # stay query builder, public stay serializer, JWT/cookie helper
│ ├── lib/ # MongoDB connection, Cloudinary config
│ └── server.js # Express app entry point
│
├── frontend/
│ ├── src/
│ │ ├── pages/ # Route-level pages (Home, BrowseStays, StayDetails,
│ │ │ # BookStay, HostOnboarding, RoomOwnerDashboard,
│ │ │ # adminDashboard, Wishlist, Notifications, Support…)
│ │ ├── components/
│ │ │ ├── admin/ # Admin dashboard UI (users, stays, bookings, payments)
│ │ │ ├── roomOwner/ # Host dashboard UI (listings, bookings, revenue)
│ │ │ ├── user/ # Guest-facing UI (profile, wishlist, reviews)
│ │ │ └── auth/ # Login, Signup, ProtectedRoute, PublicRoute
│ │ ├── stores/ # Zustand stores (auth, stay, booking, payment, ledger,
│ │ │ # wishlist, review, notification, support, host onboarding)
│ │ ├── i18n/ # i18next config + en/si/ta translation JSON
│ │ ├── config/ # roles.js, routes.js, brand.js
│ │ ├── lib/axios.js # Configured Axios instance
│ │ └── App.jsx # Route definitions
│ └── provider/ # GoogleMapsProvider / map context
│
├── scripts/
│ └── seed-demo-data.mjs # Guarded demo data seeder
│
├── .env.example # Backend environment template
├── frontend/.env.example # Frontend environment template
└── package.json # Backend/root dependencies & scripts
# Clone the repository
git clone <your-repo-url>
cd gedara-stay-main
# Install backend/root dependencies
npm install
# Install frontend dependencies
cd frontend
npm install
cd ..
Copy the example files and fill in real values. Never commit actual .env files.
cp .env.example .env
cp frontend/.env.example frontend/.env
Backend — .env
| Variable | Description | Required |
|---|---|---|
NODE_ENV | development | production | No (defaults to development) |
PORT | API port | No (defaults to 5000) |
MONGO_URI | MongoDB connection string | Yes — server refuses to start without it |
JWT_SECRET | Secret for signing JWTs | Yes — server refuses to start with a missing/placeholder value |
CLIENT_URL | Frontend origin | No (defaults to http://localhost:5173) |
Frontend — frontend/.env
| Variable | Description | Required |
|---|---|---|
VITE_API_BASE_URL | Backend API base URL | No (defaults to http://localhost:5000/api) |
VITE_GOOGLE_MAPS_API_KEY | Google Maps API key | Yes, for location picker/autocomplete |
Start the backend:
npm run dev # nodemon, auto-restarts on change
# or
npm start # plain node
Start the frontend (in a second terminal):
cd frontend
npm run dev
http://localhost:5000http://localhost:5173GET http://localhost:5000/api/healthRoot / backend (package.json)
| Script | Description |
|---|---|
npm run dev | Start backend with nodemon |
npm start | Start backend with node |
npm run seed:demo | Run the guarded demo data seeder |
Frontend (frontend/package.json)
| Script | Description |
|---|---|
npm run dev | Start Vite dev server |
npm run build | Production build |
npm run preview | Preview the production build |
npm run lint | Run ESLint |
MONGO_URI.npm start/npm run dev, Mongoose creates collections and indexes automatically from the schemas in backend/models/. SEED_DEMO_CONFIRM=true npm run seed:demo
The script is intentionally guarded:
SEED_DEMO_CONFIRM=trueSEED_DEMO_TARGET=demo if NODE_ENV=productionMONGO_URI and a non-placeholder JWT_SECRETIt creates demo admin, roomOwner, and guest accounts (demo.admin@gedarastay.local, demo.host@gedarastay.local, demo.guest@gedarastay.local), sample stays, bookings, payments, ledger entries, support cases, and notifications. Passwords are either read from DEMO_ADMIN_PASSWORD / DEMO_HOST_PASSWORD / DEMO_GUEST_PASSWORD or randomly generated and printed to the console — rotate or discard these before any real deployment.
Base URL: /api. All protected routes expect a token httpOnly cookie set by /auth/login or /auth/signup/*.
| Resource | Base path | Notes |
|---|---|---|
| Auth | /api/auth | Signup (user/roomOwner), login, logout, profile get/update, admin user list, room-owner verification |
| User | /api/user | Guest dashboard overview |
| Stays | /api/stays | Public browse/search (GET /), stay detail, host CRUD, admin review, status changes |
| Bookings | /api/bookings, /api/booking | Availability, quote, create, list mine, get by id, cancel |
| Admin Bookings | /api/admin/bookings | Admin booking list/detail |
| Host |
The full route definitions (including method, middleware, and validation) live in
backend/routes/.
The UI ships with English, Sinhala, and Tamil translations via i18next, organized into namespaces (common, navigation, auth, stays, booking, dashboard, support, notifications, validation, accessibility) under frontend/src/i18n/locales/. Language preference is persisted to localStorage and applied to <html lang>. Note: per the maintainers' own README, Sinhala/Tamil translations are draft coverage and need human review before production use.
Add screenshots or a short demo GIF here once available.
| Page | Screenshot |
|---|---|
| Home / Browse Stays | docs/screenshots/home.png |
| Stay Details | docs/screenshots/stay-details.png |
| Booking & Checkout | docs/screenshots/booking.png |
| Host Dashboard | docs/screenshots/host-dashboard.png |
| Admin Verification Queue | docs/screenshots/admin-verification.png |
No automated tests are currently included in this repository. There is no test runner configured in either package.json, and no *.test.js/*.spec.js files exist in the codebase. The only current quality checks are:
npm audit
npm audit --omit=dev
cd frontend
npm audit
npm audit --omit=dev
npm run lint
npm run build
Adding unit tests (e.g., Vitest for the frontend, Jest/Vitest + Supertest for the backend) and at least smoke-level integration tests for the booking → payment webhook flow would be a high-value next step.
This project is explicitly positioned by its own codebase as a portfolio/demo build, not production-ready:
license: ISC being declaredpackage.json name (test_mapsapi) doesn't match the product name, one stray .tsx file exists without TypeScript tooling configured, and the streamifier dependency is unusedContributions are welcome. To propose a change:
git checkout -b feature/your-feature)npm run lint and npm run build in frontend/ before submittingPlease avoid committing real credentials, .env files, or production data in any PR.
This project is declared under the ISC License in package.json. Add a LICENSE file at the repository root with the full ISC (or your chosen license) text to make this legally explicit.
Built as a portfolio demonstration of a full-stack, multi-role marketplace with real-world concerns: verification workflows, payment reconciliation, and role-based access control.
The case-study preview is collapsed. Activate the button to make the full content available.
Oct 2025 – Nov 2025
A full-stack developer Q&A platform where programmers can ask, answer, vote, save questions, explore tags, and generate AI-assisted responses.
Jan 2026 – Jan 2026
A hands-on DevOps playground for developers learning to containerize an Express API and run replicated workloads on Kubernetes with Docker and Minikube.
CORS_ORIGINS | Comma-separated allowed origins | No (falls back to CLIENT_URL) |
COOKIE_SAMESITE | lax | strict | none | No (defaults to lax) |
CLOUDINARY_CLOUD_NAME | Cloudinary cloud name | Yes, for image uploads |
CLOUDINARY_API_KEY | Cloudinary API key | Yes, for image uploads |
CLOUDINARY_API_SECRET | Cloudinary API secret | Yes, for image uploads |
STRIPE_SECRET_KEY | Stripe secret key (test mode) | Yes, for payments |
STRIPE_WEBHOOK_SECRET | Stripe webhook signing secret | Yes, for webhook verification |
STRIPE_API_VERSION | Stripe API version override | No |
PAYMENT_CURRENCY | Default currency | No (defaults to LKR) |
PAYMENT_DEADLINE_MINUTES | Guest payment window after host accepts | No (defaults to 1440 = 24h) |
PLATFORM_COMMISSION_RATE | Platform commission (decimal) | No (defaults to 0.10) |
PAYMENT_SUCCESS_URL / PAYMENT_CANCEL_URL | Stripe redirect URLs | No (default to local frontend routes) |
LOG_LEVEL | Log verbosity | No |
/api/host |
| Host bookings actions, onboarding, identity/property/compliance verification uploads, stay submission |
| Admin Verifications | /api/admin/verifications | Queues + approve/reject/request-changes for identity, property, compliance, stay approval |
| Wishlist | /api/wishlist | Add, list, reorder, remove |
| Ledger | /api/ledger | Admin platform balance, host payout balance |
| Payments | /api/payments | Stripe checkout session creation, status lookup, webhook (signature-verified), admin payment list |
| Reviews | /api/reviews | Create (with images), list by stay/booking/owner/user, edit, delete, owner reply |
| Notifications | /api/notifications | List, unread count, mark read/all-read, dismiss, archive |
| Support | /api/support | Create/list/view support cases, add messages, close |
| Admin Operations | /api/admin | Support case management, refund review queue, payout queue, safety report queue |
| Health | /api/health | Basic liveness check |