Browse Source

first commit

Loic 1 day ago
commit
ad5ee0478b

+ 15 - 0
.env.example

@@ -0,0 +1,15 @@
+# Environment variables for development
+NODE_ENV=development
+
+# Database configuration
+DATABASE_URL=postgresql://postgres:password@localhost:5432/byop_sample
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=password
+POSTGRES_DB=byop_sample
+
+# Backend configuration
+PORT=5000
+JWT_SECRET=your-development-secret-key-change-in-production
+
+# Frontend configuration  
+REACT_APP_API_URL=http://localhost:5000

+ 49 - 0
.github/copilot-instructions.md

@@ -0,0 +1,49 @@
+<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->
+
+# BYOP Sample Application - Copilot Instructions
+
+This is a multi-component web application designed for testing the BYOP (Build Your Own Platform) engine. When working on this project:
+
+## Project Context
+- **Frontend**: React 18 application with modern UI components
+- **Backend**: Node.js/Express API with PostgreSQL integration
+- **Database**: PostgreSQL with initialization scripts
+- **Deployment**: Docker containerization with multi-stage builds
+
+## Coding Guidelines
+
+### Frontend (React)
+- Use functional components with hooks
+- Implement proper error handling and loading states
+- Follow React best practices for state management
+- Use modern CSS with flexbox/grid layouts
+- Ensure responsive design principles
+
+### Backend (Node.js)
+- Use async/await for database operations
+- Implement proper error handling middleware
+- Follow RESTful API design principles
+- Include input validation and sanitization
+- Use environment variables for configuration
+
+### Docker & Deployment
+- Create multi-stage builds for production optimization
+- Include health checks in containers
+- Use non-root users for security
+- Optimize image sizes and build times
+- Follow Docker best practices
+
+## Testing Considerations
+When suggesting code changes, consider:
+- Component isolation for BYOP testing
+- Environment variable flexibility
+- Health check endpoints
+- Graceful shutdown handling
+- Database connection resilience
+
+## BYOP-Specific Features
+- Each component should be independently deployable
+- Include proper service discovery configuration
+- Implement Traefik-compatible labeling
+- Support for different deployment environments
+- Clear separation of concerns between services

+ 62 - 0
.gitignore

@@ -0,0 +1,62 @@
+# Dependencies
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Environment variables
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Build outputs
+frontend/build/
+backend/dist/
+*.tgz
+*.tar.gz
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Coverage directory used by tools like istanbul
+coverage/
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Docker
+.dockerignore
+
+# Logs
+logs
+*.log
+
+# Database
+*.sqlite3
+*.db
+
+# Temporary files
+tmp/
+temp/

+ 228 - 0
README.md

