Make base path configurable for root-domain deployment (IONOS)

Bisher war /hifi an mehreren Stellen fest einprogrammiert (vite base,
Router basename, API-Client, Upload-URLs, .htaccess). Fuer den geplanten
Deploy auf IONOS unter der Domain-Wurzel wird das jetzt ueber
VITE_BASE_PATH (Frontend-Build) bzw. eine optionale base_path.php
(Backend, gleiches Muster wie db.php/setup.php) gesteuert - lokal ohne
diese Werte bleibt alles unveraendert bei /hifi.

.htaccess.ionos enthaelt die Root-Domain-Variante fuer den CI-Build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Maaxxs 2026-07-05 14:23:30 +02:00
parent a3854fe5b8
commit 618a32182c
10 changed files with 58 additions and 18 deletions

4
.gitignore vendored
View file

@ -27,6 +27,10 @@
# pro Umgebung unterschiedlich und darf nicht öffentlich einsehbar sein
/hifi/api/config/setup.php
# Wird nur im IONOS-Deploy-Now-Build-Schritt geschrieben (Root-Domain statt /hifi),
# lokal nicht vorhanden -> Fallback in config.php greift dann
/hifi/api/config/base_path.php
# Claude-Code-Tooling (Skills/lokale Einstellungen) - gehört nicht zum Website-Projekt
/hifi/.claude/
/hifi/.agents/

View file

