Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions apps/core/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# ==============================================================================
# Asset-Forge Environment Configuration
# ==============================================================================
# Copy this file to .env and fill in your values
# Required variables are marked with (REQUIRED)
# ==============================================================================

# ------------------------------------------------------------------------------
# Node Environment
# ------------------------------------------------------------------------------
NODE_ENV=development # development | production | test

# ------------------------------------------------------------------------------
# Database (REQUIRED)
# ------------------------------------------------------------------------------
DATABASE_URL=postgresql://user:password@localhost:5432/assetforge

# ------------------------------------------------------------------------------
# Server Configuration
# ------------------------------------------------------------------------------
PORT=3004
API_PORT=3004

# ------------------------------------------------------------------------------
# Authentication (Privy)
# Both required for authentication to work
# ------------------------------------------------------------------------------
PRIVY_APP_ID=your_privy_app_id
PRIVY_APP_SECRET=your_privy_app_secret

# Frontend Privy ID (same as PRIVY_APP_ID, used by Vite)
VITE_PRIVY_APP_ID=your_privy_app_id

# ------------------------------------------------------------------------------
# API Key Encryption
# Required for secure storage of user-provided API keys
# Generate with: openssl rand -base64 32
# ------------------------------------------------------------------------------
API_KEY_ENCRYPTION_SECRET=your_32_character_encryption_secret

# ------------------------------------------------------------------------------
# AI Services
# At least one AI service is recommended
# ------------------------------------------------------------------------------
# Vercel AI Gateway (recommended - single key for multiple providers)
AI_GATEWAY_API_KEY=your_ai_gateway_key

# Or direct provider keys
OPENAI_API_KEY=sk-your_openai_key
# ANTHROPIC_API_KEY=your_anthropic_key

# ------------------------------------------------------------------------------
# 3D Asset Generation (Meshy AI)
# Required for 3D model generation features
# ------------------------------------------------------------------------------
MESHY_API_KEY=your_meshy_api_key
MESHY_MODEL_DEFAULT=meshy-4
MESHY_POLL_INTERVAL_MS=10000
MESHY_TIMEOUT_MS=300000

# ------------------------------------------------------------------------------
# Voice/Audio Generation (ElevenLabs)
# Optional - for voice synthesis and sound effects
# ------------------------------------------------------------------------------
ELEVENLABS_API_KEY=your_elevenlabs_key

# ------------------------------------------------------------------------------
# Vector Database (Qdrant)
# Optional - for semantic search features
# ------------------------------------------------------------------------------
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=your_qdrant_key

# ------------------------------------------------------------------------------
# URLs & CORS
# IMPORTANT: Configure these for production deployment
# ------------------------------------------------------------------------------
# Frontend URL (REQUIRED in production for CORS/CSRF)
FRONTEND_URL=http://localhost:3000
VITE_API_URL=http://localhost:3004

# Additional allowed CORS origins (comma-separated)
CORS_ALLOWED_ORIGINS=

# CDN Configuration (for asset delivery)
CDN_URL=https://your-cdn.example.com
CDN_API_KEY=your_cdn_api_key
CDN_WS_URL=wss://your-cdn.example.com/ws
AUTO_PUBLISH_TO_CDN=true

# Image server URL (for Meshy AI callbacks)
IMAGE_SERVER_URL=http://localhost:3004

# ------------------------------------------------------------------------------
# Webhook Configuration
# For CDN-to-app communication
# ------------------------------------------------------------------------------
WEBHOOK_SECRET=your_webhook_secret_32_chars_minimum
CDN_WEBHOOK_ENABLED=false
WEBHOOK_SYSTEM_USER_ID=

# ------------------------------------------------------------------------------
# Rate Limiting
# Enabled by default in all environments
# ------------------------------------------------------------------------------
# Set to "true" to disable rate limiting (NOT recommended)
# DISABLE_RATE_LIMITING=false

# ------------------------------------------------------------------------------
# Logging
# ------------------------------------------------------------------------------
LOG_LEVEL=info # fatal | error | warn | info | debug | trace

# ------------------------------------------------------------------------------
# Testing Only
# ------------------------------------------------------------------------------
# Secret for test JWT signing (only needed when NODE_ENV=test)
TEST_JWT_SECRET=test-secret-for-jwt-signing