@@ -0,0 +1,228 @@
+# BYOP Sample Application
+
+A comprehensive multi-component web application designed to test the **Build Your Own Platform (BYOP)** engine. This project demonstrates various deployment scenarios including single-component and multi-component architectures.
+
+## 🏗️ Architecture
+
+This application consists of three main components:
+
+### 🎨 Frontend (Port 3000)
+- **Technology**: React 18 + Nginx
+- **Features**: Modern responsive UI, API integration, real-time data
+- **Build**: Multi-stage Docker build with production optimization
+
+### 🚀 Backend API (Port 5000)
+- **Technology**: Node.js + Express + PostgreSQL
+- **Features**: RESTful API, database operations, health checks
+- **Security**: Helmet, CORS, input validation
+
+### 🗄️ Database (Port 5432)
+- **Technology**: PostgreSQL 15
+- **Features**: Persistent data storage, initialization scripts, triggers
+
+## 🚀 Quick Start
+
+### Local Development
+
+```bash
+# Install all dependencies
+npm run install-all
+
+# Start with Docker Compose (recommended)
+npm run dev
+
+# Or start services individually
+npm run dev-local
+```
+
+### BYOP Engine Testing
+
+1. **Import Components**: Import each component separately into BYOP
+   - Frontend: `https://github.com/yourusername/byop-sample-app` (frontend folder)
+   - Backend: `https://github.com/yourusername/byop-sample-app` (backend folder)
+   - Database: Use the provided PostgreSQL configuration
+
+2. **Create Single-Component Apps**: Test individual component deployment
+3. **Create Multi-Component Apps**: Test full-stack application deployment
+
+## 📁 Project Structure
+
+```
+byop-sample-app/
+├── frontend/                 # React application
+│   ├── src/
+│   │   ├── App.js           # Main application component
+│   │   ├── index.js         # React entry point
+│   │   └── index.css        # Global styles
+│   ├── public/
+│   │   └── index.html       # HTML template
+│   ├── Dockerfile           # Frontend container
+│   ├── nginx.conf          # Nginx configuration
+│   └── package.json        # Frontend dependencies
+├── backend/                 # Node.js API
+│   ├── server.js           # Express server
+│   ├── Dockerfile          # Backend container
+│   └── package.json        # Backend dependencies
+├── database/               # Database configuration
+│   └── init.sql           # Database initialization
+├── docker-compose.yml     # Multi-service orchestration
+├── package.json          # Root project configuration
+└── README.md            # This file
+```
+
+## 🔗 API Endpoints
+
+### Base URL: `http://localhost:5000`
+
+- `GET /` - API information and health
+- `GET /health` - Health check endpoint
+- `GET /api/users` - Get all users
+- `GET /api/users/:id` - Get user by ID
+- `POST /api/users` - Create new user
+- `PUT /api/users/:id` - Update user
+- `DELETE /api/users/:id` - Delete user
+- `GET /api/stats` - Get application statistics
+
+## 🧪 Testing Scenarios
+
+### Single Component Testing
+
+1. **Frontend Only**: Deploy React app with static API mocking
+2. **Backend Only**: Deploy API with external database
+3. **Database Only**: Deploy PostgreSQL with initialization
+
+### Multi-Component Testing
+
+1. **Frontend + Backend**: Full-stack without database
+2. **Backend + Database**: API with persistent storage
+3. **Full Stack**: All three components working together
+
+### BYOP-Specific Tests
+
+- ✅ Docker image building from source
+- ✅ Component dependency management
+- ✅ Environment variable injection
+- ✅ Service discovery and networking
+- ✅ Traefik routing and SSL termination
+- ✅ Health checks and monitoring
+- ✅ Graceful shutdown handling
+
+## 🌐 Environment Variables
+
+### Frontend
+```bash
+REACT_APP_API_URL=http://backend:5000  # API endpoint
+```
+
+### Backend
+```bash
+NODE_ENV=production                    # Environment mode
+DATABASE_URL=postgresql://...          # Database connection
+JWT_SECRET=your-secret-key            # JWT secret
+PORT=5000                             # Server port
+```
+
+### Database
+```bash
+POSTGRES_USER=postgres                # Database user
+POSTGRES_PASSWORD=password            # Database password
+POSTGRES_DB=byop_sample              # Database name
+```
+
+## 🐳 Docker Commands
+
+### Build individual images:
+```bash
+# Frontend
+docker build -t byop-sample-frontend ./frontend
+
+# Backend  
+docker build -t byop-sample-backend ./backend
+```
+
+### Run with Docker Compose:
+```bash
+# Start all services
+docker-compose up -d
+
+# View logs
+docker-compose logs -f
+
+# Stop all services
+docker-compose down
+```
+
+## 📊 Monitoring
+
+### Health Checks
+- Frontend: `http://localhost:3000` (visual health check)
+- Backend: `http://localhost:5000/health` (JSON response)
+- Database: Connection test via backend API
+
+### Application URLs
+- **Frontend**: http://localhost:3000
+- **Backend API**: http://localhost:5000
+- **Database**: localhost:5432 (admin tools only)
+
+## 🔧 Troubleshooting
+
+### Common Issues
+
+1. **Port conflicts**: Ensure ports 3000, 5000, 5432 are available
+2. **Database connection**: Check PostgreSQL is running and accessible
+3. **CORS errors**: Verify API_URL configuration in frontend
+4. **Build failures**: Check Docker daemon is running
+
+### Debug Commands
+```bash
+# Check container status
+docker-compose ps
+
+# View container logs
+docker-compose logs [service-name]
+
+# Exec into container
+docker-compose exec [service-name] sh
+
+# Reset everything
+docker-compose down -v
+docker system prune -f
+```
+
+## 🚀 BYOP Engine Integration
+
+This project is specifically designed to test various BYOP engine scenarios:
+
+### Component Import Testing
+- Individual component Dockerfile builds
+- Multi-stage builds with optimization
+- Health check implementation
+- Environment variable handling
+
+### App Creation Testing  
+- Single-component app deployment
+- Multi-component orchestration
+- Service-to-service communication
+- Database persistence
+
+### Preview Testing
+- Traefik routing configuration
+- SSL certificate automation
+- Custom domain assignment
+- Load balancing (if multiple instances)
+
+## 📝 License
+
+MIT License - Feel free to use this project for testing and learning purposes.
+
+## 🤝 Contributing
+
+This is a testing project for the BYOP engine. Feel free to:
+- Add more complex scenarios
+- Improve error handling
+- Add monitoring capabilities
+- Create additional microservices
+
+---
+
+**Happy Testing! 🎉**