@ -1,4 +1,6 @@
const API_BASE = '/hifi/api';
// BASE_URL spiegelt immer den "base"-Wert aus vite.config.js wider (mit
// abschließendem Slash, z.B. "/hifi/" lokal oder "/" auf IONOS).
export const API_BASE = `${import.meta.env.BASE_URL.replace(/\/$/, '')}/api`;
async function request(path, options = {}) {
const res = await fetch(`${API_BASE}${path}`, {

View file

@ -9,7 +9,7 @@ import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter basename="/hifi">
<BrowserRouter basename={import.meta.env.BASE_URL.replace(/\/$/, '') || '/'}>
<ThemeProvider>
<AuthProvider>
<CookieConsentProvider>

View file

@ -1,5 +1,5 @@
import { useState } from 'react';
import { api } from '../../../api/client.js';
import { api, API_BASE } from '../../../api/client.js';
export default function ExportImportSettings() {
const [importFile, setImportFile] = useState(null);
@ -38,7 +38,7 @@ export default function ExportImportSettings() {
herunter. Benutzerkonten und Kontaktanfragen sind bewusst <strong>nicht</strong> enthalten.
</p>
<a
href="/hifi/api/settings/export"
href={`${API_BASE}/settings/export`}
className="inline-block rounded-md bg-brand-500 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-600"
>
Jetzt exportieren

View file

@ -1,9 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// Lokal (XAMPP) läuft die Seite unter /hifi/, auf IONOS Deploy Now unter der
// Domain-Wurzel. VITE_BASE_PATH wird nur im CI-Build-Schritt auf "/" gesetzt,
// lokal bleibt der Default unverändert.
const base = process.env.VITE_BASE_PATH || '/hifi/';
export default defineConfig({
plugins: [react()],
base: '/hifi/',
base,
build: {
outDir: '../hifi',
emptyOutDir: false,

View file

@ -1,3 +1,6 @@
# Für die lokale XAMPP-Entwicklung (Seite liegt unter /hifi/). Die Root-Domain-
# Variante für den IONOS-Deploy-Now-Build liegt in .htaccess.ionos und wird dort
# beim Bauen automatisch anstelle dieser Datei verwendet.
RewriteEngine On
RewriteBase /hifi/

15
hifi/.htaccess.ionos Normal file
View file

@ -0,0 +1,15 @@
RewriteEngine On
RewriteBase /
# API-Requests an den PHP-Front-Controller weiterleiten
RewriteCond %{REQUEST_URI} ^/api/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^api/(.*)$ api/public/index.php [QSA,L]
# Dynamische sitemap.xml (DB-generiert)
RewriteRule ^sitemap\.xml$ api/public/sitemap.php [L]
# Alles andere: SPA-Fallback auf index.html (React Router übernimmt das Routing)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [QSA,L]

View file

@ -24,8 +24,10 @@ return [
'to_email' => 'kontakt@example.com',
],
'app' => [
// Muss zum Vite "base" passen und wird für Session-Cookie-Pfad genutzt.
'base_path' => '/hifi',
// Muss zum Vite "base" passen und wird für Session-Cookie-Pfad und Upload-URLs
// genutzt. Lokal (XAMPP) ist das /hifi, im IONOS-Deploy-Now-Build wird beim
// Bauen eine base_path.php mit '/' geschrieben (siehe .github Workflow).
'base_path' => is_file(__DIR__ . '/base_path.php') ? require __DIR__ . '/base_path.php' : '/hifi',
'session_name' => 'hifi_admin_session',
// Öffentliche Domain der Seite (für sitemap.xml, robots.txt, Canonical-URLs). Ohne abschließenden Slash.
'site_url' => 'https://hifi-planet.de',

View file

@ -21,7 +21,13 @@ class SettingsController
'services' => ['id', 'icon_name', 'title', 'description', 'image_path', 'cta_label', 'cta_url', 'sort_order', 'created_at', 'updated_at'],
];
private const UPLOADS_URL_PREFIX = '/hifi/api/uploads/';
// Kein const, da abhängig von der Umgebung (base_path unterscheidet sich
// zwischen lokalem /hifi und der Root-Domain auf IONOS).
private static function uploadsUrlPrefix(): string
{
$config = require __DIR__ . '/../../config/config.php';
return rtrim($config['app']['base_path'], '/') . '/api/uploads/';
}
public static function exportData(): void
{
@ -164,7 +170,7 @@ class SettingsController
private static function collectImage(?string $path, array &$images): void
{
if (!$path || isset($images[$path]) || !str_starts_with($path, self::UPLOADS_URL_PREFIX)) {
if (!$path || isset($images[$path]) || !str_starts_with($path, self::uploadsUrlPrefix())) {
return;
}
$filename = basename($path);
@ -190,14 +196,14 @@ class SettingsController
private static function defaultServices(): array
{
return [
['car', 'Car-Hifi', 'Individuelle Sound-Umbauten vom dezenten Upgrade bis zum kompromisslosen High-End-System konfigurierbar direkt auf unserer Seite.', self::UPLOADS_URL_PREFIX . 'seed-leistung-car-hifi.jpg', 'Fahrzeug konfigurieren', '/fahrzeuge', 0],
['caravan', 'Wohnmobil & Caravan', 'Sound- und Elektronik-Lösungen speziell für Reisemobile und Caravans reversibel oder fest verbaut.', self::UPLOADS_URL_PREFIX . 'seed-leistung-wohnmobil.jpg', null, null, 1],
['car-front', 'Oldtimer', 'Zeitgemäßer Klang für Klassiker wir modernisieren die Anlage, ohne den Charakter deines Oldtimers zu verlieren.', self::UPLOADS_URL_PREFIX . 'seed-leistung-oldtimer.jpg', null, null, 2],
['cog', 'CNC Zerspanen', 'Fräsen, Bohren, Schneiden und Schleifen auf einer Fläche von 1500 × 3000 mm für Prototypen, Kleinserien und Großauflagen. Massivholz, Verbundwerkstoffe, Plattenmaterial und mehr.', self::UPLOADS_URL_PREFIX . 'seed-leistung-cnc-zerspanen.jpg', null, null, 3],
['zap', 'CNC Lasertechnik', 'Präzises Laserschneiden und -gravieren für Leder, Glas, Acryl, Holz, Karton und mehr für Einzelstücke ebenso wie Serienfertigung.', self::UPLOADS_URL_PREFIX . 'seed-leistung-cnc-laser.jpg', null, null, 4],
['boxes', '3D-Druck', 'Über 20 Drucker (FDM & SLA) für individuelle Halterungen, Blenden und Kleinserien Fertigung meist in 13 Tagen, auf Wunsch auch farbig.', self::UPLOADS_URL_PREFIX . 'seed-leistung-3d-druck.jpg', null, null, 5],
['shield-alert', 'Alarmanlagen', 'Zuverlässiger Diebstahlschutz für dein Fahrzeug, abgestimmt auf deine Ansprüche und dein Budget.', self::UPLOADS_URL_PREFIX . 'seed-leistung-alarmanlagen.jpg', null, null, 6],
['video', 'Dash Cams', 'Beweissichere Aufzeichnung fürs Auto wir beraten dich zur passenden Kamera und übernehmen den unauffälligen Einbau.', self::UPLOADS_URL_PREFIX . 'seed-leistung-dashcam.jpg', null, null, 7],
['car', 'Car-Hifi', 'Individuelle Sound-Umbauten vom dezenten Upgrade bis zum kompromisslosen High-End-System konfigurierbar direkt auf unserer Seite.', self::uploadsUrlPrefix() . 'seed-leistung-car-hifi.jpg', 'Fahrzeug konfigurieren', '/fahrzeuge', 0],
['caravan', 'Wohnmobil & Caravan', 'Sound- und Elektronik-Lösungen speziell für Reisemobile und Caravans reversibel oder fest verbaut.', self::uploadsUrlPrefix() . 'seed-leistung-wohnmobil.jpg', null, null, 1],
['car-front', 'Oldtimer', 'Zeitgemäßer Klang für Klassiker wir modernisieren die Anlage, ohne den Charakter deines Oldtimers zu verlieren.', self::uploadsUrlPrefix() . 'seed-leistung-oldtimer.jpg', null, null, 2],
['cog', 'CNC Zerspanen', 'Fräsen, Bohren, Schneiden und Schleifen auf einer Fläche von 1500 × 3000 mm für Prototypen, Kleinserien und Großauflagen. Massivholz, Verbundwerkstoffe, Plattenmaterial und mehr.', self::uploadsUrlPrefix() . 'seed-leistung-cnc-zerspanen.jpg', null, null, 3],
['zap', 'CNC Lasertechnik', 'Präzises Laserschneiden und -gravieren für Leder, Glas, Acryl, Holz, Karton und mehr für Einzelstücke ebenso wie Serienfertigung.', self::uploadsUrlPrefix() . 'seed-leistung-cnc-laser.jpg', null, null, 4],
['boxes', '3D-Druck', 'Über 20 Drucker (FDM & SLA) für individuelle Halterungen, Blenden und Kleinserien Fertigung meist in 13 Tagen, auf Wunsch auch farbig.', self::uploadsUrlPrefix() . 'seed-leistung-3d-druck.jpg', null, null, 5],
['shield-alert', 'Alarmanlagen', 'Zuverlässiger Diebstahlschutz für dein Fahrzeug, abgestimmt auf deine Ansprüche und dein Budget.', self::uploadsUrlPrefix() . 'seed-leistung-alarmanlagen.jpg', null, null, 6],
['video', 'Dash Cams', 'Beweissichere Aufzeichnung fürs Auto wir beraten dich zur passenden Kamera und übernehmen den unauffälligen Einbau.', self::uploadsUrlPrefix() . 'seed-leistung-dashcam.jpg', null, null, 7],
];
}
}

View file

@ -40,6 +40,9 @@ class UploadController
Http::error('Datei konnte nicht gespeichert werden', 500);
}
Http::send(['path' => '/hifi/api/uploads/' . $filename], 201);
$config = require __DIR__ . '/../../config/config.php';
$prefix = rtrim($config['app']['base_path'], '/') . '/api/uploads/';
Http::send(['path' => $prefix . $filename], 201);
}
}