Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MediaProject

MediaProject is a full-stack social platform for discovering, tracking, and discussing movies and books. It was built as a university MVP and combines media discovery, personal libraries, community features, real-time messaging, recommendations, and import tools in one application.

Project status: Release candidate. Backend, frontend, lint, and production browser validation have passed locally; public deployment and operational sign-off remain pending.

The ordered production checklist, migration sequence, smoke tests, and rollback procedure are documented in docs/PRODUCTION_RUNBOOK.md.

Highlights

  • Discover movies through TMDB and books through Google Books.
  • Track watched/read progress, ratings, favorites, plans, and personal notes.
  • Import Letterboxd data through a staged preview, confirmation, and rollback workflow.
  • Publish reviews and media quotes with likes, replies, reports, and moderation.
  • Follow or block users and explore personalized social feeds.
  • Chat in real time with rich movie, book, GIF, reply, reaction, shared pinned-message, message edit/delete, typing indicators, conversation search, archive, and per-conversation notification mute support.
  • Search movies, books, people, and public community lists from one keyboard-friendly global search, ranked by textual relevance and community signals.
  • Invite an eligible friend to a Buddy Watch or Buddy Read session, accept or decline invitations, schedule the session, compare both participants' progress, and complete it together. A unified personal schedule combines upcoming Buddy plans and joined club events without per-club client requests, supports bulk calendar export, and gives each participant an independent 15-minute, one-hour, one-day, or disabled reminder preference.
  • Create public or private movie, book, and mixed-media clubs, discover public communities, join or leave them, browse member rosters, nominate media, vote on candidates, let club leadership confirm the shared selection, and discuss that selection through spoiler-protected posts and replies. Club leaders can also schedule online, in-person, or hybrid watch/read events with RSVP states, cancellation notices, and configurable reminders. Private invitations expire after seven days and can be accepted from the club hub; owners can appoint moderators, while authorized club leadership can remove members without erasing the discussion history they contributed. Owners can update club settings and use a reversible archive mode that preserves history while revoking pending invitations, cancelling future events, and preventing new activity.
  • Use recommendation, matching, quiz, badge, streak, and leaderboard features.
  • Try a Stripe-hosted Test Mode Premium subscription flow with signed webhook activation, Event ID idempotency, and no card data entering MediaProject.
  • Manage users, badges, reports, and moderation actions from an admin area.
  • Generate selected media content through a Groq-backed AI integration.

Technology stack

Backend

  • ASP.NET Core 8 Web API
  • Entity Framework Core 8 and SQL Server
  • JWT Bearer authentication and role-based authorization
  • Email OTP multi-factor authentication for administrator accounts
  • Rotating, server-revocable refresh sessions with hashed token storage
  • SignalR for real-time communication
  • Resilient typed HTTP clients for TMDB, Google Books, Groq, and Resend
  • Stripe Test Mode Checkout with backend-owned Price IDs and signed webhooks
  • Provider-based quiz session storage with optional Redis scale-out
  • FluentValidation
  • BCrypt password hashing
  • Swagger / OpenAPI

Frontend

  • React 19 and TypeScript
  • Vite 8 and Tailwind CSS 4
  • TanStack Query
  • React Router
  • Zustand
  • React Hook Form and Zod
  • Axios and the SignalR JavaScript client

Architecture

The backend follows a layered architecture:

MediaProject.Domain
        ↑
MediaProject.Application
        ↑
MediaProject.Infrastructure
        ↑
MediaProject.API
  • Domain contains entities and shared domain types.
  • Application contains DTOs, validation, service contracts, and repository contracts.
  • Infrastructure implements persistence, external API clients, and application services.
  • API exposes HTTP endpoints, authentication, rate limiting, middleware, Swagger, and SignalR hubs.
  • Frontend is organized around application features and route-level code splitting.

Local development

Prerequisites

  • .NET 8 SDK
  • SQL Server or SQL Server Express
  • Node.js 24 and npm
  • TMDB API key
  • Google Books API key
  • Groq API key
  • Optional Resend account and verified sending domain for local email testing
  • Optional GIPHY browser API key for GIF search
  • Optional Redis instance for multi-instance quiz sessions
  • Optional Stripe Test Mode account and Stripe CLI for Premium checkout testing

1. Configure backend secrets

The API project uses .NET user secrets for sensitive local configuration:

dotnet user-secrets set "JwtSettings:Secret" "use-a-random-secret-with-at-least-32-bytes" --project MediaProject.API
dotnet user-secrets set "TmdbSettings:ApiKey" "your-tmdb-api-key" --project MediaProject.API
dotnet user-secrets set "GoogleBooksSettings:ApiKey" "your-google-books-api-key" --project MediaProject.API
dotnet user-secrets set "GroqSettings:ApiKey" "your-groq-api-key" --project MediaProject.API

The repository intentionally contains no database connection string. Configure ConnectionStrings:DefaultConnection through user secrets for local development:

dotnet user-secrets set "ConnectionStrings:DefaultConnection" "your-connection-string" --project MediaProject.API

Email confirmation and password recovery are disabled by default in local development. To exercise the complete account flow with Resend, verify a sending domain first and configure:

dotnet user-secrets set "Email:Enabled" "true" --project MediaProject.API
dotnet user-secrets set "Email:ApiKey" "re_your-resend-api-key" --project MediaProject.API
dotnet user-secrets set "Email:FromEmail" "auth@updates.example.com" --project MediaProject.API
dotnet user-secrets set "Email:SupportEmail" "support@example.com" --project MediaProject.API
dotnet user-secrets set "Email:FromName" "MediaProject" --project MediaProject.API
dotnet user-secrets set "Email:FrontendBaseUrl" "http://localhost:5173" --project MediaProject.API
dotnet user-secrets set "Auth:AccountSecurity:RequireConfirmedEmail" "true" --project MediaProject.API
dotnet user-secrets set "Auth:AccountSecurity:RequireAdminMfa" "true" --project MediaProject.API

Resend requires SPF and DKIM DNS records for the sending domain. A dedicated subdomain such as updates.example.com keeps application mail isolated from the root domain. The API stores only SHA-256 hashes of verification and reset tokens, makes every token single-use, expires verification links after 24 hours and reset links after 30 minutes, and sends requests with an idempotency key. Forgot-password and resend-verification responses are deliberately generic so an email address cannot be enumerated.

For a public deployment, provide the same values through the platform secret manager or environment variables:

Email__Enabled=true
Email__ApiKey=re_...
Email__FromEmail=auth@updates.example.com
Email__SupportEmail=support@example.com
Email__FromName=MediaProject
Email__FrontendBaseUrl=https://app.example.com
Auth__AccountSecurity__RequireConfirmedEmail=true
Auth__AccountSecurity__RequireAdminMfa=true

Production startup rejects disabled or incomplete email configuration, an unconfirmed-email or administrator-MFA policy that is turned off, and loopback or non-HTTPS frontend URLs. Administrator password login creates no session until the single-use six-digit email code is verified. Codes expire after ten minutes by default, are stored only as keyed hashes, and are locked after five failed attempts. Do not place the API key in appsettings.json or a frontend VITE_ variable.

The current release is intentionally limited to one API replica. Keep Deployment__Mode=SingleInstance and configure the hosting platform's minimum and maximum API replica count to 1. Production startup rejects any other mode because rate-limit counters, authenticated-user security cache invalidation, and SignalR connection state are currently process-local. Do not enable horizontal auto-scaling until distributed rate limiting, a distributed security-state cache, and a SignalR backplane have been configured together.

Quiz sessions use a bounded, thread-safe in-process provider by default, which keeps local development dependency-free. Multi-instance deployments should select the Redis provider and configure its connection string outside source control:

dotnet user-secrets set "Cache:QuizSessions:Provider" "Redis" --project MediaProject.API
dotnet user-secrets set "ConnectionStrings:Redis" "localhost:6379,abortConnect=false" --project MediaProject.API

The Redis identity needs GET, SET, DEL, PTTL, TIME, and script execution permissions for the configured mediaproject:quiz-session: key prefix.

Stripe Test Mode Premium setup, Price ID configuration, webhook listener, test card, and migration instructions are documented in docs/STRIPE_TEST_MODE.md. The integration rejects sk_live_ keys and never accepts card fields in MediaProject.

External HTTP clients use centrally configured total/attempt timeouts, bounded connection pools, transient retries for idempotent TMDB and Google Books reads, and circuit breakers. Groq requests are never automatically retried because a POST may consume quota even when its response is lost. Defaults are stored in ExternalHttp, while provider-specific total timeouts are stored in each provider section of appsettings.json.

Reverse-proxy processing is disabled by default. Production deployments behind Nginx, IIS, a cloud load balancer, or another proxy must enable ReverseProxy and list only infrastructure-controlled proxy IP addresses or CIDR networks. Forwarded headers are processed before HTTPS redirection, authentication, rate limiting, logging, and session IP auditing. Never trust arbitrary forwarded headers from the public internet.

2. Create the database

dotnet ef database update --project MediaProject.Infrastructure --startup-project MediaProject.API

3. Run the API

dotnet run --project MediaProject.API --launch-profile https

Swagger is available at https://localhost:7119/swagger in the Development environment.

Health and diagnostics

The API exposes two anonymous, rate-limit-exempt health endpoints for hosting platforms and container orchestrators:

  • GET /health/live confirms that the API process is running.
  • GET /health/ready confirms that the API, SQL Server connection, and the configured quiz-session store are ready to serve requests.

Both endpoints return a compact JSON payload with status, UTC timestamp, duration, trace ID, and individual check results. Internal exceptions and connection details are never included in the response. Normal API responses also include an X-Trace-Id header, and requests are written to structured application logs without query strings or request bodies.

