This commit is contained in:
2025-11-08 18:22:15 +01:00
parent 7b7ca33781
commit cba9aef518
40 changed files with 8781 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{compose.yaml,compose.*.yaml}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false

40
.env Normal file
View File

@@ -0,0 +1,40 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
###< symfony/framework-bundle ###
###> symfony/routing ###
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
DEFAULT_URI=http://localhost
###< symfony/routing ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%.db"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
DATABASE_URL="mysql://immorechner_user:immorechner_pass@db:3306/immorechner?serverVersion=mariadb-11.7.1&charset=utf8mb4"
###< doctrine/doctrine-bundle ###
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###

367
README.md Normal file
View File

@@ -0,0 +1,367 @@
# Immorechner
Eine moderne Webanwendung zur Immobilienberechnung, entwickelt mit Symfony 7.3, PHP 8.4 und MariaDB.
## Features
- **REST-API**: Vollständige REST-API mit automatischer Dokumentation über API Platform
- **Web-Interface**: Benutzerfreundliche Weboberfläche mit Twig-Templates
- **User Management**: Benutzerverwaltung mit Rollenbasierter Zugriffskontrolle (USER, ADMIN, MODERATOR)
- **Docker-Setup**: Vollständig containerisierte Entwicklungsumgebung
- **Datenbank-Migrationen**: Versionskontrollierte Datenbankschema-Verwaltung mit Doctrine
- **CORS-Unterstützung**: Konfigurierbare CORS-Einstellungen für API-Zugriffe
- **phpMyAdmin**: Integriertes Datenbank-Verwaltungstool
## Technologie-Stack
- **Backend**: PHP 8.4
- **Framework**: Symfony 7.3
- **Datenbank**: MariaDB (Latest)
- **ORM**: Doctrine 3.0
- **API**: API Platform 4.2
- **Template Engine**: Twig 3.22
- **Webserver**: Apache 2.4 mit mod_rewrite
- **Container**: Docker & Docker Compose
- **Datenbank-Tool**: phpMyAdmin
## Voraussetzungen
- Docker Desktop (Windows/Mac) oder Docker Engine + Docker Compose (Linux)
- Git
## Installation
### 1. Repository klonen
```bash
git clone <repository-url>
cd immorechner
```
### 2. Docker-Container starten
```bash
docker-compose up -d --build
```
Dieser Befehl:
- Baut das PHP 8.4 Image mit allen benötigten Extensions
- Startet MariaDB
- Startet phpMyAdmin
- Startet den Apache-Webserver
### 3. Dependencies installieren
```bash
docker-compose exec web composer install
```
### 4. Datenbank-Schema erstellen
```bash
docker-compose exec web php bin/console doctrine:migrations:migrate --no-interaction
```
### 5. Cache leeren
```bash
docker-compose exec web php bin/console cache:clear
```
## Verwendung
### Anwendung aufrufen
- **Web-Interface**: http://localhost:8080
- **API-Dokumentation**: http://localhost:8080/api
- **phpMyAdmin**: http://localhost:8081
- Server: `db`
- Benutzer: `root`
- Passwort: `root`
### REST-API Endpoints
#### User-API
| Methode | Endpoint | Beschreibung |
|---------|----------|--------------|
| GET | `/api/users` | Alle User abrufen |
| GET | `/api/users/{id}` | Einzelnen User abrufen |
| POST | `/api/users` | Neuen User erstellen |
| PUT | `/api/users/{id}` | User aktualisieren |
| DELETE | `/api/users/{id}` | User löschen |
#### Beispiel: User erstellen
```bash
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{
"name": "Max Mustermann",
"email": "max@example.com",
"role": "user"
}'
```
#### Beispiel: Alle User abrufen
```bash
curl http://localhost:8080/api/users
```
## Datenbank-Schema
### User-Tabelle
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| id | INT | Primärschlüssel (Auto-Increment) |
| name | VARCHAR(255) | Benutzername |
| email | VARCHAR(255) | E-Mail-Adresse (Unique) |
| role | ENUM | Benutzerrolle (user, admin, moderator) |
| created_at | DATETIME | Erstellungsdatum |
## Entwicklung
### Neue Entity erstellen
```bash
docker-compose exec web php bin/console make:entity
```
### Migration erstellen
```bash
docker-compose exec web php bin/console doctrine:migrations:diff
```
### Migration ausführen
```bash
docker-compose exec web php bin/console doctrine:migrations:migrate
```
### Controller erstellen
```bash
docker-compose exec web php bin/console make:controller
```
### Cache leeren
```bash
docker-compose exec web php bin/console cache:clear
```
### Symfony Console aufrufen
```bash
docker-compose exec web php bin/console
```
## Docker-Befehle
### Container starten
```bash
docker-compose up -d
```
### Container stoppen
```bash
docker-compose down
```
### Container neu bauen
```bash
docker-compose up -d --build
```
### Container-Logs anzeigen
```bash
# Alle Container
docker-compose logs -f
# Nur Web-Container
docker-compose logs -f web
# Nur Datenbank-Container
docker-compose logs -f db
```
### In Container einloggen
```bash
# Web-Container (PHP/Apache)
docker-compose exec web bash
# Datenbank-Container
docker-compose exec db bash
```
### Composer-Befehle ausführen
```bash
docker-compose exec web composer require <paket-name>
docker-compose exec web composer update
docker-compose exec web composer dump-autoload
```
## Projekt-Struktur
```
immorechner/
├── bin/ # Symfony Console und andere Binaries
├── config/ # Symfony Konfigurationsdateien
├── docker/ # Docker-Konfigurationsdateien
│ └── apache/ # Apache VirtualHost Konfiguration
├── migrations/ # Doctrine Datenbank-Migrationen
├── public/ # Web-Root (index.php, Assets)
├── src/ # PHP-Quellcode
│ ├── Controller/ # Controller
│ ├── Entity/ # Doctrine Entities
│ ├── Enum/ # PHP Enums
│ ├── Repository/ # Doctrine Repositories
│ └── Kernel.php # Symfony Kernel
├── templates/ # Twig-Templates
│ ├── base.html.twig # Basis-Template
│ └── home/ # Homepage-Templates
├── var/ # Cache, Logs
├── vendor/ # Composer Dependencies
├── .env # Umgebungsvariablen
├── .env.example # Beispiel-Umgebungsvariablen
├── .gitignore # Git Ignore-Datei
├── composer.json # Composer-Konfiguration
├── docker-compose.yml # Docker Compose-Konfiguration
├── Dockerfile # Docker-Image für PHP/Apache
└── README.md # Diese Datei
```
## Umgebungsvariablen
Die Anwendung verwendet folgende Umgebungsvariablen (definiert in `.env`):
```env
# Symfony
APP_ENV=dev
APP_SECRET=<generierter-secret-key>
# Datenbank
DATABASE_URL="mysql://immorechner_user:immorechner_pass@db:3306/immorechner?serverVersion=mariadb-11.7.1&charset=utf8mb4"
# CORS
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
# Routing
DEFAULT_URI=http://localhost
```
## Konfiguration
### CORS anpassen
CORS-Einstellungen können in `config/packages/nelmio_cors.yaml` angepasst werden.
### Datenbank-Verbindung ändern
Datenbank-Verbindung in `.env` anpassen:
```env
DATABASE_URL="mysql://user:password@host:port/database?serverVersion=mariadb-11.7.1"
```
### Apache-Konfiguration
Apache-VirtualHost-Konfiguration befindet sich in `docker/apache/000-default.conf`.
## Fehlerbehebung
### Container starten nicht
```bash
# Container-Logs prüfen
docker-compose logs
# Container-Status prüfen
docker ps -a
# Container neu bauen
docker-compose down
docker-compose up -d --build
```
### "Class not found" Fehler
```bash
# Autoloader neu generieren
docker-compose exec web composer dump-autoload
# Cache leeren
docker-compose exec web php bin/console cache:clear
# Container neu starten
docker-compose restart web
```
### Datenbank-Verbindungsfehler
```bash
# Prüfen ob DB-Container läuft
docker ps | grep db
# DB-Logs prüfen
docker-compose logs db
# In DB-Container einloggen und testen
docker-compose exec db mysql -u root -proot
```
### Port bereits belegt
Wenn Port 8080, 8081 oder 3306 bereits belegt ist, können die Ports in `docker-compose.yml` angepasst werden:
```yaml
services:
web:
ports:
- "8090:80" # Statt 8080:80
```
## API-Dokumentation
Die vollständige API-Dokumentation ist verfügbar unter:
- **Swagger UI**: http://localhost:8080/api
Die API unterstützt mehrere Formate:
- JSON-LD (Standard)
- JSON
- HTML
Beispiel für JSON-Format:
```bash
curl -H "Accept: application/json" http://localhost:8080/api/users
```
## Sicherheitshinweise
**Wichtig für Produktion:**
1. Ändern Sie alle Standardpasswörter in `docker-compose.yml` und `.env`
2. Generieren Sie einen neuen `APP_SECRET` für `.env`
3. Setzen Sie `APP_ENV=prod` in der Produktion
4. Konfigurieren Sie CORS entsprechend Ihrer Domain
5. Aktivieren Sie HTTPS
6. Verwenden Sie sichere Datenbank-Credentials
7. Entfernen Sie phpMyAdmin aus der Produktion
## Lizenz
[Lizenz hier einfügen]
## Kontakt
[Kontaktinformationen hier einfügen]