+ 30 - 0
backend/Dockerfile

@@ -0,0 +1,30 @@
+FROM node:18-alpine
+
+WORKDIR /app
+
+# Copy package files
+COPY package*.json ./
+
+# Install dependencies
+RUN npm ci --only=production
+
+# Copy source code
+COPY . .
+
+# Create non-root user
+RUN addgroup -g 1001 -S nodejs
+RUN adduser -S nodejs -u 1001
+
+# Change ownership of app folder
+RUN chown -R nodejs:nodejs /app
+USER nodejs
+
+# Expose port
+EXPOSE 5000
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+  CMD curl -f http://localhost:5000/health || exit 1
+
+# Start the application
+CMD ["npm", "start"]

+ 27 - 0
backend/package.json

@@ -0,0 +1,27 @@
+{
+  "name": "byop-sample-backend",
+  "version": "1.0.0",
+  "description": "Backend API for BYOP sample application",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js",
+    "dev": "nodemon server.js",
+    "build": "echo 'No build step required for this Node.js app'"
+  },
+  "keywords": ["nodejs", "express", "postgresql", "api", "byop"],
+  "author": "BYOP Team",
+  "license": "MIT",
+  "dependencies": {
+    "express": "^4.18.2",
+    "cors": "^2.8.5",
+    "helmet": "^6.0.1",
+    "morgan": "^1.10.0",
+    "pg": "^8.8.0",
+    "dotenv": "^16.0.3",
+    "jsonwebtoken": "^9.0.0",
+    "bcryptjs": "^2.4.3"
+  },
+  "devDependencies": {
+    "nodemon": "^2.0.20"
+  }
+}

+ 232 - 0
backend/server.js

