Compare commits
6 Commits
main
...
2a439b8df4
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a439b8df4 | |||
| 7ee3d6441a | |||
| 3de979fc65 | |||
| 894cc2d3fd | |||
| e3fd36d302 | |||
| 5e8fdf8068 |
9
.env
9
.env
@@ -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
10
.idea/.gitignore
generated
vendored
@@ -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
8
.idea/modules.xml
generated
@@ -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
8
.idea/pawra.iml
generated
@@ -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
20
.idea/php.xml
generated
@@ -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
6
.idea/vcs.xml
generated
@@ -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>
|
||||
10
Dockerfile
10
Dockerfile
@@ -2,12 +2,10 @@ FROM php:8.3-apache
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
30
compose.yaml
30
compose.yaml
@@ -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
4
config/routes.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
use src\Controller\HomeController;
|
||||
|
||||
$router->get('/', [new HomeController(), '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>
|
||||
4
public/.htaccess
Normal file
4
public/.htaccess
Normal file
@@ -0,0 +1,4 @@
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
@@ -1,27 +1,13 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../src/Autoloader.php';
|
||||
|
||||
use Controllers\HomeController;
|
||||
use src\Database;
|
||||
use src\Router;
|
||||
|
||||
session_start();
|
||||
$autoloader = new Autoloader();
|
||||
$autoloader->addNamespace('src', __DIR__ . '/../src');
|
||||
$autoloader->register();
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
$file = __DIR__ . '/../' . str_replace('\\', '/', $class) . '.php';
|
||||
$router = new Router();
|
||||
require_once __DIR__ . '/../config/routes.php';
|
||||
|
||||
if (file_exists($file)) {
|
||||
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();
|
||||
}
|
||||
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
|
||||
35
src/Autoloader.php
Normal file
35
src/Autoloader.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/Controller/BaseController.php
Normal file
34
src/Controller/BaseController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
12
src/Controller/HomeController.php
Normal file
12
src/Controller/HomeController.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace src\Controller;
|
||||
|
||||
class HomeController extends BaseController
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
$this->render('home', [
|
||||
'title' => 'meow :3'
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
31
src/Router.php
Normal 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);
|
||||
}
|
||||
}
|
||||
20
src/View.php
20
src/View.php
@@ -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";
|
||||
10
templates/home.php
Normal file
10
templates/home.php
Normal 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
10
templates/layout.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= htmlspecialchars($title) ?></title>
|
||||
</head>
|
||||
<body>
|
||||
<?= $content ?>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user