604 lines
20 KiB
PHP
604 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* Copyright seit 2024 Webshop System
|
|
*
|
|
* Customer 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 CustomerApiController extends ApiController
|
|
{
|
|
/**
|
|
* Kunde registrieren
|
|
*/
|
|
public function register()
|
|
{
|
|
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);
|
|
}
|
|
|
|
// Validierung
|
|
$required = ['email', 'password', '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);
|
|
}
|
|
|
|
if (strlen($input['password']) < 8) {
|
|
$this->sendError('Password must be at least 8 characters long', 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);
|
|
}
|
|
|
|
// Passwort hashen
|
|
$hashedPassword = password_hash($input['password'], PASSWORD_DEFAULT);
|
|
|
|
// Kunde erstellen
|
|
$stmt = $this->conn->prepare('
|
|
INSERT INTO ws_customer (email, password, first_name, last_name,
|
|
phone, newsletter, active, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, 1, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$input['email'],
|
|
$hashedPassword,
|
|
$input['first_name'],
|
|
$input['last_name'],
|
|
$input['phone'] ?? '',
|
|
isset($input['newsletter']) ? 1 : 0
|
|
]);
|
|
|
|
$customerId = $this->conn->lastInsertId();
|
|
|
|
// JWT-Token generieren
|
|
$token = $this->generateJWT($customerId, $input['email']);
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => [
|
|
'customer_id' => $customerId,
|
|
'email' => $input['email'],
|
|
'first_name' => $input['first_name'],
|
|
'last_name' => $input['last_name'],
|
|
'token' => $token
|
|
],
|
|
'message' => 'Customer registered successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to register customer: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kunde anmelden
|
|
*/
|
|
public function login()
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (empty($input['email']) || empty($input['password'])) {
|
|
$this->sendError('Email and password are required', 400);
|
|
}
|
|
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT id, email, password, first_name, last_name, active
|
|
FROM ws_customer
|
|
WHERE email = ?
|
|
');
|
|
$stmt->execute([$input['email']]);
|
|
$customer = $stmt->fetchAssociative();
|
|
|
|
if (!$customer) {
|
|
$this->sendError('Invalid credentials', 401);
|
|
}
|
|
|
|
if (!$customer['active']) {
|
|
$this->sendError('Account is deactivated', 401);
|
|
}
|
|
|
|
if (!password_verify($input['password'], $customer['password'])) {
|
|
$this->sendError('Invalid credentials', 401);
|
|
}
|
|
|
|
// JWT-Token generieren
|
|
$token = $this->generateJWT($customer['id'], $customer['email']);
|
|
|
|
// Login-Zeit aktualisieren
|
|
$stmt = $this->conn->prepare('
|
|
UPDATE ws_customer
|
|
SET last_login = NOW()
|
|
WHERE id = ?
|
|
');
|
|
$stmt->execute([$customer['id']]);
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => [
|
|
'customer_id' => $customer['id'],
|
|
'email' => $customer['email'],
|
|
'first_name' => $customer['first_name'],
|
|
'last_name' => $customer['last_name'],
|
|
'token' => $token
|
|
],
|
|
'message' => 'Login successful'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to login: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kundenprofil abrufen
|
|
*/
|
|
public function getProfile($customerId)
|
|
{
|
|
$token = $this->getBearerToken();
|
|
$decodedToken = $this->validateJWT($token);
|
|
|
|
if (!$decodedToken || $decodedToken['customer_id'] != $customerId) {
|
|
$this->sendError('Unauthorized', 401);
|
|
}
|
|
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
SELECT id, email, first_name, last_name, phone, newsletter,
|
|
created_at, last_login
|
|
FROM ws_customer
|
|
WHERE id = ? AND active = 1
|
|
');
|
|
$stmt->execute([$customerId]);
|
|
$customer = $stmt->fetchAssociative();
|
|
|
|
if (!$customer) {
|
|
$this->sendError('Customer not found', 404);
|
|
}
|
|
|
|
// Adressen laden
|
|
$stmt = $this->conn->prepare('
|
|
SELECT * FROM ws_customer_address
|
|
WHERE customer_id = ?
|
|
');
|
|
$stmt->execute([$customerId]);
|
|
$addresses = $stmt->fetchAllAssociative();
|
|
|
|
$customer['addresses'] = $addresses;
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => $customer
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to get profile: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kundenprofil aktualisieren
|
|
*/
|
|
public function updateProfile($customerId)
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$token = $this->getBearerToken();
|
|
$decodedToken = $this->validateJWT($token);
|
|
|
|
if (!$decodedToken || $decodedToken['customer_id'] != $customerId) {
|
|
$this->sendError('Unauthorized', 401);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input) {
|
|
$this->sendError('Invalid JSON input', 400);
|
|
}
|
|
|
|
try {
|
|
$updateFields = [];
|
|
$params = [];
|
|
|
|
if (isset($input['first_name'])) {
|
|
$updateFields[] = 'first_name = ?';
|
|
$params[] = $input['first_name'];
|
|
}
|
|
|
|
if (isset($input['last_name'])) {
|
|
$updateFields[] = 'last_name = ?';
|
|
$params[] = $input['last_name'];
|
|
}
|
|
|
|
if (isset($input['phone'])) {
|
|
$updateFields[] = 'phone = ?';
|
|
$params[] = $input['phone'];
|
|
}
|
|
|
|
if (isset($input['newsletter'])) {
|
|
$updateFields[] = 'newsletter = ?';
|
|
$params[] = $input['newsletter'] ? 1 : 0;
|
|
}
|
|
|
|
if (empty($updateFields)) {
|
|
$this->sendError('No fields to update', 400);
|
|
}
|
|
|
|
$updateFields[] = 'updated_at = NOW()';
|
|
$params[] = $customerId;
|
|
|
|
$sql = 'UPDATE ws_customer SET ' . implode(', ', $updateFields) . ' WHERE id = ?';
|
|
$stmt = $this->conn->prepare($sql);
|
|
$stmt->execute($params);
|
|
|
|
if ($stmt->rowCount() === 0) {
|
|
$this->sendError('Customer not found', 404);
|
|
}
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'message' => 'Profile updated successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to update profile: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Passwort ändern
|
|
*/
|
|
public function changePassword($customerId)
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$token = $this->getBearerToken();
|
|
$decodedToken = $this->validateJWT($token);
|
|
|
|
if (!$decodedToken || $decodedToken['customer_id'] != $customerId) {
|
|
$this->sendError('Unauthorized', 401);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input) {
|
|
$this->sendError('Invalid JSON input', 400);
|
|
}
|
|
|
|
if (empty($input['current_password']) || empty($input['new_password'])) {
|
|
$this->sendError('Current password and new password are required', 400);
|
|
}
|
|
|
|
if (strlen($input['new_password']) < 8) {
|
|
$this->sendError('New password must be at least 8 characters long', 400);
|
|
}
|
|
|
|
try {
|
|
// Aktuelles Passwort prüfen
|
|
$stmt = $this->conn->prepare('SELECT password FROM ws_customer WHERE id = ?');
|
|
$stmt->execute([$customerId]);
|
|
$customer = $stmt->fetchAssociative();
|
|
|
|
if (!$customer) {
|
|
$this->sendError('Customer not found', 404);
|
|
}
|
|
|
|
if (!password_verify($input['current_password'], $customer['password'])) {
|
|
$this->sendError('Current password is incorrect', 400);
|
|
}
|
|
|
|
// Neues Passwort hashen und speichern
|
|
$hashedPassword = password_hash($input['new_password'], PASSWORD_DEFAULT);
|
|
|
|
$stmt = $this->conn->prepare('
|
|
UPDATE ws_customer
|
|
SET password = ?, updated_at = NOW()
|
|
WHERE id = ?
|
|
');
|
|
$stmt->execute([$hashedPassword, $customerId]);
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'message' => 'Password changed successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to change password: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adresse hinzufügen
|
|
*/
|
|
public function addAddress($customerId)
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$token = $this->getBearerToken();
|
|
$decodedToken = $this->validateJWT($token);
|
|
|
|
if (!$decodedToken || $decodedToken['customer_id'] != $customerId) {
|
|
$this->sendError('Unauthorized', 401);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input) {
|
|
$this->sendError('Invalid JSON input', 400);
|
|
}
|
|
|
|
$required = ['first_name', 'last_name', 'address', 'city', 'postal_code', 'country'];
|
|
foreach ($required as $field) {
|
|
if (empty($input[$field])) {
|
|
$this->sendError("Missing required field: $field", 400);
|
|
}
|
|
}
|
|
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
INSERT INTO ws_customer_address (customer_id, first_name, last_name,
|
|
company, address, address2, city,
|
|
postal_code, country, phone, is_default)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
');
|
|
|
|
$stmt->execute([
|
|
$customerId,
|
|
$input['first_name'],
|
|
$input['last_name'],
|
|
$input['company'] ?? '',
|
|
$input['address'],
|
|
$input['address2'] ?? '',
|
|
$input['city'],
|
|
$input['postal_code'],
|
|
$input['country'],
|
|
$input['phone'] ?? '',
|
|
isset($input['is_default']) ? 1 : 0
|
|
]);
|
|
|
|
$addressId = $this->conn->lastInsertId();
|
|
|
|
// Wenn Standard-Adresse, andere Adressen auf nicht-Standard setzen
|
|
if (isset($input['is_default']) && $input['is_default']) {
|
|
$stmt = $this->conn->prepare('
|
|
UPDATE ws_customer_address
|
|
SET is_default = 0
|
|
WHERE customer_id = ? AND id != ?
|
|
');
|
|
$stmt->execute([$customerId, $addressId]);
|
|
}
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'data' => [
|
|
'address_id' => $addressId
|
|
],
|
|
'message' => 'Address added successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to add address: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adresse aktualisieren
|
|
*/
|
|
public function updateAddress($customerId, $addressId)
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$token = $this->getBearerToken();
|
|
$decodedToken = $this->validateJWT($token);
|
|
|
|
if (!$decodedToken || $decodedToken['customer_id'] != $customerId) {
|
|
$this->sendError('Unauthorized', 401);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input) {
|
|
$this->sendError('Invalid JSON input', 400);
|
|
}
|
|
|
|
try {
|
|
$updateFields = [];
|
|
$params = [];
|
|
|
|
$fields = ['first_name', 'last_name', 'company', 'address', 'address2',
|
|
'city', 'postal_code', 'country', 'phone', 'is_default'];
|
|
|
|
foreach ($fields as $field) {
|
|
if (isset($input[$field])) {
|
|
$updateFields[] = "$field = ?";
|
|
$params[] = $input[$field];
|
|
}
|
|
}
|
|
|
|
if (empty($updateFields)) {
|
|
$this->sendError('No fields to update', 400);
|
|
}
|
|
|
|
$params[] = $addressId;
|
|
$params[] = $customerId;
|
|
|
|
$sql = 'UPDATE ws_customer_address SET ' . implode(', ', $updateFields) .
|
|
' WHERE id = ? AND customer_id = ?';
|
|
$stmt = $this->conn->prepare($sql);
|
|
$stmt->execute($params);
|
|
|
|
if ($stmt->rowCount() === 0) {
|
|
$this->sendError('Address not found', 404);
|
|
}
|
|
|
|
// Wenn Standard-Adresse, andere Adressen auf nicht-Standard setzen
|
|
if (isset($input['is_default']) && $input['is_default']) {
|
|
$stmt = $this->conn->prepare('
|
|
UPDATE ws_customer_address
|
|
SET is_default = 0
|
|
WHERE customer_id = ? AND id != ?
|
|
');
|
|
$stmt->execute([$customerId, $addressId]);
|
|
}
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'message' => 'Address updated successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to update address: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adresse löschen
|
|
*/
|
|
public function deleteAddress($customerId, $addressId)
|
|
{
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
|
|
$this->sendError('Method not allowed', 405);
|
|
}
|
|
|
|
$token = $this->getBearerToken();
|
|
$decodedToken = $this->validateJWT($token);
|
|
|
|
if (!$decodedToken || $decodedToken['customer_id'] != $customerId) {
|
|
$this->sendError('Unauthorized', 401);
|
|
}
|
|
|
|
try {
|
|
$stmt = $this->conn->prepare('
|
|
DELETE FROM ws_customer_address
|
|
WHERE id = ? AND customer_id = ?
|
|
');
|
|
$stmt->execute([$addressId, $customerId]);
|
|
|
|
if ($stmt->rowCount() === 0) {
|
|
$this->sendError('Address not found', 404);
|
|
}
|
|
|
|
$this->sendResponse([
|
|
'success' => true,
|
|
'message' => 'Address deleted successfully'
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
$this->sendError('Failed to delete address: ' . $e->getMessage(), 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* JWT-Token generieren
|
|
*/
|
|
private function generateJWT($customerId, $email)
|
|
{
|
|
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
|
|
$payload = json_encode([
|
|
'customer_id' => $customerId,
|
|
'email' => $email,
|
|
'iat' => time(),
|
|
'exp' => time() + (60 * 60 * 24 * 7) // 7 Tage
|
|
]);
|
|
|
|
$base64Header = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
|
|
$base64Payload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));
|
|
|
|
$signature = hash_hmac('sha256', $base64Header . "." . $base64Payload,
|
|
getenv('JWT_SECRET') ?: 'webshop_secret_key', true);
|
|
$base64Signature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
|
|
|
|
return $base64Header . "." . $base64Payload . "." . $base64Signature;
|
|
}
|
|
|
|
/**
|
|
* JWT-Token validieren
|
|
*/
|
|
private function validateJWT($token)
|
|
{
|
|
if (!$token) {
|
|
return false;
|
|
}
|
|
|
|
$parts = explode('.', $token);
|
|
if (count($parts) !== 3) {
|
|
return false;
|
|
}
|
|
|
|
list($header, $payload, $signature) = $parts;
|
|
|
|
$validSignature = hash_hmac('sha256', $header . "." . $payload,
|
|
getenv('JWT_SECRET') ?: 'webshop_secret_key', true);
|
|
$validSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($validSignature));
|
|
|
|
if ($signature !== $validSignature) {
|
|
return false;
|
|
}
|
|
|
|
$payload = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $payload)), true);
|
|
|
|
if (!$payload || $payload['exp'] < time()) {
|
|
return false;
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
/**
|
|
* Bearer-Token aus Header extrahieren
|
|
*/
|
|
private function getBearerToken()
|
|
{
|
|
$headers = getallheaders();
|
|
$authHeader = $headers['Authorization'] ?? '';
|
|
|
|
if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|