Skip to content

Commit 60b6c16

Browse files
committed
Initial commit
0 parents  commit 60b6c16

58 files changed

Lines changed: 1678 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/php.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: PHP Composer
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Validate composer.json and composer.lock
21+
run: composer validate --strict
22+
23+
- name: Cache Composer packages
24+
id: composer-cache
25+
uses: actions/cache@v3
26+
with:
27+
path: vendor
28+
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
29+
restore-keys: |
30+
${{ runner.os }}-php-
31+
32+
- name: Install dependencies
33+
run: composer install --prefer-dist --no-progress
34+
35+
- name: Run test suite
36+
run: composer test

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# folders
2+
/.phpunit.cache
3+
/node_modules
4+
/vendor
5+
/.fleet
6+
/.idea
7+
/.nova
8+
/.vscode
9+
/.zedg
10+
11+
# files
12+
.env
13+
.env.backup
14+
.env.production
15+
.phpunit.result.cache
16+
composer.lock

Core/App.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Core;
4+
5+
class App
6+
{
7+
public static function setContainer(Container $container): void
8+
{
9+
static::$container = $container;
10+
}
11+
12+
public static function container(): Container
13+
{
14+
return static::$container;
15+
}
16+
17+
public static function bind(string $key, callable $resolver): void
18+
{
19+
static::$container->bind($key, $resolver);
20+
}
21+
22+
public static function resolve(string $key): mixed
23+
{
24+
return static::$container->resolve($key);
25+
}
26+
27+
private static Container $container;
28+
}

Core/Authenticator.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Core;
4+
5+
class Authenticator
6+
{
7+
public function attempt(string $email, string $password): bool
8+
{
9+
$user = App::resolve(Database::class)->query('SELECT * FROM users WHERE email = :email', [
10+
'email' => $email,
11+
])->fetch();
12+
13+
if ($user) {
14+
if (password_verify($password, $user['password'])) {
15+
$this->login($user);
16+
return true;
17+
}
18+
}
19+
return false;
20+
}
21+
22+
public function login(array $user = []): void
23+
{
24+
$_SESSION['user'] = [
25+
'email' => $user['email']
26+
];
27+
28+
session_regenerate_id(true);
29+
}
30+
31+
public function logout(): void
32+
{
33+
Session::destroy();
34+
}
35+
}

Core/Container.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Core;
4+
5+
class Container
6+
{
7+
public function bind(string $key, callable $resolver): void
8+
{
9+
$this->bindings[$key] = $resolver;
10+
}
11+
12+
public function resolve(string $key): mixed
13+
{
14+
if (!array_key_exists($key, $this->bindings)) {
15+
throw new \Exception("No matching binding found for '{$key}'");
16+
}
17+
18+
$resolver = $this->bindings[$key];
19+
return call_user_func($resolver);
20+
}
21+
22+
private array $bindings = [];
23+
}

Core/Database.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Core;
4+
5+
use PDO;
6+
7+
class Database
8+
{
9+
public function __construct($config, $username = 'pma', $password = 'pmapass')
10+
{
11+
$dsn = 'mysql:' . http_build_query($config, '', ';');
12+
$this->conn = new PDO($dsn, $username, $password, [
13+
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
14+
]);
15+
}
16+
17+
public function query(string $query, array $params = []): Database
18+
{
19+
$this->stmt = $this->conn->prepare($query);
20+
$this->stmt->execute($params);
21+
return $this;
22+
}
23+
24+
public function fetch(): array|bool
25+
{
26+
return $this->stmt->fetch();
27+
}
28+
29+
public function fetchOrAbort(): array|bool
30+
{
31+
$record = $this->stmt->fetch();
32+
if (!$record) {
33+
abort();
34+
}
35+
return $record;
36+
}
37+
38+
public function fetchAll(): array
39+
{
40+
return $this->stmt->fetchAll();
41+
}
42+
43+
private $conn;
44+
private $stmt;
45+
}

Core/Middleware/Auth.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Core\Middleware;
4+
5+
final class Auth extends Middleware
6+
{
7+
#[\Override]
8+
public function handle(): void
9+
{
10+
if (!$_SESSION['user'] ?? false) {
11+
header('location: /');
12+
exit();
13+
}
14+
}
15+
}

Core/Middleware/Guest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Core\Middleware;
4+
5+
final class Guest extends Middleware
6+
{
7+
#[\Override]
8+
public function handle(): void
9+
{
10+
if ($_SESSION['user'] ?? false) {
11+
header('location: /');
12+
exit();
13+
}
14+
}
15+
}

Core/Middleware/Middleware.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Core\Middleware;
4+
5+
class Middleware
6+
{
7+
public function handle(): void {}
8+
public static function resolve(?string $key): void
9+
{
10+
if (!$key) {
11+
return;
12+
}
13+
14+
$middleware = static::MAP[$key] ?? false;
15+
if (!$middleware) {
16+
throw new \Exception("No matching middleware found for key '$key'.");
17+
}
18+
19+
(new $middleware)->handle();
20+
}
21+
22+
private const array MAP = [
23+
'guest' => Guest::class,
24+
'auth' => Auth::class
25+
];
26+
}

Core/Response.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Core;
4+
5+
class Response
6+
{
7+
const int NOT_FOUND = 404;
8+
const int FORBIDDEN = 403;
9+
const int INTERNAL_SERVER_ERROR = 500;
10+
}

0 commit comments

Comments
 (0)