541 lines
16 KiB
PHP
541 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* API-Controller für das Webshop-System
|
|
*
|
|
* @author Webshop System
|
|
* @license GPL v3
|
|
*/
|
|
|
|
namespace App\API\Controllers;
|
|
|
|
use Doctrine\DBAL\DriverManager;
|
|
use Doctrine\DBAL\Exception;
|
|
|
|
class ApiController
|
|
{
|
|
private $conn;
|
|
private $apiKey;
|
|
private $allowedOrigins = ['*'];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->initDatabase();
|
|
$this->handleCors();
|
|
$this->validateApiKey();
|
|
}
|
|
|
|
/**
|
|
* Datenbank-Verbindung initialisieren
|
|
*/
|
|
private function initDatabase()
|
|
{
|
|
$connectionParams = [
|
|
'dbname' => getenv('DB_DATABASE') ?: 'freeshop',
|
|
'user' => getenv('DB_USERNAME') ?: 'freeshop_user',
|
|
'password' => getenv('DB_PASSWORD') ?: 'freeshop_password',
|
|
'host' => getenv('DB_HOST') ?: 'db',
|
|
'driver' => 'pdo_mysql',
|
|
'port' => getenv('DB_PORT') ?: 3306,
|
|
'charset' => 'utf8mb4',
|
|
];
|
|
|
|
try {
|
|
$this->conn = DriverManager::getConnection($connectionParams);
|
|
} catch (Exception $e) {
|
|
$this->sendError('Database connection failed', 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CORS-Header setzen
|
|
*/
|
|
private function handleCors()
|
|
{
|
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
|
|
|
|
if (in_array('*', $this->allowedOrigins) || in_array($origin, $this->allowedOrigins)) {
|
|
header('Access-Control-Allow-Origin: ' . $origin);
|
|
}
|
|
|
|
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
|
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key');
|
|
header('Access-Control-Max-Age: 86400');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
http_response_code(200);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API-Key validieren
|
|
*/
|
|
private function validateApiKey()
|
|
{
|
|
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? $_GET['api_key'] ?? null;
|
|
|
|
if (!$apiKey) {
|
|
$this->sendError('API key required', 401);
|
|
}
|
|
|
|
// API-Key in der Datenbank validieren
|
|
try {
|
|
$stmt = $this->conn->prepare('SELECT * FROM ws_api_key WHERE key_value = ? AND active = 1');
|
|
$stmt->execute([$apiKey]);
|
|
$keyData = $stmt->fetchAssociative();
|
|
|
|
if (!$keyData) {
|
|
$this->sendError('Invalid API key', 401);
|
|
}
|
|
|
|
$this->apiKey = $keyData;
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('API key validation failed', 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Produkte abrufen
|
|
*/
|
|
public function getProducts()
|
|
{
|
|
$page = max(1, intval($_GET['page'] ?? 1));
|
|
$limit = min(50, max(1, intval($_GET['limit'] ?? 20)));
|
|
$category = $_GET['category'] ?? null;
|
|
$search = $_GET['search'] ?? null;
|
|
$minPrice = $_GET['min_price'] ?? null;
|
|
$maxPrice = $_GET['max_price'] ?? null;
|
|
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
try {
|
|
$whereConditions = ['p.active = 1'];
|
|
$params = [];
|
|
|
|
if ($category) {
|
|
$whereConditions[] = 'p.category_id = ?';
|
|
$params[] = $category;
|
|
}
|
|
|
|
if ($search) {
|
|
$whereConditions[] = '(p.name LIKE ? OR p.description LIKE ?)';
|
|
$params[] = '%' . $search . '%';
|
|
$params[] = '%' . $search . '%';
|
|
}
|
|
|
|
if ($minPrice) {
|
|
$whereConditions[] = 'p.price >= ?';
|
|
$params[] = $minPrice;
|
|
}
|
|
|
|
if ($maxPrice) {
|
|
$whereConditions[] = 'p.price <= ?';
|
|
$params[] = $maxPrice;
|
|
}
|
|
|
|
$whereClause = implode(' AND ', $whereConditions);
|
|
|
|
// Gesamtanzahl
|
|
$countSql = "
|
|
SELECT COUNT(*) as total
|
|
FROM ws_product p
|
|
LEFT JOIN ws_category c ON p.category_id = c.id
|
|
WHERE $whereClause
|
|
";
|
|
|
|
$stmt = $this->conn->prepare($countSql);
|
|
$stmt->execute($params);
|
|
$totalCount = $stmt->fetchAssociative()['total'];
|
|
|
|
// Produkte laden
|
|
$sql = "
|
|
SELECT p.*, c.name as category_name
|
|
FROM ws_product p
|
|
LEFT JOIN ws_category c ON p.category_id = c.id
|
|
WHERE $whereClause
|
|
ORDER BY p.created_at DESC
|
|
LIMIT $limit OFFSET $offset
|
|
";
|
|
|
|
$stmt = $this->conn->prepare($sql);
|
|
$stmt->execute($params);
|
|
$products = $stmt->fetchAllAssociative();
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => $products,
|
|
'pagination' => [
|
|
'page' => $page,
|
|
'limit' => $limit,
|
|
'total' => $totalCount,
|
|
'pages' => ceil($totalCount / $limit)
|
|
]
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to fetch products: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Einzelnes Produkt abrufen
|
|
*/
|
|
public function getProduct($id)
|
|
{
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT p.*, c.name as category_name
|
|
FROM ws_product p
|
|
LEFT JOIN ws_category c ON p.category_id = c.id
|
|
WHERE p.id = ? AND p.active = 1
|
|
');
|
|
$stmt->execute([$id]);
|
|
$product = $stmt->fetchAssociative();
|
|
|
|
if (!$product) {
|
|
$this->sendError('Product not found', 404);
|
|
}
|
|
|
|
// Bewertungen laden
|
|
$stmt = $this->conn->prepare('
|
|
SELECT r.*, c.first_name, c.last_name
|
|
FROM ws_review r
|
|
LEFT JOIN ws_customer c ON r.customer_id = c.id
|
|
WHERE r.product_id = ? AND r.active = 1
|
|
ORDER BY r.created_at DESC
|
|
');
|
|
$stmt->execute([$id]);
|
|
$reviews = $stmt->fetchAllAssociative();
|
|
|
|
$product['reviews'] = $reviews;
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => $product
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to fetch product: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kategorien abrufen
|
|
*/
|
|
public function getCategories()
|
|
{
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT * FROM ws_category
|
|
WHERE active = 1
|
|
ORDER BY sort_order ASC, name ASC
|
|
');
|
|
$stmt->execute();
|
|
$categories = $stmt->fetchAllAssociative();
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => $categories
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to fetch categories: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bestellung erstellen
|
|
*/
|
|
public function createOrder()
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input) {
|
|
$this->sendError('Invalid JSON input', 400);
|
|
}
|
|
|
|
$required = ['customer_name', 'customer_email', 'items'];
|
|
foreach ($required as $field) {
|
|
if (empty($input[$field])) {
|
|
$this->sendError("Missing required field: $field", 400);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$this->conn->beginTransaction();
|
|
|
|
// Bestellung erstellen
|
|
$stmt = $this->conn->prepare('
|
|
INSERT INTO ws_order (customer_name, customer_email, customer_phone,
|
|
total_amount, status, created_at)
|
|
VALUES (?, ?, ?, ?, ?, NOW())
|
|
');
|
|
|
|
$totalAmount = 0;
|
|
foreach ($input['items'] as $item) {
|
|
$totalAmount += $item['price'] * $item['quantity'];
|
|
}
|
|
|
|
$stmt->execute([
|
|
$input['customer_name'],
|
|
$input['customer_email'],
|
|
$input['customer_phone'] ?? '',
|
|
$totalAmount,
|
|
'pending'
|
|
]);
|
|
|
|
$orderId = $this->conn->lastInsertId();
|
|
|
|
// Bestellpositionen erstellen
|
|
$stmt = $this->conn->prepare('
|
|
INSERT INTO ws_order_item (order_id, product_id, product_name,
|
|
quantity, price, total_price)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
');
|
|
|
|
foreach ($input['items'] as $item) {
|
|
$stmt->execute([
|
|
$orderId,
|
|
$item['product_id'],
|
|
$item['product_name'],
|
|
$item['quantity'],
|
|
$item['price'],
|
|
$item['price'] * $item['quantity']
|
|
]);
|
|
}
|
|
|
|
$this->conn->commit();
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => [
|
|
'order_id' => $orderId,
|
|
'total_amount' => $totalAmount
|
|
],
|
|
'message' => 'Order created successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->conn->rollBack();
|
|
$this->sendError('Failed to create order: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bestellung abrufen
|
|
*/
|
|
public function getOrder($id)
|
|
{
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT * FROM ws_order WHERE id = ?
|
|
');
|
|
$stmt->execute([$id]);
|
|
$order = $stmt->fetchAssociative();
|
|
|
|
if (!$order) {
|
|
$this->sendError('Order not found', 404);
|
|
}
|
|
|
|
// Bestellpositionen laden
|
|
$stmt = $this->conn->prepare('
|
|
SELECT * FROM ws_order_item WHERE order_id = ?
|
|
');
|
|
$stmt->execute([$id]);
|
|
$items = $stmt->fetchAllAssociative();
|
|
|
|
$order['items'] = $items;
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => $order
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to fetch order: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bestellstatus aktualisieren
|
|
*/
|
|
public function updateOrderStatus($id)
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (empty($input['status'])) {
|
|
$this->sendError('Status is required', 400);
|
|
}
|
|
|
|
$allowedStatuses = ['pending', 'confirmed', 'shipped', 'delivered', 'cancelled'];
|
|
if (!in_array($input['status'], $allowedStatuses)) {
|
|
$this->sendError('Invalid status', 400);
|
|
}
|
|
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
UPDATE ws_order
|
|
SET status = ?, updated_at = NOW()
|
|
WHERE id = ?
|
|
');
|
|
$stmt->execute([$input['status'], $id]);
|
|
|
|
if ($stmt->rowCount() === 0) {
|
|
$this->sendError('Order not found', 404);
|
|
}
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'message' => 'Order status updated successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to update order status: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kunde erstellen
|
|
*/
|
|
public function createCustomer()
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input) {
|
|
$this->sendError('Invalid JSON input', 400);
|
|
}
|
|
|
|
$required = ['email', 'first_name', 'last_name'];
|
|
foreach ($required as $field) {
|
|
if (empty($input[$field])) {
|
|
$this->sendError("Missing required field: $field", 400);
|
|
}
|
|
}
|
|
|
|
if (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
|
|
$this->sendError('Invalid email address', 400);
|
|
}
|
|
|
|
try {
|
|
// Prüfen ob E-Mail bereits existiert
|
|
$stmt = $this->conn->prepare('SELECT id FROM ws_customer WHERE email = ?');
|
|
$stmt->execute([$input['email']]);
|
|
$existing = $stmt->fetchAssociative();
|
|
|
|
if ($existing) {
|
|
$this->sendError('Email already exists', 409);
|
|
}
|
|
|
|
// Kunde erstellen
|
|
$stmt = $this->conn->prepare('
|
|
INSERT INTO ws_customer (email, first_name, last_name, phone,
|
|
newsletter, active, created_at)
|
|
VALUES (?, ?, ?, ?, ?, 1, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$input['email'],
|
|
$input['first_name'],
|
|
$input['last_name'],
|
|
$input['phone'] ?? '',
|
|
isset($input['newsletter']) ? 1 : 0
|
|
]);
|
|
|
|
$customerId = $this->conn->lastInsertId();
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => [
|
|
'customer_id' => $customerId,
|
|
'email' => $input['email']
|
|
],
|
|
'message' => 'Customer created successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to create customer: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API-Statistiken
|
|
*/
|
|
public function getStats()
|
|
{
|
|
try {
|
|
// Produkt-Statistiken
|
|
$stmt = $this->conn->prepare('SELECT COUNT(*) as total FROM ws_product WHERE active = 1');
|
|
$stmt->execute();
|
|
$productCount = $stmt->fetchAssociative()['total'];
|
|
|
|
// Kategorie-Statistiken
|
|
$stmt = $this->conn->prepare('SELECT COUNT(*) as total FROM ws_category WHERE active = 1');
|
|
$stmt->execute();
|
|
$categoryCount = $stmt->fetchAssociative()['total'];
|
|
|
|
// Bestellungs-Statistiken
|
|
$stmt = $this->conn->prepare('SELECT COUNT(*) as total FROM ws_order');
|
|
$stmt->execute();
|
|
$orderCount = $stmt->fetchAssociative()['total'];
|
|
|
|
// Kunden-Statistiken
|
|
$stmt = $this->conn->prepare('SELECT COUNT(*) as total FROM ws_customer WHERE active = 1');
|
|
$stmt->execute();
|
|
$customerCount = $stmt->fetchAssociative()['total'];
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => [
|
|
'products' => $productCount,
|
|
'categories' => $categoryCount,
|
|
'orders' => $orderCount,
|
|
'customers' => $customerCount
|
|
]
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to fetch statistics: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Erfolgreiche Antwort senden
|
|
*/
|
|
private function sendResponse($data, $statusCode = 200)
|
|
{
|
|
http_response_code($statusCode);
|
|
header('Content-Type: application/json');
|
|
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Fehler-Antwort senden
|
|
*/
|
|
private function sendError($message, $statusCode = 400)
|
|
{
|
|
http_response_code($statusCode);
|
|
header('Content-Type: application/json');
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => $message,
|
|
'status_code' => $statusCode
|
|
], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
}
|