Compare commits

...

6 Commits

Author SHA1 Message Date
2a439b8df4 add docker support :3 2026-04-21 15:00:03 +02:00
7ee3d6441a fixing stuff 2026-04-21 14:03:37 +02:00
3de979fc65 owowowo 2026-04-21 13:56:05 +02:00
894cc2d3fd little stash... 2026-04-21 13:43:08 +02:00
e3fd36d302 ... 2026-04-21 11:12:06 +02:00
5e8fdf8068 clear :3 2026-04-19 22:12:52 +02:00
25 changed files with 152 additions and 311 deletions

9
.env
View File

@@ -1,9 +0,0 @@
# Database
DB_HOST=database
DB_NAME=pawra
DB_USER=appuser
DB_PASSWORD=pick-a-real-password-here
DB_ROOT_PASSWORD=pick-another-real-password
# App
APP_ENV=development

10
.idea/.gitignore generated vendored
View File

@@ -1,10 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/pawra.iml" filepath="$PROJECT_DIR$/.idea/pawra.iml" />
</modules>
</component>
</project>

8
.idea/pawra.iml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

20
.idea/php.xml generated
View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.5" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -2,12 +2,10 @@ FROM php:8.3-apache
RUN a2enmod rewrite RUN a2enmod rewrite
RUN docker-php-ext-install pdo_mysql
COPY docker/apache.conf /etc/apache2/sites-available/000-default.conf
COPY . /var/www/html COPY . /var/www/html
RUN chown -R www-data:www-data /var/www/html RUN sed -i 's|/var/www/html|/var/www/html/public|g' \
/etc/apache2/sites-available/000-default.conf
WORKDIR /var/www/html RUN sed -i 's|AllowOverride None|AllowOverride All|g' \
/etc/apache2/apache2.conf

View File

@@ -1,30 +0,0 @@
services:
server:
build: .
restart: always
ports:
- "8080:80"
volumes:
- ".:/var/www/html"
environment:
- DB_HOST=${DB_HOST}
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
depends_on:
database:
condition: service_healthy
database:
image: mariadb:lts
restart: always
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MARIADB_DATABASE: ${DB_NAME}
MARIADB_USER: ${DB_USER}
MARIADB_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5

4
config/routes.php Normal file
View File

@@ -0,0 +1,4 @@
<?php
use src\Controller\HomeController;
$router->get('/', [new HomeController(), 'index']);

View File

@@ -1,14 +0,0 @@
<VirtualHost *:80>
DocumentRoot /var/www/html/public
PassEnv DB_HOST DB_NAME DB_USER DB_PASSWORD
<Directory /var/www/html/public>
AllowOverride All
Require all granted
Options -Indexes
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

4
public/.htaccess Normal file
View File

@@ -0,0 +1,4 @@
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

View File

@@ -1,27 +1,13 @@
<?php <?php
require_once __DIR__ . '/../src/Autoloader.php';
use Controllers\HomeController; use src\Router;
use src\Database;
session_start(); $autoloader = new Autoloader();
$autoloader->addNamespace('src', __DIR__ . '/../src');
$autoloader->register();
spl_autoload_register(function ($class) { $router = new Router();
$file = __DIR__ . '/../' . str_replace('\\', '/', $class) . '.php'; require_once __DIR__ . '/../config/routes.php';
if (file_exists($file)) { $router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
require_once $file;
}
});
require_once __DIR__ . '/../src/Controllers/HomeController.php';
require_once __DIR__ . '/../src/Database.php';
$path = $_SERVER['REQUEST_URI'] ?? '/';
if ($path === '/') {
$db = Database::getInstance();
$controller = new HomeController($db);
$controller->index();
}