@@ -0,0 +1,232 @@
+const express = require('express');
+const cors = require('cors');
+const helmet = require('helmet');
+const morgan = require('morgan');
+const { Pool } = require('pg');
+require('dotenv').config();
+
+const app = express();
+const PORT = process.env.PORT || 5000;
+
+// Database configuration
+const pool = new Pool({
+  connectionString: process.env.DATABASE_URL || 'postgresql://postgres:password@localhost:5432/byop_sample',
+  ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
+});
+
+// Middleware
+app.use(helmet());
+app.use(cors());
+app.use(morgan('combined'));
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+
+// Health check endpoint
+app.get('/health', (req, res) => {
+  res.status(200).json({
+    status: 'healthy',
+    timestamp: new Date().toISOString(),
+    uptime: process.uptime(),
+    environment: process.env.NODE_ENV || 'development'
+  });
+});
+
+// Root endpoint
+app.get('/', (req, res) => {
+  res.json({
+    message: '🚀 BYOP Sample Backend API',
+    version: '1.0.0',
+    endpoints: {
+      health: '/health',
+      users: '/api/users',
+      stats: '/api/stats'
+    },
+    database: {
+      status: 'connected',
+      host: process.env.DATABASE_URL ? 'configured' : 'localhost'
+    }
+  });
+});
+
+// Initialize database tables
+async function initializeDatabase() {
+  try {
+    await pool.query(`
+      CREATE TABLE IF NOT EXISTS users (
+        id SERIAL PRIMARY KEY,
+        name VARCHAR(255) NOT NULL,
+        email VARCHAR(255) UNIQUE NOT NULL,
+        role VARCHAR(100) DEFAULT 'user',
+        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+      )
+    `);
+    
+    console.log('✅ Database tables initialized');
+  } catch (error) {
+    console.error('❌ Database initialization error:', error);
+  }
+}
+
+// Get all users
+app.get('/api/users', async (req, res) => {
+  try {
+    const result = await pool.query('SELECT * FROM users ORDER BY created_at DESC');
+    res.json(result.rows);
+  } catch (error) {
+    console.error('Error fetching users:', error);
+    res.status(500).json({ error: 'Failed to fetch users', details: error.message });
+  }
+});
+
+// Get user by ID
+app.get('/api/users/:id', async (req, res) => {
+  try {
+    const { id } = req.params;
+    const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
+    
+    if (result.rows.length === 0) {
+      return res.status(404).json({ error: 'User not found' });
+    }
+    
+    res.json(result.rows[0]);
+  } catch (error) {
+    console.error('Error fetching user:', error);
+    res.status(500).json({ error: 'Failed to fetch user', details: error.message });
+  }
+});
+
+// Create new user
+app.post('/api/users', async (req, res) => {
+  try {
+    const { name, email, role = 'user' } = req.body;
+    
+    if (!name || !email) {
+      return res.status(400).json({ error: 'Name and email are required' });
+    }
+    
+    const result = await pool.query(
+      'INSERT INTO users (name, email, role) VALUES ($1, $2, $3) RETURNING *',
+      [name, email, role]
+    );
+    
+    res.status(201).json(result.rows[0]);
+  } catch (error) {
+    console.error('Error creating user:', error);
+    
+    if (error.code === '23505') { // Unique violation
+      return res.status(409).json({ error: 'User with this email already exists' });
+    }
+    
+    res.status(500).json({ error: 'Failed to create user', details: error.message });
+  }
+});
+
+// Update user
+app.put('/api/users/:id', async (req, res) => {
+  try {
+    const { id } = req.params;
+    const { name, email, role } = req.body;
+    
+    const result = await pool.query(
+      'UPDATE users SET name = COALESCE($1, name), email = COALESCE($2, email), role = COALESCE($3, role), updated_at = CURRENT_TIMESTAMP WHERE id = $4 RETURNING *',
+      [name, email, role, id]
+    );
+    
+    if (result.rows.length === 0) {
+      return res.status(404).json({ error: 'User not found' });
+    }
+    
+    res.json(result.rows[0]);
+  } catch (error) {
+    console.error('Error updating user:', error);
+    res.status(500).json({ error: 'Failed to update user', details: error.message });
+  }
+});
+
+// Delete user
+app.delete('/api/users/:id', async (req, res) => {
+  try {
+    const { id } = req.params;
+    const result = await pool.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]);
+    
+    if (result.rows.length === 0) {
+      return res.status(404).json({ error: 'User not found' });
+    }
+    
+    res.json({ message: 'User deleted successfully', user: result.rows[0] });
+  } catch (error) {
+    console.error('Error deleting user:', error);
+    res.status(500).json({ error: 'Failed to delete user', details: error.message });
+  }
+});
+
+// Get application statistics
+app.get('/api/stats', async (req, res) => {
+  try {
+    const totalUsersResult = await pool.query('SELECT COUNT(*) as count FROM users');
+    const recentUsersResult = await pool.query('SELECT COUNT(*) as count FROM users WHERE created_at > NOW() - INTERVAL \'7 days\'');
+    
+    const stats = {
+      totalUsers: parseInt(totalUsersResult.rows[0].count),
+      activeUsers: parseInt(recentUsersResult.rows[0].count),
+      serverStatus: 'Running',
+      databaseStatus: 'Connected',
+      uptime: Math.floor(process.uptime()),
+      timestamp: new Date().toISOString(),
+      environment: process.env.NODE_ENV || 'development'
+    };
+    
+    res.json(stats);
+  } catch (error) {
+    console.error('Error fetching stats:', error);
+    res.status(500).json({ 
+      error: 'Failed to fetch stats', 
+      details: error.message,
+      serverStatus: 'Running',
+      databaseStatus: 'Error',
+      totalUsers: 0,
+      activeUsers: 0
+    });
+  }
+});
+
+// Error handling middleware
+app.use((err, req, res, next) => {
+  console.error('Unhandled error:', err);
+  res.status(500).json({ 
+    error: 'Internal server error',
+    details: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong'
+  });
+});
+
+// 404 handler
+app.use('*', (req, res) => {
+  res.status(404).json({ error: 'Endpoint not found' });
+});
+
+// Start server
+app.listen(PORT, '0.0.0.0', async () => {
+  console.log(`🚀 BYOP Sample Backend running on port ${PORT}`);
+  console.log(`📊 Environment: ${process.env.NODE_ENV || 'development'}`);
+  console.log(`🔗 Database: ${process.env.DATABASE_URL ? 'Configured' : 'Default local'}`);
+  
+  // Initialize database
+  await initializeDatabase();
+  
+  console.log('✅ Server ready to accept connections');
+});
+
+// Graceful shutdown handling
+process.on('SIGTERM', () => {
+  console.log('🛑 SIGTERM received, shutting down gracefully');
+  server.close(() => {
+    console.log('💤 Process terminated');
+    pool.end();
+  });
+});
+
+process.on('SIGINT', () => {
+  console.log('🛑 SIGINT received, shutting down gracefully');
+  process.exit(0);
+});

