updated frontend

This commit is contained in:
magdo
2026-04-01 22:01:36 +02:00
parent 91e48d2178
commit 3ee9c03b85
42 changed files with 1498 additions and 23 deletions
+179
View File
@@ -0,0 +1,179 @@
# Test Data Generation Scripts
This directory contains scripts to help manage test data for the webstore backend.
## Available Scripts
### 0. Generate Placeholder Images
Creates placeholder JPEG images for all 17 products used in seeding.
**File:** `generate-placeholder-images.js`
**What it does:**
- Generates 17 placeholder product images (400x300px)
- Saves images to `../images/` directory
- Creates `uploads/` directory for user-uploaded images
**Usage:**
```bash
# Using npm script
npm run generate-images
# Or directly with Node
node scripts/generate-placeholder-images.js
```
**When to run:**
- First time setting up development environment
- When `/images` route returns 404 (images not found)
- After cleaning up the images directory
**Images created (17 total):**
- Shoes: street-runner.jpg, trail-edge.jpg, urban-sprint.jpg, classic-comfort.jpg
- Bags: urban-tote.jpg, backpack-pro.jpg, crossbody.jpg, duffle.jpg
- Accessories: classic-cap.jpg, silk-scarf.jpg, leather-belt.jpg, watch-straps.jpg
- Clothing: cotton-tshirt.jpg, denim-jacket.jpg, yoga-leggings.jpg
- Electronics: earbuds.jpg, usb-hub.jpg
---
### 1. Seed Script (Automatic)
Runs automatically when the backend starts in development mode.
**File:** `../prisma/seed.js`
**What it does:**
- Creates 5 product categories (Shoes, Bags, Accessories, Clothing, Electronics)
- Creates 16 sample products across all categories
- Creates 3 test user accounts
**Test User Credentials:**
```
Email: admin@test.com
Password: admin123
Email: john@test.com
Password: password123
Email: jane@test.com
Password: password123
```
---
## 2. Generate Test Data Script
Generates additional bulk test data on demand.
### Usage Options
#### Option A: Windows Batch File (Recommended for Windows)
```bash
# Generate default test data (10 products, 5 orders)
scripts\generate-test-data.bat
# Generate 20 products instead of 10
scripts\generate-test-data.bat --products 20
# Generate 20 products and 10 orders
scripts\generate-test-data.bat --products 20 --orders 10
# DANGEROUS: Reset database and regenerate all data
scripts\generate-test-data.bat --reset --products 50 --orders 20
```
#### Option B: NPM Script
```bash
# Generate default test data
npm run generate-data
# With arguments (from project root)
npm run generate-data -- --products 30 --orders 15
```
#### Option C: Direct Node.js
```bash
# From project root
node scripts/generate-test-data.js --products 25 --orders 10
```
### Command-line Options
| Option | Description | Default |
|--------|-------------|---------|
| `--products N` | Number of additional products to generate | 10 |
| `--orders N` | Number of test orders to generate | 5 |
| `--reset` | ⚠️ **DANGEROUS**: Delete all data first | (not set) |
### Examples
```bash
# Quick test: Generate 5 products and 2 orders
scripts\generate-test-data.bat --products 5 --orders 2
# Moderate data: 30 products and 10 orders
npm run generate-data -- --products 30 --orders 10
# Heavy load test: 100 products and 50 orders
scripts\generate-test-data.bat --products 100 --orders 50
# Start fresh: Reset and generate 50 products and 20 orders
scripts\generate-test-data.bat --reset --products 50 --orders 20
```
## What Gets Generated
### Products
- Random product names with auto-incrementing counters
- Random prices between 5,990 and 24,990 HUF
- Random stock quantities (5-55 units)
- Random category assignment
- Placeholder image URLs
### Test Users
- 5 additional test user accounts
- Email: `user[1-5]@test.com`
- Password: `password123`
### Orders
- Random customer names
- Random customer emails
- Random order items (1-3 products per order)
- Automatically calculated total prices
## Important Notes
⚠️ **WARNING**: The `--reset` flag will delete:
- All orders and order items
- All products
- All categories
- All users
Only use `--reset` if you know what you're doing!
## Troubleshooting
### "Node.js is not installed or not in PATH"
- Install Node.js from https://nodejs.org/
- Make sure it's in your system PATH
### "Database connection error"
- Ensure the database is running
- Check `.env` file has correct `DATABASE_URL`
- Run migrations first: `npm run prisma:push`
### "Unique constraint failed" errors
- Product names must be unique
- The script skips duplicates automatically
- Use `--reset` to start fresh
## Docker Compose Integration
The test data generation automatically runs in docker-compose:
```bash
# Start backend (will seed data automatically)
docker-compose up api
# The initial data is seeded when the container first starts
```
For production, you may want to disable auto-seeding in the `docker-compose.yml` file.
@@ -8,14 +8,14 @@ set "ARCHIVE_FILE=%IMAGE_OUT_DIR%\webstore-production-images.tar"
set "API_IMAGE=webstore-api:prod"
set "DB_IMAGE=postgres:16-alpine"
echo [1/6] Checking Docker availability...
echo [1/7] Checking Docker availability...
docker --version >nul 2>&1
if errorlevel 1 (
echo ERROR: Docker CLI is not available. Install Docker Desktop first.
exit /b 1
)
echo [2/6] Building production API image: %API_IMAGE%
echo [2/7] Building production API image: %API_IMAGE%
pushd "%ROOT_DIR%" >nul
docker build -t "%API_IMAGE%" -f Dockerfile .
if errorlevel 1 (
@@ -25,17 +25,33 @@ if errorlevel 1 (
)
popd >nul
echo [3/6] Pulling database image: %DB_IMAGE%
echo [3/7] Pulling database image: %DB_IMAGE%
docker pull "%DB_IMAGE%"
if errorlevel 1 (
echo ERROR: Failed to pull database image.
exit /b 1
)
echo [4/6] Preparing image output directory: %IMAGE_OUT_DIR%
echo [4/7] Preparing image output directory: %IMAGE_OUT_DIR%
if not exist "%IMAGE_OUT_DIR%" mkdir "%IMAGE_OUT_DIR%"
echo [5/6] Exporting images to archive...
echo [5/7] Creating placeholder product images...
pushd "%ROOT_DIR%" >nul
REM Create uploads directory
if not exist "%IMAGE_OUT_DIR%\uploads" mkdir "%IMAGE_OUT_DIR%\uploads"
REM Use Node.js to generate placeholder images reliably
echo Generating placeholder images using Node.js...
node "%ROOT_DIR%\scripts\generate-placeholder-images.js"
if errorlevel 1 (
echo WARNING: Could not generate placeholder images automatically
echo Note: Images can be added manually to %IMAGE_OUT_DIR%
)
popd >nul
echo [6/7] Exporting images to archive...
if exist "%ARCHIVE_FILE%" del /f /q "%ARCHIVE_FILE%"
docker save -o "%ARCHIVE_FILE%" "%API_IMAGE%" "%DB_IMAGE%"
if errorlevel 1 (
@@ -43,7 +59,35 @@ if errorlevel 1 (
exit /b 1
)
echo [6/6] Done.
echo [7/7] Done.
echo.
echo ========================================
echo PRODUCTION BUILD COMPLETE
echo ========================================
echo.
echo Created archive: "%ARCHIVE_FILE%"
echo Share this file with students together with production.compose.yml and .production.env.
echo.
echo FILES TO DISTRIBUTE TO STUDENTS:
echo 1. "%ARCHIVE_FILE%"
echo 2. "%IMAGE_OUT_DIR%\production.compose.yml"
echo 3. "%IMAGE_OUT_DIR%\.production.env" (with proper configuration)
echo 4. "%IMAGE_OUT_DIR%\run-student-production.bat"
echo.
echo WHAT'S INCLUDED:
echo - Production API Docker image
echo - PostgreSQL database image
echo - Initial seeding script (creates 5 categories, 16 products, 3 test users)
echo - Placeholder product images (17 images)
echo - Upload directory for student-generated images
echo.
echo STUDENTS SHOULD:
echo 1. Extract webstore-production-images.tar
echo 2. Copy .production.env and configure database credentials
echo 3. Run run-student-production.bat
echo 4. Database will auto-seed with sample data
echo 5. Test users available:
echo - admin@test.com / admin123
echo - john@test.com / password123
echo - jane@test.com / password123
echo.
exit /b 0
@@ -0,0 +1,112 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Image list from README
const images = [
// Shoes
'street-runner.jpg',
'trail-edge.jpg',
'urban-sprint.jpg',
'classic-comfort.jpg',
// Bags
'urban-tote.jpg',
'backpack-pro.jpg',
'crossbody.jpg',
'duffle.jpg',
// Accessories
'classic-cap.jpg',
'silk-scarf.jpg',
'leather-belt.jpg',
'watch-straps.jpg',
// Clothing
'cotton-tshirt.jpg',
'denim-jacket.jpg',
'yoga-leggings.jpg',
// Electronics
'earbuds.jpg',
'usb-hub.jpg',
];
const imagesDir = path.join(__dirname, '..', 'images');
const uploadsDir = path.join(imagesDir, 'uploads');
// Create directories if they don't exist
if (!fs.existsSync(imagesDir)) {
fs.mkdirSync(imagesDir, { recursive: true });
console.log(`Created ${imagesDir}`);
}
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
console.log(`Created ${uploadsDir}`);
}
// Create simple JPEG placeholder images (minimal valid JPEG structure)
// This is a minimal JPEG structure that works with image viewers
const createPlaceholderJPEG = (filename, width, height) => {
// Minimal JPEG header and footer
const jpegHeader = Buffer.from([
0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46,
0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43,
0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08,
0x07, 0x07, 0x07, 0x09, 0x09, 0x08, 0x0A, 0x0C,
0x14, 0x0D, 0x0C, 0x0B, 0x0B, 0x0C, 0x19, 0x12,
0x13, 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D,
0x1A, 0x1C, 0x1C, 0x20, 0x24, 0x2E, 0x27, 0x20,
0x22, 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29,
0x2C, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1F, 0x27,
0x39, 0x3D, 0x38, 0x32, 0x3C, 0x2E, 0x33, 0x34,
0x32, 0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, height,
0x00, width, 0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4,
0x00, 0x1F, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xFF,
0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, 0x01, 0x03,
0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04,
0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, 0x03, 0x00,
0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,
0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1,
0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72,
0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A,
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35,
0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45,
0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65,
0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75,
0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85,
0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3,
0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2,
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA,
0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8,
0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6,
0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4,
0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xDA,
0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3F, 0x00,
0xFB, 0xD0, 0xFF, 0xD9,
]);
return jpegHeader;
};
let created = 0;
images.forEach((filename) => {
const filepath = path.join(imagesDir, filename);
if (!fs.existsSync(filepath)) {
const jpegData = createPlaceholderJPEG(filename, 400, 300);
fs.writeFileSync(filepath, jpegData);
console.log(`✓ Created ${filename}`);
created++;
} else {
console.log(`✓ Already exists: ${filename}`);
}
});
console.log(`\n${created} placeholder images generated successfully!`);
console.log(`Images directory: ${imagesDir}`);
@@ -0,0 +1,87 @@
@echo off
setlocal EnableExtensions EnableDelayedExpansion
::
:: Generate test data script for webstore backend
:: Usage: generate-test-data.bat [--products N] [--orders N] [--reset]
::
:: Examples:
:: generate-test-data.bat (Creates 10 products, 5 orders)
:: generate-test-data.bat --products 20 (Creates 20 products, 5 orders)
:: generate-test-data.bat --products 50 --orders 10 --reset (Resets DB and generates data)
::
cd /d "%~dp0.."
echo.
echo ========================================
echo Test Data Generator for Webstore
echo ========================================
echo.
set "PRODUCTS=10"
set "ORDERS=5"
set "RESET_FLAG="
:parse_args
if "%~1"=="" goto start_generation
if "%~1"=="--products" (
if "%~2"=="" (
echo ERROR: --products requires a number argument
exit /b 1
)
set "PRODUCTS=%~2"
shift
shift
goto parse_args
)
if "%~1"=="--orders" (
if "%~2"=="" (
echo ERROR: --orders requires a number argument
exit /b 1
)
set "ORDERS=%~2"
shift
shift
goto parse_args
)
if "%~1"=="--reset" (
set "RESET_FLAG=--reset"
shift
goto parse_args
)
shift
goto parse_args
:start_generation
echo Configuration:
echo Products to generate: %PRODUCTS%
echo Orders to generate: %ORDERS%
if defined RESET_FLAG (
echo Reset database: YES (WARNING: All data will be deleted!)
) else (
echo Reset database: NO
)
echo.
echo Checking Node.js availability...
node --version >nul 2>&1
if errorlevel 1 (
echo ERROR: Node.js is not installed or not in PATH
exit /b 1
)
echo Running test data generator...
node scripts/generate-test-data.js --products %PRODUCTS% --orders %ORDERS% %RESET_FLAG%
if errorlevel 1 (
echo.
echo ERROR: Test data generation failed!
exit /b 1
)
echo.
echo ========================================
echo ✓ Test data generated successfully!
echo ========================================
exit /b 0
@@ -0,0 +1,263 @@
#!/usr/bin/env node
/**
* Script to generate bulk test data for the webstore database
* Usage: node scripts/generate-test-data.js [options]
*
* Options:
* --products N Generate N additional products (default: 10)
* --orders N Generate N test orders (default: 5)
* --reset Delete all data before seeding (careful!)
*/
require("dotenv").config({ path: ".env" });
const { PrismaClient } = require("@prisma/client");
const bcryptjs = require("bcryptjs");
const prisma = new PrismaClient();
// Parse command line arguments
const args = process.argv.slice(2);
const options = {
productsCount: 10,
ordersCount: 5,
shouldReset: false
};
for (let i = 0; i < args.length; i++) {
if (args[i] === "--products" && args[i + 1]) {
options.productsCount = parseInt(args[i + 1], 10);
i++;
} else if (args[i] === "--orders" && args[i + 1]) {
options.ordersCount = parseInt(args[i + 1], 10);
i++;
} else if (args[i] === "--reset") {
options.shouldReset = true;
}
}
const productNames = [
"Premium Leather Boots",
"Summer Canvas Shoes",
"Minimalist Sneakers",
"Casual Loafers",
"Running Shoes Pro",
"Hiking Boots",
"Beach Sandals",
"Formal Dress Shoes",
"Winter Snow Boots",
"Slip-on Comfort Shoes",
"Designer Heels",
"Waterproof Outdoor Boots",
"Lightweight Mesh Runners",
"Classic Oxfords",
"Athletic Training Shoes"
];
const descriptions = [
"Premium quality with exceptional comfort",
"Perfect for everyday wear and activities",
"Stylish design with modern aesthetics",
"Durable materials built to last",
"Eco-friendly sustainable production",
"Handcrafted with attention to detail",
"Weather-resistant and waterproof",
"Ergonomic design for all-day comfort",
"Available in multiple colors",
"Perfect for professional environments"
];
function generateRandomPrice() {
return (Math.floor(Math.random() * 20) + 5).toString() + "990.00";
}
function generateRandomStock() {
return Math.floor(Math.random() * 50) + 5;
}
function getRandomElement(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
async function resetDatabase() {
console.log("⚠️ Resetting database...");
try {
// Delete in order of dependencies
await prisma.orderItem.deleteMany({});
await prisma.order.deleteMany({});
await prisma.product.deleteMany({});
await prisma.category.deleteMany({});
await prisma.user.deleteMany({});
console.log("✓ Database reset completed");
} catch (error) {
console.error("Error resetting database:", error.message);
throw error;
}
}
async function seedInitialData() {
console.log("Seeding initial categories...");
const categories = [
{ name: "Shoes", slug: "shoes" },
{ name: "Bags", slug: "bags" },
{ name: "Accessories", slug: "accessories" },
{ name: "Clothing", slug: "clothing" },
{ name: "Electronics", slug: "electronics" }
];
for (const category of categories) {
await prisma.category.upsert({
where: { slug: category.slug },
update: {},
create: category
});
}
console.log("✓ Categories seeded");
}
async function generateProducts(count) {
console.log(`\nGenerating ${count} additional products...`);
const categories = await prisma.category.findMany();
const generatedProducts = [];
for (let i = 0; i < count; i++) {
const category = getRandomElement(categories);
const name = `${getRandomElement(productNames)} ${i + 1}`;
try {
const product = await prisma.product.create({
data: {
name,
description: getRandomElement(descriptions),
price: generateRandomPrice(),
stock: generateRandomStock(),
categoryId: category.id,
imageUrl: `/images/placeholder-${i + 1}.jpg`
}
});
generatedProducts.push(product);
if ((i + 1) % 5 === 0) {
process.stdout.write(`\r Progress: ${i + 1}/${count} products`);
}
} catch (error) {
if (!error.message.includes("Unique constraint failed")) {
console.error(`\nError creating product: ${error.message}`);
}
}
}
console.log(`\r✓ Generated ${generatedProducts.length} products`);
return generatedProducts;
}
async function generateTestUsers(count = 5) {
console.log(`\nGenerating ${count} test users...`);
const users = [];
for (let i = 1; i <= count; i++) {
const email = `user${i}@test.com`;
const passwordHash = await bcryptjs.hash("password123", 10);
try {
const user = await prisma.user.upsert({
where: { email },
update: {},
create: {
name: `Test User ${i}`,
email,
passwordHash
}
});
users.push(user);
} catch (error) {
console.error(`Error creating user: ${error.message}`);
}
}
console.log(`✓ Generated ${users.length} test users`);
return users;
}
async function generateOrders(count) {
console.log(`\nGenerating ${count} test orders...`);
const products = await prisma.product.findMany({ take: 20 });
const createdOrders = [];
for (let i = 0; i < count; i++) {
try {
const itemsCount = Math.floor(Math.random() * 3) + 1;
const items = [];
let totalPrice = 0;
for (let j = 0; j < itemsCount; j++) {
const product = getRandomElement(products);
const quantity = Math.floor(Math.random() * 3) + 1;
items.push({
productId: product.id,
quantity,
unitPrice: product.price
});
totalPrice += parseFloat(product.price) * quantity;
}
const order = await prisma.order.create({
data: {
customerName: `Customer ${i + 1}`,
customerEmail: `customer${i + 1}@example.com`,
totalPrice: totalPrice.toFixed(2),
items: {
create: items
}
},
include: {
items: true
}
});
createdOrders.push(order);
if ((i + 1) % 2 === 0) {
process.stdout.write(`\r Progress: ${i + 1}/${count} orders`);
}
} catch (error) {
console.error(`\nError creating order: ${error.message}`);
}
}
console.log(`\r✓ Generated ${createdOrders.length} orders`);
return createdOrders;
}
async function main() {
console.log("🚀 Test Data Generator for Webstore\n");
console.log("Options:");
console.log(` Products to generate: ${options.productsCount}`);
console.log(` Orders to generate: ${options.ordersCount}`);
console.log(` Reset database first: ${options.shouldReset ? "YES" : "NO"}\n`);
try {
if (options.shouldReset) {
await resetDatabase();
}
await seedInitialData();
await generateTestUsers(5);
await generateProducts(options.productsCount);
await generateOrders(options.ordersCount);
console.log("\n✨ Test data generation completed successfully!");
} catch (error) {
console.error("\n❌ Error during data generation:", error.message);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
main();