vcard4reseller/backend/src/Controller/ResellerProvisioningController.php
Thomas Peterson ebaf509a2f Fundament: Symfony+API-Platform-Backend & Vue-SPA (Phase 0–2)
Stack & Setup
- Dockerisierte Dev-Umgebung (PHP 8.4-FPM, Nginx, MariaDB 11.4)
- Symfony 7.4 + API Platform 4.3, Doctrine ORM, LexikJWT, Messenger
- Vue 3 + TS (Vite), Vue Router, Pinia, Axios

Kern-Domäne & Auth
- Entitäten: User, PlatformPlan, Reseller, Company, Domain, Location,
  Employee, ContactLink (UUIDv7)
- JWT-Login (/api/login), Rollen-Hierarchie, /api/me
- Mandantentrennung via API-Platform-Query-Extension (Lesen) +
  TenantStampProcessor (Schreiben)

Öffentliche Profile (SSR)
- Profil-Landingpage, vCard-Download, QR-Code im Marken-Look
- Stabiler NFC/QR-Kurz-Link /t/{code} -> Redirect aufs aktuelle Profil
- Firmenspezifisches Branding (Farben/Logo) auf der Profilseite

Verwaltungsoberfläche (SPA)
- Brand-Look (dunkle Sidebar), rollenbasierte Navigation
- Dashboard, Reseller (+Provisioning), Firmen, Mitarbeiter, Standorte,
  Domains, Design/Branding mit Live-Vorschau

Konzept & Doku: docs/KONZEPT.md (inkl. Wallet/Sync §12), README.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 11:12:53 +02:00

80 lines
2.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Controller;
use App\Entity\PlatformPlan;
use App\Entity\Reseller;
use App\Entity\User;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
/**
* Legt einen Reseller an optional zusammen mit seinem Admin-Benutzer,
* damit sich der Reseller direkt einloggen kann. Nur für Plattform-Admins.
*/
#[IsGranted('ROLE_PLATFORM_ADMIN')]
final class ResellerProvisioningController
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly UserPasswordHasherInterface $hasher,
) {
}
#[Route('/api/platform/provision-reseller', name: 'platform_provision_reseller', methods: ['POST'])]
public function __invoke(Request $request): JsonResponse
{
$d = json_decode($request->getContent(), true) ?? [];
$name = trim((string) ($d['name'] ?? ''));
$slug = trim((string) ($d['slug'] ?? ''));
if ('' === $name || '' === $slug) {
return new JsonResponse(['error' => 'name und slug sind erforderlich.'], 422);
}
$reseller = (new Reseller())->setName($name)->setSlug($slug);
if (!empty($d['primaryDomain'])) {
$reseller->setPrimaryDomain((string) $d['primaryDomain']);
}
if (!empty($d['planId'])) {
$plan = $this->em->getRepository(PlatformPlan::class)->find($d['planId']);
if ($plan instanceof PlatformPlan) {
$reseller->setPlatformPlan($plan);
}
}
$adminEmail = trim((string) ($d['adminEmail'] ?? ''));
$adminPassword = (string) ($d['adminPassword'] ?? '');
$admin = null;
if ('' !== $adminEmail && '' !== $adminPassword) {
$admin = (new User())
->setEmail($adminEmail)
->setRoles([User::ROLE_RESELLER_ADMIN])
->setReseller($reseller);
$admin->setPassword($this->hasher->hashPassword($admin, $adminPassword));
}
try {
$this->em->persist($reseller);
if ($admin) {
$this->em->persist($admin);
}
$this->em->flush();
} catch (UniqueConstraintViolationException) {
return new JsonResponse(['error' => 'Slug oder Admin-E-Mail bereits vergeben.'], 422);
}
return new JsonResponse([
'id' => (string) $reseller->getId(),
'name' => $reseller->getName(),
'slug' => $reseller->getSlug(),
'adminCreated' => null !== $admin,
], 201);
}
}