# ------------------------------------------------------------------------------
# Railway Platform (auto-set by Railway)
# ------------------------------------------------------------------------------
# RAILWAY_VOLUME_MOUNT_PATH=/data
# RAILWAY_PUBLIC_DOMAIN=your-app.railway.app

# ------------------------------------------------------------------------------
# Image Hosting (Legacy)
# ------------------------------------------------------------------------------
# IMGUR_CLIENT_ID=your_imgur_client_id

# ==============================================================================
# DEPLOYMENT CHECKLIST
# ==============================================================================
# Before deploying to production, ensure:
#
# 1. Security:
# [ ] NODE_ENV=production
# [ ] FRONTEND_URL is set (required for CORS/CSRF)
# [ ] API_KEY_ENCRYPTION_SECRET is a strong random value
# [ ] WEBHOOK_SECRET is set if using CDN webhooks
# [ ] All secrets are unique and not shared across environments
#
# 2. Database:
# [ ] DATABASE_URL points to production database
# [ ] Database migrations have been applied
# [ ] Connection pool is appropriately sized
#
# 3. Authentication:
# [ ] PRIVY_APP_ID and PRIVY_APP_SECRET are set
# [ ] VITE_PRIVY_APP_ID matches PRIVY_APP_ID
#
# 4. AI Services:
# [ ] At least one AI provider key is configured
# [ ] MESHY_API_KEY is set for 3D generation
#
# 5. URLs:
# [ ] FRONTEND_URL is the production frontend domain
# [ ] IMAGE_SERVER_URL is accessible by Meshy for callbacks
# [ ] CDN_URL is configured if using CDN
#
# 6. Monitoring:
# [ ] LOG_LEVEL=info or LOG_LEVEL=warn for production
# [ ] Error tracking service configured (Sentry, etc.)
#
# ==============================================================================
95 changes: 85 additions & 10 deletions apps/core/__tests__/helpers/api.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,78 @@
/**
* API Test Helper
* November 2025 Best Practices (Elysia):
* - Use app.handle() pattern instead of spinning up servers
* - Request builder utilities
* - Response assertion helpers
*
* RECOMMENDED: Use Eden Treaty for type-safe testing
* - Pass Elysia instance directly to treaty() - zero network overhead
* - Full TypeScript autocomplete for routes
* - Compile-time type checking for request/response
*
* ALTERNATIVE: Use app.handle() for low-level testing
* - Manual Request construction
* - Useful when you need fine-grained control
*
* @see https://elysiajs.com/patterns/unit-test
* @see https://elysiajs.com/eden/treaty/unit-test
*/

import type { Elysia } from "elysia";
import { Elysia } from "elysia";
import { treaty } from "@elysiajs/eden";
import type { AuthUser } from "../../server/middleware/auth";
import { createAuthHeader } from "./auth";

/**
* Type alias for any Elysia instance
* Using Elysia base type allows TypeScript to infer specific type parameters
* from the actual app instance passed to the functions below.
*/
type AnyElysiaApp = Elysia;

/**
* Create a type-safe Eden Treaty client from an Elysia instance
*
* This is the RECOMMENDED approach for testing per Elysia best practices.
* Eden Treaty provides:
* - Full type safety with autocomplete
* - Zero network overhead (calls app.handle() internally)
* - Compile-time validation of requests/responses
*
* @example
* ```typescript
* const app = new Elysia().use(assetRoutes);
* const api = createTestClient(app);
*
* // Type-safe requests with autocomplete
* const { data, error } = await api.api.assets.get();
* const { data: asset } = await api.api.assets({ id: 'test' }).get();
* ```
*/
export function createTestClient<T extends AnyElysiaApp>(app: T) {
return treaty(app);
}

/**
* Create a type-safe Eden Treaty client with auth headers
*
* @example
* ```typescript
* const api = createAuthTestClient(app, testUser.authUser);
* const { data } = await api.api.assets.get(); // Authenticated request
* ```
*/
export function createAuthTestClient<T extends AnyElysiaApp>(
app: T,
user: AuthUser,
) {
return treaty(app, {
headers: {
Authorization: createAuthHeader(
user.privyUserId,
user.email || undefined,
),
},
});
}

