Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d3d15e8e5 | |||
| cfa72be700 | |||
| 0ee598fbdd | |||
| 702803d982 | |||
| b86d6129c3 | |||
| 2a439b8df4 | |||
| 7ee3d6441a | |||
| 3de979fc65 | |||
| 894cc2d3fd | |||
| e3fd36d302 | |||
| 5e8fdf8068 |
@@ -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
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
Generated
-10
@@ -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
|
|
||||||
Generated
-8
@@ -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>
|
|
||||||
Generated
-8
@@ -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>
|
|
||||||
Generated
-20
@@ -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>
|
|
||||||
Generated
-6
@@ -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>
|
|
||||||
+18
-9
@@ -1,13 +1,22 @@
|
|||||||
|
FROM node:22-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY assets/ ./assets/
|
||||||
|
COPY templates/ ./templates/
|
||||||
|
|
||||||
|
# JS bauen
|
||||||
|
RUN ./node_modules/.bin/esbuild ./assets/javascript/test.js --bundle --outfile=./public/build/app.js --minify
|
||||||
|
|
||||||
|
# CSS bauen
|
||||||
|
RUN ./node_modules/.bin/tailwindcss -i ./assets/css/app.css -o ./public/build/app.css --minify
|
||||||
|
|
||||||
FROM php:8.3-apache
|
FROM php:8.3-apache
|
||||||
|
|
||||||
RUN a2enmod rewrite
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
RUN docker-php-ext-install pdo_mysql
|
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
|
||||||
|
COPY --from=builder /app/public/build /var/www/html/public/build
|
||||||
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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7', '#dda0dd'];
|
||||||
|
let current = 0;
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
current = (current + 1) % colors.length;
|
||||||
|
document.body.style.backgroundColor = colors[current];
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
window.test = function(world) {
|
||||||
|
console.log('Hello, ' + world + '!');
|
||||||
|
}
|
||||||
+31
-21
@@ -1,30 +1,40 @@
|
|||||||
services:
|
services:
|
||||||
server:
|
app:
|
||||||
build: .
|
build:
|
||||||
restart: always
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: pawra-app
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
volumes:
|
|
||||||
- ".:/var/www/html"
|
|
||||||
environment:
|
environment:
|
||||||
- DB_HOST=${DB_HOST}
|
DB_HOST: db
|
||||||
- DB_NAME=${DB_NAME}
|
DB_PORT: 3306
|
||||||
- DB_USER=${DB_USER}
|
DB_NAME: pawra
|
||||||
- DB_PASSWORD=${DB_PASSWORD}
|
DB_USER: pawra
|
||||||
|
DB_PASS: pawra_pw
|
||||||
depends_on:
|
depends_on:
|
||||||
database:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
database:
|
db:
|
||||||
image: mariadb:lts
|
image: mysql:8.4
|
||||||
restart: always
|
container_name: pawra-db
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
MYSQL_ROOT_PASSWORD: root_pw
|
||||||
MARIADB_DATABASE: ${DB_NAME}
|
MYSQL_DATABASE: pawra
|
||||||
MARIADB_USER: ${DB_USER}
|
MYSQL_USER: pawra
|
||||||
MARIADB_PASSWORD: ${DB_PASSWORD}
|
MYSQL_PASSWORD: pawra_pw
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
- ./migrations/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -proot_pw || exit 1"]
|
||||||
interval: 10s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 3s
|
||||||
retries: 5
|
retries: 20
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
use src\Controller\HomeController;
|
||||||
|
|
||||||
|
$router->get('/', [HomeController::class, 'index']);
|
||||||
@@ -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>
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
CREATE DATABASE IF NOT EXISTS auth_system
|
||||||
|
CHARACTER SET utf8mb4
|
||||||
|
COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
USE auth_system;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(30) NOT NULL UNIQUE,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_login DATETIME NULL,
|
||||||
|
is_active TINYINT(1) NOT NULL DEFAULT 1
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS remember_tokens (
|
||||||
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT UNSIGNED NOT NULL,
|
||||||
|
token_hash VARCHAR(255) NOT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
Generated
+1540
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "pawra",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git@git.pawly.dev:cheeseburger/pawra.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/cli": "^4.2.4",
|
||||||
|
"esbuild": "0.28.0",
|
||||||
|
"tailwindcss": "^4.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [QSA,L]
|
||||||
+21
-17
@@ -1,27 +1,31 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once __DIR__ . '/../src/Autoloader.php';
|
||||||
|
|
||||||
use Controllers\HomeController;
|
$autoloader = new Autoloader();
|
||||||
|
$autoloader->addNamespace('src', __DIR__ . '/../src');
|
||||||
|
$autoloader->register();
|
||||||
|
|
||||||
|
use src\Container;
|
||||||
|
use src\Router;
|
||||||
|
use src\Middleware\TestMiddleware;
|
||||||
use src\Database;
|
use src\Database;
|
||||||
|
|
||||||
session_start();
|
$container = new Container();
|
||||||
|
|
||||||
spl_autoload_register(function ($class) {
|
$container->bind(Database::class, function() {
|
||||||
$file = __DIR__ . '/../' . str_replace('\\', '/', $class) . '.php';
|
return new Database(
|
||||||
|
$_ENV['DB_HOST'] ?? '127.0.0.1',
|
||||||
if (file_exists($file)) {
|
$_ENV['DB_PORT'] ?? '3306',
|
||||||
require_once $file;
|
$_ENV['DB_NAME'] ?? 'pawra',
|
||||||
}
|
$_ENV['DB_USER'] ?? 'root',
|
||||||
|
$_ENV['DB_PASS'] ?? ''
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
require_once __DIR__ . '/../src/Controllers/HomeController.php';
|
$router = new Router($container);
|
||||||
require_once __DIR__ . '/../src/Database.php';
|
|
||||||
|
|
||||||
$path = $_SERVER['REQUEST_URI'] ?? '/';
|
$router->addGlobalMiddleware(TestMiddleware::class);
|
||||||
|
|
||||||
if ($path === '/') {
|
require_once __DIR__ . '/../config/routes.php';
|
||||||
$db = Database::getInstance();
|
|
||||||
|
|
||||||
$controller = new HomeController($db);
|
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
|
||||||
|
|
||||||
$controller->index();
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
namespace src;
|
||||||
|
|
||||||
|
class Container
|
||||||
|
{
|
||||||
|
private array $bindings = [];
|
||||||
|
|
||||||
|
public function bind(string $abstract, callable $factory): void
|
||||||
|
{
|
||||||
|
$this->bindings[$abstract] = $factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function make(string $class): object
|
||||||
|
{
|
||||||
|
if (isset($this->bindings[$class])) {
|
||||||
|
return ($this->bindings[$class])($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resolve($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolve(string $class): object
|
||||||
|
{
|
||||||
|
$reflector = new \ReflectionClass($class);
|
||||||
|
$constructor = $reflector->getConstructor();
|
||||||
|
|
||||||
|
if ($constructor === null) {
|
||||||
|
return new $class();
|
||||||
|
}
|
||||||
|
|
||||||
|
$dependencies = array_map(function($param) {
|
||||||
|
$type = $param->getType();
|
||||||
|
|
||||||
|
if ($type === null) {
|
||||||
|
throw new \Exception("Cannot resolve parameter \${$param->getName()} — no type hint.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type->isBuiltin()) {
|
||||||
|
if ($param->isDefaultValueAvailable()) {
|
||||||
|
return $param->getDefaultValue();
|
||||||
|
}
|
||||||
|
throw new \Exception("Cannot resolve primitive parameter \${$param->getName()} — no default value.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->make($type->getName());
|
||||||
|
}, $constructor->getParameters());
|
||||||
|
|
||||||
|
return $reflector->newInstanceArgs($dependencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
namespace src\Controller;
|
||||||
|
|
||||||
|
abstract class BaseController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected string $templatePath = __DIR__ . '/../../templates/'
|
||||||
|
) {}
|
||||||
|
|
||||||
|
protected function render(string $view, array $data = []): void
|
||||||
|
{
|
||||||
|
extract($data);
|
||||||
|
|
||||||
|
$viewPath = $this->templatePath . '/' . $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
namespace src\Controller;
|
||||||
|
|
||||||
|
use src\Database;
|
||||||
|
|
||||||
|
class HomeController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(private Database $db)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): void
|
||||||
|
{
|
||||||
|
$stmt = $this->db->pdo()->query('SELECT NOW() as now');
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
|
||||||
|
$this->render('home', [
|
||||||
|
'title' => 'meow :3',
|
||||||
|
'now' => $row['now'] ?? null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+16
-18
@@ -1,32 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace src;
|
namespace src;
|
||||||
|
|
||||||
use PDO;
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
|
private PDO $pdo;
|
||||||
|
|
||||||
private static ?PDO $instance = null;
|
public function __construct(
|
||||||
|
string $host,
|
||||||
|
string $port,
|
||||||
|
string $name,
|
||||||
|
string $user,
|
||||||
|
string $password
|
||||||
|
) {
|
||||||
|
$dsn = "mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4";
|
||||||
|
|
||||||
public static function getInstance(): PDO
|
$this->pdo = new PDO($dsn, $user, $password, [
|
||||||
{
|
|
||||||
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_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return self::$instance;
|
|
||||||
|
public function pdo(): PDO
|
||||||
|
{
|
||||||
|
return $this->pdo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
namespace src\Middleware;
|
||||||
|
|
||||||
|
interface MiddlewareInterface
|
||||||
|
{
|
||||||
|
public function handle(callable $next): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
namespace src\Middleware;
|
||||||
|
|
||||||
|
class TestMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
public function handle(callable $next): void
|
||||||
|
{
|
||||||
|
error_log('TestMiddleware: Handling request...');
|
||||||
|
$next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
namespace src;
|
||||||
|
|
||||||
|
class Router
|
||||||
|
{
|
||||||
|
private array $routes = [];
|
||||||
|
private array $globalMiddleware = [];
|
||||||
|
private Container $container;
|
||||||
|
|
||||||
|
public function __construct(Container $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $path, array $handler, array $middleware = []): void
|
||||||
|
{
|
||||||
|
$this->routes['GET'][$path] = compact('handler', 'middleware');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post(string $path, array $handler, array $middleware = []): void
|
||||||
|
{
|
||||||
|
$this->routes['POST'][$path] = compact('handler', 'middleware');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addGlobalMiddleware(string $middleware): void
|
||||||
|
{
|
||||||
|
$this->globalMiddleware[] = $middleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(string $method, string $uri): void
|
||||||
|
{
|
||||||
|
$route = $this->routes[$method][$uri] ?? null;
|
||||||
|
|
||||||
|
if ($route === null) {
|
||||||
|
http_response_code(404);
|
||||||
|
require __DIR__ . '/../views/404.php';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$controllerClass, $action] = $route['handler'];
|
||||||
|
|
||||||
|
$final = function() use ($controllerClass, $action) {
|
||||||
|
$controller = $this->container->make($controllerClass);
|
||||||
|
$controller->$action();
|
||||||
|
};
|
||||||
|
|
||||||
|
$middleware = array_merge($this->globalMiddleware, $route['middleware']);
|
||||||
|
$this->run($middleware, $final);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function run(array $middleware, callable $final): void
|
||||||
|
{
|
||||||
|
if (empty($middleware)) {
|
||||||
|
$final();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chain = array_reduce(
|
||||||
|
array_reverse($middleware),
|
||||||
|
function(callable $carry, string $middlewareClass) {
|
||||||
|
return function() use ($carry, $middlewareClass) {
|
||||||
|
$instance = $this->container->make($middlewareClass);
|
||||||
|
$instance->handle($carry);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
$final
|
||||||
|
);
|
||||||
|
|
||||||
|
$chain();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace src\ViewModels;
|
|
||||||
|
|
||||||
class HomeData
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public string $pageTitle,
|
|
||||||
public array $posts
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
@@ -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 }; ?>
|
|
||||||
@@ -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";
|
|
||||||
@@ -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";
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<h1><?= htmlspecialchars($title) ?></h1>
|
||||||
|
<p>Willkommen auf meiner Seite!</p>
|
||||||
|
|
||||||
|
<p class="text-red-500">I am red.</p>
|
||||||
|
|
||||||
|
<button onclick="test('World')">Test</button>
|
||||||
|
|
||||||
|
<p><?= htmlspecialchars($now) ?></p>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$content = ob_get_clean();
|
||||||
|
require __DIR__ . '/layout.php';
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title><?= htmlspecialchars($title) ?></title>
|
||||||
|
<link rel="stylesheet" href="/build/app.css">
|
||||||
|
<script src="/build/app.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?= $content ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user