+ 42 - 0
database/init.sql

@@ -0,0 +1,42 @@
+-- Initialize BYOP Sample Database
+\echo 'Starting database initialization...'
+
+-- Create users table
+CREATE TABLE IF NOT EXISTS users (
+    id SERIAL PRIMARY KEY,
+    name VARCHAR(255) NOT NULL,
+    email VARCHAR(255) UNIQUE NOT NULL,
+    role VARCHAR(100) DEFAULT 'user',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Insert sample data
+INSERT INTO users (name, email, role) VALUES 
+    ('John Doe', 'john@byop.dev', 'admin'),
+    ('Jane Smith', 'jane@byop.dev', 'developer'),
+    ('Mike Johnson', 'mike@byop.dev', 'designer'),
+    ('Sarah Wilson', 'sarah@byop.dev', 'tester')
+ON CONFLICT (email) DO NOTHING;
+
+-- Create indexes for better performance
+CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
+CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
+
+-- Create a function to update the updated_at timestamp
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+    NEW.updated_at = CURRENT_TIMESTAMP;
+    RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+-- Create trigger to automatically update updated_at
+DROP TRIGGER IF EXISTS update_users_updated_at ON users;
+CREATE TRIGGER update_users_updated_at
+    BEFORE UPDATE ON users
+    FOR EACH ROW
+    EXECUTE FUNCTION update_updated_at_column();
+
+\echo 'Database initialization completed successfully!'

+ 54 - 0
docker-compose.yml

@@ -0,0 +1,54 @@
+version: '3.8'
+
+services:
+  # Frontend - React Application
+  frontend:
+    build:
+      context: ./frontend
+      dockerfile: Dockerfile
+    ports:
+      - "3000:3000"
+    environment:
+      - REACT_APP_API_URL=http://backend:5000
+    depends_on:
+      - backend
+    networks:
+      - app-network
+
+  # Backend - Node.js API
+  backend:
+    build:
+      context: ./backend
+      dockerfile: Dockerfile
+    ports:
+      - "5000:5000"
+    environment:
+      - NODE_ENV=development
+      - DATABASE_URL=postgresql://postgres:password@database:5432/byop_sample
+      - JWT_SECRET=your-secret-key-here
+    depends_on:
+      - database
+    networks:
+      - app-network
+
+  # Database - PostgreSQL
+  database:
+    image: postgres:15
+    environment:
+      - POSTGRES_USER=postgres
+      - POSTGRES_PASSWORD=password
+      - POSTGRES_DB=byop_sample
+    ports:
+      - "5432:5432"
+    volumes:
+      - postgres_data:/var/lib/postgresql/data
+      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
+    networks:
+      - app-network
+
+volumes:
+  postgres_data:
+
+networks:
+  app-network:
+    driver: bridge

+ 28 - 0
frontend/Dockerfile

@@ -0,0 +1,28 @@
+# Frontend Build Stage
+FROM node:18-alpine as build
+
+WORKDIR /app
+
+# Copy package files
+COPY package*.json ./
+RUN npm ci --only=production
+
+# Copy source code
+COPY . .
+
+# Build the application
+RUN npm run build
+
+# Production Stage
+FROM nginx:alpine
+
+# Copy built application
+COPY --from=build /app/build /usr/share/nginx/html
+
+# Copy nginx configuration
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+# Expose port
+EXPOSE 3000
+
+CMD ["nginx", "-g", "daemon off;"]

+ 22 - 0
frontend/nginx.conf

@@ -0,0 +1,22 @@
+server {
+    listen 3000;
+    server_name localhost;
+    root /usr/share/nginx/html;
+    index index.html;
+
+    # Handle React Router
+    location / {
+        try_files $uri $uri/ /index.html;
+    }
+
+    # Cache static assets
+    location /static/ {
+        expires 1y;
+        add_header Cache-Control "public, immutable";
+    }
+
+    # Security headers
+    add_header X-Frame-Options "SAMEORIGIN" always;
+    add_header X-Content-Type-Options "nosniff" always;
+    add_header X-XSS-Protection "1; mode=block" always;
+}

+ 41 - 0
frontend/package.json

@@ -0,0 +1,41 @@
+{
+  "name": "byop-sample-frontend",
+  "version": "1.0.0",
+  "private": true,
+  "dependencies": {
+    "@testing-library/jest-dom": "^5.16.4",
+    "@testing-library/react": "^13.3.0",
+    "@testing-library/user-event": "^13.5.0",
+    "axios": "^1.4.0",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-router-dom": "^6.8.0",
+    "react-scripts": "5.0.1",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "proxy": "http://localhost:5000"
+}

+ 15 - 0
frontend/public/index.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#667eea" />
+    <meta name="description" content="BYOP Sample Application - Multi-component testing platform" />
+    <title>BYOP Sample App</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+  </body>
+</html>

+ 151 - 0
frontend/src/App.js

@@ -0,0 +1,151 @@
+import React, { useState, useEffect } from 'react';
+import axios from 'axios';
+
+const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000';
+
+function App() {
+  const [users, setUsers] = useState([]);
+  const [stats, setStats] = useState({ totalUsers: 0, activeUsers: 0, serverStatus: 'Unknown' });
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState(null);
+
+  useEffect(() => {
+    fetchData();
+  }, []);
+
+  const fetchData = async () => {
+    try {
+      setLoading(true);
+      setError(null);
+
+      // Fetch users and stats in parallel
+      const [usersResponse, statsResponse] = await Promise.all([
+        axios.get(`${API_URL}/api/users`),
+        axios.get(`${API_URL}/api/stats`)
+      ]);
+
+      setUsers(usersResponse.data);
+      setStats(statsResponse.data);
+    } catch (err) {
+      console.error('Error fetching data:', err);
+      setError(`Failed to fetch data: ${err.message}`);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const addSampleUser = async () => {
+    try {
+      const sampleUsers = [
+        { name: 'Alice Johnson', email: 'alice@example.com', role: 'Developer' },
+        { name: 'Bob Smith', email: 'bob@example.com', role: 'Designer' },
+        { name: 'Charlie Brown', email: 'charlie@example.com', role: 'Manager' },
+        { name: 'Diana Prince', email: 'diana@example.com', role: 'Tester' }
+      ];
+      
+      const randomUser = sampleUsers[Math.floor(Math.random() * sampleUsers.length)];
+      await axios.post(`${API_URL}/api/users`, randomUser);
+      
+      // Refresh data
+      fetchData();
+    } catch (err) {
+      console.error('Error adding user:', err);
+      setError(`Failed to add user: ${err.message}`);
+    }
+  };
+
+  if (loading) {
+    return (
+      <div className="container">
+        <div className="card">
+          <div className="loading">Loading BYOP Sample Application...</div>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="container">
+      <div className="card">
+        <div className="header">
+          <h1>🚀 BYOP Sample App</h1>
+          <p>Multi-Component Application Testing Platform</p>
+        </div>
+
+        {error && (
+          <div className="error">
+            {error}
+          </div>
+        )}
+
+        <div className="system-info">
+          <h4>🔧 System Information</h4>
+          <p><strong>Frontend:</strong> React 18 (Port 3000)</p>
+          <p><strong>Backend API:</strong> Node.js + Express (Port 5000)</p>
+          <p><strong>Database:</strong> PostgreSQL (Port 5432)</p>
+          <p><strong>Environment:</strong> {process.env.NODE_ENV || 'development'}</p>
+          <p><strong>API URL:</strong> {API_URL}</p>
+        </div>
+
+        <div className="stats-grid">
+          <div className="stat-card">
+            <h3>{stats.totalUsers}</h3>
+            <p>Total Users</p>
+          </div>
+          <div className="stat-card">
+            <h3>{stats.activeUsers}</h3>
+            <p>Active Users</p>
+          </div>
+          <div className="stat-card">
+            <h3>{stats.serverStatus}</h3>
+            <p>Server Status</p>
+          </div>
+        </div>
+
+        <div className="users-section">
+          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
+            <h2>👥 Users ({users.length})</h2>
+            <button className="btn" onClick={addSampleUser}>
+              Add Sample User
+            </button>
+          </div>
+
+          {users.length === 0 ? (
+            <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
+              <p>No users found. Click "Add Sample User" to get started!</p>
+            </div>
+          ) : (
+            <div className="users-grid">
+              {users.map((user) => (
+                <div key={user.id} className="user-card">
+                  <h4>{user.name}</h4>
+                  <p><strong>Email:</strong> {user.email}</p>
+                  <p><strong>Role:</strong> {user.role}</p>
+                  <p><strong>Created:</strong> {new Date(user.created_at).toLocaleDateString()}</p>
+                </div>
+              ))}
+            </div>
+          )}
+        </div>
+
+        <div style={{ textAlign: 'center', marginTop: '40px', padding: '20px', background: '#f8f9fa', borderRadius: '8px' }}>
+          <h3>🧪 BYOP Engine Test Status</h3>
+          <p>This application demonstrates:</p>
+          <ul style={{ textAlign: 'left', maxWidth: '600px', margin: '0 auto' }}>
+            <li>✅ Multi-component architecture (Frontend + Backend + Database)</li>
+            <li>✅ Docker containerization with individual Dockerfiles</li>
+            <li>✅ Docker Compose orchestration</li>
+            <li>✅ API communication between services</li>
+            <li>✅ Database persistence and operations</li>
+            <li>✅ Production-ready builds with Nginx</li>
+          </ul>
+          <button className="btn" onClick={fetchData} style={{ marginTop: '20px' }}>
+            🔄 Refresh Data
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default App;

+ 154 - 0
frontend/src/index.css

@@ -0,0 +1,154 @@
+body {
+  margin: 0;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  min-height: 100vh;
+}
+
+code {
+  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+    monospace;
+}
+
+.container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+.card {
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 10px;
+  padding: 30px;
+  margin: 20px 0;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(10px);
+}
+
+.header {
+  text-align: center;
+  color: #333;
+  margin-bottom: 40px;
+}
+
+.header h1 {
+  font-size: 3rem;
+  margin-bottom: 10px;
+  background: linear-gradient(45deg, #667eea, #764ba2);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.header p {
+  font-size: 1.2rem;
+  color: #666;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  gap: 20px;
+  margin-bottom: 40px;
+}
+
+.stat-card {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  padding: 20px;
+  border-radius: 10px;
+  text-align: center;
+}
+
+.stat-card h3 {
+  margin: 0 0 10px 0;
+  font-size: 2rem;
+}
+
+.stat-card p {
+  margin: 0;
+  opacity: 0.9;
+}
+
+.users-section {
+  margin-top: 40px;
+}
+
+.users-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
+}
+
+.user-card {
+  background: #f8f9fa;
+  border: 1px solid #e9ecef;
+  border-radius: 8px;
+  padding: 20px;
+  transition: transform 0.2s;
+}
+
+.user-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.user-card h4 {
+  margin: 0 0 10px 0;
+  color: #495057;
+}
+
+.user-card p {
+  margin: 5px 0;
+  color: #6c757d;
+}
+
+.loading, .error {
+  text-align: center;
+  padding: 40px;
+  font-size: 1.2rem;
+}
+
+.error {
+  color: #dc3545;
+}
+
+.loading {
+  color: #667eea;
+}
+
+.btn {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border: none;
+  padding: 12px 24px;
+  border-radius: 6px;
+  cursor: pointer;
+  font-size: 1rem;
+  transition: transform 0.2s;
+}
+
+.btn:hover {
+  transform: translateY(-1px);
+}
+
+.system-info {
+  background: #f8f9fa;
+  border-left: 4px solid #667eea;
+  padding: 15px;
+  margin: 20px 0;
+}
+
+.system-info h4 {
+  margin: 0 0 10px 0;
+  color: #495057;
+}
+
+.system-info p {
+  margin: 5px 0;
+  color: #6c757d;
+}

+ 11 - 0
frontend/src/index.js

@@ -0,0 +1,11 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>
+);

+ 373 - 0
package-lock.json

@@ -0,0 +1,373 @@
+{
+  "name": "byop-sample-app",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "byop-sample-app",
+      "version": "1.0.0",
+      "license": "MIT",
+      "devDependencies": {
+        "concurrently": "^7.6.0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.27.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
+      "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/chalk/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/concurrently": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz",
+      "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^4.1.0",
+        "date-fns": "^2.29.1",
+        "lodash": "^4.17.21",
+        "rxjs": "^7.0.0",
+        "shell-quote": "^1.7.3",
+        "spawn-command": "^0.0.2-1",
+        "supports-color": "^8.1.0",
+        "tree-kill": "^1.2.2",
+        "yargs": "^17.3.1"
+      },
+      "bin": {
+        "conc": "dist/bin/concurrently.js",
+        "concurrently": "dist/bin/concurrently.js"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+      }
+    },
+    "node_modules/date-fns": {
+      "version": "2.30.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+      "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.21.0"
+      },
+      "engines": {
+        "node": ">=0.11"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/date-fns"
+      }
+    },
+    "node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "7.8.2",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+      "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
+    "node_modules/shell-quote": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+      "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/spawn-command": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
+      "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
+      "dev": true
+    },
+    "node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "8.1.1",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+      "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-color?sponsor=1"
+      }
+    },
+    "node_modules/tree-kill": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+      "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "tree-kill": "cli.js"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "dev": true,
+      "license": "0BSD"
+    },
+    "node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    }
+  }
+}

+ 19 - 0
package.json

@@ -0,0 +1,19 @@
+{
+  "name": "byop-sample-app",
+  "version": "1.0.0",
+  "description": "Sample multi-component application for testing BYOP engine",
+  "main": "index.js",
+  "scripts": {
+    "install-all": "npm install && cd frontend && npm install && cd ../backend && npm install",
+    "dev": "docker-compose up -d",
+    "dev-local": "concurrently \"cd backend && npm run dev\" \"cd frontend && npm start\"",
+    "build": "cd frontend && npm run build && cd ../backend && npm run build",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": ["byop", "docker", "react", "nodejs", "postgresql", "microservices"],
+  "author": "BYOP Team",
+  "license": "MIT",
+  "devDependencies": {
+    "concurrently": "^7.6.0"
+  }
+}