Our Little World
A private website for couples to share answers to meaningful questions, express feelings, upload photos, and cherish memories together in a secure and beautifully designed digital space.
Overview
Our Little World is a private, two-person web application built exclusively for couples. It serves as a shared digital space where they can log feelings, answer personal questions together, upload and browse photos, preserve memories on a visual timeline, and access a password-protected private section for sensitive content.
There is no public access, no user registration, and no social features. The security model is intentionally lightweight — built around a single BCrypt-hashed shared password, IP-based access restriction, and JWT session tokens issued in HttpOnly cookies.
Tech Stack
- ASP.NET Core / C# — REST API backend with EF Core, JWT authentication, and a custom middleware pipeline.
- React / Next.js — Client-side frontend deployed to Netlify.
- TypeScript — Full type safety across all frontend components and API calls.
- Tailwind CSS — Utility-first styling with a romantic soft-pink and purple palette.
- PostgreSQL — Primary relational database via EF Core and the Npgsql provider.
- Cloudinary — Photo storage and delivery for both regular and secure photos.
- Docker — Backend containerized and deployed to Railway with automatic startup migrations.
- Railway / Netlify — Backend on Railway, frontend on Netlify CDN.
- GitHub Actions — Separate CI/CD pipelines for backend and frontend, both gated on a passing build.
Features
- Feelings tracker — Autocomplete dropdown of 100+ feelings, subject and context fields, recent entries list, and a 500-entry cap with user-friendly messaging.
- Q&A journal — Pre-seeded and custom questions with individual answer boxes per user, save timestamps, and a "Suggest a Question" button that randomly highlights a predefined question.
- Photo album — Grid gallery with lazy loading, Cloudinary upload and delete, and client-side image compression on upload.
- Memory timeline — Scrollable timeline cards with median-timestamp pagination to avoid full-table scans as the dataset grows.
- Secure section — BCrypt-hashed password enforced by an ASP.NET Core action filter. On success, a short-lived JWT is issued as an HttpOnly cookie so re-entry is not required within the session.
- Admin interface — Password-protected CRUD dashboard for managing questions, feelings, and photos without direct database access.
- Surprise Me — Randomly surfaces a question, feeling, or memory from the database on button click.
Key Challenges
Security without a login system: The site has no traditional user accounts. All access control is handled by a single BCrypt-hashed password for the secure section, verified server-side by an ASP.NET Core action filter. An IP allowlist middleware (IpRestrictionMiddleware) blocks all traffic from unauthorized addresses before it reaches any route in the pipeline.
JWT in HttpOnly cookies: After the secure section password is verified, the backend issues a JWT stored in an HttpOnly cookie rather than returning it to the frontend for localStorage. This prevents client-side script access to the token entirely.
Railway PostgreSQL connection handling: Railway injects the database connection string as a PostgreSQL URI (postgresql://...). The startup code detects this format and converts it to Npgsql's key-value format before passing it to EF Core. A migration guard also detects cases where __EFMigrationsHistory was written by a previous deploy but the actual tables were never created (a known issue with pgBouncer mid-flight) and resets the history before re-running migrations.
Photo storage security: Secure photos and regular photos are stored in separate Cloudinary contexts. Secure photo URLs are never exposed to the frontend without a valid session token, so guessing or sharing a URL cannot bypass the password gate.
Activity auditing: A custom ActivityLoggingMiddleware records IP address, page visited, action, and timestamp to a database table on every request. It runs after routing (so the path is fully resolved) but before controllers, using an IServiceScopeFactory internally to safely create a scoped DbContext from a middleware that is registered as scoped itself.
Outcomes
Both the backend and frontend deploy automatically on push to main. The backend self-migrates and self-seeds on container startup. The site is live and in active personal use.