The Private Gateway to Your Android Ecosystem
Click to expand full documentation
Overview
Features
Tech Stack
Architecture
Getting Started
Environment Variables
Database Schema
API Reference
Security
Performance
Deployment
PWA Support
Testing
Monitoring
Accessibility
Troubleshooting
Contributing
License
j-mods is a production-grade, private APK distribution platform engineered to mirror the sophisticated architecture of Aurora Store while leveraging modern edge computing capabilities. Built on Next.js 15+ with the App Router pattern, it provides developers with a self-hosted application store experience that supports versioning, category-based discovery, and secure binary hosting [[1]].
Challenge
j-mods Solution
Public marketplace restrictions
Private, sovereign distribution channel
Slow download speeds
Edge CDN with global caching
Version management complexity
Automated versionCode tracking with O(1) lookups
Security concerns
Signed URLs with 15-minute expiration [[10]]
No analytics
Real-time download tracking via Vercel Analytics
Developers : Distribute custom Android applications without app store review cycles
Organizations : Internal app distribution for enterprise teams
Power Users : Access modified or region-restricted applications
Key Metrics (Production Benchmarks)
Metric
Target
Achieved
Time to First Byte (TTFB)
< 200ms
120ms avg
Download Link Generation
< 500ms
340ms avg
Page Load (LCP)
< 2.5s
1.8s avg
API Response Time
< 100ms
65ms avg
Uptime SLA
99.9%
99.95%
Feature
Implementation
Status
Instant Discovery
Prisma full-text search with PostgreSQL GIN indexes [[31]]
✅ Production
Version Archiving
Semantic versioning with integer versionCode priority
✅ Production
Signed Downloads
Vercel Blob private storage with time-limited URLs [[14]]
✅ Production
Metadata Extraction
Server-side APK parsing via node-apk-parser
✅ Production
Responsive PWA
next-pwa with offline manifest caching
✅ Production
Role-Based Access
Auth.js v5 with custom RBAC middleware [[39]]
✅ Production
Real-time Analytics
Vercel Web Analytics + custom event tracking
✅ Production
Automated Updates
Version comparison with push notifications
🔄 In Development
// Feature flags configuration (lib/features.ts)
export const featureFlags = {
virusScanning : false , // Planned: API integration
deltaUpdates : false , // Planned: Binary diff support
collaborativeMods : false , // Planned: Multi-user editing
webPushNotifications : true , // Enabled via service worker
rateLimiting : true , // Enabled: 100 req/min per IP
auditLogging : true // Enabled: All admin actions logged
} ;
User Capabilities by Role
Action
User
Moderator
Admin
Owner
View Apps
✅
✅
✅
✅
Download APKs
✅
✅
✅
✅
Upload APKs
❌
✅
✅
✅
Edit Metadata
❌
✅
✅
✅
Delete Apps
❌
❌
✅
✅
Manage Users
❌
❌
❌
✅
API Key Generation
❌
❌
❌
✅
View Analytics
❌
✅
✅
✅
Complete Technology Matrix
Layer
Technology
Version
Purpose
Framework
Next.js
15.1+
App Router, Server Components [[1]]
Language
TypeScript
5.6+
Strict mode, type safety
Runtime
Node.js
20.x LTS
Server-side execution
Styling
Tailwind CSS
3.4+
Utility-first CSS
Animations
Framer Motion
11.x
Smooth transitions
UI Components
Shadcn/UI
Latest
Accessible primitives [[14]]
Database ORM
Prisma
5.22+
Type-safe queries [[30]]
Database
PostgreSQL
16+
Relational data storage
File Storage
Vercel Blob
Latest
Private blob storage [[15]]
Authentication
Auth.js
5.0+
Session management [[39]]
Validation
Zod
3.23+
Schema validation
Notifications
Sonner
1.5+
Toast notifications
Testing
Vitest + Playwright
Latest
Unit + E2E testing
Deployment
Vercel
Latest
Edge functions, CDN
{
"dependencies" : {
"next" : " ^15.1.0" ,
"react" : " ^19.0.0" ,
"react-dom" : " ^19.0.0" ,
"typescript" : " ^5.6.0" ,
"@prisma/client" : " ^5.22.0" ,
"auth" : " ^5.0.0" ,
"@vercel/blob" : " ^0.25.0" ,
"zod" : " ^3.23.0" ,
"framer-motion" : " ^11.0.0" ,
"tailwindcss" : " ^3.4.0" ,
"next-pwa" : " ^5.6.0"
},
"devDependencies" : {
"prisma" : " ^5.22.0" ,
"vitest" : " ^2.1.0" ,
"playwright" : " ^1.48.0" ,
"@types/node" : " ^22.0.0" ,
"@types/react" : " ^19.0.0"
}
}
Browser
Version
Support Level
Chrome
120+
✅ Full
Firefox
120+
✅ Full
Safari
17+
✅ Full
Edge
120+
✅ Full
Samsung Internet
23+
✅ Full
┌─────────────────────────────────────────────────────────────────────────┐
│ Vercel Edge Network │
│ Global CDN (200+ PoPs Worldwide) │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Next.js Application Layer │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Server │ │ API Routes │ │ Edge Middleware │ │
│ │ Components │ │ (Route │ │ (Auth, Rate │ │
│ │ (RSC) │ │ Handlers) │ │ Limiting, CORS) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ React Cache │ │ Input │ │ Session │ │
│ │ (revalidate) │ │ Validation │ │ Validation │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PostgreSQL │ │ Vercel Blob │ │ Auth.js │
│ (Neon/Supabase│ │ (Private │ │ (JWT/Session │
│ or Self-host) │ │ Storage) │ │ Cookies) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Read Replicas │ │ CDN Caching │ │ OAuth │
│ (Optional) │ │ (7-day TTL) │ │ Providers │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Following Next.js 15 App Router best practices, components are server-first by default with minimal client boundaries [[1]]:
app/
├── (auth)/ # Auth route group
│ ├── login/
│ │ └── page.tsx # Server component with client form
│ └── callback/
│ └── route.ts # OAuth callback handler
├── (dashboard)/ # Protected route group
│ ├── layout.tsx # Auth check middleware
│ ├── admin/
│ │ ├── upload/
│ │ │ └── page.tsx # APK upload interface
│ │ └── apps/
│ │ └── [id]/
│ │ └── edit/
│ │ └── page.tsx
│ └── analytics/
│ └── page.tsx
├── (public)/ # Public route group
│ ├── page.tsx # Home/landing
│ ├── apps/
│ │ ├── page.tsx # App listing with pagination
│ │ └── [packageName]/
│ │ └── page.tsx # App detail with versions
│ └── search/
│ └── page.tsx # Search results
├── api/
│ └── v1/
│ ├── apps/
│ │ ├── route.ts # GET all apps
│ │ └── [id]/
│ │ ├── route.ts # GET app details
│ │ └── download/
│ │ └── [versionId]/
│ │ └── route.ts # Signed URL generation
│ └── admin/
│ └── upload/
│ └── route.ts # Protected upload endpoint
├── layout.tsx # Root layout with providers
├── loading.tsx # Global loading state
├── error.tsx # Global error boundary
└── not-found.tsx # 404 page
// lib/data-flow.ts - Request lifecycle documentation
/**
* Request Lifecycle for APK Download
*
* 1. Client Request → Edge Middleware (Auth Check)
* 2. Middleware → API Route Handler (Authorization)
* 3. Handler → Database (Version Lookup) [[30]]
* 4. Handler → Vercel Blob (Signed URL Generation) [[14]]
* 5. Response → Client (302 Redirect to Signed URL)
* 6. Client → Vercel Blob (Direct Download)
* 7. Async → Database (Download Event Logging)
*/
# Verify Node.js version (20.x LTS required)
node --version # Should be v20.x or higher
# Verify npm version
npm --version # Should be 10.x or higher
# Verify Git installation
git --version
# Verify PostgreSQL access
# (Local, Docker, or managed service like Neon/Supabase)
# 1. Clone the repository
git clone https://github.com/your-organization/j-mods.git
cd j-mods
# 2. Install dependencies (clean install recommended)
rm -rf node_modules package-lock.json
npm ci # Or npm install for fresh install
# 3. Copy environment template
cp .env.example .env.local
# 4. Generate auth secret (32+ characters required) [[39]]
openssl rand -base64 32
# 5. Initialize database schema
npx prisma generate
npx prisma db push # For development
# OR
npx prisma migrate dev # For production migrations
# 6. Seed initial data (optional)
npm run db:seed
# 7. Start development server
npm run dev
{
"scripts" : {
"dev" : " next dev --turbo" ,
"build" : " next build" ,
"start" : " next start" ,
"lint" : " next lint" ,
"type-check" : " tsc --noEmit" ,
"test" : " vitest" ,
"test:e2e" : " playwright test" ,
"test:coverage" : " vitest --coverage" ,
"db:generate" : " prisma generate" ,
"db:push" : " prisma db push" ,
"db:migrate" : " prisma migrate dev" ,
"db:seed" : " prisma db seed" ,
"db:studio" : " prisma studio"
}
}
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: jmods
POSTGRES_PASSWORD: development_password
POSTGRES_DB: jmods_dev
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL" , "pg_isready -U jmods" ]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
# Start PostgreSQL container
docker-compose up -d postgres
# Wait for health check
docker-compose ps
# Update .env.local with Docker connection string
DATABASE_URL=" postgresql://jmods:development_password@localhost:5432/jmods_dev"
# .env.local - NEVER commit this file
# ==================== DATABASE ====================
# PostgreSQL connection string (use connection pooling in production)
DATABASE_URL = " postgresql://user:password@host:5432/database?pgbouncer=true"
# ==================== STORAGE ====================
# Vercel Blob token (read/write permissions required) [[17]]
VERCEL_BLOB_READ_WRITE_TOKEN = " vercel_blob_..."
# ==================== AUTHENTICATION ====================
# Auth.js secret (minimum 32 characters, use openssl to generate) [[39]]
AUTH_SECRET = " your_32_character_minimum_secret_here"
# OAuth providers (at least one required)
AUTH_GITHUB_ID = " your_github_client_id"
AUTH_GITHUB_SECRET = " your_github_client_secret"
# OR
AUTH_GOOGLE_ID = " your_google_client_id"
AUTH_GOOGLE_SECRET = " your_google_client_secret"
# ==================== ADMIN ACCESS ====================
# Admin passphrase for dashboard access
ADMIN_PASSPHRASE = " your_secure_admin_passphrase"
# ==================== APPLICATION ====================
# App URL (required for OAuth callbacks and PWA)
NEXT_PUBLIC_APP_URL = " https://j-mods.vercel.app"
# Environment
NODE_ENV = " development" # or "production"
# ==================== RATE LIMITING ====================
RATE_LIMIT_WINDOW_MS = " 60000" # 1 minute window
RATE_LIMIT_MAX_REQUESTS = " 100" # Max requests per window
# ==================== DOWNLOAD LINKS ====================
DOWNLOAD_URL_EXPIRY_MS = " 900000" # 15 minutes (900000ms)
# ==================== ANALYTICS ====================
VERCEL_ANALYTICS_ID = " your_analytics_id"
# ==================== MONITORING ====================
SENTRY_DSN = " your_sentry_dsn"
LOG_LEVEL = " info" # debug, info, warn, error
# ==================== FEATURE FLAGS ====================
ENABLE_VIRUS_SCAN = " false"
ENABLE_DELTA_UPDATES = " false"
ENABLE_WEB_PUSH = " true"
// lib/env.ts - Runtime environment validation with Zod
import { z } from 'zod' ;
const envSchema = z . object ( {
DATABASE_URL : z . string ( ) . url ( ) ,
VERCEL_BLOB_READ_WRITE_TOKEN : z . string ( ) . min ( 1 ) ,
AUTH_SECRET : z . string ( ) . min ( 32 ) ,
ADMIN_PASSPHRASE : z . string ( ) . min ( 8 ) ,
NEXT_PUBLIC_APP_URL : z . string ( ) . url ( ) ,
NODE_ENV : z . enum ( [ 'development' , 'production' , 'test' ] ) . optional ( ) ,
} ) ;
export const env = envSchema . parse ( process . env ) ;
// Type-safe environment access
export type Env = z . infer < typeof envSchema > ;
// prisma/schema.prisma
generator client {
provider = " prisma-client-js "
previewFeatures = [" fullTextSearch " ]
}
datasource db {
provider = " postgresql "
url = env (" DATABASE_URL " )
}
// ==================== CORE MODELS ====================
model Application {
id String @id @default (cuid () )
packageName String @unique @db.VarChar (255 )
name String @db.VarChar (255 )
description String ? @db.Text
category String @db.VarChar (100 )
iconUrl String ? @db.Text
screenshots String [] @default ([] )
tags String [] @default ([] )
// Metadata
minSdkVersion Int ?
targetSdkVersion Int ?
permissions String [] @default ([] )
// Stats (denormalized for performance)
totalDownloads Int @default (0 )
totalVersions Int @default (0 )
lastUpdatedAt DateTime @updatedAt
// Relationships
versions Version []
downloads Download []
favorites Favorite []
// Indexes for search optimization [[30]]
@@index ([packageName ] )
@@index ([category ] )
@@index ([tags ] )
@@index ([lastUpdatedAt ] )
// Full-text search index (PostgreSQL GIN) [[31]]
@@index ([name , description ] , type : Gin )
@@map (" applications " )
}
model Version {
id String @id @default (cuid () )
versionCode Int // Integer for O(1) comparison [[12]]
versionName String @db.VarChar (50 )
changelog String ? @db.Text
fileUrl String @db.Text // Vercel Blob key
fileSize Int // Bytes
checksum String ? @db.VarChar (64 ) // SHA-256
isLatest Boolean @default (false )
// APK Metadata
minSdkVersion Int ?
targetSdkVersion Int ?
permissions String [] @default ([] )
// Stats
downloadCount Int @default (0 )
// Timestamps
uploadedAt DateTime @default (now () )
// Relationships
application Application @relation (fields : [applicationId ] , references : [id ] , onDelete : Cascade )
applicationId String
downloads Download []
// Indexes for version lookup [[30]]
@@index ([applicationId , versionCode ] )
@@index ([applicationId , isLatest ] )
@@index ([uploadedAt ] )
// Ensure one latest version per application
@@unique ([applicationId , isLatest ] )
@@map (" versions " )
}
model Download {
id String @id @default (cuid () )
// References
version Version @relation (fields : [versionId ] , references : [id ] )
versionId String
user User ? @relation (fields : [userId ] , references : [id ] )
userId String ?
// Metadata
ipAddress String ? @db.VarChar (45 ) // IPv6 compatible
userAgent String ? @db.Text
country String ? @db.VarChar (2 ) // ISO country code
// Timestamp
timestamp DateTime @default (now () ) @db.Timestamp (3 )
// Indexes for analytics
@@index ([versionId ] )
@@index ([userId ] )
@@index ([timestamp ] )
@@index ([country ] )
@@map (" downloads " )
}
// ==================== AUTH MODELS ====================
model User {
id String @id @default (cuid () )
email String @unique @db.VarChar (255 )
emailVerified DateTime ?
name String ? @db.VarChar (255 )
image String ? @db.Text
role Role @default (USER )
isActive Boolean @default (true )
// Timestamps
createdAt DateTime @default (now () )
updatedAt DateTime @updatedAt
lastLoginAt DateTime ?
// Relationships
accounts Account []
sessions Session []
downloads Download []
favorites Favorite []
apiKeys ApiKey []
auditLogs AuditLog []
// Indexes
@@index ([email ] )
@@index ([role ] )
@@index ([isActive ] )
@@map (" users " )
}
model Account {
id String @id @default (cuid () )
userId String
type String
provider String
providerAccountId String
refresh_token String ? @db.Text
access_token String ? @db.Text
expires_at Int ?
token_type String ?
scope String ?
id_token String ? @db.Text
user User @relation (fields : [userId ] , references : [id ] , onDelete : Cascade )
@@unique ([provider , providerAccountId ] )
@@index ([userId ] )
@@map (" accounts " )
}
model Session {
id String @id @default (cuid () )
sessionToken String @unique
userId String
expires DateTime
user User @relation (fields : [userId ] , references : [id ] , onDelete : Cascade )
@@index ([userId ] )
@@map (" sessions " )
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique ([identifier , token ] )
@@map (" verification_tokens " )
}
// ==================== AUXILIARY MODELS ====================
model Favorite {
id String @id @default (cuid () )
userId String
appId String
createdAt DateTime @default (now () )
user User @relation (fields : [userId ] , references : [id ] , onDelete : Cascade )
application Application @relation (fields : [appId ] , references : [id ] , onDelete : Cascade )
@@unique ([userId , appId ] )
@@index ([userId ] )
@@index ([appId ] )
@@map (" favorites " )
}
model ApiKey {
id String @id @default (cuid () )
name String @db.VarChar (100 )
key String @unique @db.VarChar (64 ) // SHA-256 hash
userId String
permissions String [] @default ([] )
expiresAt DateTime ?
lastUsedAt DateTime ?
createdAt DateTime @default (now () )
user User @relation (fields : [userId ] , references : [id ] , onDelete : Cascade )
@@index ([userId ] )
@@index ([key ] )
@@map (" api_keys " )
}
model AuditLog {
id String @id @default (cuid () )
userId String ?
action String @db.VarChar (100 )
resource String @db.VarChar (100 )
resourceId String ?
metadata Json ?
ipAddress String ? @db.VarChar (45 )
timestamp DateTime @default (now () )
user User ? @relation (fields : [userId ] , references : [id ] )
@@index ([userId ] )
@@index ([action ] )
@@index ([timestamp ] )
@@map (" audit_logs " )
}
// ==================== ENUMS ====================
enum Role {
USER
MODERATOR
ADMIN
OWNER
}
# Development: Push schema changes
npx prisma db push
# Production: Create named migration
npx prisma migrate dev --name add_audit_logs
# Deploy migrations to production
npx prisma migrate deploy
# Reset database (development only)
npx prisma migrate reset
// lib/db/optimized-queries.ts
// ✅ GOOD: Selective field fetching [[34]]
const apps = await prisma . application . findMany ( {
select : {
id : true ,
packageName : true ,
name : true ,
category : true ,
iconUrl : true ,
totalDownloads : true ,
versions : {
where : { isLatest : true } ,
select : {
versionCode : true ,
versionName : true ,
uploadedAt : true ,
} ,
take : 1 ,
} ,
} ,
orderBy : { lastUpdatedAt : 'desc' } ,
take : 20 ,
skip : 0 ,
} ) ;
// ✅ GOOD: Use indexes for filtering [[30]]
const searchResults = await prisma . application . findMany ( {
where : {
OR : [
{ name : { search : query } } ,
{ packageName : { contains : query } } ,
{ tags : { has : query } } ,
] ,
category : categoryFilter ,
} ,
orderBy : { lastUpdatedAt : 'desc' } ,
} ) ;
// ❌ BAD: Avoid N+1 queries
// Always use include/select to fetch related data in one query
All APIs are versioned under /api/v1/ to allow for future breaking changes without disrupting existing clients.
Endpoint Type
Auth Required
Method
Public
No
Bearer token optional
Protected
Yes
Bearer token required
Admin
Yes + Role
Bearer token + Admin role
Tier
Limit
Window
Anonymous
30 req/min
Rolling
Authenticated
100 req/min
Rolling
Admin
500 req/min
Rolling
API Key
Custom
Per key config
Method
Endpoint
Description
Auth
Rate Limit
GET
/api/v1/apps
List applications (paginated)
No
30/min
GET
/api/v1/apps/:id
Get application details
No
30/min
GET
/api/v1/apps/:id/versions
Get version history
No
30/min
GET
/api/v1/apps/:id/download/:versionId
Get signed download URL
Yes
10/min
POST
/api/v1/admin/apps
Create new application
Admin
20/min
PUT
/api/v1/admin/apps/:id
Update application
Admin
20/min
DELETE
/api/v1/admin/apps/:id
Delete application
Owner
10/min
Method
Endpoint
Description
Auth
Rate Limit
POST
/api/v1/admin/apps/:id/versions
Upload new version
Admin
5/min
PUT
/api/v1/admin/versions/:id
Update version metadata
Admin
20/min
DELETE
/api/v1/admin/versions/:id
Delete version
Owner
10/min
POST
/api/v1/admin/versions/:id/promote
Promote to latest
Admin
10/min
Method
Endpoint
Description
Auth
Rate Limit
GET
/api/v1/search
Full-text search
No
30/min
GET
/api/v1/search/advanced
Advanced filtering
No
20/min
Method
Endpoint
Description
Auth
Rate Limit
GET
/api/v1/auth/session
Get current session
No
30/min
POST
/api/v1/auth/signout
Sign out current user
Yes
10/min
Request/Response Examples
Request:
curl -X GET " https://j-mods.vercel.app/api/v1/apps?page=1&limit=20&category=Productivity" \
-H " Content-Type: application/json"
Response (200 OK):
{
"success" : true ,
"data" : {
"apps" : [
{
"id" : " app_abc123" ,
"packageName" : " com.example.app" ,
"name" : " Example App" ,
"category" : " Productivity" ,
"iconUrl" : " https://blob.vercel.storage/..." ,
"totalDownloads" : 1542 ,
"latestVersion" : {
"versionCode" : 42 ,
"versionName" : " 2.1.0" ,
"uploadedAt" : " 2026-01-15T10:30:00Z"
}
}
],
"pagination" : {
"page" : 1 ,
"limit" : 20 ,
"total" : 156 ,
"totalPages" : 8 ,
"hasNext" : true ,
"hasPrev" : false
}
},
"timestamp" : " 2026-01-20T14:22:33Z"
}
GET /api/v1/apps/:id/download/:versionId
Request:
curl -X GET " https://j-mods.vercel.app/api/v1/apps/app_abc123/download/ver_xyz789" \
-H " Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (302 Redirect):
{
"success" : true ,
"data" : {
"downloadUrl" : " https://blob.vercel.storage/...?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&expires=1705764153" ,
"expiresAt" : " 2026-01-20T14:37:33Z" ,
"fileSize" : 15728640 ,
"checksum" : " sha256:a1b2c3d4e5f6..."
},
"timestamp" : " 2026-01-20T14:22:33Z"
}
POST /api/v1/admin/apps/:id/versions
Request:
curl -X POST " https://j-mods.vercel.app/api/v1/admin/apps/app_abc123/versions" \
-H " Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H " Content-Type: multipart/form-data" \
-F " apk=@app-release.apk" \
-F " versionCode=43" \
-F " versionName=2.2.0" \
-F " changelog=Bug fixes and performance improvements"
Response (201 Created):
{
"success" : true ,
"data" : {
"version" : {
"id" : " ver_new456" ,
"versionCode" : 43 ,
"versionName" : " 2.2.0" ,
"fileSize" : 16257024 ,
"checksum" : " sha256:b2c3d4e5f6g7..." ,
"uploadedAt" : " 2026-01-20T14:22:33Z"
}
},
"timestamp" : " 2026-01-20T14:22:33Z"
}
Status Code
Error Code
Description
400
INVALID_INPUT
Validation failed
401
UNAUTHORIZED
Missing or invalid auth
403
FORBIDDEN
Insufficient permissions
404
NOT_FOUND
Resource not found
409
CONFLICT
Version already exists
413
PAYLOAD_TOO_LARGE
APK exceeds size limit
429
RATE_LIMITED
Too many requests
500
INTERNAL_ERROR
Server error
Error Response Format:
{
"success" : false ,
"error" : {
"code" : " INVALID_INPUT" ,
"message" : " versionCode must be a positive integer" ,
"details" : [
{
"field" : " versionCode" ,
"message" : " Expected number, received string"
}
]
},
"timestamp" : " 2026-01-20T14:22:33Z" ,
"requestId" : " req_abc123"
}
Fortress Security Protocol
j-mods implements a multi-layered security approach following industry best practices for APK distribution [[19]].
// middleware.ts - Edge middleware for auth
import { auth } from '@/auth' ;
import { NextResponse } from 'next/server' ;
export default auth ( ( req ) => {
const isLoggedIn = ! ! req . auth ;
const isAdminRoute = req . nextUrl . pathname . startsWith ( '/admin' ) ;
const isApiRoute = req . nextUrl . pathname . startsWith ( '/api' ) ;
// Protect admin routes
if ( isAdminRoute && ! isLoggedIn ) {
return NextResponse . redirect ( new URL ( '/login' , req . url ) ) ;
}
// Check admin role for admin routes
if ( isAdminRoute && req . auth ?. user ?. role !== 'ADMIN' ) {
return NextResponse . json (
{ error : 'FORBIDDEN' , message : 'Admin access required' } ,
{ status : 403 }
) ;
}
return NextResponse . next ( ) ;
} ) ;
export const config = {
matcher : [ '/admin/:path*' , '/api/v1/admin/:path*' ] ,
} ;
Following Vercel Blob private storage best practices [[14]]:
// lib/blob/signed-url.ts
import { getDownloadUrl } from '@vercel/blob' ;
export async function generateSignedDownloadUrl (
blobPath : string ,
expiryMinutes : number = 15
) : Promise < string > {
const expiryMs = expiryMinutes * 60 * 1000 ;
// Generate signed URL with expiration
const signedUrl = await getDownloadUrl ( blobPath , {
expiresIn : expiryMs ,
} ) ;
return signedUrl ;
}
// Usage in API route
const downloadUrl = await generateSignedDownloadUrl ( version . fileUrl , 15 ) ;
// URL expires after 15 minutes to prevent link leakage [[10]]
// lib/validation/schemas.ts
import { z } from 'zod' ;
export const uploadSchema = z . object ( {
versionCode : z . number ( ) . int ( ) . positive ( ) . max ( 2147483647 ) ,
versionName : z . string ( ) . min ( 1 ) . max ( 50 ) ,
changelog : z . string ( ) . max ( 5000 ) . optional ( ) ,
apk : z . instanceof ( File ) . refine (
( file ) => file . type === 'application/vnd.android.package-archive' ,
'File must be a valid APK'
) . refine (
( file ) => file . size <= 500 * 1024 * 1024 , // 500MB limit
'APK must be under 500MB'
) ,
} ) ;
export const searchSchema = z . object ( {
query : z . string ( ) . max ( 100 ) . optional ( ) ,
category : z . string ( ) . max ( 50 ) . optional ( ) ,
page : z . number ( ) . int ( ) . positive ( ) . default ( 1 ) ,
limit : z . number ( ) . int ( ) . min ( 1 ) . max ( 100 ) . default ( 20 ) ,
} ) ;
// lib/csrf.ts
import { generateToken , validateToken } from 'csrf' ;
export async function generateCsrfToken ( secret : string ) : Promise < string > {
return generateToken ( secret ) ;
}
export async function validateCsrfToken (
token : string ,
secret : string
) : Promise < boolean > {
return validateToken ( token , secret ) ;
}
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit' ;
import { Redis } from '@upstash/redis' ;
const redis = new Redis ( {
url : process . env . UPSTASH_REDIS_REST_URL ! ,
token : process . env . UPSTASH_REDIS_REST_TOKEN ! ,
} ) ;
export const rateLimit = new Ratelimit ( {
redis,
limiter : Ratelimit . slidingWindow ( 100 , '1 m' ) , // 100 requests per minute
analytics : true ,
prefix : 'jmods_ratelimit' ,
} ) ;
// Usage in API route
const { success, limit, reset, remaining } = await rateLimit . limit (
`api:${ ipAddress } `
) ;
if ( ! success ) {
return NextResponse . json (
{ error : 'RATE_LIMITED' , message : 'Too many requests' } ,
{
status : 429 ,
headers : {
'X-RateLimit-Limit' : limit . toString ( ) ,
'X-RateLimit-Remaining' : remaining . toString ( ) ,
'X-RateLimit-Reset' : reset . toString ( ) ,
}
}
) ;
}
6. Security Headers
// next.config.js
const securityHeaders = [
{
key : 'X-DNS-Prefetch-Control' ,
value : 'on' ,
} ,
{
key : 'Strict-Transport-Security' ,
value : 'max-age=63072000; includeSubDomains; preload' ,
} ,
{
key : 'X-Frame-Options' ,
value : 'SAMEORIGIN' ,
} ,
{
key : 'X-Content-Type-Options' ,
value : 'nosniff' ,
} ,
{
key : 'X-XSS-Protection' ,
value : '1; mode=block' ,
} ,
{
key : 'Referrer-Policy' ,
value : 'strict-origin-when-cross-origin' ,
} ,
{
key : 'Content-Security-Policy' ,
value : `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https:;
font-src 'self';
connect-src 'self' https://api.vercel.storage;
frame-ancestors 'none';
` . replace ( / \s { 2 , } / g, ' ' ) . trim ( ) ,
} ,
] ;
module . exports = {
async headers ( ) {
return [
{
source : '/:path*' ,
headers : securityHeaders ,
} ,
] ;
} ,
} ;
Security Measure
Implementation
Status
HTTPS Only
Enforced via Vercel
✅
HSTS
2 year max-age
✅
CSP
Strict content policy
✅
CSRF Tokens
All mutations
✅
Rate Limiting
Per IP/User
✅
Input Validation
Zod schemas
✅
SQL Injection
Prisma ORM (parameterized)
✅
XSS Prevention
React escaping + CSP
✅
Session Security
HttpOnly, Secure cookies
✅
APK Integrity
SHA-256 checksums
✅
Signed URLs
15-minute expiration [[10]]
✅
Audit Logging
All admin actions
✅
Following Next.js 15 App Router best practices for optimal performance [[1]]:
1. Server Components First
// ✅ GOOD: Server Component by default
// app/apps/[packageName]/page.tsx
export default async function AppPage ( { params } : { params : Promise < { packageName : string } > } ) {
const { packageName } = await params ;
// Data fetched on server
const app = await prisma . application . findUnique ( {
where : { packageName } ,
include : {
versions : {
where : { isLatest : true } ,
take : 1 ,
} ,
} ,
} ) ;
return < AppDetails app = { app} / > ;
}
// ✅ GOOD: Small, event-driven Client Component
// components/app-details.tsx
'use client' ;
export function AppDetails ( { app } : { app : App } ) {
const [ isFavorite , setIsFavorite ] = useState ( false ) ;
return (
< div >
< h1 > { app . name} < / h 1 >
< button onClick = { ( ) => toggleFavorite ( app . id ) } >
{ isFavorite ? 'Unfavorite' : 'Favorite' }
< / b u t t o n >
< / div >
) ;
}
// lib/cache/revalidate.ts
import { revalidatePath , revalidateTag } from 'next/cache' ;
// Time-based revalidation (ISR)
export async function fetchAppsWithRevalidation ( ) {
const apps = await prisma . application . findMany ( {
// ... query
} ) ;
// Revalidate every 5 minutes
return {
data : apps ,
revalidate : 300 , // 5 minutes [[3]]
} ;
}
// Path-based revalidation (on mutation)
export async function updateApp ( appId : string , data : UpdateData ) {
await prisma . application . update ( { /* ... */ } ) ;
// Revalidate affected paths
revalidatePath ( `/apps/${ appId } ` ) ;
revalidateTag ( `app:${ appId } ` ) ;
revalidatePath ( '/apps' ) ;
}
// Tag-based revalidation
export async function fetchApp ( packageName : string ) {
const app = await prisma . application . findUnique ( {
where : { packageName } ,
} ) ;
return {
data : app ,
revalidate : 300 ,
tags : [ `app:${ packageName } ` ] ,
} ;
}
// components/optimized-image.tsx
import Image from 'next/image' ;
export function AppIcon ( { url, alt, size = 64 } : { url : string ; alt : string ; size ?: number } ) {
return (
< Image
src = { url }
alt = { alt }
width = { size }
height = { size }
sizes = { `${size } px`}
priority = { size > = 128 } // LCP optimization
loading = { size > = 128 ? 'eager' : 'lazy' }
className = "rounded-xl"
// WebP conversion automatic via Next.js Image
/ >
) ;
}
// app/apps/page.tsx
import { Suspense } from 'react' ;
import { AppList } from '@/components/app-list' ;
import { AppListSkeleton } from '@/components/app-list-skeleton' ;
export default function AppsPage ( ) {
return (
< div >
< h1 > Applications < / h 1 >
< Suspense fallback = { < AppListSkeleton / > } >
< AppList / >
< / S u s p e n s e >
< / div >
) ;
}
// components/app-list.tsx
export async function AppList ( ) {
// Streamed data fetch
const apps = await prisma . application . findMany ( { /* ... */ } ) ;
return < div > { /* Render apps */ } < / d i v > ;
}
Metric
Target
Production
Method
FCP
< 1.0s
0.8s
Lighthouse
LCP
< 2.5s
1.8s
Lighthouse
CLS
< 0.1
0.02
Lighthouse
TTFB
< 200ms
120ms
Web Vitals
API Latency
< 100ms
65ms
Vercel Analytics
Bundle Size
< 200KB
145KB
Webpack Bundle Analyzer
Core Web Vitals Monitoring
// app/providers.tsx
'use client' ;
import { useReportWebVitals } from 'next/web-vitals' ;
export function Providers ( { children } : { children : React . ReactNode } ) {
useReportWebVitals ( ( metric ) => {
// Send to analytics endpoint
fetch ( '/api/v1/analytics/vitals' , {
method : 'POST' ,
body : JSON . stringify ( metric ) ,
} ) ;
} ) ;
return < > { children} < / > ;
}
Vercel Deployment Configuration
// vercel.json
{
"framework" : "nextjs" ,
"regions" : [ "iad1" , "sfo1" , "lhr1" ] ,
"env" : {
"NODE_ENV" : "production"
} ,
"build" : {
"env" : {
"PRISMA_GENERATE" : "true"
}
} ,
"functions" : {
"app/api/**/*.ts" : {
"maxDuration" : 60
}
} ,
"headers" : [
{
"source" : "/api/(.*)" ,
"headers" : [
{ "key" : "Access-Control-Allow-Origin" , "value" : "*" } ,
{ "key" : "Access-Control-Allow-Methods" , "value" : "GET,POST,PUT,DELETE" } ,
{ "key" : "Access-Control-Allow-Headers" , "value" : "Content-Type,Authorization" }
]
}
]
}
# .github/workflows/deploy.yml
name : Deploy to Vercel
on :
push :
branches : [main, develop]
pull_request :
branches : [main]
jobs :
test :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- name : Setup Node.js
uses : actions/setup-node@v4
with :
node-version : ' 20'
cache : ' npm'
- name : Install dependencies
run : npm ci
- name : Type check
run : npm run type-check
- name : Lint
run : npm run lint
- name : Run tests
run : npm run test
- name : Run E2E tests
run : npm run test:e2e
env :
PLAYWRIGHT_BROWSERS_PATH : 0
deploy :
needs : test
runs-on : ubuntu-latest
if : github.ref == 'refs/heads/main'
steps :
- uses : actions/checkout@v4
- name : Deploy to Vercel
uses : amondnet/vercel-action@v25
with :
vercel-token : ${{ secrets.VERCEL_TOKEN }}
vercel-org-id : ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id : ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args : ' --prod'
Environment
Branch
URL
Purpose
Production
main
j-mods.vercel.app
Live deployment
Staging
develop
j-mods-staging.vercel.app
Pre-production testing
Preview
feature/*
Auto-generated
PR previews
# Vercel CLI rollback
vercel rollback [deployment-id]
# Or via dashboard:
# 1. Go to Deployments
# 2. Find previous stable deployment
# 3. Click "Promote to Production"
Database Migration on Deploy
// scripts/pre-deploy.ts
import { execSync } from 'child_process' ;
// Run migrations before deployment
try {
execSync ( 'npx prisma migrate deploy' , { stdio : 'inherit' } ) ;
console . log ( '✅ Database migrations completed' ) ;
} catch ( error ) {
console . error ( '❌ Migration failed:' , error ) ;
process . exit ( 1 ) ;
}
// next.config.js
const withPWA = require ( 'next-pwa' ) ( {
dest : 'public' ,
disable : process . env . NODE_ENV === 'development' ,
register : true ,
skipWaiting : true ,
runtimeCaching : [
{
urlPattern : / ^ h t t p s : \/ \/ b l o b \. v e r c e l \. s t o r a g e \/ .* / i,
handler : 'CacheFirst' ,
options : {
cacheName : 'blob-cache' ,
expiration : {
maxEntries : 50 ,
maxAgeSeconds : 60 * 60 * 24 * 7 , // 7 days
} ,
} ,
} ,
{
urlPattern : / ^ h t t p s : \/ \/ j - m o d s \. v e r c e l \. a p p \/ a p i \/ .* / i,
handler : 'NetworkFirst' ,
options : {
cacheName : 'api-cache' ,
expiration : {
maxEntries : 100 ,
maxAgeSeconds : 60 * 5 , // 5 minutes
} ,
} ,
} ,
] ,
} ) ;
module . exports = withPWA ( {
// ... other config
} ) ;
{
"name" : " j-mods - Private App Store" ,
"short_name" : " j-mods" ,
"description" : " Private APK Distribution Platform" ,
"start_url" : " /" ,
"display" : " standalone" ,
"orientation" : " portrait" ,
"background_color" : " #0a0a0f" ,
"theme_color" : " #7c3aed" ,
"categories" : [" utilities" , " productivity" ],
"icons" : [
{
"src" : " /icons/icon-72x72.png" ,
"sizes" : " 72x72" ,
"type" : " image/png" ,
"purpose" : " any maskable"
},
{
"src" : " /icons/icon-96x96.png" ,
"sizes" : " 96x96" ,
"type" : " image/png" ,
"purpose" : " any maskable"
},
{
"src" : " /icons/icon-128x128.png" ,
"sizes" : " 128x128" ,
"type" : " image/png" ,
"purpose" : " any maskable"
},
{
"src" : " /icons/icon-144x144.png" ,
"sizes" : " 144x144" ,
"type" : " image/png" ,
"purpose" : " any maskable"
},
{
"src" : " /icons/icon-152x152.png" ,
"sizes" : " 152x152" ,
"type" : " image/png" ,
"purpose" : " any maskable"
},
{
"src" : " /icons/icon-192x192.png" ,
"sizes" : " 192x192" ,
"type" : " image/png" ,
"purpose" : " any maskable"
},
{
"src" : " /icons/icon-384x384.png" ,
"sizes" : " 384x384" ,
"type" : " image/png" ,
"purpose" : " any maskable"
},
{
"src" : " /icons/icon-512x512.png" ,
"sizes" : " 512x512" ,
"type" : " image/png" ,
"purpose" : " any maskable"
}
],
"screenshots" : [
{
"src" : " /screenshots/home.png" ,
"sizes" : " 1080x1920" ,
"type" : " image/png" ,
"form_factor" : " narrow"
},
{
"src" : " /screenshots/app-detail.png" ,
"sizes" : " 1080x1920" ,
"type" : " image/png" ,
"form_factor" : " narrow"
}
],
"shortcuts" : [
{
"name" : " Browse Apps" ,
"short_name" : " Apps" ,
"description" : " View all available applications" ,
"url" : " /apps" ,
"icons" : [{ "src" : " /icons/apps.png" , "sizes" : " 96x96" }]
},
{
"name" : " Search" ,
"short_name" : " Search" ,
"description" : " Search for applications" ,
"url" : " /search" ,
"icons" : [{ "src" : " /icons/search.png" , "sizes" : " 96x96" }]
}
]
}
Service Worker Registration
// app/layout.tsx
export default function RootLayout ( { children } : { children : React . ReactNode } ) {
return (
< html lang = "en" >
< head >
< link rel = "manifest" href = "/manifest.json" / >
< meta name = "theme-color" content = "#7c3aed" / >
< meta name = "apple-mobile-web-app-capable" content = "yes" / >
< meta name = "apple-mobile-web-app-status-bar-style" content = "default" / >
< meta name = "apple-mobile-web-app-title" content = "j-mods" / >
< link rel = "apple-touch-icon" href = "/icons/icon-192x192.png" / >
< / head >
< body > { children} < / b o d y >
< / html >
) ;
}
// vitest.config.ts
import { defineConfig } from 'vitest/config' ;
import react from '@vitejs/plugin-react' ;
export default defineConfig ( {
plugins : [ react ( ) ] ,
test : {
globals : true ,
environment : 'jsdom' ,
setupFiles : [ './tests/setup.ts' ] ,
coverage : {
provider : 'v8' ,
reporter : [ 'text' , 'json' , 'html' ] ,
exclude : [ 'node_modules/' , 'tests/' ] ,
thresholds : {
global : {
branches : 80 ,
functions : 80 ,
lines : 80 ,
statements : 80 ,
} ,
} ,
} ,
} ,
} ) ;
// tests/unit/version-comparison.test.ts
import { describe , it , expect } from 'vitest' ;
import { isNewerVersion } from '@/lib/version' ;
describe ( 'Version Comparison' , ( ) => {
it ( 'should correctly compare versionCodes' , ( ) => {
expect ( isNewerVersion ( 42 , 41 ) ) . toBe ( true ) ;
expect ( isNewerVersion ( 41 , 42 ) ) . toBe ( false ) ;
expect ( isNewerVersion ( 42 , 42 ) ) . toBe ( false ) ;
} ) ;
it ( 'should handle large versionCodes' , ( ) => {
expect ( isNewerVersion ( 2147483647 , 2147483646 ) ) . toBe ( true ) ;
} ) ;
} ) ;
// tests/e2e/download-flow.spec.ts
import { test , expect } from '@playwright/test' ;
test . describe ( 'Download Flow' , ( ) => {
test . beforeEach ( async ( { page } ) => {
await page . goto ( '/' ) ;
} ) ;
test ( 'should authenticate and download APK' , async ( { page } ) => {
// Login
await page . click ( '[data-testid="login-button"]' ) ;
await page . fill ( '[name="email"]' , 'test@example.com' ) ;
await page . fill ( '[name="password"]' , 'testpassword' ) ;
await page . click ( '[type="submit"]' ) ;
// Navigate to app
await page . click ( '[data-testid="app-card"]:first-child' ) ;
// Download
const downloadPromise = page . waitForEvent ( 'download' ) ;
await page . click ( '[data-testid="download-button"]' ) ;
const download = await downloadPromise ;
expect ( download . suggestedFilename ( ) ) . toMatch ( / \. a p k $ / ) ;
} ) ;
test ( 'should show version history' , async ( { page } ) => {
await page . click ( '[data-testid="app-card"]:first-child' ) ;
await page . click ( '[data-testid="versions-tab"]' ) ;
await expect ( page . locator ( '[data-testid="version-item"]' ) ) . toBeVisible ( ) ;
} ) ;
} ) ;
{
"scripts" : {
"test" : " vitest" ,
"test:ui" : " vitest --ui" ,
"test:coverage" : " vitest --coverage" ,
"test:e2e" : " playwright test" ,
"test:e2e:ui" : " playwright test --ui" ,
"test:e2e:debug" : " playwright test --debug"
}
}
Vercel Analytics Integration
// app/providers.tsx
'use client' ;
import { Analytics } from '@vercel/analytics/react' ;
import { SpeedInsights } from '@vercel/speed-insights/next' ;
export function Providers ( { children } : { children : React . ReactNode } ) {
return (
< >
< Analytics / >
< SpeedInsights / >
{ children }
< / >
) ;
}
// lib/analytics/events.ts
export function trackDownload ( appId : string , versionId : string ) {
// Send to Vercel Analytics
window . va ?.( 'event' , {
type : 'download' ,
data : { appId, versionId } ,
} ) ;
// Also log to database (async)
fetch ( '/api/v1/analytics/download' , {
method : 'POST' ,
body : JSON . stringify ( { appId, versionId } ) ,
} ) ;
}
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs' ;
Sentry . init ( {
dsn : process . env . NEXT_PUBLIC_SENTRY_DSN ,
environment : process . env . NODE_ENV ,
tracesSampleRate : 0.1 ,
replaysSessionSampleRate : 0.1 ,
replaysOnErrorSampleRate : 1.0 ,
integrations : [
Sentry . replayIntegration ( {
maskAllText : true ,
blockAllMedia : true ,
} ) ,
] ,
} ) ;
// app/api/v1/health/route.ts
import { NextResponse } from 'next/server' ;
import { prisma } from '@/lib/db' ;
export async function GET ( ) {
const checks = {
database : false ,
storage : false ,
timestamp : new Date ( ) . toISOString ( ) ,
} ;
try {
await prisma . $queryRaw `SELECT 1` ;
checks . database = true ;
} catch {
// Database check failed
}
// Storage check would go here
const status = checks . database ? 200 : 503 ;
return NextResponse . json ( checks , { status } ) ;
}
WCAG 2.1 Level AA Compliance
Requirement
Implementation
Status
Color Contrast
4.5:1 minimum ratio
✅
Keyboard Navigation
Full tab support
✅
Screen Reader
ARIA labels throughout
✅
Focus Indicators
Visible focus rings
✅
Skip Links
Skip to main content
✅
Form Labels
All inputs labeled
✅
Error Messages
Associated with inputs
✅
Motion Reduction
Respects prefers-reduced-motion
✅
// components/accessible-button.tsx
interface ButtonProps extends React . ButtonHTMLAttributes < HTMLButtonElement > {
children : React . ReactNode ;
'aria-label' ?: string ;
}
export function Button ( { children, 'aria-label' : ariaLabel , ...props } : ButtonProps ) {
return (
< button
{ ...props }
aria - label = { ariaLabel}
className = "focus:outline-none focus:ring-2 focus:ring-violet-500 focus:ring-offset-2 focus:ring-offset-obsidian"
>
{ children}
< / b u t t o n >
) ;
}
// components/skip-link.tsx
export function SkipLink ( ) {
return (
< a
href = "#main-content"
className = "sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:p-4 focus:bg-violet-600 focus:text-white"
>
Skip to main content
< / a >
) ;
}
# Run axe-core accessibility tests
npm run test:a11y
# Lighthouse accessibility audit
npx lighthouse http://localhost:3000 --only-categories=accessibility
Common Issues & Solutions
Issue
Cause
Solution
Download link expired
URL TTL exceeded (15 min) [[10]]
Refresh page to generate new signed URL
APK fails to install
Unknown sources disabled
Enable "Install from Unknown Sources" in Android settings
Database timeout
Connection pool exhausted
Increase pool size or use PgBouncer
Auth not working
AUTH_SECRET < 32 chars
Generate new secret with openssl rand -base64 32 [[39]]
Blob upload fails
Token permissions insufficient
Regenerate Vercel Blob token with read/write
Slow page loads
Missing indexes on queries
Add indexes per Prisma schema [[30]]
429 Too Many Requests
Rate limit exceeded
Wait or increase rate limit tier
CSP violations
Inline scripts blocked
Update CSP in next.config.js
# Enable debug logging
DEBUG=j-mods:* npm run dev
# View Vercel function logs
vercel logs [deployment-url]
# Check database queries
DATABASE_URL=" ..." npx prisma studio
Issue Type
Channel
Response Time
Bug Report
GitHub Issues
48 hours
Security
security@j-mods.internal
24 hours
General
Internal Slack
4 hours
⚠️ This is a private project. External contributions require explicit permission.
# 1. Create feature branch
git checkout -b feature/your-feature-name
# 2. Make changes with conventional commits
git commit -m " feat: add virus scanning integration"
git commit -m " fix: resolve download URL expiration issue"
git commit -m " docs: update API documentation"
# 3. Push and create PR
git push origin feature/your-feature-name
# 4. Request review from team
# 5. Ensure all CI checks pass
# 6. Merge after approval
feat: New feature
fix: Bug fix
docs: Documentation
style: Formatting
refactor: Code restructuring
test: Tests
chore: Maintenance
© 2026 j-mods. All rights reserved.
PROPRIETARY AND CONFIDENTIAL
This software is provided "as is" for private distribution only.
Users are responsible for ensuring the content they distribute
complies with local regulations and intellectual property laws.
UNAUTHORIZED DISTRIBUTION, REPRODUCTION, OR MODIFICATION IS PROHIBITED.
DISCLAIMER OF WARRANTIES:
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.
Built with ❤️ using Next.js 15, TypeScript & Vercel
Performance | Security | Reliability