A 3D picture wall designer application that allows users to digitally design and visualize picture wall layouts in a 3D environment. Built as a full-stack web application with modern technologies.
- 3D Design Interface: Interactive 3D canvas for designing picture walls using Three.js and React Three Fiber
- User Authentication: Secure JWT-based authentication with ASP.NET Core Identity
- Email Confirmation: Automated email confirmation system powered by Resend
- Design Management: Create, save, edit, and delete picture wall designs
- Screenshot Capture: Automatic thumbnail generation for designs using Cloudflare R2 storage
- Responsive UI: Modern, intuitive interface built with Next.js and TypeScript
- Framework: Next.js 15.5.5 with App Router
- Language: TypeScript
- 3D Graphics: React Three Fiber, Three.js 0.180.0
- UI Library: @react-three/drei for 3D helpers
- Testing: Jest 29.7.0 with React Testing Library
- Framework: ASP.NET Core Web API (.NET 9)
- Database: PostgreSQL with Entity Framework Core 9.0
- Authentication: ASP.NET Core Identity with JWT tokens
- Testing: xUnit 2.4.2 with Moq
- Email: Resend API
- File Storage: Cloudflare R2
- Hosting: Railway (both frontend and backend)
- CI/CD: GitHub Actions
- Containerization: Docker
- Purpose: User registration confirmation emails
- Dashboard: resend.com/emails
- Configuration:
Email:ApiKeyin Railway environment variables - Required Variables:
Email:ApiKey: Resend API keyEmail:FromEmail: Sender email (e.g.,onboarding@resend.dev)Email:FromName: Sender display name (e.g.,Atelje)
- Testing: Register a new user and verify confirmation email delivery
- Purpose: Design screenshot thumbnails and storage
- Dashboard: Cloudflare Dashboard β R2
- Configuration:
R2:*variables in Railway environment variables - Bucket Name:
atelje-screenshots - Public URL:
https://pub-20ea7aa8a86f43f4be960730b132a87f.r2.dev - Required Variables:
R2:AccountId: Cloudflare account IDR2:AccessKey: R2 access keyR2:SecretKey: R2 secret keyR2:BucketName: Bucket nameR2:PublicUrl: Public bucket URL
- Testing: Create a new design and verify screenshot thumbnail appears
- Purpose: Primary data storage (users, designs, authentication)
- Provider: Railway PostgreSQL
- Configuration: Automatically detected via Railway environment variables
- Variables:
PGHOST,PGPORT,PGDATABASE,PGUSER,PGPASSWORD - Local Alternative: Local PostgreSQL installation (see installation steps)
Before you begin, ensure you have the following installed:
- Node.js: v18.18.0+ or v20.0.0+ (v23.x recommended)
- .NET SDK: 9.0.x
- PostgreSQL: 14+ (local installation or Railway database)
- IDE:
- JetBrains Rider (recommended for backend)
- Visual Studio Code (recommended for frontend)
For Node.js:
# Using Homebrew
brew install node
# Or using nvm (recommended)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 23
nvm use 23For .NET:
# Using Homebrew
brew install --cask dotnet-sdkFor PostgreSQL:
# Using Homebrew
brew install postgresql@14
brew services start postgresql@14git clone <your-repository-url>
cd ateljecd backendUsing Rider:
- Open
backend.slnin Rider - Right-click the solution β "Restore NuGet Packages"
- Build β Build Solution
Using CLI:
dotnet restore backend.sln
dotnet build backend.slnOption A: Local PostgreSQL Database
Create a local PostgreSQL database:
# Connect to PostgreSQL
psql postgres
# Create database
CREATE DATABASE atelje_db;
# Create user (optional)
CREATE USER atelje_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE atelje_db TO atelje_user;
# Exit
\qUpdate backend/Atelje/appsettings.Development.json:
{
"ConnectionStrings": {
"TestDatabase": "Host=localhost;Database=atelje_db;Username=atelje_user;Password=your_password"
}
}Option B: Railway Database
The application automatically detects Railway database environment variables:
PGHOSTPGPORTPGDATABASEPGUSERPGPASSWORD
No additional configuration needed if these are set.
Store sensitive configuration in user secrets (NOT in appsettings.json):
Using Rider:
- Right-click
Atelje.csprojβ "Manage User Secrets"
Using CLI:
cd backend/Atelje
dotnet user-secrets init
dotnet user-secrets set "Jwt:Key" "your-super-secret-jwt-key-min-32-chars"
dotnet user-secrets set "Jwt:Issuer" "http://localhost:5225"
dotnet user-secrets set "Jwt:Audience" "http://localhost:3000"
dotnet user-secrets set "Email:ApiKey" "your-resend-api-key"Required User Secrets:
Jwt:Key: Secret key for JWT token generation (minimum 32 characters)Jwt:Issuer: Backend URL (e.g.,http://localhost:5225)Jwt:Audience: Frontend URL (e.g.,http://localhost:3000)Email:ApiKey: Resend API key for email confirmation
Optional Cloudflare R2 Secrets (for screenshot uploads):
R2:AccountIdR2:AccessKeyR2:SecretKeyR2:BucketNameR2:PublicUrl
The application automatically applies migrations on startup, but you can run them manually:
cd backend/Atelje
dotnet ef database updateOr using Rider:
- Tools β Entity Framework Core β Update Database
Using Rider:
- Select "http" or "https" run configuration
- Click the green play button
Using CLI:
cd backend/Atelje
dotnet runThe API will be available at:
- HTTP:
http://localhost:5225 - HTTPS:
https://localhost:7020
API Documentation (Scalar):
- Development:
http://localhost:5225/scalar/v1
cd frontend # From project rootnpm installNote: You may see EBADENGINE warnings for Node v23 - these are safe to ignore. Jest and other packages work correctly on Node v23.
Create a .env.local file in the frontend directory:
cp .env.local.example .env.localEdit .env.local:
NEXT_PUBLIC_API_URL=http://localhost:5225For Production:
NEXT_PUBLIC_API_URL=https://your-backend-url.railway.appIf the backend API has changed, regenerate the TypeScript API client:
# Ensure backend is running first
npm run generate:apiThis generates TypeScript types and API functions from the OpenAPI specification.
Development mode:
npm run devThe app will be available at http://localhost:3000
Production build:
npm run build
npm run startRun all tests:
cd backend
dotnet testRun with detailed output:
dotnet test --verbosity detailedExclude local-only tests (useful for CI):
dotnet test --filter "Category!=LocalOnly"In Rider:
- View β Tool Windows β Unit Tests
- Right-click β Run All Tests
Run all tests:
cd frontend
npm testWatch mode:
npm run test:watchRun with coverage:
npm test -- --coverageatelje/
βββ backend/
β βββ Atelje/ # Main API project
β β βββ Controllers/ # API endpoints
β β βββ Services/ # Business logic
β β βββ Data/ # Database context
β β βββ Models/ # Entity models
β β βββ DTOs/ # Data transfer objects
β β βββ Migrations/ # EF Core migrations
β β βββ Program.cs # Application entry point
β βββ Atelje.Tests/ # xUnit test project
β βββ backend.sln # Solution file
β βββ Dockerfile # Backend container config
β
βββ frontend/
β βββ src/
β β βββ app/ # Next.js App Router pages
β β βββ api/ # Generated API client
β β βββ components/ # React components
β β βββ contexts/ # React contexts (Auth, Modal)
β β βββ features/ # Feature-based modules
β β βββ hooks/ # Custom React hooks
β βββ public/ # Static assets
β βββ jest.config.mjs # Jest configuration
β βββ next.config.ts # Next.js configuration
β βββ package.json # Frontend dependencies
β
βββ TESTING_GUIDE.md # Frontend testing guide
βββ BACKEND_TESTING_GUIDE.md # Backend testing guide
βββ README.md # This file
| Variable | Required | Description | Example |
|---|---|---|---|
Jwt:Key |
Yes | JWT signing key (32+ chars) | your-super-secret-key-here |
Jwt:Issuer |
Yes | JWT token issuer | http://localhost:5225 |
Jwt:Audience |
Yes | JWT token audience | http://localhost:3000 |
Email:ApiKey |
Yes | Resend API key | re_xxxxxxxxx |
Email:FromEmail |
Yes | Sender email address | noreply@atelje.app |
Email:FromName |
Yes | Sender name | Atelje |
R2:AccountId |
No | Cloudflare R2 account ID | - |
R2:AccessKey |
No | Cloudflare R2 access key | - |
R2:SecretKey |
No | Cloudflare R2 secret key | - |
R2:BucketName |
No | Cloudflare R2 bucket name | - |
R2:PublicUrl |
No | Public R2 bucket URL | - |
The backend automatically detects these Railway variables:
| Variable | Description |
|---|---|
PGHOST |
PostgreSQL host |
PGPORT |
PostgreSQL port (default: 5432) |
PGDATABASE |
Database name |
PGUSER |
Database username |
PGPASSWORD |
Database password |
| Variable | Required | Description | Example |
|---|---|---|---|
NEXT_PUBLIC_API_URL |
Yes | Backend API URL | http://localhost:5225 |
Both frontend and backend are configured for Railway deployment:
Backend:
- Uses the included
Dockerfile - Automatically applies migrations on startup
- Detects Railway PostgreSQL environment variables
Frontend:
- Configured in
package.jsonbuild script - Uses Next.js standalone output
- Requires
NEXT_PUBLIC_API_URLenvironment variable
The backend includes a health check endpoint:
GET /health
Returns database connection status and overall application health.
- Frontend Testing Guide - Jest and React Testing Library setup
- Backend Testing Guide - xUnit and integration testing
This is a student capstone project. For questions or issues, please contact the development team.
MIT License
Copyright (c) 2025 Andrea WingΓ₯rdh, Jennie Westerlund, Josefine Ahlstrand
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
- Development Team: Andy, Josefine, Jennie
- Project Duration: 5 weeks
- Institution: YRGO
- β
Check database connection string in
appsettings.Development.json - β
Verify PostgreSQL is running:
pg_isready - β
Check user secrets are configured:
dotnet user-secrets list - β Review logs for specific error messages
- β
Verify backend is running on
http://localhost:5225 - β
Check
.env.localhas correctNEXT_PUBLIC_API_URL - β Check browser console for CORS errors
- β
Verify backend CORS is configured for
http://localhost:3000
- β
Delete
Migrationsfolder and rundotnet ef migrations add InitialCreate - β
Drop database and recreate:
dotnet ef database dropthendotnet ef database update - β Check connection string format is correct
- β Build β Build Solution
- β Right-click solution β Restore NuGet Packages
- β Invalidate Caches: File β Invalidate Caches β Invalidate and Restart
- β Check Resend API key is set in user secrets
- β
Verify email configuration in
appsettings.json - β Check spam folder for confirmation emails
- β Review backend logs for email sending errors
When running in development mode, interactive API documentation is available:
- Scalar UI:
http://localhost:5225/scalar/v1 - OpenAPI Spec:
http://localhost:5225/openapi/v1.json
Quick start for local development:
# Terminal 1 - Backend
cd backend/Atelje
dotnet user-secrets set "Jwt:Key" "your-super-secret-jwt-key-at-least-32-characters"
dotnet user-secrets set "Jwt:Issuer" "http://localhost:5225"
dotnet user-secrets set "Jwt:Audience" "http://localhost:3000"
dotnet user-secrets set "Email:ApiKey" "your-resend-key"
dotnet run
# Terminal 2 - Frontend
cd frontend
npm install
echo "NEXT_PUBLIC_API_URL=http://localhost:5225" > .env.local
npm run devOpen http://localhost:3000 and start designing! π¨