21
bin/console Normal file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_dir(dirname(__DIR__).'/vendor')) {
throw new LogicException('Dependencies are missing. Try running "composer install".');
}
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
return new Application($kernel);
};

49
composer.json Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "immorechner/app",
"type": "project",
"minimum-stability": "stable",
"require": {
"php": ">=8.4",
"symfony/runtime": "^7.3",
"symfony/dotenv": "^7.3",
"symfony/flex": "^2.9",
"symfony/framework-bundle": "^7.3",
"symfony/yaml": "^7.3",
"doctrine/orm": "^3.5",
"doctrine/doctrine-bundle": "^3.0",
"doctrine/doctrine-migrations-bundle": "^3.6",
"api-platform/doctrine-orm": "^4.2",
"api-platform/symfony": "^4.2",
"nelmio/cors-bundle": "^2.6",
"phpdocumentor/reflection-docblock": "^5.6",
"symfony/asset": "^7.3",
"symfony/expression-language": "^7.3",
"symfony/security-bundle": "^7.3",
"phpstan/phpdoc-parser": "^2.3",
"symfony/property-access": "^7.3",
"symfony/property-info": "^7.3",
"symfony/serializer": "^7.3",
"symfony/twig-bundle": "^7.3",
"symfony/validator": "^7.3"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"config": {
"allow-plugins": {
"symfony/runtime": true,
"symfony/flex": true
},
"platform": {
"php": "8.4"
}
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
}
}
}