4. Run the frontend

Copy-Item MediaProject.Frontend/.env.example MediaProject.Frontend/.env.local
Set-Location MediaProject.Frontend
npm ci
npm run dev

The example frontend configuration targets the local HTTPS API and SignalR hub. Add VITE_GIPHY_API_KEY only to .env.local when GIF search is needed. Values prefixed with VITE_ are embedded into the browser bundle and are not secrets; restrict browser keys by allowed origin and quota in the provider dashboard. Production builds fail before bundling when API or SignalR URLs are missing, malformed, contain credentials/query data, or do not use HTTPS.

Authentication sessions

Access tokens are kept only in frontend runtime memory and are never persisted to Local Storage or Session Storage. A random refresh token is stored in a HttpOnly, Secure, scoped cookie, while the database stores only its SHA-256 hash. Refresh operations rotate the token atomically; replaying a revoked token revokes the user's remaining active sessions. Refresh and logout endpoints also require a custom CSRF-protection header.

The refresh cookie defaults to SameSite=Lax in production. Development uses SameSite=None because the Vite frontend and HTTPS API run on different schemes/origins. Production deployments should keep the frontend and API on the same site where possible and must use HTTPS.

Quality checks

dotnet build MediaProject.sln --configuration Release
dotnet test MediaProject.sln --configuration Release
npm.cmd --prefix MediaProject.Frontend run lint
npm.cmd --prefix MediaProject.Frontend run build
npm.cmd --prefix MediaProject.Frontend run test:e2e
npm.cmd --prefix MediaProject.Frontend run test:e2e:production

GitHub Actions first runs scripts/check-repository-hygiene.sh. The check rejects generated output, IDE state, local environment files, logs, inline connection strings, and non-empty secret values in tracked configuration files. It then runs backend restore/build/tests and frontend install/lint checks. The Chromium suite is executed against a Vite production build served through vite preview, so route chunks, lazy imports, and production asset references are validated on pushes and pull requests targeting main.

To clean an existing Windows working tree that previously tracked generated files, run:

powershell -ExecutionPolicy Bypass -File scripts/clean-repository.ps1

Review the resulting Git deletions before committing.

The browser suite currently covers authentication and refresh sessions, social activity and moderation, movie/book detail actions, quiz hint and cooldown behavior, notification state transitions, direct messaging, private block-list management, and the complete Letterboxd preview/import/rollback workflow. Deque axe-core also checks representative anonymous and authenticated pages against automated WCAG 2 A/AA rules, while keyboard tests cover login and skip-navigation behavior. Route metadata tests verify document titles and live announcements, and a forced lazy-route failure verifies the branded recovery screen. Quiz and rollback dialogs support safe initial focus and Escape dismissal; mobile chat supports keyboard submission and exposes its message stream as an accessible live log.

Security notes

  • Secrets, connection strings, local environment files, IDE state, and build output are excluded from source control and Docker build contexts.
  • Startup fails when required secrets or the database connection are missing.
  • Production startup rejects wildcard or loopback hosts, placeholder/non-HTTPS CORS origins, CORS values containing paths/query data/credentials, and database connections using disabled encryption, trusted server certificates, persisted security information, or the high-privilege sa account.
  • Runtime database migration is disabled in production; schema changes are applied as an explicit, observable release step before the new API starts.
  • Passwords are hashed with BCrypt.
  • The API validates JWT issuer, audience, lifetime, signature, and user status.
  • Access tokens are memory-only; refresh tokens are hashed, rotated, revocable, and delivered through HttpOnly secure cookies.
  • Admin endpoints use role-based authorization.
  • Global, feature-specific, and stricter anonymous authentication rate limits are configured.
  • Repeated invalid passwords trigger an account-level temporary lockout.
  • Password changes and the "sign out all devices" action revoke refresh sessions and increment a server-validated security version, invalidating previously issued access tokens.
  • Users can inspect active refresh sessions, identify the current device, and revoke an individual device without affecting their other sessions.
  • AI-generated media content requires authentication, resolves movie/book context from canonical provider IDs, and has a configurable per-user daily quota.
  • Trusted reverse-proxy handling is opt-in and restricted to configured proxy addresses or networks.
  • Production responses use HSTS and restrictive browser security headers.
  • CORS uses an explicit origin allowlist.
  • Health responses do not expose exception or connection details.

Release and operations

Review SECURITY.md, configure production secrets and infrastructure outside source control, then run the complete release gate before deployment.

Run the complete local release gate from the repository root:

powershell -ExecutionPolicy Bypass -File scripts/verify-release.ps1

Post-release roadmap

  • Move runtime uploads to object storage and add SignalR scale-out before horizontally scaling the API.
  • Periodically review framework and dependency support windows, then plan the next LTS upgrade as a dedicated compatibility project.

License

No public license has been selected yet. All rights are reserved until a license is added.

About

No description, website, or topics provided.

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages