Technical documentation for developers
Ratio Tuta is built with modern web technologies for performance, scalability, and developer experience.
ratio-tuta/
├── src/
│ ├── app/ # Next.js 15 App Router
│ │ ├── api/ # API endpoints
│ │ ├── auth/ # Authentication pages
│ │ ├── dashboard/ # Admin dashboard
│ │ ├── cash-register/ # POS interface
│ │ └── docs/ # Documentation
│ ├── components/ # React components
│ │ ├── admin-zone/ # Admin UI components
│ │ ├── cash-register/ # POS components
│ │ └── ui/ # Reusable UI elements
│ ├── lib/ # Utility libraries
│ │ ├── api-client.ts # CSRF-enabled API wrapper
│ │ ├── auth.ts # Authentication helpers
│ │ ├── csrf.ts # CSRF token generation
│ │ ├── session.ts # Session management
│ │ └── prisma.ts # Database client
│ └── hooks/ # Custom React hooks
├── prisma/
│ └── schema.prisma # Database schema
├── lib/ # Shared backend utilities
├── public/ # Static assets
└── messages/ # i18n translations/app/apiuseCsrfToken)💡 Architecture Principle: Architecture Principle: We follow a pragmatic approach - use server components for data fetching, client components for interactivity, and API routes for mutations.
PostgreSQL database managed with Prisma ORM for type-safe queries and migrations.
model User {
id String @id @default(cuid())
email String @unique
emailHmac String @unique // HMAC for lookups
emailEncrypted String // AES-256-GCM encrypted
passwordHash String
firstName String
lastName String
role Role @default(USER)
emailVerified Boolean @default(false)
avatarUrl String?
createdAt DateTime @default(now())
}Security: Emails are encrypted (AES-256-GCM) and HMAC-indexed for secure lookups.
model Team {
id String @id @default(cuid())
name String
ownerId String
members TeamMember[]
places Place[]
items Item[]
plan SubscriptionPlan @default(FREE)
}
model TeamMember {
id String @id @default(cuid())
teamId String
userId String
role TeamRole @default(MEMBER)
}Multi-tenancy: Team-based isolation for security and data separation.
model Place {
id String @id @default(cuid())
teamId String
name String
address1 String?
city String?
country String?
timezone String?
currency String @default("EUR")
isActive Boolean @default(true)
receipts Receipt[]
createdAt DateTime @default(now())
}model Item {
id String @id @default(cuid())
teamId String
name String
sku String?
price Float
pricePaid Float?
taxRateBps Int // Basis points (100 = 1%)
imageUrl String
measurementType MeasurementType @default(PCS)
stockQuantity Float @default(0)
categoryId String?
isActive Boolean @default(true)
}Measurement Types: PCS, WEIGHT, LENGTH, VOLUME, AREA
model Receipt {
id String @id @default(cuid())
placeId String
userId String
totalAmount Float
taxAmount Float
amountGiven Float
changeAmount Float
paymentOption PaymentOption
lineItems ReceiptLineItem[]
createdAt DateTime @default(now())
}
model ReceiptLineItem {
id String @id @default(cuid())
receiptId String
itemId String
quantity Float
unitPrice Float
totalPrice Float
taxRateBps Int
}// Run migrations
npm run prisma:migrate
// Generate Prisma Client
npm run prisma:generate
// Open Prisma Studio (GUI)
npm run prisma:studio
// Reset database (development only!)
npm run prisma:reset⚠️ Security: All database queries use parameterized statements via Prisma. Email addresses are encrypted at rest and HMAC-indexed for secure lookups.
RESTful API built with Next.js 15 API Routes. All endpoints require authentication unless marked as public.
POST /api/loginLogin with email & password. Returns user data and sets session cookie.
// Request
{ "email": "user@example.com", "password": "***", "remember": true }
// Response
{ "id": "...", "email": "...", "role": "USER" }POST /api/register/selfRegister new user account. Sends verification email.
// Request
{ "name": "John Doe", "email": "...", "password": "...", "teamName": "..." }
// Response
{ "message": "Account created. Please verify your email." }POST /api/logoutEnd session and clear cookies. Requires CSRF token.
| Endpoint | Method | Description | CSRF |
|---|---|---|---|
/api/users/me | GET | Get current user | ❌ |
/api/users/me | PATCH | Update profile | ✅ |
/api/users/me | DELETE | Delete account | ✅ |
/api/places | GET | List places | ❌ |
/api/places | POST | Create place | ✅ |
/api/places/[id] | PATCH | Update place | ✅ |
/api/places/[id] | DELETE | Delete place | ✅ |
/api/items | GET | List items | ❌ |
/api/items | POST | Create item | ✅ |
/api/receipts | POST | Create receipt | ✅ |
Frontend uses a CSRF-enabled API wrapper:
import { api, ApiError } from '@/lib/api-client'
// GET request (no CSRF needed)
const data = await api.get('/api/places')
// POST request (CSRF auto-included)
const newPlace = await api.post('/api/places', {
name: 'New Store',
currency: 'EUR'
})
// PATCH request
const updated = await api.patch('/api/places/123', {
name: 'Updated Name'
})
// DELETE request
await api.delete('/api/places/123')
// Error handling
try {
await api.post('/api/places', data)
} catch (err) {
if (err instanceof ApiError) {
console.error(err.message, err.status)
}
}✅ CSRF Protection: All POST/PATCH/PUT/DELETE requests automatically include a CSRF token via the X-CSRF-Token header.
Comprehensive security implementation following OWASP guidelines and industry best practices.
Implementation: Stateless HMAC-based tokens tied to user sessions
randomValue.hmac(randomValue:userId)// Backend validation
import { requireCsrfToken } from '@lib/csrf'
export async function POST(req: Request) {
const session = await getSession()
requireCsrfToken(req, session) // Throws if invalid
// ... handle request
}// next.config.ts
headers: [
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'geolocation=(), microphone=()' },
{
key: 'Content-Security-Policy',
value: "default-src 'self'; img-src 'self' data: https://*.amazonaws.com;"
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
}
]All security-sensitive actions are logged:
import { logAudit } from '@lib/logger'
await logAudit({
action: 'user.login.success',
status: 'SUCCESS',
userId: user.id,
metadata: { ip: req.ip }
})🔒 Security Checklist: Never commit secrets (.env), always use HTTPS in production, rotate JWT secrets regularly, monitor audit logs for suspicious activity.
Deploy to production with Vercel, PostgreSQL, and AWS services.
Required environment variables (see .env.example):
# Database
DATABASE_URL="postgresql://..."
# Sessions
SESSION_SECRET="strong-random-secret-min-32-chars"
# AWS S3 (file uploads)
AWS_ACCESS_KEY_ID="..."
AWS_SECRET_ACCESS_KEY="..."
AWS_REGION="us-east-1"
AWS_S3_BUCKET="ratio-tuta-files"
# Email (Resend)
RESEND_API_KEY="re_..."
RESEND_FROM_EMAIL="noreply@yourdomain.com"
# Redis (rate limiting)
UPSTASH_REDIS_REST_URL="https://..."
UPSTASH_REDIS_REST_TOKEN="..."
# App
NEXT_PUBLIC_APP_URL="https://yourdomain.com"
NODE_ENV="production"# Use Vercel Postgres, Supabase, or any PostgreSQL provider
# Run migrations
npx prisma migrate deploy# Create Redis database at upstash.com
# Copy REST URL and token to env vars# Install Vercel CLI
npm i -g vercel
# Deploy
vercel --prod
# Or connect GitHub repo for auto-deploys🚀 Performance: Next.js automatically optimizes your app with code splitting, image optimization, and edge caching. Vercel provides global CDN distribution.