/**
* Helper function to create a Request object
* Based on Elysia test best practices
Expand All @@ -36,7 +99,11 @@ export function get(path: string, headers?: HeadersInit): Request {
/**
* Create a POST request with JSON body
*/
export function post(path: string, body: any, headers?: HeadersInit): Request {
export function post(
path: string,
body: unknown,
headers?: HeadersInit,
): Request {
return req(path, {
method: "POST",
headers: {
Expand All @@ -50,7 +117,11 @@ export function post(path: string, body: any, headers?: HeadersInit): Request {
/**
* Create a PATCH request with JSON body
*/
export function patch(path: string, body: any, headers?: HeadersInit): Request {
export function patch(
path: string,
body: unknown,
headers?: HeadersInit,
): Request {
return req(path, {
method: "PATCH",
headers: {
Expand Down Expand Up @@ -89,7 +160,7 @@ export function authGet(path: string, user: AuthUser): Request {
/**
* Create an authenticated POST request
*/
export function authPost(path: string, body: any, user: AuthUser): Request {
export function authPost(path: string, body: unknown, user: AuthUser): Request {
return req(path, {
method: "POST",
headers: {
Expand All @@ -106,7 +177,11 @@ export function authPost(path: string, body: any, user: AuthUser): Request {
/**
* Create an authenticated PATCH request
*/
export function authPatch(path: string, body: any, user: AuthUser): Request {
export function authPatch(
path: string,
body: unknown,
user: AuthUser,
): Request {
return req(path, {
method: "PATCH",
headers: {
Expand Down Expand Up @@ -149,7 +224,7 @@ export async function testRoute(
/**
* Test a route and parse JSON response
*/
export async function testRouteJSON<T = any>(
export async function testRouteJSON<T = unknown>(
app: Elysia,
request: Request,
): Promise<{ response: Response; data: T }> {
Expand Down Expand Up @@ -200,7 +275,7 @@ export function assertHeader(
/**
* Extract JSON from response with error handling
*/
export async function extractJSON<T = any>(response: Response): Promise<T> {
export async function extractJSON<T = unknown>(response: Response): Promise<T> {
try {
return await response.json();
} catch (error) {
Expand Down
36 changes: 30 additions & 6 deletions apps/core/__tests__/helpers/auth.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
/**
* Authentication Test Helper
* November 2025 Best Practices:
* - Mock JWT tokens for testing
* - Properly signed JWT tokens for testing
* - Privy authentication helpers
* - Test authentication contexts
*/

import { createHmac } from "crypto";
import type { AuthUser } from "../../server/middleware/auth";

/**
* Generate a mock JWT token for testing
* Note: This is NOT cryptographically valid, just for testing
* Get test JWT secret - MUST match TEST_JWT_SECRET env var in tests
* Set TEST_JWT_SECRET=test-secret-for-jwt-signing in test environment
*
* This function throws if TEST_JWT_SECRET is not set, matching the behavior
* in auth.plugin.ts to ensure consistent security handling.
*/
function getTestJwtSecret(): string {
const secret = process.env.TEST_JWT_SECRET;
if (!secret) {
throw new Error(
"TEST_JWT_SECRET environment variable is required for testing. " +
"Set TEST_JWT_SECRET=test-secret-for-jwt-signing in your test environment.",
);
}
return secret;
}

/**
* Generate a properly signed JWT token for testing
* Uses HMAC-SHA256 with TEST_JWT_SECRET for signature verification
*/
export function createMockJWT(payload: {
sub: string;
Expand All @@ -20,15 +39,20 @@ export function createMockJWT(payload: {
}): string {
const header = Buffer.from(
JSON.stringify({ alg: "HS256", typ: "JWT" }),
).toString("base64");
).toString("base64url");
const body = Buffer.from(
JSON.stringify({
...payload,
iat: payload.iat || Math.floor(Date.now() / 1000),
exp: payload.exp || Math.floor(Date.now() / 1000) + 3600, // 1 hour
}),
).toString("base64");
const signature = "mock-signature";
).toString("base64url");

// Generate proper HMAC signature
const signatureInput = `${header}.${body}`;
const signature = createHmac("sha256", getTestJwtSecret())
.update(signatureInput)
.digest("base64url");

return `${header}.${body}.${signature}`;
}
Expand Down
Loading
Loading