7443
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
config/bundles.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
];

View File

@@ -0,0 +1,7 @@
api_platform:
title: Hello API Platform
version: 1.0.0
defaults:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Origin']

View File

@@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View File

@@ -0,0 +1,48 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '16'
profiling_collect_backtrace: '%kernel.debug%'
orm:
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
identity_generation_preferences:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true
mappings:
App:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
controller_resolver:
auto_mapping: false
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View File

@@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false

View File

@@ -0,0 +1,15 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
# Note that the session will be started ONLY if you read or write from it.
session: true
#esi: true
#fragments: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

View File

@@ -0,0 +1,10 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/': null

View File

@@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true

View File

@@ -0,0 +1,10 @@
framework:
router:
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
default_uri: '%env(DEFAULT_URI)%'
when@prod:
framework:
router:
strict_requirements: null

View File

@@ -0,0 +1,39 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View File

@@ -0,0 +1,6 @@
twig:
file_name_pattern: '*.twig'
when@test:
twig:
strict_variables: true

View File

@@ -0,0 +1,11 @@
framework:
validation:
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

5
config/preload.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

5
config/routes.yaml Normal file
View File

@@ -0,0 +1,5 @@
controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute

View File

@@ -0,0 +1,4 @@
api_platform:
resource: .
type: api_platform
prefix: /api

View File

@@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
prefix: /_error

View File

@@ -0,0 +1,3 @@
_security_logout:
resource: security.route_loader.logout
type: service

20
config/services.yaml Normal file
View File

@@ -0,0 +1,20 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

0
migrations/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20251108171050 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE users (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, role VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, UNIQUE INDEX UNIQ_1483A5E9E7927C74 (email), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE users');
}
}

66
public/.htaccess Normal file
View File

@@ -0,0 +1,66 @@
# Use the front controller as index file. It serves as a fallback solution when
# every other rewrite/redirect fails (e.g. in an aliased environment without
# mod_rewrite). Additionally, this reduces the matching process for the
# start page (path "/") because otherwise Apache will apply the rewriting rules
# to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
DirectoryIndex index.php
# By default, Apache does not evaluate symbolic links if you did not enable this
# feature in your server configuration. Uncomment the following line if you
# install assets as symlinks or if you experience problems related to symlinks
# when compiling LESS/Sass/CoffeeScript assets.
# Options +FollowSymlinks
# Disabling MultiViews prevents unwanted negotiation, e.g. "/index" should not resolve
# to the front controller "/index.php" but be rewritten to "/index.php/index".
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# Determine the RewriteBase automatically and set it as environment variable.
# If you are using Apache aliases to do mass virtual hosting or installed the
# project in a subdirectory, the base path will be prepended to allow proper
# resolution of the index.php file and to redirect to the correct URI. It will
# work in environments without path prefix as well, providing a safe, one-size
# fits all solution. But as you do not need it in this case, you can comment
# the following 2 lines to eliminate the overhead.
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
RewriteRule .* - [E=BASE:%1]
# Sets the HTTP_AUTHORIZATION header removed by Apache
RewriteCond %{HTTP:Authorization} .+
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
# Redirect to URI without front controller to prevent duplicate content
# (with and without `/index.php`). Only do this redirect on the initial
# rewrite by Apache and not on subsequent cycles. Otherwise we would get an
# endless redirect loop (request -> rewrite to front controller ->
# redirect -> request -> ...).
# So in case you get a "too many redirects" error or you always get redirected
# to the start page because your Apache does not expose the REDIRECT_STATUS
# environment variable, you have 2 choices:
# - disable this feature by commenting the following 2 lines or
# - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
# following RewriteCond (best solution)
RewriteCond %{ENV:REDIRECT_STATUS} =""
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
# If the requested filename exists, simply serve it.
# We only want to let Apache serve files and not directories.
# Rewrite all other queries to the front controller.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ %{ENV:BASE}/index.php [L]
</IfModule>
<IfModule !mod_rewrite.c>
<IfModule mod_alias.c>
# When mod_rewrite is not available, we instruct a temporary redirect of
# the start page to the front controller explicitly so that the website
# and the generated links can still be used.
RedirectMatch 307 ^/$ /index.php/
# RedirectTemp cannot be used instead
</IfModule>
</IfModule>

9
public/index.php Normal file
View File

@@ -0,0 +1,9 @@
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

0
src/ApiResource/.gitignore vendored Normal file
View File

0
src/Controller/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'app_home')]
public function index(): Response
{
return $this->render('home/index.html.twig', [
'controller_name' => 'HomeController',
]);
}
}

0
src/Entity/.gitignore vendored Normal file
View File

104
src/Entity/User.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use App\Enum\UserRole;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
#[ApiResource(
operations: [
new Get(),
new GetCollection(),
new Post(),
new Put(),
new Delete()
]
)]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 255)]
private string $name;
#[ORM\Column(type: 'string', length: 255, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
private string $email;
#[ORM\Column(type: 'string', enumType: UserRole::class)]
private UserRole $role;
#[ORM\Column(type: 'datetime')]
private \DateTimeInterface $createdAt;
public function __construct()
{
$this->createdAt = new \DateTime();
$this->role = UserRole::USER;
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getRole(): UserRole
{
return $this->role;
}
public function setRole(UserRole $role): self
{
$this->role = $role;
return $this;
}
public function getCreatedAt(): \DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}

19
src/Enum/UserRole.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
namespace App\Enum;
enum UserRole: string
{
case USER = 'user';
case ADMIN = 'admin';
case MODERATOR = 'moderator';
public function getLabel(): string
{
return match($this) {
self::USER => 'Benutzer',
self::ADMIN => 'Administrator',
self::MODERATOR => 'Moderator',
};
}
}

11
src/Kernel.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}

0
src/Repository/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<User>
*/
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
/**
* Find users by role
*/
public function findByRole(string $role): array
{
return $this->createQueryBuilder('u')
->andWhere('u.role = :role')
->setParameter('role', $role)
->orderBy('u.createdAt', 'DESC')
->getQuery()
->getResult();
}
/**
* Find user by email
*/
public function findOneByEmail(string $email): ?User
{
return $this->createQueryBuilder('u')
->andWhere('u.email = :email')
->setParameter('email', $email)
->getQuery()
->getOneOrNullResult();
}
}

181
symfony.lock Normal file
View File

