Compare commits
153 Commits
a20226595b
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a283d755de | |||
| 4da6e6fb5f | |||
| 0fbbfdc997 | |||
| b1061e67ac | |||
| ae31f57ee0 | |||
| 28955feda7 | |||
| c192eeba65 | |||
| 45778d83d0 | |||
| bf3cebcf86 | |||
| 478f2429f5 | |||
| f6a10c8133 | |||
| 386472e680 | |||
| 9cb34839ec | |||
| bc5c04c7be | |||
| 702bb94004 | |||
| b455fdb20e | |||
| 34b7b47d0e | |||
| a85cdc6356 | |||
| 6a62ae58fc | |||
| 5ce2215e44 | |||
| e20206d4a5 | |||
| 053724f9b7 | |||
| 5f9654b5af | |||
| bf2f4954ee | |||
| e6fcd54e22 | |||
| 9deff439d7 | |||
| 2008888a16 | |||
| 7d92daeccd | |||
| 0b661c7ccc | |||
| 45d4ebcded | |||
| 9828ee762a | |||
| 7723dd0722 | |||
| 96bdbfc62a | |||
| 73c7dc1b5e | |||
| 93e5cebce0 | |||
| 215ed1bc7f | |||
| 72a0ad6364 | |||
| 9d6bd65583 | |||
| 2c9f8caff4 | |||
| a950f6770a | |||
| a3ec9ae318 | |||
| eb3516bfb2 | |||
| 627654e0a7 | |||
| 15ca7e8879 | |||
| 42529a66d4 | |||
| 5ebdee09d8 | |||
| ee9126b9e8 | |||
| c1b35aeef4 | |||
| 619f704c4e | |||
| 3beb06ceb0 | |||
| f25b0127e4 | |||
| f8fecbae4f | |||
| e850d14c2b | |||
| 24fad3bb59 | |||
| 7144b017b3 | |||
| 4dcb7531d3 | |||
| 6018e8bee5 | |||
| ce7ba50b9d | |||
| 08731f8559 | |||
| 74fd55084f | |||
| 43e2c6c0ad | |||
| 64311f2765 | |||
| d71eaf2ef2 | |||
| 12f7176467 | |||
| 683b6020b7 | |||
| 581cacb636 | |||
| a515c447e0 | |||
| 0929371c10 | |||
| ff0e192768 | |||
| a0b327f744 | |||
| 103b93184f | |||
| 7665b7185d | |||
| d8f02675a0 | |||
| 96fa86d53a | |||
| 922aea543d | |||
| 0a0baca051 | |||
| 36a4659915 | |||
| 2091bdb4e3 | |||
| 202e89382a | |||
| bb1e0eebf5 | |||
| 9a2673aba2 | |||
| 83c519fdfd | |||
| 91cdf15cc1 | |||
| d9755c732c | |||
| 7e30d515b6 | |||
| 95555a30a5 | |||
| 65848f094b | |||
| dd48f72c42 | |||
| 1b62b890b9 | |||
| b40ae7d701 | |||
| f303c7423c | |||
| 1bff7f46d7 | |||
| 85d20e034a | |||
| 30849019af | |||
| 700faf4351 | |||
| 5251c43a6b | |||
| 7ee04b0a4b | |||
| 2fde820942 | |||
| 06ce614c2b | |||
| 20db2301cb | |||
| 3e22aaf30c | |||
| 2168e37413 | |||
| af588ab174 | |||
| d94c342bd6 | |||
| 7b897071f5 | |||
| 4b89a7e9ca | |||
| b3c5841e36 | |||
| 8d91e805dd | |||
| 132e8790a3 | |||
| 976313305f | |||
| e9407c6c72 | |||
| 0ba01eb9f3 | |||
| 3fa2f07ed3 | |||
| d608a990b6 | |||
| 066251b3a7 | |||
| 7461a42586 | |||
| 0951285ff8 | |||
| b8edde019f | |||
| 87d949361e | |||
| ab1df63788 | |||
| 156d277e77 | |||
| 88f535a1ed | |||
| 797b166fd0 | |||
| 10e5e8fc5a | |||
| 9149cb25fe | |||
| f06bb1549e | |||
| eb20e236e5 | |||
| edf8b7cecf | |||
| 03da043be3 | |||
| 4d25387a47 | |||
| c1fffec124 | |||
| 1d93997d82 | |||
| c5842e58f2 | |||
| 49a65d4d1a | |||
| 6bb19ad6b0 | |||
| 7043d56329 | |||
| fac22f76c6 | |||
| 15b264ded2 | |||
| 32d4a62246 | |||
| 5f573a468f | |||
| 70da3e66fd | |||
| 15d3507b04 | |||
| f6626019ae | |||
| 3d8b9928d6 | |||
| c62321f854 | |||
| 0da314a31d | |||
| 07107708d0 | |||
| 2bd4c2215a | |||
| 8b61a1bf24 | |||
| 4dc97daf63 | |||
| cceab89523 | |||
| f394538776 | |||
| 36a0480cc6 |
+3
-2
@@ -1,2 +1,3 @@
|
|||||||
DENY FROM ALL
|
Order deny,allow
|
||||||
ALLOW FROM 127.0.0.1
|
Deny from all
|
||||||
|
Allow from 127.0.0.1
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
spl_autoload_register(function ($name) {
|
||||||
|
$_name = lcfirst($name);
|
||||||
|
|
||||||
|
require_once "/home/k/khofmann/public_html/phpCourse/tasks/oop/classes/$_name/$_name.php";
|
||||||
|
});
|
||||||
+11
-10
@@ -6,22 +6,23 @@ class DB extends PDO
|
|||||||
parent::__construct($dsn, $username, $password, $options);
|
parent::__construct($dsn, $username, $password, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function openConnection()
|
static function openConnection($config = "/home/k/khofmann/public_html/phpCourse/configs/db.ini")
|
||||||
{
|
{
|
||||||
$conn = null;
|
$conn = null;
|
||||||
try {
|
try {
|
||||||
$dataAccess = parse_ini_file("../configs/db.ini", true);
|
$dataAccess = parse_ini_file($config, true)["ZEDAT"];
|
||||||
|
|
||||||
$conn = new PDO(
|
$conn = new PDO(
|
||||||
"mysql:host=" . $dataAccess["ZEDAT"]["host"] .
|
"mysql:host={$dataAccess["host"]};dbname={$dataAccess["database"]};charset={$dataAccess["charset"]}",
|
||||||
";dbname=" . $dataAccess["ZEDAT"]["database"] . ";charset=utf8",
|
$dataAccess["user"],
|
||||||
$dataAccess["ZEDAT"]["user"],
|
$dataAccess["passwd"],
|
||||||
$dataAccess["ZEDAT"]["passwd"],
|
[
|
||||||
array(PDO::ATTR_PERSISTENT => false)
|
PDO::ATTR_PERSISTENT => false,
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
|
||||||
} catch (PDOException $exc) {
|
} catch (PDOException $exc) {
|
||||||
echo $exc->getMessage();
|
echo $exc->getMessage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<?php
|
|
||||||
ini_set("display_errors", 1);
|
|
||||||
ini_set("default_charset", "utf-8");
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
+1
-1
@@ -14,7 +14,7 @@ class Headers
|
|||||||
|
|
||||||
static function redirect(string $newUrl, bool $permanent = FALSE)
|
static function redirect(string $newUrl, bool $permanent = FALSE)
|
||||||
{
|
{
|
||||||
header('Location: ' . $newUrl, true, $permanent ? 301 : 302);
|
header('Location: ' . $newUrl, true, $permanent ? 301 : 303);
|
||||||
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
function guidv4($data = null)
|
||||||
|
{
|
||||||
|
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
|
||||||
|
$data = $data ?? random_bytes(16);
|
||||||
|
assert(strlen($data) == 16);
|
||||||
|
|
||||||
|
// Set version to 0100
|
||||||
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
||||||
|
// Set bits 6-7 to 10
|
||||||
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
||||||
|
|
||||||
|
// Output the 36 character UUID.
|
||||||
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||||
|
}
|
||||||
@@ -2,4 +2,11 @@
|
|||||||
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
|
<symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
|
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="arrow-left" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8" />
|
||||||
|
</symbol>
|
||||||
|
<symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" />
|
||||||
|
</symbol>
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
ini_set("display_errors", 1);
|
||||||
|
ini_set("default_charset", "utf-8");
|
||||||
|
ini_set('session.cookie_httponly', 1);
|
||||||
|
ini_set('session.cookie_secure', 1);
|
||||||
|
ini_set('session.use_only_cookies', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
@@ -3,3 +3,4 @@ host = usersql.zedat.fu-berlin.de
|
|||||||
user = khofmann-sql
|
user = khofmann-sql
|
||||||
passwd = xz8c7m7p
|
passwd = xz8c7m7p
|
||||||
database = khofmann-db1
|
database = khofmann-db1
|
||||||
|
charset = utf8
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
RewriteBase /phpCourse/exam/
|
||||||
|
|
||||||
|
## API routes
|
||||||
|
RewriteCond %{REQUEST_FILENAME} /api/.*
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !/api/docs
|
||||||
|
RewriteRule ^ api/index.php [L,NC,QSA]
|
||||||
|
|
||||||
|
## Rewrite all routes other than the specified paths
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !/api/docs
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !/dist
|
||||||
|
RewriteRule ^ dist/index.html [L,NC,QSA]
|
||||||
|
|
||||||
|
## Rewrite all files unless in the directories specified
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !/storage/.*
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !/dist/.*
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !/api/docs
|
||||||
|
RewriteRule ^ dist/index.html [L,NC,QSA]
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
# Übersicht der Inhalt
|
||||||
|
|
||||||
|
### Fett markierte Verzeichnisse und deren Inhalt sind von besonderem Interesse
|
||||||
|
|
||||||
|
- **api**
|
||||||
|
- docs
|
||||||
|
- API Dokumentation (openAPI 3.0)
|
||||||
|
- Login
|
||||||
|
- `Login.php` Endpunkt Klasse für `api/api/login`
|
||||||
|
- Logout
|
||||||
|
- `Logout.php` Endpunkt Klasse für `api/api/login`
|
||||||
|
- Posts
|
||||||
|
- `Posts.php` Endpunkt Klasse für `api/api/posts`
|
||||||
|
- `Posts.php` Endpunkt Klasse für `api/api/posts/{id}`
|
||||||
|
- Refresh
|
||||||
|
- `Refresh.php` Endpunkt Klasse für `api/api/refresh`
|
||||||
|
- Register
|
||||||
|
- `Register.php` Endpunkt Klasse für `api/api/register`
|
||||||
|
- Users
|
||||||
|
- `Users.php` Endpunkt Klasse für `api/users`
|
||||||
|
- `Users.php` Endpunkt Klasse für `api/users/{id}`
|
||||||
|
- Image
|
||||||
|
- `Image.php` Endpunkt Klasse für `api/users{id}/image`
|
||||||
|
- Permissions
|
||||||
|
- `Permissions.php` Endpunkt Klasse für `api/users/{id}/permissions`
|
||||||
|
- Posts
|
||||||
|
- `Posts.php` Endpunkt Klasse für `api/users{id}/posts`
|
||||||
|
- `index.php`
|
||||||
|
- API Einstiegspunkt
|
||||||
|
- **classes**
|
||||||
|
- Api
|
||||||
|
- `Api.php`: Basisklasse für Endpunkte
|
||||||
|
- ApiError
|
||||||
|
- `ApiError.php`: Facade für Fehler die das Api zurück gibt
|
||||||
|
- Auth
|
||||||
|
- `AdminAuth.php`: Middleware für Authentifizierung zuzüglich Admin Rechte
|
||||||
|
- `Auth.php`: Middleware für Authentifizierung
|
||||||
|
- `OptAuth.php`: Middleware für optionale Authentifizierung
|
||||||
|
- Config
|
||||||
|
- `Config.php`: Singleton für Applikationskonfiguration
|
||||||
|
- Database
|
||||||
|
- `Database.php`: Singleton für Datenbankzugriffe (Wrapper um PDO)
|
||||||
|
- GUID
|
||||||
|
- `GUID.php`: Facade für GUID Algorithmen
|
||||||
|
- Input
|
||||||
|
- `Input.php`: Facade die SimpleRouters `Input` Klasse wrapped für
|
||||||
|
einfacheren Zugriff auf häufig verwendete Methoden
|
||||||
|
- Models
|
||||||
|
- Post
|
||||||
|
- `Post.php`: Modellklasse für Posts. Abstrahiert und kapselt
|
||||||
|
Datenbankzugriffe
|
||||||
|
- User
|
||||||
|
- `User.php`: Modellklasse für User. Abstrahiert und kapselt
|
||||||
|
Datenbankzugriffe
|
||||||
|
- Request
|
||||||
|
- `Request.php`: Facade die SimpleRouters `Request` Klasse wrapped für
|
||||||
|
einfacheren Zugriff auf häufig verwendete Methoden
|
||||||
|
- Response
|
||||||
|
- `Response.php`: Facade die SimpleRouters `Response` Klasse wrapped für
|
||||||
|
einfacheren Zugriff auf häufig verwendete Methoden
|
||||||
|
- **config**
|
||||||
|
- `app.php`: Applikationskonfiguration
|
||||||
|
- `database.php`: Datenbankverbindungsdaten
|
||||||
|
- dist
|
||||||
|
- React Buildartefakte.
|
||||||
|
- react
|
||||||
|
- React Projekt
|
||||||
|
- **routes**
|
||||||
|
- `routes.php`: SimpleRouter Routenkonfiguration für die Applikation
|
||||||
|
- storage:
|
||||||
|
- Speicherort für Profilbilder
|
||||||
|
- **utils**:
|
||||||
|
- `helpers.php`: Kleine Helferlein
|
||||||
|
- vendor:
|
||||||
|
- Verzeichnis für Composer Pakete
|
||||||
|
|
||||||
|
# Autoloader
|
||||||
|
|
||||||
|
Verwendet wird hier der Composer Autoloader. Konfiguriert werden dessen
|
||||||
|
Namespaces in der `composer.json`. Durch `composer dump-autoload` wird
|
||||||
|
der Autoloader aktualisiert sollten neue Namespaces hinzugefügt werden.
|
||||||
|
|
||||||
|
# Voraussetzungen um das React Projekt zu bauen
|
||||||
|
- Node 18
|
||||||
|
- PNPM
|
||||||
|
|
||||||
|
# Veränderungen Datenbank
|
||||||
|
|
||||||
|
## Tabelle `egb_benutzer`
|
||||||
|
- Neue Spalten `token` (Auth token): VarChar(36), Nullable, UNIQUE Constraint
|
||||||
|
- Neue Spalten `tokenExpiry` (Auth token verfall): DateTime, Nullable
|
||||||
|
- Neue Spalten `refreshToken` (Auth refresh token): VarChar(36), Nullable, UNIQUE Constraint
|
||||||
|
- Neue Spalten `refreshExpiry` (Auth refresh token verfall): VarChar(36), DateTime
|
||||||
|
- Abänderung der Spalte `zeitstempel`: Entfernen des `ON UPDATE` (da sonst die Mitgliedszeit beim Ändern der Daten sich ändert)
|
||||||
|
- Abänderung der Spalte `benutzername`: Non-Nullable gemacht, UNIQUE Constraint
|
||||||
|
- Abänderung der Spalte `email`: Non-Nullable gemacht, UNIQUE Constraint
|
||||||
|
- Abänderung der Spalte `passwort`: Non-Nullable gemacht
|
||||||
|
- Abänderung der Spalte `confirmationcode`: Default auf `NULL` gesetzt
|
||||||
|
|
||||||
|
## Tabelle `egb_gaestebuch`
|
||||||
|
- Abänderung der Spalte `benutzer_id`: Non-Nullable gemacht
|
||||||
|
- Abänderung der Spalte `beitrag`: Non-Nullable gemacht
|
||||||
|
- Hinzufüge eines Foreign Key Constraints auf `benutzer_id` mit CASCADE (Löschen des Users löscht Beiträge)
|
||||||
|
|
||||||
|
# Notwendige Anpassung für die Verzeichnisstruktur eines anderen Hosters
|
||||||
|
|
||||||
|
## HTACCESS
|
||||||
|
|
||||||
|
### `.htaccess`
|
||||||
|
- RewriteBase anpassen
|
||||||
|
|
||||||
|
### `react/public/.htaccess`
|
||||||
|
- RewriteBase anpassen
|
||||||
|
- **WICHTIG:** React Projekt neu bauen damit die Datei an den korrekten Platz kopiert wird
|
||||||
|
|
||||||
|
## PHP
|
||||||
|
|
||||||
|
### `classes/Models/User.php`
|
||||||
|
- Link in der Confirmation E-Mail
|
||||||
|
|
||||||
|
### `config/app.php`
|
||||||
|
- Alle Pfade
|
||||||
|
|
||||||
|
## JS
|
||||||
|
**WICHTIG:** Nach allen Änderungen muss das React Projekt neu gebaut werden
|
||||||
|
|
||||||
|
### `react/vite.config.ts`
|
||||||
|
- `base` Pfad
|
||||||
|
|
||||||
|
### `react/src/i18n.ts`
|
||||||
|
- `loadPath` Pfad
|
||||||
|
|
||||||
|
### `react/src/main.tsx`
|
||||||
|
- `createRouter.basepath` Pfad
|
||||||
|
|
||||||
|
### `react/src/api/Api.ts`
|
||||||
|
- `BASE` API Url
|
||||||
|
|
||||||
|
### Projekt bauen
|
||||||
|
```
|
||||||
|
cd react
|
||||||
|
pnpm install
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Login;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login route handlers
|
||||||
|
*/
|
||||||
|
class Login extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Login POST handler
|
||||||
|
*
|
||||||
|
* Log in a user. Required inputs are `email` and `password`.
|
||||||
|
*
|
||||||
|
* Returns user and tokens
|
||||||
|
*
|
||||||
|
* @throws 400 Missing field
|
||||||
|
* @throws 401 Invalid credentials (login fails)
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 500 Failed to log in user
|
||||||
|
*/
|
||||||
|
public function post(): void
|
||||||
|
{
|
||||||
|
// Fetch all required inputs.
|
||||||
|
// Throw 400 error if a required one is missing.
|
||||||
|
$missing = [];
|
||||||
|
$email = Input::post("email");
|
||||||
|
$password = Input::post("password");
|
||||||
|
if (empty($email)) array_push($missing, "email");
|
||||||
|
if (empty($password)) array_push($missing, "password");
|
||||||
|
if (count($missing) > 0) throw ApiError::missingField($missing);
|
||||||
|
|
||||||
|
// Try and log in user.
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::logIn($email, $password));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "Failed":
|
||||||
|
throw ApiError::failed("Login failed");
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
case "Invalid":
|
||||||
|
throw ApiError::notAllowed("Invalid username or password");
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Logout;
|
||||||
|
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Request\Request;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout route handlers
|
||||||
|
*/
|
||||||
|
class Logout extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Logout POST handler
|
||||||
|
*
|
||||||
|
* Logout a user. User is retrieved using the authentication `token`.
|
||||||
|
*
|
||||||
|
* Returns user.
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
*/
|
||||||
|
public function post(): void
|
||||||
|
{
|
||||||
|
// Get user auth token.
|
||||||
|
$token = Request::token();
|
||||||
|
|
||||||
|
// Log out.
|
||||||
|
Response::json(User::getByToken($token)->logOut());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Posts;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
use Khofmann\Models\Post\Post;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Request\Request;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts route handlers
|
||||||
|
*/
|
||||||
|
class Posts extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Posts GET handler
|
||||||
|
*
|
||||||
|
* Lists posts. Optional parameters are `l` (limit of returned list) and `p` (page, i.e. offset).
|
||||||
|
*/
|
||||||
|
public function get()
|
||||||
|
{
|
||||||
|
// Fetch and constrain all parameters.
|
||||||
|
$page = max(0, intval(Input::get("p", 0)));
|
||||||
|
$limit = constrain(0, 30, intval(Input::get("l", 10)));
|
||||||
|
$authed = Request::token() !== null;
|
||||||
|
|
||||||
|
// Return list of posts.
|
||||||
|
Response::json(Post::list($page, $limit, $authed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts POST handler
|
||||||
|
*
|
||||||
|
* Create a new posts. Required inputs are `content`. Optional parameter is `l` (limit of list for which the returned pages is calculated).
|
||||||
|
*
|
||||||
|
* Returns created post and resulting amount of pages for a given limit.
|
||||||
|
*
|
||||||
|
* @throws 400 Missing fields
|
||||||
|
*/
|
||||||
|
public function post(): void
|
||||||
|
{
|
||||||
|
// Fetch all required inputs.
|
||||||
|
// Throw 400 error if a required one is missing.
|
||||||
|
$content = Input::post("content");
|
||||||
|
// This one is optional
|
||||||
|
$limit = constrain(0, 30, intval(Input::get("l", 10)));
|
||||||
|
if (empty($content)) throw ApiError::missingField(["content"]);
|
||||||
|
|
||||||
|
// Get logged in user
|
||||||
|
$self = User::getByToken(Request::token());
|
||||||
|
|
||||||
|
// Try to create a new post for logged in user.
|
||||||
|
try {
|
||||||
|
Response::json(Post::create($self, $content, $limit));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts PATCH handler
|
||||||
|
*
|
||||||
|
* Update a posts.
|
||||||
|
*
|
||||||
|
* Returns updated post.
|
||||||
|
*
|
||||||
|
* @param mixed $id ID of post to update
|
||||||
|
*
|
||||||
|
* @throws 401 Not authorized (trying to edit a different users post if not admin)
|
||||||
|
* @throws 404 Post not found
|
||||||
|
* @throws 500 Failed to update user
|
||||||
|
*/
|
||||||
|
public function patch($id): void
|
||||||
|
{
|
||||||
|
// Fetch all inputs.
|
||||||
|
$content = Input::patch("content");
|
||||||
|
|
||||||
|
// Fetch authed user.
|
||||||
|
$self = User::getByToken(Request::token());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try fetch the post in question, 404 if not found.
|
||||||
|
$post = Post::getByID($id);
|
||||||
|
|
||||||
|
// Throw 400 if we aren't admin but trying to edit another users post.
|
||||||
|
if (!$self->getIsAdmin() && $post->getUser()->getID() !== $self->getID()) throw ApiError::notAllowed("Not allowed");
|
||||||
|
|
||||||
|
// Try update.
|
||||||
|
Response::json($post->update($content));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("post");
|
||||||
|
default:
|
||||||
|
// Due to how the failed field is handled, it's ApiError is inside the models update
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts DELETE handler
|
||||||
|
*
|
||||||
|
* Delete a post. Optional parameter is `l` (limit of list for which the returned pages is calculated).
|
||||||
|
*
|
||||||
|
* Returns deleted post and resulting amount of pages for a given limit.
|
||||||
|
*
|
||||||
|
* @param mixed $id ID of posts to delete
|
||||||
|
*
|
||||||
|
* @throws 404 Post not found
|
||||||
|
*/
|
||||||
|
public function delete($id): void
|
||||||
|
{
|
||||||
|
// Fetch and constrain all parameters.
|
||||||
|
$limit = constrain(0, 30, intval(Input::get("l", 10)));
|
||||||
|
// Try delete, 404 if post was not found.
|
||||||
|
try {
|
||||||
|
Response::json(Post::getByID($id)->delete($limit));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("post");
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Refresh;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Request\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh route handlers
|
||||||
|
*/
|
||||||
|
class Refresh extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Refresh POST handler
|
||||||
|
*
|
||||||
|
* Refresh a users session. User is retrieved using the authentication `token`.
|
||||||
|
*
|
||||||
|
* Returns user and tokens.
|
||||||
|
*
|
||||||
|
* @throws 401 Missing field
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 500 Failed to refresh tokens
|
||||||
|
*/
|
||||||
|
public function post(): void
|
||||||
|
{
|
||||||
|
// Fetch all required inputs.
|
||||||
|
// Throw 400 error if a required one is missing.
|
||||||
|
$token = Request::token();
|
||||||
|
$refreshToken = Input::post("refreshToken");
|
||||||
|
if (empty($refreshToken)) throw ApiError::missingField(["refreshToken"]);
|
||||||
|
|
||||||
|
// Try and log in user.
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::refresh($token, $refreshToken));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "Failed":
|
||||||
|
throw ApiError::failed("Refresh failed");
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::unauthorized("Not authorized");
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Register;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register route handlers
|
||||||
|
*/
|
||||||
|
class Register extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register POST handler
|
||||||
|
*
|
||||||
|
* Register a new user. Required inputs are `username`, `email`, and `password`.
|
||||||
|
*
|
||||||
|
* Returns user.
|
||||||
|
*
|
||||||
|
* @throws 400 Missing fields
|
||||||
|
* @throws 400 Duplicate
|
||||||
|
* @throws 404 Failure to create
|
||||||
|
*/
|
||||||
|
public function post(): void
|
||||||
|
{
|
||||||
|
// Fetch all required inputs.
|
||||||
|
// Throw 400 error if a required one is missing.
|
||||||
|
$missing = [];
|
||||||
|
$username = Input::post("username");
|
||||||
|
$email = Input::post("email");
|
||||||
|
$password = Input::post("password");
|
||||||
|
if (empty($username)) array_push($missing, "username");
|
||||||
|
if (empty($email)) array_push($missing, "email");
|
||||||
|
if (empty($password)) array_push($missing, "password");
|
||||||
|
if (count($missing) > 0) throw ApiError::missingField($missing);
|
||||||
|
|
||||||
|
// Try and create a new user, 400 if duplicate.
|
||||||
|
try {
|
||||||
|
Response::json(User::create($username, $email, $password));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::failed("Failed to create user");
|
||||||
|
case "Duplicate":
|
||||||
|
throw ApiError::duplicate("user");
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register PATCH handler
|
||||||
|
*
|
||||||
|
* Confirms a user. Required input is `code`.
|
||||||
|
*
|
||||||
|
* Returns user.
|
||||||
|
*
|
||||||
|
* @throws 400 Missing field
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 404 User already confirmed
|
||||||
|
*/
|
||||||
|
public function patch(): void
|
||||||
|
{
|
||||||
|
// Fetch all required inputs.
|
||||||
|
// Throw 400 error if a required one is missing.
|
||||||
|
$code = Input::post("code");
|
||||||
|
if (empty($code)) throw ApiError::missingField(["code"]);
|
||||||
|
|
||||||
|
// Try and confirm user, 404 if user not found.
|
||||||
|
try {
|
||||||
|
Response::json(User::confirm($code));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Users\Image;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Request\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User image route handlers
|
||||||
|
*/
|
||||||
|
class Image extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Image POST handler
|
||||||
|
*
|
||||||
|
* Set a new user image.
|
||||||
|
*
|
||||||
|
* Returns updated user.
|
||||||
|
*
|
||||||
|
* @param mixed $id User ID
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 500 Failed to update user image
|
||||||
|
*/
|
||||||
|
public function post($id): void
|
||||||
|
{
|
||||||
|
// Fetch all inputs.
|
||||||
|
$image = Input::file("image");
|
||||||
|
$predefined = Input::post("predefined");
|
||||||
|
|
||||||
|
// Try and update user image.
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByID($id)->updateImage($image, $predefined));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
// Due to how the failed field is handled, it's ApiError is inside the models update
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image POST handler
|
||||||
|
*
|
||||||
|
* Set a new user image. User is retrieved using the authentication `token`.
|
||||||
|
*
|
||||||
|
* Returns updated user.
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 500 Failed to update user image
|
||||||
|
*/
|
||||||
|
public function postSelf(): void
|
||||||
|
{
|
||||||
|
// Fetch all inputs.
|
||||||
|
$token = Request::token();
|
||||||
|
$image = Input::file("image");
|
||||||
|
$predefined = Input::post("predefined");
|
||||||
|
|
||||||
|
// Try and update user image.
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByToken($token)->updateImage($image, $predefined));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
// Due to how the failed field is handled, it's ApiError is inside the models update
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Users\Permissions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Request\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User permissions route handlers
|
||||||
|
*/
|
||||||
|
class Permissions extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Permissions PATCH handler
|
||||||
|
*
|
||||||
|
* Sets user admin or not.
|
||||||
|
*
|
||||||
|
* Returns updated user.
|
||||||
|
*
|
||||||
|
* @param mixed $id User ID
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 500 Failed to update user permissions
|
||||||
|
*/
|
||||||
|
public function patch($id): void
|
||||||
|
{
|
||||||
|
// Fetch all inputs.
|
||||||
|
$isAdmin = Input::post("isAdmin");
|
||||||
|
|
||||||
|
// Try and update user image.
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByID($id)->updatePermissions($isAdmin));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
// Due to how the failed field is handled, it's ApiError is inside the models update
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Users\Posts;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User posts route handlers
|
||||||
|
*/
|
||||||
|
class Posts extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Posts GET handler
|
||||||
|
*
|
||||||
|
* Lists posts for a user. Optional parameters are `l` (limit of returned list), `p` (page, i.e. offset), `s` (sort order).
|
||||||
|
*
|
||||||
|
* Returns list of posts.
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
*/
|
||||||
|
public function get($id): void
|
||||||
|
{
|
||||||
|
// Fetch and constrain all parameters.
|
||||||
|
$page = max(0, intval(Input::get("p", 0)));
|
||||||
|
$limit = constrain(0, 30, intval(Input::get("l", 10)));
|
||||||
|
$sort = Input::get("s", "asc");
|
||||||
|
$sort = in_array($sort, ["asc", "desc"]) ? $sort : "asc";
|
||||||
|
|
||||||
|
// Try and get users posts
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByID($id)->posts($page, $limit, $sort));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
// Due to how the failed field is handled, it's ApiError is inside the models update
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Users;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Api\Api;
|
||||||
|
use Khofmann\Input\Input;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Request\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users route handlers
|
||||||
|
*/
|
||||||
|
class Users extends Api
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Users GET handler
|
||||||
|
*
|
||||||
|
* Lists users. Optional parameters are `l` (limit of returned list) and `p` (page, i.e. offset).
|
||||||
|
*
|
||||||
|
* Returns list of users.
|
||||||
|
*/
|
||||||
|
public function list()
|
||||||
|
{
|
||||||
|
// Fetch and constrain all parameters.
|
||||||
|
$page = max(0, intval(Input::get("p", 0)));
|
||||||
|
$limit = constrain(0, 30, intval(Input::get("l", 10)));
|
||||||
|
|
||||||
|
// Return list of users.
|
||||||
|
Response::json(User::list($page, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User GET handler
|
||||||
|
*
|
||||||
|
* Get a single user.
|
||||||
|
*
|
||||||
|
* Returns user.
|
||||||
|
*
|
||||||
|
* @param mixed $id User ID
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
*/
|
||||||
|
public function get($id): void
|
||||||
|
{
|
||||||
|
// Try and get a user, 404 if not found.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByID($id));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users PATCH handler
|
||||||
|
*
|
||||||
|
* Update a user.
|
||||||
|
*
|
||||||
|
* Returns updated user.
|
||||||
|
*
|
||||||
|
* @param mixed $id User ID
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 500 Failed to update user
|
||||||
|
*/
|
||||||
|
public function patch($id): void
|
||||||
|
{
|
||||||
|
// Fetch all inputs.
|
||||||
|
$username = Input::patch("username");
|
||||||
|
$password = Input::patch("password");
|
||||||
|
$email = Input::patch("email");
|
||||||
|
|
||||||
|
// Try and update user.
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByID($id)->update($username, $password, $email));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
// Due to how the failed field is handled, it's ApiError is inside the models update
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users PATCH handler
|
||||||
|
*
|
||||||
|
* Update a user. User is retrieved using the authentication `token`.
|
||||||
|
*
|
||||||
|
* Returns updated user.
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
* @throws 500 Failed to update user
|
||||||
|
*/
|
||||||
|
public function patchSelf(): void
|
||||||
|
{
|
||||||
|
// Fetch all inputs.
|
||||||
|
$token = Request::token();
|
||||||
|
$username = Input::patch("username");
|
||||||
|
$password = Input::patch("password");
|
||||||
|
$email = Input::patch("email");
|
||||||
|
|
||||||
|
// Try and update user.
|
||||||
|
// Throw errors according to situation.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByToken($token)->update($username, $password, $email));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
// Due to how the failed field is handled, it's ApiError is inside the models update
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users DELETE handler
|
||||||
|
*
|
||||||
|
* Deletes a user. Optional parameter is `l` (limit of list for which the returned pages is calculated).
|
||||||
|
*
|
||||||
|
* Returns deleted user and resulting amount of pages for a given limit.
|
||||||
|
*
|
||||||
|
* @param mixed $id User ID
|
||||||
|
*
|
||||||
|
* @throws 404 User not found
|
||||||
|
*/
|
||||||
|
public function delete($id): void
|
||||||
|
{
|
||||||
|
// Fetch and constrain all parameters.
|
||||||
|
$limit = constrain(0, 30, intval(Input::get("l", 10)));
|
||||||
|
// Try to delete user, 404 if not found.
|
||||||
|
try {
|
||||||
|
Response::json(User::getByID($id)->delete($limit));
|
||||||
|
} catch (Exception $err) {
|
||||||
|
switch ($err->getMessage()) {
|
||||||
|
case "NotFound":
|
||||||
|
throw ApiError::notFound("user");
|
||||||
|
default:
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,987 @@
|
|||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: PHP Course Exam
|
||||||
|
version: 1.0.0
|
||||||
|
contact:
|
||||||
|
name: Kilian Kurt Hofmann
|
||||||
|
email: khofmann@zedat.fu-berlin.de
|
||||||
|
description: PHP Course (ABV FU Berlin) 2024 Exam
|
||||||
|
paths:
|
||||||
|
/login:
|
||||||
|
post:
|
||||||
|
summary: Login
|
||||||
|
description: Log in user.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/LoginRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/LoginResponse"
|
||||||
|
400:
|
||||||
|
description: Missing fields.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/MissingFieldResponse"
|
||||||
|
examples:
|
||||||
|
Missing fields:
|
||||||
|
value:
|
||||||
|
{ "code": "MissingField", "fields": ["email", "password"] }
|
||||||
|
401:
|
||||||
|
description: Invalid credentials.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UnauthorizedResponse"
|
||||||
|
examples:
|
||||||
|
Invalid username or password:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"code": "Unauthorized",
|
||||||
|
"message": "Invalid username or password",
|
||||||
|
}
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
500:
|
||||||
|
description: Failed.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FailedResponse"
|
||||||
|
examples:
|
||||||
|
Failed:
|
||||||
|
value: { "code": "Failed", "message": "Login failed" }
|
||||||
|
tags:
|
||||||
|
- Login/Logout
|
||||||
|
/logout:
|
||||||
|
post:
|
||||||
|
summary: Logout
|
||||||
|
description: Log out user.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BooleanResponse"
|
||||||
|
examples:
|
||||||
|
Success:
|
||||||
|
value: true
|
||||||
|
tags:
|
||||||
|
- Login/Logout
|
||||||
|
/posts:
|
||||||
|
get:
|
||||||
|
summary: List posts
|
||||||
|
description: List all posts, return full user data if authenticated.
|
||||||
|
security:
|
||||||
|
- {}
|
||||||
|
- BasicAuth: []
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: p
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
default: 0
|
||||||
|
description: Current page.
|
||||||
|
- in: query
|
||||||
|
name: l
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 30
|
||||||
|
default: 10
|
||||||
|
description: The number of items to return.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostListResponse"
|
||||||
|
examples:
|
||||||
|
Not authenticated:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"pages": 0,
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"user": { "username": "string", "image": "string" },
|
||||||
|
"content": "string",
|
||||||
|
"postedAt":
|
||||||
|
{
|
||||||
|
"date": "2019-08-24T14:15:22Z",
|
||||||
|
"timezone_type": 0,
|
||||||
|
"timezone": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
Authenticated:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"pages": 1,
|
||||||
|
"data":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"user":
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"username": "string",
|
||||||
|
"status": 0,
|
||||||
|
"email": "string",
|
||||||
|
"image": "string",
|
||||||
|
"isAdmin": true,
|
||||||
|
"memberSince":
|
||||||
|
{
|
||||||
|
"date": "2019-08-24T14:15:22Z",
|
||||||
|
"timezone_type": 0,
|
||||||
|
"timezone": "string",
|
||||||
|
},
|
||||||
|
"postCount": 0,
|
||||||
|
},
|
||||||
|
"content": "string",
|
||||||
|
"postedAt":
|
||||||
|
{
|
||||||
|
"date": "2019-08-24T14:15:22Z",
|
||||||
|
"timezone_type": 0,
|
||||||
|
"timezone": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
tags:
|
||||||
|
- Post
|
||||||
|
post:
|
||||||
|
summary: New post
|
||||||
|
description: Create a new post.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostCreateRequest"
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: l
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 30
|
||||||
|
default: 10
|
||||||
|
description: Number of items per page, influences returned pages count.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostCreateResponse"
|
||||||
|
400:
|
||||||
|
description: Missing fields.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/MissingFieldResponse"
|
||||||
|
examples:
|
||||||
|
Missing fields:
|
||||||
|
value: { "code": "MissingField", "fields": ["content"] }
|
||||||
|
tags:
|
||||||
|
- Post
|
||||||
|
/posts/{id}:
|
||||||
|
patch:
|
||||||
|
summary: Update post
|
||||||
|
description: Update post with ID. <br>
|
||||||
|
Requires logged in user to have admin permissions for posts not made by them.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
- BasicAuth: [isAdmin]
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: Post ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostUpdateRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostResponse"
|
||||||
|
401:
|
||||||
|
description: Not allowed.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UnauthorizedResponse"
|
||||||
|
examples:
|
||||||
|
Not allowed:
|
||||||
|
value: { "code": "Unauthorized", "message": "Not allowed" }
|
||||||
|
404:
|
||||||
|
description: Post not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
Post not found:
|
||||||
|
value: { "code": "NotFound", "entity": "post" }
|
||||||
|
500:
|
||||||
|
description: Update failed.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FailedUpdateResponse"
|
||||||
|
examples:
|
||||||
|
Failed:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"code": "FailedUpdate",
|
||||||
|
"fields": ["content"],
|
||||||
|
"reasons": ["string"],
|
||||||
|
}
|
||||||
|
tags:
|
||||||
|
- Post
|
||||||
|
delete:
|
||||||
|
summary: Delete post
|
||||||
|
description: Delete post with ID.
|
||||||
|
security:
|
||||||
|
- BasicAuth: [isAdmin]
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: Post ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
- in: query
|
||||||
|
name: l
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 30
|
||||||
|
default: 10
|
||||||
|
description: Number of items per page, influences returned pages count.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostDeleteResponse"
|
||||||
|
404:
|
||||||
|
description: Post not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
Post not found:
|
||||||
|
value: { "code": "NotFound", "entity": "post" }
|
||||||
|
tags:
|
||||||
|
- Post
|
||||||
|
/refresh:
|
||||||
|
post:
|
||||||
|
summary: Refresh
|
||||||
|
description: Token refresh.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/RefreshRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/LoginResponse"
|
||||||
|
400:
|
||||||
|
description: Missing fields.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/MissingFieldResponse"
|
||||||
|
examples:
|
||||||
|
Missing fields:
|
||||||
|
value: { "code": "MissingField", "fields": ["refreshToken"] }
|
||||||
|
500:
|
||||||
|
description: Failed.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FailedResponse"
|
||||||
|
examples:
|
||||||
|
Failed:
|
||||||
|
value: { "code": "Failed", "message": "Refresh failed" }
|
||||||
|
tags:
|
||||||
|
- Refresh
|
||||||
|
/register:
|
||||||
|
post:
|
||||||
|
summary: Register
|
||||||
|
description: Register a new user.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/RegisterRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
400:
|
||||||
|
description: Missing fields or duplicate.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
oneOf:
|
||||||
|
- $ref: "#/components/schemas/MissingFieldResponse"
|
||||||
|
- $ref: "#/components/schemas/DuplicateResponse"
|
||||||
|
examples:
|
||||||
|
Missing fields:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"code": "MissingField",
|
||||||
|
"fields": ["username", "email", "password"],
|
||||||
|
}
|
||||||
|
Duplicate:
|
||||||
|
value: { "code": "Duplicate", "entity": "user" }
|
||||||
|
404:
|
||||||
|
description: Failed to create
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FailedResponse"
|
||||||
|
examples:
|
||||||
|
Failed to create:
|
||||||
|
value:
|
||||||
|
{ "code": "Failed", "message": "Failed to create user" }
|
||||||
|
tags:
|
||||||
|
- Register
|
||||||
|
patch:
|
||||||
|
summary: Confirm register
|
||||||
|
description: Confirm a registration.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ConfirmRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
400:
|
||||||
|
description: Missing fields.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/MissingFieldResponse"
|
||||||
|
examples:
|
||||||
|
Missing fields:
|
||||||
|
value: { "code": "MissingField", "fields": ["code"] }
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
tags:
|
||||||
|
- Register
|
||||||
|
/users:
|
||||||
|
get:
|
||||||
|
summary: List users
|
||||||
|
description: List all users.
|
||||||
|
security:
|
||||||
|
- BasicAuth: [isAdmin]
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: p
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
default: 0
|
||||||
|
description: Current page.
|
||||||
|
- in: query
|
||||||
|
name: l
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 30
|
||||||
|
default: 10
|
||||||
|
description: The number of items to return.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserListResponse"
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
|
/users/{id}:
|
||||||
|
get:
|
||||||
|
summary: Get user
|
||||||
|
description: Get user by ID.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: User ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
|
patch:
|
||||||
|
summary: Update user
|
||||||
|
description: Update user with ID. <br>
|
||||||
|
Use special ID <code>self</code> to update logged in user. <br>
|
||||||
|
Requires logged in user to have admin permissions for any ID other than <code>self</code>.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
- BasicAuth: [isAdmin]
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: User ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserUpdateRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
500:
|
||||||
|
description: Update failed.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FailedUpdateResponse"
|
||||||
|
examples:
|
||||||
|
Failed username:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"code": "FailedUpdate",
|
||||||
|
"fields": ["username", "password", "email"],
|
||||||
|
"reasons": ["string", "string", "string"],
|
||||||
|
}
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
|
delete:
|
||||||
|
summary: Delete user
|
||||||
|
description: Delete user with ID.
|
||||||
|
security:
|
||||||
|
- BasicAuth: [isAdmin]
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: User ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
- in: query
|
||||||
|
name: l
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 30
|
||||||
|
default: 10
|
||||||
|
description: Number of items per page, influences returned pages count.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserDeleteResponse"
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
|
/users/{id}/image:
|
||||||
|
post:
|
||||||
|
summary: Update user image
|
||||||
|
description: Update user image with ID. <br>
|
||||||
|
Use special ID <code>self</code> to update logged in user. <br>
|
||||||
|
Requires logged in user to have admin permissions for any ID other than <code>self</code>.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
- BasicAuth: [isAdmin]
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: User ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserImageUpdateRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
500:
|
||||||
|
description: Update failed.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FailedUpdateResponse"
|
||||||
|
examples:
|
||||||
|
Failed username:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"code": "FailedUpdate",
|
||||||
|
"fields": ["image", "predefined"],
|
||||||
|
"reasons": ["string", "string"],
|
||||||
|
}
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
|
/users/{id}/permissions:
|
||||||
|
post:
|
||||||
|
summary: Update user permissions
|
||||||
|
description: Update user permissions with ID.
|
||||||
|
security:
|
||||||
|
- BasicAuth: [isAdmin]
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: User ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserPermissionsUpdateRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
500:
|
||||||
|
description: Update failed.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/FailedUpdateResponse"
|
||||||
|
examples:
|
||||||
|
Failed username:
|
||||||
|
value:
|
||||||
|
{
|
||||||
|
"code": "FailedUpdate",
|
||||||
|
"fields": ["isAdmin"],
|
||||||
|
"reasons": ["string"],
|
||||||
|
}
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
|
/users/{id}/posts:
|
||||||
|
get:
|
||||||
|
summary: Get user posts
|
||||||
|
description: Get a users posts ID.
|
||||||
|
security:
|
||||||
|
- BasicAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: User ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int14
|
||||||
|
- in: query
|
||||||
|
name: p
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
default: 0
|
||||||
|
description: Current page.
|
||||||
|
- in: query
|
||||||
|
name: l
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
maximum: 30
|
||||||
|
default: 10
|
||||||
|
description: The number of items to return.
|
||||||
|
- in: query
|
||||||
|
name: s
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- asc
|
||||||
|
- desc
|
||||||
|
default: asc
|
||||||
|
description: Sort order by time.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Success.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/PostListResponse"
|
||||||
|
404:
|
||||||
|
description: User not found.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotFoundResponse"
|
||||||
|
examples:
|
||||||
|
User not found:
|
||||||
|
value: { "code": "NotFound", "entity": "user" }
|
||||||
|
tags:
|
||||||
|
- User
|
||||||
|
|
||||||
|
externalDocs:
|
||||||
|
url: https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/docs/
|
||||||
|
security: []
|
||||||
|
servers:
|
||||||
|
- url: https://khofmann.userpage.fu-berlin.de/phpCourse/exam/api/
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
BooleanResponse:
|
||||||
|
type: boolean
|
||||||
|
MissingFieldResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: MissingField
|
||||||
|
fields:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
NotFoundResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: NotFound
|
||||||
|
entity:
|
||||||
|
type: string
|
||||||
|
UnauthorizedResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code: type:NotAllowed
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
FailedResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: Failed
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
DuplicateResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: Duplicate
|
||||||
|
entity:
|
||||||
|
type: string
|
||||||
|
FailedUpdateResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: FailedUpdate
|
||||||
|
fields:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
reasons:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
ErrorResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
LoginRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
LoginResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
refreshToken:
|
||||||
|
type: string
|
||||||
|
UserResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: number
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
image:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
isAdmin:
|
||||||
|
type: boolean
|
||||||
|
memberSince:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
timezone_type:
|
||||||
|
type: number
|
||||||
|
timezone:
|
||||||
|
type: string
|
||||||
|
postCount:
|
||||||
|
type: number
|
||||||
|
UserUpdateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
UserPermissionsUpdateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
isAdmin:
|
||||||
|
type: boolean
|
||||||
|
UserImageUpdateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
image:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
predefined:
|
||||||
|
type: string
|
||||||
|
RegisterRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- email
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
ConfirmRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
format: uuid4
|
||||||
|
UserListResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pages:
|
||||||
|
type: number
|
||||||
|
data:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
PostResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
user:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
postedAt:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
timezone_type:
|
||||||
|
type: number
|
||||||
|
timezone:
|
||||||
|
type: string
|
||||||
|
PostCreateResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pages:
|
||||||
|
type: number
|
||||||
|
post:
|
||||||
|
$ref: "#/components/schemas/PostResponse"
|
||||||
|
PostDeleteResponse:
|
||||||
|
$ref: "#/components/schemas/PostCreateResponse"
|
||||||
|
PostListResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pages:
|
||||||
|
type: number
|
||||||
|
data:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/PostResponse"
|
||||||
|
PostUpdateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
PostCreateRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- content
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
RefreshRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- refreshToken
|
||||||
|
properties:
|
||||||
|
refreshToken:
|
||||||
|
type: string
|
||||||
|
format: uuid4
|
||||||
|
UserDeleteResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pages:
|
||||||
|
type: number
|
||||||
|
post:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
securitySchemes:
|
||||||
|
BasicAuth:
|
||||||
|
type: apiKey
|
||||||
|
name: token
|
||||||
|
in: header
|
||||||
|
tags:
|
||||||
|
- name: Login/Logout
|
||||||
|
- name: Post
|
||||||
|
- name: Register
|
||||||
|
- name: Refresh
|
||||||
|
- name: User
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
// Enable Errors
|
||||||
|
ini_set("display_errors", 1); // DISABLE FOR RELEASE
|
||||||
|
ini_set("default_charset", "utf-8");
|
||||||
|
ini_set("session.cookie_httponly", 1);
|
||||||
|
ini_set("session.cookie_secure", 1);
|
||||||
|
ini_set("session.use_only_cookies", 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
// Load composer Autoloader
|
||||||
|
require_once __DIR__ . "/../vendor/autoload.php";
|
||||||
|
// Helpers
|
||||||
|
require_once __DIR__ . "/../utils/helpers.php";
|
||||||
|
// Namespaces
|
||||||
|
use Pecee\SimpleRouter\SimpleRouter;
|
||||||
|
use Pecee\SimpleRouter\Handlers\EventHandler;
|
||||||
|
use Pecee\SimpleRouter\Event\EventArgument;
|
||||||
|
use Pecee\SimpleRouter\Route\ILoadableRoute;
|
||||||
|
use Pecee\SimpleRouter\Route\IGroupRoute;
|
||||||
|
use Khofmann\Config\Config;
|
||||||
|
// Router base path
|
||||||
|
$basePath = Config::getBasePath() . "api";
|
||||||
|
// Router event handler for prepending base path
|
||||||
|
$eventHandler = new EventHandler();
|
||||||
|
$eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function (EventArgument $event) use ($basePath) {
|
||||||
|
$route = $event->route;
|
||||||
|
// Skip routes added by group as these will inherit the url
|
||||||
|
if ($event->isSubRoute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Prepend based on what type of Route
|
||||||
|
switch (true) {
|
||||||
|
case $route instanceof ILoadableRoute:
|
||||||
|
$route->prependUrl($basePath);
|
||||||
|
break;
|
||||||
|
case $route instanceof IGroupRoute:
|
||||||
|
$route->prependPrefix($basePath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Add the handler
|
||||||
|
SimpleRouter::addEventHandler($eventHandler);
|
||||||
|
// Load external routes file
|
||||||
|
require_once __DIR__ . "/../routes/routes.php";
|
||||||
|
// Start the routing
|
||||||
|
SimpleRouter::start();
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Api;
|
||||||
|
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all API handler classes.
|
||||||
|
*
|
||||||
|
* Sets common headers
|
||||||
|
*/
|
||||||
|
class Api
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
Response::response()
|
||||||
|
->header("Cache-control: no-cache")
|
||||||
|
->header("Access-Control-Allow-Origin: *")
|
||||||
|
->header("Access-Control-Allow-Methods: *")
|
||||||
|
->header("Access-Control-Allow-Headers: *");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\ApiError;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for common API errors
|
||||||
|
*/
|
||||||
|
class ApiError extends Exception
|
||||||
|
{
|
||||||
|
private function __construct($message = "", $code = 0)
|
||||||
|
{
|
||||||
|
parent::__construct($message, $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error for missing fields
|
||||||
|
*
|
||||||
|
* @param array $fields Array of strings denoting which fields were missing
|
||||||
|
*/
|
||||||
|
public static function missingField(array $fields): ApiError
|
||||||
|
{
|
||||||
|
return new ApiError(json_encode([
|
||||||
|
"code" => "MissingField",
|
||||||
|
"fields" => $fields,
|
||||||
|
]), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error for duplicates
|
||||||
|
*
|
||||||
|
* @param string entity Entity for which a duplicate exists
|
||||||
|
*/
|
||||||
|
public static function duplicate(string $entity): ApiError
|
||||||
|
{
|
||||||
|
return new ApiError(json_encode([
|
||||||
|
"code" => "Duplicate",
|
||||||
|
"entity" => $entity,
|
||||||
|
]), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error for missing permissions
|
||||||
|
*
|
||||||
|
* @param string message Message specifics
|
||||||
|
*/
|
||||||
|
public static function notAllowed(string $message)
|
||||||
|
{
|
||||||
|
return new ApiError(json_encode([
|
||||||
|
"code" => "NotAllowed",
|
||||||
|
"message" => $message,
|
||||||
|
]), 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error for missing authentication
|
||||||
|
*
|
||||||
|
* @param string $message Message specifics
|
||||||
|
*/
|
||||||
|
public static function unauthorized(string $message)
|
||||||
|
{
|
||||||
|
return new ApiError(json_encode([
|
||||||
|
"code" => "Unauthorized",
|
||||||
|
"message" => $message,
|
||||||
|
]), 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error for not found
|
||||||
|
*
|
||||||
|
* @param string entity Entity for which a duplicate exists
|
||||||
|
*/
|
||||||
|
public static function notFound(string $entity)
|
||||||
|
{
|
||||||
|
return new ApiError(json_encode([
|
||||||
|
"code" => "NotFound",
|
||||||
|
"entity" => $entity,
|
||||||
|
]), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic error
|
||||||
|
*
|
||||||
|
* @param string message Message specifics
|
||||||
|
*/
|
||||||
|
public static function failed(string $message)
|
||||||
|
{
|
||||||
|
return new ApiError(json_encode([
|
||||||
|
"code" => "Failed",
|
||||||
|
"message" => $message,
|
||||||
|
]), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error for missing fields
|
||||||
|
*
|
||||||
|
* @param array $fields Array of strings denoting which fields failed
|
||||||
|
* @param array $fields Array of strings denoting why the fields failed
|
||||||
|
*/
|
||||||
|
public static function failedUpdate(array $fields, array $reasons)
|
||||||
|
{
|
||||||
|
return new ApiError(json_encode([
|
||||||
|
"code" => "FailedUpdate",
|
||||||
|
"fields" => $fields,
|
||||||
|
"reasons" => $reasons,
|
||||||
|
]), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Auth;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Pecee\Http\Middleware\IMiddleware;
|
||||||
|
use Pecee\Http\Request;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware for admin authenticated routes
|
||||||
|
*/
|
||||||
|
class AdminAuth implements IMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Request handler
|
||||||
|
*
|
||||||
|
* Returns 401 if `token`is missing, no user is found with the `token`or user is not admin.
|
||||||
|
*
|
||||||
|
* Keeps session fresh if request is authenticated.
|
||||||
|
*
|
||||||
|
* @param Request $request Incoming request
|
||||||
|
*/
|
||||||
|
public function handle(Request $request): void
|
||||||
|
{
|
||||||
|
$token = $request->getHeader("token");
|
||||||
|
|
||||||
|
// No token
|
||||||
|
if ($token === null) {
|
||||||
|
Response::response()
|
||||||
|
->header("Cache-control: no-cache")
|
||||||
|
->header("Access-Control-Allow-Origin: *")
|
||||||
|
->header("Access-Control-Allow-Methods: *")
|
||||||
|
->header("Access-Control-Allow-Headers: *")
|
||||||
|
->httpCode(401)
|
||||||
|
->json(["code" => "Unauthorized", "message" => "Not Authorized"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user
|
||||||
|
$user = User::getByToken($token);
|
||||||
|
// Check if user is admin
|
||||||
|
if (!$user->getIsAdmin()) {
|
||||||
|
Response::response()
|
||||||
|
->header("Cache-control: no-cache")
|
||||||
|
->header("Access-Control-Allow-Origin: *")
|
||||||
|
->header("Access-Control-Allow-Methods: *")
|
||||||
|
->header("Access-Control-Allow-Headers: *")
|
||||||
|
->httpCode(401)
|
||||||
|
->json(["code" => "NotAllowed", "message" => "Not Authorized"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep fresh
|
||||||
|
$user->keepFresh();
|
||||||
|
} catch (Exception $err) {
|
||||||
|
// No user with this token exists
|
||||||
|
Response::response()
|
||||||
|
->header("Cache-control: no-cache")
|
||||||
|
->header("Access-Control-Allow-Origin: *")
|
||||||
|
->header("Access-Control-Allow-Methods: *")
|
||||||
|
->header("Access-Control-Allow-Headers: *")
|
||||||
|
->httpCode(401)
|
||||||
|
->json(["code" => "Unauthorized", "message" => "Not Authorized"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Auth;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Pecee\Http\Middleware\IMiddleware;
|
||||||
|
use Pecee\Http\Request;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware for authenticated routes
|
||||||
|
*/
|
||||||
|
class Auth implements IMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Request handler
|
||||||
|
*
|
||||||
|
* Returns 401 if `token`is missing, or no user is found with the `token`.
|
||||||
|
*
|
||||||
|
* Keeps session fresh if request is authenticated.
|
||||||
|
*
|
||||||
|
* @param Request $request Incoming request
|
||||||
|
*/
|
||||||
|
public function handle(Request $request): void
|
||||||
|
{
|
||||||
|
$token = $request->getHeader("token");
|
||||||
|
|
||||||
|
// No token
|
||||||
|
if ($token === null) {
|
||||||
|
Response::response()
|
||||||
|
->header("Cache-control: no-cache")
|
||||||
|
->header("Access-Control-Allow-Origin: *")
|
||||||
|
->header("Access-Control-Allow-Methods: *")
|
||||||
|
->header("Access-Control-Allow-Headers: *")
|
||||||
|
->httpCode(401)
|
||||||
|
->json(["code" => "Unauthorized", "message" => "Not Authorized"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user
|
||||||
|
$user = User::getByToken($token);
|
||||||
|
|
||||||
|
// Keep fresh
|
||||||
|
$user->keepFresh();
|
||||||
|
} catch (Exception $err) {
|
||||||
|
// No user with this token exists
|
||||||
|
Response::response()
|
||||||
|
->header("Cache-control: no-cache")
|
||||||
|
->header("Access-Control-Allow-Origin: *")
|
||||||
|
->header("Access-Control-Allow-Methods: *")
|
||||||
|
->header("Access-Control-Allow-Headers: *")
|
||||||
|
->httpCode(401)
|
||||||
|
->json(["code" => "Unauthorized", "message" => "Not Authorized"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Auth;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Pecee\Http\Middleware\IMiddleware;
|
||||||
|
use Pecee\Http\Request;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use Khofmann\Response\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware for optional authenticated routes
|
||||||
|
*/
|
||||||
|
class OptAuth implements IMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Request handler
|
||||||
|
*
|
||||||
|
* Returns 401 if no user is found with the `token`.
|
||||||
|
*
|
||||||
|
* Keeps session fresh if request is authenticated.
|
||||||
|
*
|
||||||
|
* @param Request $request Incoming request
|
||||||
|
*/
|
||||||
|
public function handle(Request $request): void
|
||||||
|
{
|
||||||
|
$token = $request->getHeader("token");
|
||||||
|
|
||||||
|
// No token, since authentication is optional, pass
|
||||||
|
if ($token === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user
|
||||||
|
$user = User::getByToken($token);
|
||||||
|
|
||||||
|
// Keep fresh
|
||||||
|
$user->keepFresh();
|
||||||
|
} catch (Exception $err) {
|
||||||
|
// No user with this token exists
|
||||||
|
Response::response()
|
||||||
|
->header("Cache-control: no-cache")
|
||||||
|
->header("Access-Control-Allow-Origin: *")
|
||||||
|
->header("Access-Control-Allow-Methods: *")
|
||||||
|
->header("Access-Control-Allow-Headers: *")
|
||||||
|
->httpCode(401)
|
||||||
|
->json(["code" => "Unauthorized", "message" => "Not Authorized"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Config;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for application configuration
|
||||||
|
*/
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
// Instances array to ensure singleton pattern
|
||||||
|
private static array $instances = [];
|
||||||
|
|
||||||
|
// Configuration arrays
|
||||||
|
private array $app;
|
||||||
|
private array $database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads configurations into arrays.
|
||||||
|
*
|
||||||
|
* Private since singleton pattern.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$this->app = require_once __DIR__ . "/../../config/app.php";
|
||||||
|
$this->database = require_once __DIR__ . "/../../config/database.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disallow clone
|
||||||
|
*/
|
||||||
|
private function __clone()
|
||||||
|
{
|
||||||
|
throw new Exception("Cannot clone a singleton.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disallow wakeup
|
||||||
|
*/
|
||||||
|
private function __wakeup()
|
||||||
|
{
|
||||||
|
throw new Exception("Cannot unserialize a singleton.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance. Ensures singleton pattern.
|
||||||
|
*
|
||||||
|
* Private to ensure only facade methods can be used.
|
||||||
|
*/
|
||||||
|
private static function getInstance(): Config
|
||||||
|
{
|
||||||
|
$cls = static::class;
|
||||||
|
if (!isset(self::$instances[$cls])) {
|
||||||
|
self::$instances[$cls] = new static();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instances[$cls];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Application base URI path
|
||||||
|
*/
|
||||||
|
public static function getBasePath(): string
|
||||||
|
{
|
||||||
|
return Config::getInstance()->app["basePath"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Application base file system path
|
||||||
|
*/
|
||||||
|
public static function getBaseFSPath(): string
|
||||||
|
{
|
||||||
|
return Config::getInstance()->app["baseFSPath"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Application storage URI path
|
||||||
|
*/
|
||||||
|
public static function getStoragePath(): string
|
||||||
|
{
|
||||||
|
return Config::getInstance()->app["storagePath"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Application storage file system path
|
||||||
|
*/
|
||||||
|
public static function getStorageFSPath(): string
|
||||||
|
{
|
||||||
|
return Config::getInstance()->app["storageFSPath"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Application base URI path
|
||||||
|
*/
|
||||||
|
public static function getDatabase(): array
|
||||||
|
{
|
||||||
|
return Config::getInstance()->database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Application base URI path
|
||||||
|
*/
|
||||||
|
public static function getTokenExpiry(): string
|
||||||
|
{
|
||||||
|
return Config::getInstance()->app["tokenExpiry"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Application base URI path
|
||||||
|
*/
|
||||||
|
public static function getRefreshTokenExpiry(): string
|
||||||
|
{
|
||||||
|
return Config::getInstance()->app["refreshTokenExpiry"];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Database;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
use Khofmann\Config\Config;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for database access
|
||||||
|
*/
|
||||||
|
class Database extends PDO
|
||||||
|
{
|
||||||
|
// Instances array to ensure singleton pattern
|
||||||
|
private static array $instances = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private since singleton pattern.
|
||||||
|
*/
|
||||||
|
private function __construct(string $dsn, string $username = null, string $password = null, array $options = null)
|
||||||
|
{
|
||||||
|
parent::__construct($dsn, $username, $password, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disallow clone
|
||||||
|
*/
|
||||||
|
private function __clone()
|
||||||
|
{
|
||||||
|
throw new Exception("Cannot clone a singleton.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disallow wakeup
|
||||||
|
*/
|
||||||
|
private function __wakeup()
|
||||||
|
{
|
||||||
|
throw new Exception("Cannot unserialize a singleton.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance. Ensures singleton pattern.
|
||||||
|
*
|
||||||
|
* Loads configuration from `Config` facade
|
||||||
|
*/
|
||||||
|
public static function getInstance(): Database
|
||||||
|
{
|
||||||
|
$cls = static::class;
|
||||||
|
if (!isset(self::$instances[$cls])) {
|
||||||
|
$dataAccess = Config::getDatabase();
|
||||||
|
self::$instances[$cls] = new static(
|
||||||
|
"mysql:host={$dataAccess["host"]};dbname={$dataAccess["database"]};charset={$dataAccess["charset"]}",
|
||||||
|
$dataAccess["user"],
|
||||||
|
$dataAccess["passwd"],
|
||||||
|
[
|
||||||
|
PDO::ATTR_PERSISTENT => false,
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instances[$cls];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\GUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for GUID generators
|
||||||
|
*/
|
||||||
|
class GUID
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Private since facade.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a UUID v4.
|
||||||
|
*
|
||||||
|
* @param mixed $data Optional data
|
||||||
|
*
|
||||||
|
* @return string UUID v4
|
||||||
|
*/
|
||||||
|
public static function v4($data = null): string
|
||||||
|
{
|
||||||
|
// Generate 16 bytes (128 bits) of random data or use the data passed into the function.
|
||||||
|
$data = $data ?? random_bytes(16);
|
||||||
|
assert(strlen($data) == 16);
|
||||||
|
|
||||||
|
// Set version to 0100
|
||||||
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
||||||
|
// Set bits 6-7 to 10
|
||||||
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
||||||
|
|
||||||
|
// Output the 36 character UUID.
|
||||||
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Input;
|
||||||
|
|
||||||
|
use Khofmann\Request\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for Input (wraps SimpleRouter)
|
||||||
|
*/
|
||||||
|
class Input
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Private since facade.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get POST parameter.
|
||||||
|
*
|
||||||
|
* @param string $index Parameter name
|
||||||
|
* @param mixed $defaultValue Default value if parameter was null
|
||||||
|
*/
|
||||||
|
public static function post(string $index, $defaultValue = null)
|
||||||
|
{
|
||||||
|
$value = Request::request()->getInputHandler()->post($index, $defaultValue);
|
||||||
|
return !is_object($value) ? $value : $value->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get PATCH parameter.
|
||||||
|
*
|
||||||
|
* @param string $index Parameter name
|
||||||
|
* @param mixed $defaultValue Default value if parameter was null
|
||||||
|
*/
|
||||||
|
public static function patch(string $index, $defaultValue = null)
|
||||||
|
{
|
||||||
|
$value = Request::request()->getInputHandler()->post($index, $defaultValue);
|
||||||
|
return !is_object($value) ? $value : $value->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query string parameter.
|
||||||
|
*
|
||||||
|
* @param string $index Parameter name
|
||||||
|
* @param mixed $defaultValue Default value if parameter was null
|
||||||
|
*/
|
||||||
|
public static function get(string $index, $defaultValue = null)
|
||||||
|
{
|
||||||
|
$value = Request::request()->getInputHandler()->get($index, $defaultValue);
|
||||||
|
return !is_object($value) ? $value : $value->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get FILE entry.
|
||||||
|
*
|
||||||
|
* @param string $index File name
|
||||||
|
* @param mixed $defaultValue Default value if parameter was null
|
||||||
|
*/
|
||||||
|
public static function file(string $index, $defaultValue = null)
|
||||||
|
{
|
||||||
|
$value = Request::request()->getInputHandler()->file($index, $defaultValue);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Models\Post;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Exception;
|
||||||
|
use Khofmann\Models\User\User;
|
||||||
|
use JsonSerializable;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\Config\Config;
|
||||||
|
use Khofmann\Database\Database;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post database model
|
||||||
|
*
|
||||||
|
* Abstracts database access
|
||||||
|
*/
|
||||||
|
class Post implements JsonSerializable
|
||||||
|
{
|
||||||
|
private int $id;
|
||||||
|
// User is set if the post was fetched by an authenticated user
|
||||||
|
private ?User $user;
|
||||||
|
// Name and image are set if the post was fetched by a non authenticated user
|
||||||
|
private ?string $name;
|
||||||
|
private ?string $image;
|
||||||
|
private string $content;
|
||||||
|
private DateTime $postedAt;
|
||||||
|
|
||||||
|
public function __construct(int $id, ?User $user, ?string $name, ?string $image, string $content, string $postedAt)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
$this->user = $user;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->image = Config::getStoragePath() . $image;
|
||||||
|
$this->content = $content;
|
||||||
|
$this->postedAt = new DateTime($postedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Statics
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a post by ID.
|
||||||
|
*
|
||||||
|
* Also get creator (`User` object).
|
||||||
|
*
|
||||||
|
* @param int $id Post ID
|
||||||
|
*
|
||||||
|
* @throws NotFound Post not found
|
||||||
|
* @throws NotFound Creator of post was not found
|
||||||
|
*/
|
||||||
|
public static function getByID(int $id): Post
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch
|
||||||
|
WHERE
|
||||||
|
id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$data) throw new Exception("NotFound");
|
||||||
|
|
||||||
|
$user = User::getByID($data["benutzer_id"]);
|
||||||
|
return new Post($data["id"], $user, null, null, $data["beitrag"], $data["zeitstempel"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new post.
|
||||||
|
*
|
||||||
|
* @param User $user Creator of post
|
||||||
|
* @param string $content Post content
|
||||||
|
* @param int $limit Limit for posts list to calculate number of pages
|
||||||
|
*
|
||||||
|
* @return array Number of pages after creation (in accordance with `limit`) and created post
|
||||||
|
*/
|
||||||
|
public static function create(User $user, string $content, int $limit): array
|
||||||
|
{
|
||||||
|
$content = substr(trim($content), 0, 250);
|
||||||
|
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"INSERT INTO
|
||||||
|
egb_gaestebuch(benutzer_id, beitrag)
|
||||||
|
VALUES(:USR, :CON)"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":USR", $user->getID());
|
||||||
|
$stmt->bindValue(":CON", nl2br(htmlspecialchars($content)));
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$lastId = $db->lastInsertId();
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
"pages" => intdiv($count, $limit + 1) + 1,
|
||||||
|
"post" => Post::getByID($lastId)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of posts
|
||||||
|
*
|
||||||
|
* @param int $page Current page (offset)
|
||||||
|
* @param int $limit Posts per page
|
||||||
|
* @param bool $authed If `true`, include full `User` object. Defaults to `false`
|
||||||
|
*
|
||||||
|
* @return array Number of pages and posts of selected page
|
||||||
|
*/
|
||||||
|
public static function list(int $page, int $limit, bool $authed = false): array
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch
|
||||||
|
LIMIT $limit
|
||||||
|
OFFSET " . ($page * $limit)
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$list = array_map(
|
||||||
|
function ($item) use ($authed) {
|
||||||
|
$user = User::getByID($item["benutzer_id"]);
|
||||||
|
return new Post($item["id"], $authed ? $user : null, !$authed ? $user->getUsername() : null, !$authed ? $user->getImage() : null, $item["beitrag"], $item["zeitstempel"]);
|
||||||
|
},
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
|
||||||
|
return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $list];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Members
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update post
|
||||||
|
*
|
||||||
|
* Does nothing if new `content` is empty
|
||||||
|
*
|
||||||
|
* @param ?string $content New content
|
||||||
|
*
|
||||||
|
* @throws Failed Failed to update content
|
||||||
|
*/
|
||||||
|
public function update(?string $content): Post
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// Make sure we do all changes or none
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
$failed = [];
|
||||||
|
$reason = [];
|
||||||
|
if (!empty($content)) {
|
||||||
|
$content = substr(trim($content), 0, 250);
|
||||||
|
|
||||||
|
$stmt = $db->prepare("UPDATE egb_gaestebuch SET beitrag = :CON WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":CON", nl2br(htmlspecialchars($content)));
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
try {
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
$failed = ["content"];
|
||||||
|
$reason = ["generic"];
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$failed = ["content"];
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
$pdoErr = $stmt->errorInfo()[1];
|
||||||
|
$reason = ["SQL: $pdoErr"];
|
||||||
|
} else $reason = ["{$e->getCode()}"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($failed) > 0) {
|
||||||
|
// We failed, go back
|
||||||
|
$db->rollBack();
|
||||||
|
|
||||||
|
throw ApiError::failedUpdate($failed, $reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the changes
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
return Post::getByID($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete post
|
||||||
|
*
|
||||||
|
* @param int $limit Limit of list for which the returned pages is calculated.
|
||||||
|
*
|
||||||
|
* @return array Returns deleted post and resulting amount of pages for a given limit.
|
||||||
|
*/
|
||||||
|
public function delete(int $limit): array
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare("DELETE FROM egb_gaestebuch WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
|
return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $this];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Getters
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get post ID
|
||||||
|
*/
|
||||||
|
public function getId(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get post creator
|
||||||
|
*/
|
||||||
|
public function getUser(): User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get post content
|
||||||
|
*/
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get time when post was created
|
||||||
|
*/
|
||||||
|
public function getPostedAt(): DateTime
|
||||||
|
{
|
||||||
|
return $this->postedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
$user = $this->user ? $this->user : [
|
||||||
|
"username" => $this->name,
|
||||||
|
"image" => $this->image,
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'user' => $user,
|
||||||
|
'content' => $this->content,
|
||||||
|
'postedAt' => $this->postedAt,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,857 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Models\User;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use PDO;
|
||||||
|
use DateTime;
|
||||||
|
use Khofmann\Database\Database;
|
||||||
|
use Khofmann\Config\Config;
|
||||||
|
use JsonSerializable;
|
||||||
|
use Khofmann\ApiError\ApiError;
|
||||||
|
use Khofmann\GUID\GUID;
|
||||||
|
use Khofmann\Models\Post\Post;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User database model
|
||||||
|
*
|
||||||
|
* Abstracts database access
|
||||||
|
*/
|
||||||
|
class User implements JsonSerializable
|
||||||
|
{
|
||||||
|
private int $id;
|
||||||
|
private string $username;
|
||||||
|
private int $status;
|
||||||
|
private string $email;
|
||||||
|
private ?string $image;
|
||||||
|
private bool $isAdmin;
|
||||||
|
private DateTime $memberSince;
|
||||||
|
private int $postCount;
|
||||||
|
|
||||||
|
private function __construct(int $id, string $username, int $status, string $email, string $timestamp, ?string $image, bool $isAdmin, int $postCount)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
$this->username = $username;
|
||||||
|
$this->status = $status;
|
||||||
|
$this->email = $email;
|
||||||
|
$this->image = $image;
|
||||||
|
$this->isAdmin = $isAdmin;
|
||||||
|
$this->memberSince = new DateTime($timestamp);
|
||||||
|
$this->postCount = $postCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user by their confirmation code.
|
||||||
|
*
|
||||||
|
* @param string $code Confirmation code
|
||||||
|
*
|
||||||
|
* @throws NotFound User not found
|
||||||
|
*/
|
||||||
|
private static function getByConfirmCode(string $code): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel,
|
||||||
|
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||||
|
FROM
|
||||||
|
egb_benutzer AS b
|
||||||
|
WHERE
|
||||||
|
confirmationcode = :COD"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":COD", $code);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$data) throw new Exception("NotFound");
|
||||||
|
|
||||||
|
return new User($data["id"], $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Statics
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user by ID.
|
||||||
|
*
|
||||||
|
* @param int $id User ID
|
||||||
|
*
|
||||||
|
* @throws NotFound User not found
|
||||||
|
*/
|
||||||
|
public static function getByID(int $id): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel,
|
||||||
|
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||||
|
FROM
|
||||||
|
egb_benutzer AS b
|
||||||
|
WHERE
|
||||||
|
b.id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$data) throw new Exception("NotFound");
|
||||||
|
|
||||||
|
return new User($id, $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user by their email.
|
||||||
|
*
|
||||||
|
* @param string $email User email
|
||||||
|
*
|
||||||
|
* @throws NotFound User not found
|
||||||
|
*/
|
||||||
|
public static function getByEmail(string $email): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
b.id, b.benutzer, b.status, b.image, b.isadmin, b.zeitstempel,
|
||||||
|
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||||
|
FROM
|
||||||
|
egb_benutzer AS b
|
||||||
|
WHERE
|
||||||
|
email = :EMAIL"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":EMAIL", $email);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$data) throw new Exception("NotFound");
|
||||||
|
|
||||||
|
return new User($data["id"], $data["benutzer"], $data["status"], $email, $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user by their authentication token.
|
||||||
|
*
|
||||||
|
* @param string $token Authentication token
|
||||||
|
*
|
||||||
|
* @throws NotFound User not found
|
||||||
|
*/
|
||||||
|
public static function getByToken(string $token): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel,
|
||||||
|
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||||
|
FROM
|
||||||
|
egb_benutzer AS b
|
||||||
|
WHERE
|
||||||
|
token = :TOKEN AND
|
||||||
|
tokenExpiry > NOW()"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":TOKEN", $token);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$data) throw new Exception("NotFound");
|
||||||
|
|
||||||
|
return new User($data["id"], $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log in a user
|
||||||
|
*
|
||||||
|
* @param string $email User email
|
||||||
|
* @param string $password USer password
|
||||||
|
*
|
||||||
|
* @return array Tokens and user
|
||||||
|
*
|
||||||
|
* @throws Failed Could not generate tokens
|
||||||
|
* @throws Invalid Password was wrong
|
||||||
|
* @throws NotFound User not found (email wrong)
|
||||||
|
*/
|
||||||
|
public static function logIn(string $email, string $password): array
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
// Get user data
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
b.*,
|
||||||
|
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||||
|
FROM
|
||||||
|
egb_benutzer AS b
|
||||||
|
WHERE
|
||||||
|
email LIKE :EMAIL AND
|
||||||
|
status = 1"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":EMAIL", $email);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($data) {
|
||||||
|
$user = new User($data["id"], $data["benutzer"], $data["status"], $email, $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
|
||||||
|
if (password_verify($password, $data["passwort"])) {
|
||||||
|
// REHASH for safety should it somehow change
|
||||||
|
if (password_needs_rehash($data["passwort"], PASSWORD_DEFAULT)) {
|
||||||
|
$newHash = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$stmt = $db->prepare("UPDATE egb_benutzer SET passwort = :PAS WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":PAS", $newHash);
|
||||||
|
$stmt->bindValue(":ID", $user->getID());
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
// Generate tokens only if expired or missing
|
||||||
|
if (empty($data["token"]) || new DateTime($data["tokenExpiry"]) <= new DateTime()) {
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"UPDATE
|
||||||
|
egb_benutzer
|
||||||
|
SET
|
||||||
|
token = UUID(),
|
||||||
|
tokenExpiry = DATE_ADD(NOW(), INTERVAL " . Config::getTokenExpiry() . "),
|
||||||
|
refreshToken = UUID(),
|
||||||
|
refreshExpiry = DATE_ADD(NOW(), INTERVAL " . Config::getRefreshTokenExpiry() . ")
|
||||||
|
WHERE id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $user->getID());
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
// Get token
|
||||||
|
$stmt = $db->prepare("SELECT token, refreshToken FROM egb_benutzer WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":ID", $user->getID());
|
||||||
|
$stmt->execute();
|
||||||
|
[$token, $refresh] = $stmt->fetch(PDO::FETCH_NUM);
|
||||||
|
// Return user and tokens
|
||||||
|
if ($token) {
|
||||||
|
return ["user" => $user, "token" => $token, "refreshToken" => $refresh];
|
||||||
|
}
|
||||||
|
// Token generation failed
|
||||||
|
throw new Exception("Failed");
|
||||||
|
} else {
|
||||||
|
// PW wrong
|
||||||
|
throw new Exception("Invalid");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// User does not exist
|
||||||
|
throw new Exception("NotFound");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a user
|
||||||
|
*
|
||||||
|
* @param string $username Username
|
||||||
|
* @param string $email User email
|
||||||
|
* @param string $password User password
|
||||||
|
*
|
||||||
|
* @throws Duplicate User with this username or email already exists
|
||||||
|
*/
|
||||||
|
public static function create(string $username, string $email, string $password): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$guid = GUID::v4();
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"INSERT INTO
|
||||||
|
egb_benutzer(benutzer, passwort, email, confirmationcode)
|
||||||
|
VALUES(:USR, :PAS, :EMA, :COD)"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":USR", htmlspecialchars($username));
|
||||||
|
$stmt->bindValue(":PAS", password_hash($password, PASSWORD_DEFAULT));
|
||||||
|
$stmt->bindValue(":EMA", $email);
|
||||||
|
$stmt->bindValue(":COD", $guid);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$user = User::getByID($db->lastInsertId());
|
||||||
|
|
||||||
|
mail(
|
||||||
|
$email,
|
||||||
|
"Account activation GuestBookDB",
|
||||||
|
"Hello $username. To activate your account, visit https://khofmann.userpage.fu-berlin.de/phpCourse/exam/confirm?code=$guid"
|
||||||
|
);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
} catch (Exception $err) {
|
||||||
|
if ($err->getCode() === "23000") throw new Exception("Duplicate");
|
||||||
|
|
||||||
|
throw $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm a user
|
||||||
|
*
|
||||||
|
* @param string $confirmCode Confirmation code
|
||||||
|
*
|
||||||
|
* @throws NotFound User not found (invalid code or already confirmed)
|
||||||
|
*/
|
||||||
|
public static function confirm(string $confirmCode): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$user = User::getByConfirmCode($confirmCode);
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"UPDATE
|
||||||
|
egb_benutzer
|
||||||
|
SET
|
||||||
|
status = 1,
|
||||||
|
confirmationcode = NULL
|
||||||
|
WHERE id = :UID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":UID", $user->getID());
|
||||||
|
$stmt->execute();
|
||||||
|
return User::getByID($user->getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of users
|
||||||
|
*
|
||||||
|
* @param int $page Current page (offset)
|
||||||
|
* @param int $limit Users per page
|
||||||
|
*
|
||||||
|
* @return array Number of pages and posts of selected page
|
||||||
|
*/
|
||||||
|
public static function list(int $page, int $limit)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel,
|
||||||
|
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||||
|
FROM
|
||||||
|
egb_benutzer AS b
|
||||||
|
LIMIT $limit
|
||||||
|
OFFSET " . ($page * $limit)
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$list = array_map(
|
||||||
|
fn ($item) => new User($item["id"], $item["benutzer"], $item["status"], $item["email"], $item["zeitstempel"], $item["image"], $item["isadmin"] === 1, $item["postCount"]),
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
|
||||||
|
return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $list];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh a user session
|
||||||
|
*
|
||||||
|
* @param string $token Authentication token
|
||||||
|
* @param string $refreshToken Refresh token
|
||||||
|
*
|
||||||
|
* @throws NotFound User not found (tokens do not exist, refresh token expired)
|
||||||
|
* @throws Failed Could not regenerate tokens
|
||||||
|
*/
|
||||||
|
public static function refresh(string $token, string $refreshToken)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
b.id, b.benutzer, b.status, b.email, b.image, b.isadmin, b.zeitstempel, b.tokenExpiry,
|
||||||
|
(SELECT COUNT(*) FROM egb_gaestebuch WHERE benutzer_id = b.id) as postCount
|
||||||
|
FROM
|
||||||
|
egb_benutzer AS b
|
||||||
|
WHERE
|
||||||
|
token = :TOKEN AND
|
||||||
|
refreshToken = :REFRESH AND
|
||||||
|
refreshExpiry > NOW()"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":TOKEN", $token);
|
||||||
|
$stmt->bindValue(":REFRESH", $refreshToken);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetch();
|
||||||
|
|
||||||
|
if (!$data) throw new Exception("NotFound");
|
||||||
|
|
||||||
|
$user = new User($data["id"], $data["benutzer"], $data["status"], $data["email"], $data["zeitstempel"], $data["image"], $data["isadmin"] === 1, $data["postCount"]);
|
||||||
|
|
||||||
|
// Update tokens if expired
|
||||||
|
if (new DateTime($data["tokenExpiry"]) <= new DateTime()) {
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"UPDATE
|
||||||
|
egb_benutzer
|
||||||
|
SET
|
||||||
|
token = UUID(),
|
||||||
|
tokenExpiry = DATE_ADD(NOW(), INTERVAL " . Config::getTokenExpiry() . "),
|
||||||
|
refreshToken = UUID(),
|
||||||
|
refreshExpiry = DATE_ADD(NOW(), INTERVAL " . Config::getRefreshTokenExpiry() . ")
|
||||||
|
WHERE id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $user->getID());
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get token
|
||||||
|
$stmt = $db->prepare("SELECT token, refreshToken FROM egb_benutzer WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":ID", $user->getID());
|
||||||
|
$stmt->execute();
|
||||||
|
[$token, $refresh] = $stmt->fetch(PDO::FETCH_NUM);
|
||||||
|
// Return user and tokens
|
||||||
|
if ($token) {
|
||||||
|
return ["user" => $user, "token" => $token, "refreshToken" => $refresh];
|
||||||
|
}
|
||||||
|
// Token generation failed
|
||||||
|
throw new Exception("Failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Members
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function logOut(): bool
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"UPDATE
|
||||||
|
egb_benutzer
|
||||||
|
SET
|
||||||
|
token = NULL,
|
||||||
|
tokenExpiry = NULL,
|
||||||
|
refreshToken = NULL,
|
||||||
|
refreshExpiry = NULL
|
||||||
|
WHERE id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
return $stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user
|
||||||
|
*
|
||||||
|
* Does nothing if new all fields are empty
|
||||||
|
*
|
||||||
|
* @param ?string $username New username
|
||||||
|
* @param ?string $password New password
|
||||||
|
* @param ?string $email New email
|
||||||
|
*
|
||||||
|
* @throws Failed At least one field failed to update
|
||||||
|
*/
|
||||||
|
public function update(?string $username, ?string $password, ?string $email): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// Make sure we do all changes or none
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
$failed = [];
|
||||||
|
$reasons = [];
|
||||||
|
if (!empty($username)) {
|
||||||
|
$stmt = $db->prepare("UPDATE egb_benutzer SET benutzer = :USR WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":USR", htmlspecialchars($username));
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
try {
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
array_push($failed, "username");
|
||||||
|
array_push($reasons, "generic");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
array_push($failed, "username");
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
$pdoErr = $stmt->errorInfo()[1];
|
||||||
|
if ($pdoErr === 1062) array_push($reasons, "Duplicate");
|
||||||
|
else array_push($reasons, "SQL: $pdoErr");
|
||||||
|
} else array_push($reasons, "{$e->getCode()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($password)) {
|
||||||
|
$stmt = $db->prepare("UPDATE egb_benutzer SET passwort = :PAS WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":PAS", password_hash($password, PASSWORD_DEFAULT));
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
try {
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
array_push($failed, "password");
|
||||||
|
array_push($reasons, "generic");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
array_push($failed, "password");
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
$pdoErr = $stmt->errorInfo()[1];
|
||||||
|
if ($pdoErr === 1062) array_push($reasons, "Duplicate");
|
||||||
|
else array_push($reasons, "SQL: $pdoErr");
|
||||||
|
} else array_push($reasons, "{$e->getCode()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($email)) {
|
||||||
|
$stmt = $db->prepare("UPDATE egb_benutzer SET email = :EMA WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":EMA", $email);
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
try {
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
array_push($failed, "email");
|
||||||
|
array_push($reasons, "generic");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
array_push($failed, "email");
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
$pdoErr = $stmt->errorInfo()[1];
|
||||||
|
if ($pdoErr === 1062) array_push($reasons, "Duplicate");
|
||||||
|
else array_push($reasons, "SQL: $pdoErr");
|
||||||
|
} else array_push($reasons, "{$e->getCode()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($failed) > 0) {
|
||||||
|
// We failed, go back
|
||||||
|
$db->rollBack();
|
||||||
|
|
||||||
|
throw ApiError::failedUpdate($failed, $reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the changes
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
return User::getByID($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user
|
||||||
|
*
|
||||||
|
* Does nothing if all fields are empty
|
||||||
|
*
|
||||||
|
* @param mixed $image New file upload
|
||||||
|
* @param ?string $predefined Predefined avatar
|
||||||
|
*
|
||||||
|
* @param Failed Image failed to update
|
||||||
|
*/
|
||||||
|
public function updateImage($image, ?string $predefined): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// Grab old picture
|
||||||
|
$stmt = $db->prepare("SELECT image FROM egb_benutzer WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
$stmt->execute();
|
||||||
|
$oldImage = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
|
// Make sure we do all changes or none
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
$failed = [];
|
||||||
|
$reasons = [];
|
||||||
|
if (!empty($image)) {
|
||||||
|
// Generate new filename
|
||||||
|
$destinationFilename = sprintf('%s.%s', uniqid(), $image->getExtension());
|
||||||
|
// Try and update user record
|
||||||
|
try {
|
||||||
|
$stmt = $db->prepare("UPDATE egb_benutzer SET image = :IMG WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":IMG", "profilbilder/$destinationFilename");
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
array_push($failed, "image");
|
||||||
|
array_push($reasons, "generic");
|
||||||
|
}
|
||||||
|
// Move file
|
||||||
|
$image->move(Config::getStorageFSPath() . "profilbilder/$destinationFilename");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
array_push($failed, "image");
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
$pdoErr = $stmt->errorInfo()[1];
|
||||||
|
if ($pdoErr === 1062) array_push($reasons, "Duplicate");
|
||||||
|
else array_push($reasons, "SQL: $pdoErr");
|
||||||
|
} else array_push($reasons, "{$e->getCode()}");
|
||||||
|
}
|
||||||
|
} else if (!empty($predefined)) {
|
||||||
|
$stmt = $db->prepare("UPDATE egb_benutzer SET image = :IMG WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":IMG", "profilbilder/default/$predefined.svg");
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
try {
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
array_push($failed, "image");
|
||||||
|
array_push($reasons, "generic");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
array_push($failed, "image");
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
$pdoErr = $stmt->errorInfo()[1];
|
||||||
|
if ($pdoErr === 1062) array_push($reasons, "Duplicate");
|
||||||
|
else array_push($reasons, "SQL: $pdoErr");
|
||||||
|
} else array_push($reasons, "{$e->getCode()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($failed) > 0) {
|
||||||
|
// We failed, go back
|
||||||
|
$db->rollBack();
|
||||||
|
|
||||||
|
throw ApiError::failedUpdate($failed, $reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the changes
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
// Delete old picture if it isn't a default one and if it exists.
|
||||||
|
try {
|
||||||
|
if (strpos($oldImage, "default") === false && is_file(Config::getStorageFSPath() . $oldImage)) unlink(Config::getStorageFSPath() . $oldImage);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return User::getByID($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user
|
||||||
|
*
|
||||||
|
* Does nothing if new all fields are empty
|
||||||
|
*
|
||||||
|
* @param ?bool $isAdmin Admin permission
|
||||||
|
*
|
||||||
|
* @throws Failed Failed to update admin status
|
||||||
|
*/
|
||||||
|
public function updatePermissions(?bool $isAdmin): User
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
// Make sure we do all changes or none
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
$failed = [];
|
||||||
|
$reasons = [];
|
||||||
|
if (isset($isAdmin)) {
|
||||||
|
// Clear tokens to revoke access if logged in
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"UPDATE
|
||||||
|
egb_benutzer
|
||||||
|
SET
|
||||||
|
isadmin = :ADM,
|
||||||
|
token = NULL,
|
||||||
|
tokenExpiry = NULL,
|
||||||
|
refreshToken = NULL,
|
||||||
|
refreshExpiry = NULL
|
||||||
|
WHERE
|
||||||
|
id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ADM", $isAdmin);
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
try {
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
array_push($failed, "username");
|
||||||
|
array_push($reasons, "generic");
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
array_push($failed, "username");
|
||||||
|
if ($e->getCode() === "23000") {
|
||||||
|
$pdoErr = $stmt->errorInfo()[1];
|
||||||
|
if ($pdoErr === 1062) array_push($reasons, "Duplicate");
|
||||||
|
else array_push($reasons, "SQL: $pdoErr");
|
||||||
|
} else array_push($reasons, "{$e->getCode()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($failed) > 0) {
|
||||||
|
// We failed, go back
|
||||||
|
$db->rollBack();
|
||||||
|
|
||||||
|
throw ApiError::failedUpdate($failed, $reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the changes
|
||||||
|
$db->commit();
|
||||||
|
|
||||||
|
return User::getByID($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete user and image
|
||||||
|
*
|
||||||
|
* @param int $limit Limit of list for which the returned pages is calculated.
|
||||||
|
*
|
||||||
|
* @return array Returns deleted user and resulting amount of pages for a given limit.
|
||||||
|
*/
|
||||||
|
public function delete(int $limit): array
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare("DELETE FROM egb_benutzer WHERE id = :ID");
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
egb_benutzer"
|
||||||
|
);
|
||||||
|
$stmt->execute();
|
||||||
|
$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
|
// Delete picture if it isn't a default one and if it exists.
|
||||||
|
try {
|
||||||
|
if (strpos($this->image, "default") === false && is_file(Config::getStorageFSPath() . $this->image)) unlink(Config::getStorageFSPath() . $this->image);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $this];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of posts by user
|
||||||
|
*
|
||||||
|
* @param int $page Current page (offset)
|
||||||
|
* @param int $limit Users per page
|
||||||
|
* @param string $sort Sort direction
|
||||||
|
*
|
||||||
|
* @return array Number of pages and posts of selected page
|
||||||
|
*/
|
||||||
|
public function posts(int $page, int $limit, string $sort)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
COUNT(*)
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch
|
||||||
|
WHERE
|
||||||
|
benutzer_id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
$stmt->execute();
|
||||||
|
$count = $stmt->fetch(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
|
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
egb_gaestebuch
|
||||||
|
WHERE
|
||||||
|
benutzer_id = :ID
|
||||||
|
ORDER BY
|
||||||
|
id $sort
|
||||||
|
LIMIT $limit
|
||||||
|
OFFSET " . ($page * $limit)
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $this->id);
|
||||||
|
$stmt->execute();
|
||||||
|
$data = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$list = array_map(
|
||||||
|
function ($item) {
|
||||||
|
return new Post($item["id"], $this, null, null, $item["beitrag"], $item["zeitstempel"]);
|
||||||
|
},
|
||||||
|
$data
|
||||||
|
);
|
||||||
|
|
||||||
|
return ["pages" => intdiv($count, $limit + 1) + 1, "data" => $list];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh user token expiry
|
||||||
|
*/
|
||||||
|
public function keepFresh()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$stmt = $db->prepare(
|
||||||
|
"UPDATE
|
||||||
|
egb_benutzer
|
||||||
|
SET
|
||||||
|
tokenExpiry = DATE_ADD(NOW(), INTERVAL " . Config::getTokenExpiry() . "),
|
||||||
|
refreshExpiry = DATE_ADD(NOW(), INTERVAL " . Config::getRefreshTokenExpiry() . ")
|
||||||
|
WHERE id = :ID"
|
||||||
|
);
|
||||||
|
$stmt->bindValue(":ID", $this->getID());
|
||||||
|
$stmt->execute();
|
||||||
|
} catch (Exception $err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Getters
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user ID
|
||||||
|
*/
|
||||||
|
public function getID(): int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get username
|
||||||
|
*/
|
||||||
|
public function getUsername(): string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user status
|
||||||
|
*
|
||||||
|
* * 0: Not confirmed
|
||||||
|
* * 1: Confirmed
|
||||||
|
*/
|
||||||
|
public function getStatus(): int
|
||||||
|
{
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user email
|
||||||
|
*/
|
||||||
|
public function getEmail(): string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user image path
|
||||||
|
*/
|
||||||
|
public function getImage(): ?string
|
||||||
|
{
|
||||||
|
return $this->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user admin status
|
||||||
|
*/
|
||||||
|
public function getIsAdmin(): bool
|
||||||
|
{
|
||||||
|
return $this->isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get time of user creation
|
||||||
|
*/
|
||||||
|
public function getMemberSince(): DateTime
|
||||||
|
{
|
||||||
|
return $this->memberSince;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get count of posts by user
|
||||||
|
*/
|
||||||
|
public function getPostCount(): int
|
||||||
|
{
|
||||||
|
return $this->postCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'username' => $this->username,
|
||||||
|
'status' => $this->status,
|
||||||
|
'email' => htmlspecialchars($this->email),
|
||||||
|
'image' => Config::getStoragePath() . $this->image,
|
||||||
|
'isAdmin' => $this->isAdmin,
|
||||||
|
'memberSince' => $this->memberSince,
|
||||||
|
'postCount' => $this->postCount,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Request;
|
||||||
|
|
||||||
|
use Pecee\Http\Request as PRequest;
|
||||||
|
use Pecee\SimpleRouter\SimpleRouter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for Request access (wraps SimpleRouter)
|
||||||
|
*/
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Private since facade.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current request
|
||||||
|
*/
|
||||||
|
public static function request(): PRequest
|
||||||
|
{
|
||||||
|
return SimpleRouter::request();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get header value from current request
|
||||||
|
*
|
||||||
|
* @param string $name Field name
|
||||||
|
* @param mixed $defaultValue Default value if header field was null
|
||||||
|
* @param bool $tryParse When enabled the method will try to find the header from both from client (http) and server-side variants, if the header is not found.
|
||||||
|
*/
|
||||||
|
public static function header(string $name, $defaultValue = null, bool $tryParse = true): ?string
|
||||||
|
{
|
||||||
|
return Request::request()->getHeader($name, $defaultValue, $tryParse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get authentication header field
|
||||||
|
*/
|
||||||
|
public static function token()
|
||||||
|
{
|
||||||
|
return Request::header("token");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Khofmann\Response;
|
||||||
|
|
||||||
|
use Pecee\SimpleRouter\SimpleRouter;
|
||||||
|
use Pecee\Http\Response as PResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facade for Response creation (wraps SimpleRouter)
|
||||||
|
*/
|
||||||
|
class Response
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Private since facade.
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current response
|
||||||
|
*/
|
||||||
|
public static function response(): PResponse
|
||||||
|
{
|
||||||
|
return SimpleRouter::response();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create JSON response
|
||||||
|
*
|
||||||
|
* @param mixed $value Body of response
|
||||||
|
* @param int $options Options passed to `json_encode`
|
||||||
|
* @param int $dept Depth passed to `json_encode`
|
||||||
|
*/
|
||||||
|
public static function json($value, int $options = 0, int $dept = 512): void
|
||||||
|
{
|
||||||
|
if (is_bool($value)) {
|
||||||
|
Response::response()->header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode($value, $options, $dept);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
SimpleRouter::response()->json($value, $options, $dept);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create API error response
|
||||||
|
*
|
||||||
|
* @param string $value Body of error
|
||||||
|
* @param int $code HTTP code of error
|
||||||
|
*/
|
||||||
|
public static function apiError(string $value, int $code): void
|
||||||
|
{
|
||||||
|
Response::response()->header('Content-Type: application/json; charset=utf-8')->httpCode($code);
|
||||||
|
echo $value;
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create redirect response
|
||||||
|
*
|
||||||
|
* @param string $url New target
|
||||||
|
* @param ?int $code HTTP code
|
||||||
|
*/
|
||||||
|
public static function redirect(string $url, ?int $code = null): void
|
||||||
|
{
|
||||||
|
if ($code !== null) {
|
||||||
|
Response::response()->httpCode($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::response()->redirect($url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"pecee/simple-router": "^5.4"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Api\\": "api/",
|
||||||
|
"Config\\": "config/",
|
||||||
|
"Khofmann\\": "classes/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+82
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "d067465d9a99de373ba3093d61eda1d8",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "pecee/simple-router",
|
||||||
|
"version": "5.4.1.7",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/skipperbent/simple-php-router.git",
|
||||||
|
"reference": "a2843d5b1e037f8b61cc99f27eab52a28bf41dfd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/skipperbent/simple-php-router/zipball/a2843d5b1e037f8b61cc99f27eab52a28bf41dfd",
|
||||||
|
"reference": "a2843d5b1e037f8b61cc99f27eab52a28bf41dfd",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": ">=7.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1",
|
||||||
|
"phpstan/phpstan": "^1",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^1",
|
||||||
|
"phpstan/phpstan-phpunit": "^1",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1",
|
||||||
|
"phpunit/phpunit": "^8"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Pecee\\": "src/Pecee/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Simon Sessingø",
|
||||||
|
"email": "simon.sessingoe@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Simple, fast PHP router that is easy to get integrated and in almost any project. Heavily inspired by the Laravel router.",
|
||||||
|
"keywords": [
|
||||||
|
"framework",
|
||||||
|
"input-handler",
|
||||||
|
"laravel",
|
||||||
|
"pecee",
|
||||||
|
"php",
|
||||||
|
"request-handler",
|
||||||
|
"route",
|
||||||
|
"router",
|
||||||
|
"routing",
|
||||||
|
"routing-engine",
|
||||||
|
"simple-php-router",
|
||||||
|
"url-handling"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/skipperbent/simple-php-router/issues",
|
||||||
|
"source": "https://github.com/skipperbent/simple-php-router/issues"
|
||||||
|
},
|
||||||
|
"time": "2023-12-11T21:48:25+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.6.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
Allow from 127.0.0.1
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
"basePath" => "/phpCourse/exam/",
|
||||||
|
"storagePath" => "/phpCourse/exam/storage/",
|
||||||
|
"baseFSPath" => "/home/k/khofmann/public_html/phpCourse/exam/",
|
||||||
|
"storageFSPath" => "/home/k/khofmann/public_html/phpCourse/exam/storage/",
|
||||||
|
"tokenExpiry" => "5 MINUTE",
|
||||||
|
"refreshTokenExpiry" => "30 MINUTE"
|
||||||
|
];
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
"host" => "usersql.zedat.fu-berlin.de",
|
||||||
|
"user" => "khofmann-sql",
|
||||||
|
"passwd" => "xz8c7m7p",
|
||||||
|
"database" => "khofmann-db1",
|
||||||
|
"charset" => "utf8",
|
||||||
|
];
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
RewriteBase /phpCourse/exam/dist
|
||||||
|
|
||||||
|
## Disallow direct entry over the dist
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^ / [L,NC,QSA,F]
|
||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>
|
||||||
|
Page not Found
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="site-content" class="site-content">
|
||||||
|
<div class="p-5 mb-4 bg-light rounded-3">
|
||||||
|
<div class="container-fluid py-5">
|
||||||
|
<h1 class="display-5 fw-bold">404 Page not Found</h1>
|
||||||
|
<p class="col-md-8 fs-4">Page does not exist.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<title>
|
||||||
|
Server Error
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="site-content" class="site-content">
|
||||||
|
<div class="p-5 mb-4 bg-light rounded-3">
|
||||||
|
<div class="container-fluid py-5">
|
||||||
|
<h1 class="display-5 fw-bold">500 Internal Server Error</h1>
|
||||||
|
<pre class="col-md-8 fs-4"><?= $e->getMessage() ?></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Vendored
+2
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+9
File diff suppressed because one or more lines are too long
Vendored
+178
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+32
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user