Skip to main content
All Projects

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.

C#ASP.NET CoreRazor PagesCSSTypeScriptTailwind CSSReactPostgreSQLCloudinaryDockerRailwayNetlifyREST API

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.