@@ -0,0 +1,181 @@
{
"api-platform/symfony": {
"version": "4.2",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "4.0",
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
},
"files": [
"./config/packages/api_platform.yaml",
"./config/routes/api_platform.yaml",
"./src/ApiResource/.gitignore"
]
},
"doctrine/deprecations": {
"version": "1.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.0",
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
}
},
"doctrine/doctrine-bundle": {
"version": "3.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.0",
"ref": "86b1fbac469b8b1c05e5ff28a7a2cffcaacf5068"
},
"files": [
"./config/packages/doctrine.yaml",
"./src/Entity/.gitignore",
"./src/Repository/.gitignore"
]
},
"doctrine/doctrine-migrations-bundle": {
"version": "3.6",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.1",
"ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
},
"files": [
"./config/packages/doctrine_migrations.yaml",
"./migrations/.gitignore"
]
},
"nelmio/cors-bundle": {
"version": "2.6",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.5",
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
},
"files": [
"./config/packages/nelmio_cors.yaml"
]
},
"symfony/console": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461"
},
"files": [
"./bin/console"
]
},
"symfony/flex": {
"version": "2.9",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.4",
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
},
"files": [
"./.env",
"./.env.dev"
]
},
"symfony/framework-bundle": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.3",
"ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9"
},
"files": [
"./config/packages/cache.yaml",
"./config/packages/framework.yaml",
"./config/preload.php",
"./config/routes/framework.yaml",
"./config/services.yaml",
"./public/index.php",
"./src/Controller/.gitignore",
"./src/Kernel.php",
"./.editorconfig"
]
},
"symfony/property-info": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.3",
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
},
"files": [
"./config/packages/property_info.yaml"
]
},
"symfony/routing": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "ab1e60e2afd5c6f4a6795908f646e235f2564eb2"
},
"files": [
"./config/packages/routing.yaml",
"./config/routes.yaml"
]
},
"symfony/security-bundle": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "2ae08430db28c8eb4476605894296c82a642028f"
},
"files": [
"./config/packages/security.yaml",
"./config/routes/security.yaml"
]
},
"symfony/twig-bundle": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
},
"files": [
"./config/packages/twig.yaml",
"./templates/base.html.twig"
]
},
"symfony/uid": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
}
},
"symfony/validator": {
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
},
"files": [
"./config/packages/validator.yaml"
]
}
}

69
templates/base.html.twig Normal file
View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Immorechner{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
{% block stylesheets %}
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #4CAF50;
color: white;
padding: 1rem 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
header h1 {
margin: 0;
padding: 0 20px;
}
main {
margin-top: 20px;
}
footer {
margin-top: 50px;
padding: 20px;
text-align: center;
color: #666;
border-top: 1px solid #ddd;
}
</style>
{% endblock %}
</head>
<body>
<header>
<div class="container">
<h1>Immorechner</h1>
</div>
</header>
<main>
<div class="container">
{% block body %}{% endblock %}
</div>
</main>
<footer>
<div class="container">
<p>&copy; {{ "now"|date("Y") }} Immorechner - Powered by Symfony</p>
</div>
</footer>
{% block javascripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,67 @@
{% extends 'base.html.twig' %}
{% block title %}Willkommen - {{ parent() }}{% endblock %}
{% block stylesheets %}
{{ parent() }}
<style>
.welcome-box {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h2 {
color: #333;
border-bottom: 2px solid #4CAF50;
padding-bottom: 10px;
margin-bottom: 20px;
}
.info {
margin: 20px 0;
padding: 15px;
background-color: #e8f5e9;
border-left: 4px solid #4CAF50;
}
.api-link {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
}
.api-link:hover {
background-color: #45a049;
}
ul {
margin-left: 20px;
}
ul li {
margin: 8px 0;
}
</style>
{% endblock %}
{% block body %}
<div class="welcome-box">
<h2>Willkommen bei Immorechner</h2>
<div class="info">
<p><strong>Symfony-Anwendung erfolgreich installiert!</strong></p>
<p>Diese Anwendung verfügt über:</p>
<ul>
<li>Symfony 7.3 Framework</li>
<li>MariaDB Datenbank (mit Docker)</li>
<li>Doctrine ORM</li>
<li>API Platform für REST-API</li>
<li>Twig Template Engine für UI</li>
</ul>
</div>
<p>Die REST-API ist über API Platform verfügbar und bietet automatische Dokumentation.</p>
<a href="/api" class="api-link">Zur API-Dokumentation</a>
</div>
{% endblock %}