35
src/Autoloader.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
class Autoloader
{
private array $prefixes = [];
public function addNamespace(string $prefix, string $baseDir): void
{
$prefix = trim($prefix, '\\') . '\\';
$baseDir = rtrim($baseDir, '/') . '/';
$this->prefixes[$prefix] = $baseDir;
}
public function register(): void
{
spl_autoload_register([$this, 'loadClass']);
}
private function loadClass(string $class): void
{
foreach ($this->prefixes as $prefix => $baseDir) {
if (!str_starts_with($class, $prefix)) {
continue;
}
$relative = substr($class, strlen($prefix));
$file = $baseDir . str_replace('\\', '/', $relative) . '.php';
if (file_exists($file)) {
require $file;
return;
}
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace src\Controller;
abstract class BaseController
{
protected function render(string $view, array $data = []): void
{
extract($data);
$viewPath = __DIR__ . '/../../templates/' . $view . '.php';
if (!file_exists($viewPath)) {
http_response_code(500);
echo "View '{$view}' nicht gefunden.";
return;
}
require $viewPath;
}
protected function redirect(string $url): void
{
header('Location: ' . $url);
exit;
}
protected function json(mixed $data, int $status = 200): void
{
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace src\Controller;
class HomeController extends BaseController
{
public function index(): void
{
$this->render('home', [
'title' => 'meow :3'
]);
}
}

View File

@@ -1,24 +0,0 @@
<?php
namespace Controllers;
use PDO;
use src\View;
use src\ViewModels\HomeData;
class HomeController
{
public function __construct(
private PDO $db
) {}
public function index(): void
{
$data = new HomeData(
pageTitle: 'Welcome to Pawra',
posts: $this->db->query("SELECT * FROM posts LIMIT 10")->fetchAll()
);
View::render('home', $data);
}
}

View File

@@ -1,32 +0,0 @@
<?php
namespace src;
use PDO;
class Database
{
private static ?PDO $instance = null;
public static function getInstance(): PDO
{
if (self::$instance === null) {
$dataSourceName = sprintf(
'mysql:host=%s;dbname=%s',
getenv('DB_HOST'),
getenv('DB_NAME')
);
self::$instance = new PDO(
$dataSourceName,
getenv('DB_USER'),
getenv('DB_PASSWORD'),
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
}
return self::$instance;
}
}

31
src/Router.php Normal file
View File

@@ -0,0 +1,31 @@
<?php
namespace src;
class Router
{
private array $routes = [];
public function get(string $path, callable $handler): void
{
$this->routes['GET'][$path] = $handler;
}
public function post(string $path, callable $handler): void
{
$this->routes['POST'][$path] = $handler;
}
public function dispatch(string $method, string $uri): void
{
$path = parse_url($uri, PHP_URL_PATH) ?? '/';
$handler = $this->routes[$method][$path] ?? null;
if ($handler === null) {
http_response_code(404);
echo '404 Not Found';
return;
}
call_user_func($handler);
}
}

View File

@@ -1,20 +0,0 @@
<?php
namespace src;
class View
{
public static function render(string $template, object $data): void
{
$path = __DIR__ . '/Views/' . $template . '.php';
if (!file_exists($path)) {
throw new \RuntimeException("View '$template' not found");
}
$templateFunc = require $path;
$templateFunc($data);
}
}

View File

@@ -1,11 +0,0 @@
<?php
namespace src\ViewModels;
class HomeData
{
public function __construct(
public string $pageTitle,
public array $posts
) {}
}

View File

@@ -1,19 +0,0 @@
<?php
use src\ViewModels\HomeData;
return function(HomeData $data) { ?>
<!DOCTYPE html>
<html>
<head>
<title><?= htmlspecialchars($data->pageTitle) ?></title>
</head>
<body>
<h1><?= htmlspecialchars($data->pageTitle) ?></h1>
<?php foreach ($data->posts as $post): ?>
<h2><?= htmlspecialchars($post['title']) ?></h2>
<p><?= htmlspecialchars($post['body']) ?></p>
<?php endforeach; ?>
</body>
</html>
<?php }; ?>

View File

@@ -1,27 +0,0 @@
<?php
use src\Database;
require_once __DIR__ . '/Database.php';
$db = Database::getInstance();
$db->exec("CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)");
$db->exec("CREATE TABLE IF NOT EXISTS posts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)");
echo "Migrations ran successfully.\n";

View File

@@ -1,45 +0,0 @@
<?php
use src\Database;
require_once __DIR__ . '/Database.php';
$db = Database::getInstance();
$db->exec("SET FOREIGN_KEY_CHECKS = 0");
$db->exec("TRUNCATE TABLE posts");
$db->exec("TRUNCATE TABLE users");
$db->exec("SET FOREIGN_KEY_CHECKS = 1");
$users = [
['luna', 'luna@pawra.dev', 'password123'],
['marco', 'marco@pawra.dev', 'password456'],
];
$insertUser = $db->prepare(
"INSERT INTO users (username, email, password) VALUES (?, ?, ?)"
);
foreach ($users as [$username, $email, $password]) {
$insertUser->execute([
$username,
$email,
password_hash($password, PASSWORD_DEFAULT),
]);
}
$posts = [
[1, 'First post', 'Hello from Luna — Pawra is live!'],
[1, 'Getting started', 'Here is how I set up my profile...'],
[2, 'Hello Pawra', 'Marco here, excited to join!'],
];
$insertPost = $db->prepare(
"INSERT INTO posts (user_id, title, body) VALUES (?, ?, ?)"
);
foreach ($posts as $post) {
$insertPost->execute($post);
}
echo "Seeded successfully.\n";

10
templates/home.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
ob_start();
?>
<h1><?= htmlspecialchars($title) ?></h1>
<p>Willkommen auf meiner Seite!</p>
<?php
$content = ob_get_clean();
require __DIR__ . '/layout.php';

10
templates/layout.php Normal file
View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($title) ?></title>
</head>
<body>
<?= $content ?>
</body>
</html>