Ver Fonte

2.0 Flight -> Slim 3

Sergio Brighenti há 6 anos atrás
pai
commit
4a4c51996c

+ 16 - 48
app/Controllers/Controller.php

@@ -3,52 +3,34 @@
 namespace App\Controllers;
 
 
-use App\Exceptions\AuthenticationException;
-use App\Exceptions\UnauthorizedException;
-use App\Web\Session;
-use Flight;
-use App\Database\DB;
 use League\Flysystem\Adapter\Local;
 use League\Flysystem\Filesystem;
+use Slim\Container;
+use Slim\Http\Request;
+use Slim\Http\Response;
 
 abstract class Controller
 {
 
-	/**
-	 * Check if the current user is logged in
-	 * @throws AuthenticationException
-	 */
-	protected function checkLogin(): void
-	{
-		if (!Session::get('logged', false)) {
-			Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
-			throw new AuthenticationException();
-		}
-
-		if (!DB::query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->active) {
-			Session::alert('Your account is not active anymore.', 'danger');
-			Session::set('logged', false);
-			Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
-			throw new AuthenticationException();
-		}
+	/** @var Container */
+	protected $container;
 
+	public function __construct(Container $container)
+	{
+		$this->container = $container;
 	}
 
 	/**
-	 * Check if the current user is an admin
-	 * @throws AuthenticationException
-	 * @throws UnauthorizedException
+	 * @param $name
+	 * @return mixed|null
+	 * @throws \Interop\Container\Exception\ContainerException
 	 */
-	protected function checkAdmin(): void
+	public function __get($name)
 	{
-		$this->checkLogin();
-
-		if (!DB::query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->is_admin) {
-			Session::alert('Your account is not admin anymore.', 'danger');
-			Session::set('admin', false);
-			Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
-			throw new UnauthorizedException();
+		if ($this->container->has($name)) {
+			return $this->container->get($name);
 		}
+		return null;
 	}
 
 
@@ -71,20 +53,6 @@ abstract class Controller
 	 */
 	protected function getStorage(): Filesystem
 	{
-		return new Filesystem(new Local(Flight::get('config')['storage_dir']));
-	}
-
-	/**
-	 * Set http2 header for a resource if is supported
-	 * @param string $url
-	 * @param string $as
-	 */
-	protected function http2push(string $url, string $as = 'image'): void
-	{
-		if (Flight::request()->scheme === 'HTTP/2.0') {
-			$headers = isset(Flight::response()->headers()['Link']) ? Flight::response()->headers()['Link'] : [];
-			$headers[] = "<${url}>; rel=preload; as=${as}";
-			Flight::response()->header('Link', $headers);
-		}
+		return new Filesystem(new Local($this->settings['storage_dir']));
 	}
 }

+ 39 - 25
app/Controllers/DashboardController.php

@@ -3,37 +3,44 @@
 namespace App\Controllers;
 
 
-use App\Database\DB;
-use App\Traits\SingletonController;
 use App\Web\Session;
-use Flight;
 use League\Flysystem\FileNotFoundException;
+use Slim\Http\Request;
+use Slim\Http\Response;
 
 class DashboardController extends Controller
 {
-	use SingletonController;
 
 	const PER_PAGE = 21;
-	const PER_PAGE_ADMIN = 50;
-
-	public function redirects(): void
+	const PER_PAGE_ADMIN = 25;
+
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 */
+	public function redirects(Request $request, Response $response): Response
 	{
-		$this->checkLogin();
-		Flight::redirect('/home');
+		return $response->withRedirect('/home');
 	}
 
-	public function home($page = 1): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 */
+	public function home(Request $request, Response $response, $args): Response
 	{
-		$this->checkLogin();
-
+		$page = isset($args['page']) ? (int)$args['page'] : 0;
 		$page = max(0, --$page);
 
 		if (Session::get('admin', false)) {
-			$medias = DB::query('SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [self::PER_PAGE_ADMIN, $page * self::PER_PAGE_ADMIN])->fetchAll();
-			$pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count / self::PER_PAGE_ADMIN;
+			$medias = $this->database->query('SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [self::PER_PAGE_ADMIN, $page * self::PER_PAGE_ADMIN])->fetchAll();
+			$pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count / self::PER_PAGE_ADMIN;
 		} else {
-			$medias = DB::query('SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ? ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [Session::get('user_id'), self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
-			$pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?', Session::get('user_id'))->fetch()->count / self::PER_PAGE;
+			$medias = $this->database->query('SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ? ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [Session::get('user_id'), self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
+			$pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?', Session::get('user_id'))->fetch()->count / self::PER_PAGE;
 		}
 
 		$filesystem = $this->getStorage();
@@ -51,7 +58,8 @@ class DashboardController extends Controller
 			$media->size = $this->humanFilesize($size);
 		}
 
-		Flight::render(
+		return $this->view->render(
+			$response,
 			Session::get('admin', false) ? 'dashboard/admin.twig' : 'dashboard/home.twig',
 			[
 				'medias' => $medias,
@@ -62,15 +70,19 @@ class DashboardController extends Controller
 		);
 	}
 
-	public function system()
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 * @throws FileNotFoundException
+	 */
+	public function system(Request $request, Response $response): Response
 	{
-		$this->checkAdmin();
-
-		$usersCount = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count;
-		$mediasCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count;
-		$orphanFilesCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count;
+		$usersCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count;
+		$mediasCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count;
+		$orphanFilesCount = $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count;
 
-		$medias = DB::query('SELECT `users`.`user_code`, `uploads`.`code`, `uploads`.`storage_path` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`')->fetchAll();
+		$medias = $this->database->query('SELECT `users`.`user_code`, `uploads`.`code`, `uploads`.`storage_path` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`')->fetchAll();
 
 		$totalSize = 0;
 
@@ -79,7 +91,9 @@ class DashboardController extends Controller
 			$totalSize += $filesystem->getSize($media->storage_path);
 		}
 
-		Flight::render('dashboard/system.twig', [
+		return $this->view->render(
+			$response,
+			'dashboard/system.twig', [
 			'usersCount' => $usersCount,
 			'mediasCount' => $mediasCount,
 			'orphanFilesCount' => $orphanFilesCount,

+ 30 - 23
app/Controllers/LoginController.php

@@ -4,40 +4,44 @@ namespace App\Controllers;
 
 
 use App\Database\DB;
-use App\Traits\SingletonController;
-use App\Web\Log;
 use App\Web\Session;
-use Flight;
+use Slim\Http\Request;
+use Slim\Http\Response;
 
 class LoginController extends Controller
 {
-	use SingletonController;
 
-	public function show(): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 */
+	public function show(Request $request, Response $response): Response
 	{
 		if (Session::get('logged', false)) {
-			Flight::redirect('/home');
-			return;
+			return $response->withRedirect('/home');
 		}
-		Flight::render('auth/login.twig');
+		return $this->view->render($response, 'auth/login.twig');
 	}
 
-	public function login(): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 */
+	public function login(Request $request, Response $response): Response
 	{
-		$form = Flight::request()->data;
 
-		$result = DB::query('SELECT `id`,`username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? LIMIT 1', $form->username)->fetch();
+		$result = DB::query('SELECT `id`,`username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? LIMIT 1', $request->getParam('username'))->fetch();
 
-		if (!$result || !password_verify($form->password, $result->password)) {
-			Flight::redirect('login');
+		if (!$result || !password_verify($request->getParam('password'), $result->password)) {
 			Session::alert('Wrong credentials', 'danger');
-			return;
+			return $response->withRedirect('/login');
 		}
 
 		if (!$result->active) {
-			Flight::redirect('login');
 			Session::alert('Your account is disabled.', 'danger');
-			return;
+			return $response->withRedirect('/login');
 		}
 
 		Session::set('logged', true);
@@ -46,23 +50,26 @@ class LoginController extends Controller
 		Session::set('admin', $result->is_admin);
 
 		Session::alert("Welcome, $result->username!", 'info');
-		Log::info("User $result->username logged in.");
+		$this->logger->info("User $result->username logged in.");
 
 		if (Session::has('redirectTo')) {
-			Flight::redirect(Session::get('redirectTo'));
-			return;
+			return $response->withRedirect(Session::get('redirectTo'));
 		}
 
-		Flight::redirect('/home');
+		return $response->withRedirect('/home');
 	}
 
-	public function logout(): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 */
+	public function logout(Request $request, Response $response): Response
 	{
-		$this->checkLogin();
 		Session::clear();
 		Session::set('logged', false);
 		Session::alert('Goodbye!', 'warning');
-		Flight::redirect('/login');
+		return $response->withRedirect('/login');
 	}
 
 }

+ 162 - 121
app/Controllers/UploadController.php

@@ -3,99 +3,98 @@
 namespace App\Controllers;
 
 
-use App\Database\DB;
-use App\Exceptions\NotFoundException;
-use App\Traits\SingletonController;
-use App\Web\Log;
+use App\Exceptions\UnauthorizedException;
 use App\Web\Session;
-use Flight;
 use League\Flysystem\FileExistsException;
 use League\Flysystem\FileNotFoundException;
 use League\Flysystem\Filesystem;
+use Slim\Exception\NotFoundException;
+use Slim\Http\Request;
+use Slim\Http\Response;
+use Slim\Http\Stream;
 
 class UploadController extends Controller
 {
-	use SingletonController;
 
-	public function upload(): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 * @throws FileExistsException
+	 */
+	public function upload(Request $request, Response $response): Response
 	{
-		$requestData = Flight::request()->data;
 
-		$response = [
-			'message' => null,
-		];
+		$json = ['message' => null];
 
-		if (!isset($requestData->token)) {
-			$response['message'] = 'Token not specified.';
-			Flight::json($response, 400);
-			return;
+		if ($request->getParam('token') === null) {
+			$json['message'] = 'Token not specified.';
+			return $response->withJson($json, 400);
 		}
 
-		$user = DB::query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $requestData->token)->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $request->getParam('token'))->fetch();
 
 		if (!$user) {
-			$response['message'] = 'Token specified not found.';
-			Flight::json($response, 404);
-			return;
+			$json['message'] = 'Token specified not found.';
+			return $response->withJson($json, 404);
 		}
 
 		if (!$user->active) {
-			$response['message'] = 'Account disabled.';
-			Flight::json($response, 401);
-			return;
+			$json['message'] = 'Account disabled.';
+			return $response->withJson($json, 401);
 		}
 
 		do {
 			$code = uniqid();
-		} while (DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
+		} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
 
-		$file = Flight::request()->files->current();
-		$fileInfo = pathinfo($file['name']);
+		/** @var \Psr\Http\Message\UploadedFileInterface $file */
+		$file = $request->getUploadedFiles()['upload'];
+
+		$fileInfo = pathinfo($file->getClientFilename());
 		$storagePath = "$user->user_code/$code.$fileInfo[extension]";
 
-		$stream = fopen($file['tmp_name'], 'r+');
+		$this->getStorage()->writeStream($storagePath, $file->getStream()->detach());
 
-		$filesystem = $this->getStorage();
-		try {
-			$filesystem->writeStream($storagePath, $stream);
-		} catch (FileExistsException $e) {
-			Flight::halt(500);
-			return;
-		} finally {
-			fclose($stream);
-		}
-
-		DB::query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [
+		$this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [
 			$user->id,
 			$code,
-			$file['name'],
+			$file->getClientFilename(),
 			$storagePath
 		]);
 
-		$base_url = Flight::get('config')['base_url'];
+		$base_url = $this->settings['base_url'];
+
+		$json['message'] = 'OK.';
+		$json['url'] = "$base_url/$user->user_code/$code.$fileInfo[extension]";
 
-		$response['message'] = 'OK.';
-		$response['url'] = "$base_url/$user->user_code/$code.$fileInfo[extension]";
-		Flight::json($response, 201);
+		$this->logger->info("User $user->username uploaded new media.", [$this->database->raw()->lastInsertId()]);
 
-		Log::info("User $user->username uploaded new media.", [DB::raw()->lastInsertId()]);
+		return $response->withJson($json, 201);
 	}
 
-	public function show($userCode, $mediaCode): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws FileNotFoundException
+	 * @throws NotFoundException
+	 */
+	public function show(Request $request, Response $response, $args): Response
 	{
-		$media = $this->getMedia($userCode, $mediaCode);
+		$media = $this->getMedia($args['userCode'], $args['mediaCode']);
 
 		if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
 		$filesystem = $this->getStorage();
 
-		if (stristr(Flight::request()->user_agent, 'TelegramBot') ||
-			stristr(Flight::request()->user_agent, 'facebookexternalhit/') ||
-			stristr(Flight::request()->user_agent, 'Facebot')) {
-			$this->streamMedia($filesystem, $media);
+		if (stristr($request->getHeaderLine('User-Agent'), 'TelegramBot') ||
+			stristr($request->getHeaderLine('User-Agent'), 'facebookexternalhit/') ||
+			stristr($request->getHeaderLine('User-Agent'), 'Facebot')) {
+			return $this->streamMedia($request, $response, $filesystem, $media);
 		} else {
 
 			try {
@@ -104,16 +103,15 @@ class UploadController extends Controller
 				$type = explode('/', $mime)[0];
 				if ($type === 'text') {
 					$media->text = $filesystem->read($media->storage_path);
-				} elseif (in_array($type, ['image', 'video'])) {
-					$this->http2push(Flight::get('config')['base_url'] . "/$userCode/$mediaCode/raw");
+				} elseif (in_array($type, ['image', 'video']) && $request->getHeaderLine('Scheme') === 'HTTP/2.0') {
+					$response = $response->withHeader('Link', "<{$this->settings['base_url']}/$args[userCode]/$args[mediaCode]/raw>; rel=preload; as={$type}");
 				}
 
 			} catch (FileNotFoundException $e) {
-				Flight::error($e);
-				return;
+				throw $e;
 			}
 
-			Flight::render('upload/public.twig', [
+			return $this->view->render($response, 'upload/public.twig', [
 				'media' => $media,
 				'type' => $mime,
 				'extension' => pathinfo($media->filename, PATHINFO_EXTENSION)
@@ -121,68 +119,98 @@ class UploadController extends Controller
 		}
 	}
 
-	public function getRawById($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws FileNotFoundException
+	 */
+	public function getRawById(Request $request, Response $response, $args): Response
 	{
-		$this->checkAdmin();
 
-		$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (!$media) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
-		$this->streamMedia($this->getStorage(), $media);
+		return $this->streamMedia($request, $response, $this->getStorage(), $media);
 	}
 
-	public function showRaw($userCode, $mediaCode): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws FileNotFoundException
+	 */
+	public function showRaw(Request $request, Response $response, $args): Response
 	{
-		$media = $this->getMedia($userCode, $mediaCode);
+		$media = $this->getMedia($args['userCode'], $args['mediaCode']);
 
 		if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
-
-		$this->streamMedia($this->getStorage(), $media);
+		return $this->streamMedia($request, $response, $this->getStorage(), $media);
 	}
 
 
-	public function download($userCode, $mediaCode): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws FileNotFoundException
+	 */
+	public function download(Request $request, Response $response, $args): Response
 	{
-		$media = $this->getMedia($userCode, $mediaCode);
+		$media = $this->getMedia($args['userCode'], $args['mediaCode']);
 
 		if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
-
-		$this->streamMedia($this->getStorage(), $media, 'attachment');
+		return $this->streamMedia($request, $response, $this->getStorage(), $media, 'attachment');
 	}
 
-	public function togglePublish($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 */
+	public function togglePublish(Request $request, Response $response, $args): Response
 	{
-		$this->checkLogin();
-
 		if (Session::get('admin')) {
-			$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
+			$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 		} else {
-			$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$id, Session::get('user_id')])->fetch();
+			$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$args['id'], Session::get('user_id')])->fetch();
 		}
 
 		if (!$media) {
-			Flight::halt(404);
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
-		DB::query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]);
+		$this->database->query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]);
+
+		return $response->withStatus(200);
 	}
 
-	public function delete($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws UnauthorizedException
+	 */
+	public function delete(Request $request, Response $response, $args): Response
 	{
-		$this->checkLogin();
-
-		$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (Session::get('admin', false) || $media->user_id === Session::get('user_id')) {
 
@@ -190,22 +218,28 @@ class UploadController extends Controller
 			try {
 				$filesystem->delete($media->storage_path);
 			} catch (FileNotFoundException $e) {
-				Flight::halt(404);
-				return;
+				throw new NotFoundException($request, $response);
 			} finally {
-				DB::query('DELETE FROM `uploads` WHERE `id` = ?', $id);
-				Log::info('User ' . Session::get('username') . " deleted media $id");
+				$this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $args['id']);
+				$this->logger->info('User ' . Session::get('username') . ' deleted a media.', [$args['id']]);
 			}
 		} else {
-			Flight::halt(403);
+			throw new UnauthorizedException();
 		}
+
+		return $response->withStatus(200);
 	}
 
+	/**
+	 * @param $userCode
+	 * @param $mediaCode
+	 * @return mixed
+	 */
 	protected function getMedia($userCode, $mediaCode)
 	{
 		$mediaCode = pathinfo($mediaCode)['filename'];
 
-		$media = DB::query('SELECT * FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [
+		$media = $this->database->query('SELECT * FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [
 			$userCode,
 			$mediaCode
 		])->fetch();
@@ -213,36 +247,43 @@ class UploadController extends Controller
 		return $media;
 	}
 
-	protected function streamMedia(Filesystem $storage, $media, string $disposition = 'inline'): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param Filesystem $storage
+	 * @param $media
+	 * @param string $disposition
+	 * @return Response
+	 * @throws FileNotFoundException
+	 */
+	protected function streamMedia(Request $request, Response $response, Filesystem $storage, $media, string $disposition = 'inline'): Response
 	{
-		try {
-			$mime = $storage->getMimetype($media->storage_path);
-			$query = Flight::request()->query;
-
-			if ($query['width'] !== null && explode('/', $mime)[0] === 'image') {
-				Flight::response()->header('Content-Type', 'image/png');
-				Flight::response()->header('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"');
-				Flight::response()->sendHeaders();
-				ob_clean();
-
-				$image = imagecreatefromstring($storage->read($media->storage_path));
-				$scaled = imagescale($image, $query['width'], $query['height'] !== null ? $query['height'] : -1);
-
-				imagedestroy($image);
-
-				imagepng($scaled, null, 9);
-				imagedestroy($scaled);
-			} else {
-				Flight::response()->header('Content-Type', $mime);
-				Flight::response()->header('Content-Disposition', $disposition . ';filename="' . $media->filename . '"');
-				Flight::response()->header('Content-Length', $storage->getSize($media->storage_path));
-				Flight::response()->sendHeaders();
-				ob_end_clean();
-
-				fpassthru($storage->readStream($media->storage_path));
-			}
-		} catch (FileNotFoundException $e) {
-			Flight::error($e);
+		$mime = $storage->getMimetype($media->storage_path);
+
+		if ($request->getParam('width') !== null && explode('/', $mime)[0] === 'image') {
+
+			$image = imagecreatefromstring($storage->read($media->storage_path));
+			$scaled = imagescale($image, $request->getParam('width'), $request->getParam('height') !== null ? $request->getParam('height') : -1);
+			imagedestroy($image);
+
+			ob_start();
+			imagepng($scaled, null, 9);
+
+			$imagedata = ob_get_contents();
+			ob_end_clean();
+			imagedestroy($scaled);
+
+			return $response
+				->withHeader('Content-Type', 'image/png')
+				->withHeader('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"')
+				->write($imagedata);
+		} else {
+			ob_end_clean();
+			return $response
+				->withHeader('Content-Type', $mime)
+				->withHeader('Content-Disposition', $disposition . ';filename="' . $media->filename . '"')
+				->withHeader('Content-Length', $storage->getSize($media->storage_path))
+				->withBody(new Stream($storage->readStream($media->storage_path)));
 		}
 	}
 }

+ 184 - 159
app/Controllers/UserController.php

@@ -3,309 +3,333 @@
 namespace App\Controllers;
 
 
-use App\Database\DB;
-use App\Exceptions\NotFoundException;
 use App\Exceptions\UnauthorizedException;
-use App\Traits\SingletonController;
-use App\Web\Log;
 use App\Web\Session;
-use Flight;
+use Slim\Exception\NotFoundException;
+use Slim\Http\Request;
+use Slim\Http\Response;
 
 class UserController extends Controller
 {
-	use SingletonController;
-
 	const PER_PAGE = 15;
 
-	public function index($page = 1): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 */
+	public function index(Request $request, Response $response, $args): Response
 	{
-		$this->checkAdmin();
-
+		$page = isset($args['page']) ? (int)$args['page'] : 0;
 		$page = max(0, --$page);
 
-		$users = DB::query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
+		$users = $this->database->query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
 
-		$pages = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE;
+		$pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE;
 
-		Flight::render('user/index.twig', [
-			'users' => $users,
-			'next' => $page < floor($pages),
-			'previous' => $page >= 1,
-			'current_page' => ++$page,
-		]);
+		return $this->view->render($response,
+			'user/index.twig',
+			[
+				'users' => $users,
+				'next' => $page < floor($pages),
+				'previous' => $page >= 1,
+				'current_page' => ++$page,
+			]
+		);
 	}
 
-	public function create(): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 */
+	public function create(Request $request, Response $response): Response
 	{
-		$this->checkAdmin();
-		Flight::render('user/create.twig');
+		return $this->view->render($response, 'user/create.twig');
 	}
 
-	public function store(): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 */
+	public function store(Request $request, Response $response): Response
 	{
-		$this->checkAdmin();
-
-		$form = Flight::request()->data;
-
-		if (!isset($form->email) || empty($form->email)) {
+		if ($request->getParam('email') === null) {
 			Session::alert('The email is required.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/user/create');
 		}
 
-		if (!isset($form->username) || empty($form->username)) {
+		if ($request->getParam('username') === null) {
 			Session::alert('The username is required.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/user/create');
 		}
 
-		if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $form->username)->fetch()->count > 0) {
-			Session::alert('The username already taken.', 'danger');
-			Flight::redirectBack();
-			return;
+		if ($request->getParam('password') === null) {
+			Session::alert('The password is required.', 'danger');
+			return $response->withRedirect('/user/create');
 		}
 
-		if (!isset($form->password) || empty($form->password)) {
-			Session::alert('The password is required.', 'danger');
-			Flight::redirectBack();
-			return;
+		if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $request->getParam('username'))->fetch()->count > 0) {
+			Session::alert('The username already taken.', 'danger');
+			return $response->withRedirect('/user/create');
 		}
 
 		do {
 			$userCode = substr(md5(microtime()), rand(0, 26), 5);
-		} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
+		} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
 
 		$token = $this->generateNewToken();
 
-		DB::query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [
-			$form->email,
-			$form->username,
-			password_hash($form->password, PASSWORD_DEFAULT),
-			isset($form->is_admin),
-			isset($form->is_active),
+		$this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [
+			$request->getParam('email'),
+			$request->getParam('username'),
+			password_hash($request->getParam('password'), PASSWORD_DEFAULT),
+			$request->getParam('is_admin') !== null,
+			$request->getParam('is_active') !== null,
 			$userCode,
 			$token
 		]);
 
-		Session::alert("User '$form->username' created!", 'success');
-		Log::info('User ' . Session::get('username') . ' created a new user.', [array_diff($form->getData(), ['password'])]);
+		Session::alert("User '{$request->getParam('username')}' created!", 'success');
+		$this->logger->info('User ' . Session::get('username') . ' created a new user.', [array_diff($request->getParams(), ['password'])]);
 
-		Flight::redirect('/users');
+		return $response->withRedirect('/users');
 	}
 
-	public function edit($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 */
+	public function edit(Request $request, Response $response, $args): Response
 	{
-		$this->checkAdmin();
-
-		$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (!$user) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
-		Flight::render('user/edit.twig', [
+		return $this->view->render($response, 'user/edit.twig', [
 			'user' => $user
 		]);
 	}
 
-	public function update($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 */
+	public function update(Request $request, Response $response, $args): Response
 	{
-		$this->checkAdmin();
-
-		$form = Flight::request()->data;
-
-		$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (!$user) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
-		if (!isset($form->email) || empty($form->email)) {
+		if ($request->getParam('email') === null) {
 			Session::alert('The email is required.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/user/' . $args['id'] . '/edit');
 		}
 
-		if (!isset($form->username) || empty($form->username)) {
+		if ($request->getParam('username') === null) {
 			Session::alert('The username is required.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/user/' . $args['id'] . '/edit');
 		}
 
-		if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$form->username, $user->username])->fetch()->count > 0) {
+		if ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$request->getParam('username'), $user->username])->fetch()->count > 0) {
 			Session::alert('The username already taken.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/user/' . $args['id'] . '/edit');
 		}
 
-		if ($user->id === Session::get('user_id') && !isset($form->is_admin)) {
+		if ($user->id === Session::get('user_id') && $request->getParam('is_admin') === null) {
 			Session::alert('You cannot demote yourself.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/user/' . $args['id'] . '/edit');
 		}
 
-		if (isset($form->password) && !empty($form->password)) {
-			DB::query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
-				$form->email,
-				$form->username,
-				password_hash($form->password, PASSWORD_DEFAULT),
-				isset($form->is_admin),
-				isset($form->is_active),
+		if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
+			$this->database->query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
+				$request->getParam('email'),
+				$request->getParam('username'),
+				password_hash($request->getParam('password'), PASSWORD_DEFAULT),
+				$request->getParam('is_admin') !== null,
+				$request->getParam('is_active') !== null,
 				$user->id
 			]);
 		} else {
-			DB::query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
-				$form->email,
-				$form->username,
-				isset($form->is_admin),
-				isset($form->is_active),
+			$this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
+				$request->getParam('email'),
+				$request->getParam('username'),
+				$request->getParam('is_admin') !== null,
+				$request->getParam('is_active') !== null,
 				$user->id
 			]);
 		}
 
-		Session::alert("User '$form->username' updated!", 'success');
-		Log::info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($form->getData(), ['password'])]);
+		Session::alert("User '{$request->getParam('username')}' updated!", 'success');
+		$this->logger->info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($request->getParams(), ['password'])]);
 
-		Flight::redirect('/users');
+		return $response->withRedirect('/users');
 
 	}
 
-	public function delete($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 */
+	public function delete(Request $request, Response $response, $args): Response
 	{
-		$this->checkAdmin();
-
-		$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (!$user) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
 		if ($user->id === Session::get('user_id')) {
 			Session::alert('You cannot delete yourself.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/users');
 		}
 
-		DB::query('DELETE FROM `users` WHERE `id` = ?', $user->id);
+		$this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id);
 
 		Session::alert('User deleted.', 'success');
-		Log::info('User ' . Session::get('username') . " deleted $user->id.");
+		$this->logger->info('User ' . Session::get('username') . " deleted $user->id.");
 
-		Flight::redirect('/users');
+		return $response->withRedirect('/users');
 	}
 
-	public function profile(): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws UnauthorizedException
+	 */
+	public function profile(Request $request, Response $response): Response
 	{
-		$this->checkLogin();
-
-		$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch();
 
 		if (!$user) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
 		if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
-			Flight::error(new UnauthorizedException());
-			return;
+			throw new UnauthorizedException();
 		}
 
-		Flight::render('user/profile.twig', [
+		return $this->view->render($response, 'user/profile.twig', [
 			'user' => $user
 		]);
 	}
 
-	public function profileEdit($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws UnauthorizedException
+	 */
+	public function profileEdit(Request $request, Response $response, $args): Response
 	{
-		$this->checkLogin();
-
-		$form = Flight::request()->data;
-
-		$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (!$user) {
-			Flight::error(new NotFoundException());
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
 		if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
-			Flight::error(new UnauthorizedException());
-			return;
+			throw new UnauthorizedException();
 		}
 
-		if (!isset($form->email) || empty($form->email)) {
+		if ($request->getParam('email') === null) {
 			Session::alert('The email is required.', 'danger');
-			Flight::redirectBack();
-			return;
+			return $response->withRedirect('/profile');
 		}
 
-		if (isset($form->password) && !empty($form->password)) {
-			DB::query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [
-				$form->email,
-				password_hash($form->password, PASSWORD_DEFAULT),
+		if ($request->getParam('password') !== null && !empty($request->getParam('password'))) {
+			$this->database->query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [
+				$request->getParam('email'),
+				password_hash($request->getParam('password'), PASSWORD_DEFAULT),
 				$user->id
 			]);
 		} else {
-			DB::query('UPDATE `users` SET `email`=? WHERE `id` = ?', [
-				$form->email,
+			$this->database->query('UPDATE `users` SET `email`=? WHERE `id` = ?', [
+				$request->getParam('email'),
 				$user->id
 			]);
 		}
 
 		Session::alert('Profile updated successfully!', 'success');
-		Log::info('User ' . Session::get('username') . " updated profile of $user->id.");
+		$this->logger->info('User ' . Session::get('username') . " updated profile of $user->id.");
 
-		Flight::redirectBack();
+		return $response->withRedirect('/profile');
 	}
 
-	public function refreshToken($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws UnauthorizedException
+	 */
+	public function refreshToken(Request $request, Response $response, $args): Response
 	{
-		$this->checkLogin();
-
-		$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (!$user) {
-			Flight::halt(404);
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
 		if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
-			Flight::halt(403);
-			return;
+			throw new UnauthorizedException();
 		}
 
 		$token = $this->generateNewToken();
 
-		DB::query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
+		$this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
 			$token,
 			$user->id
 		]);
 
-		Log::info('User ' . Session::get('username') . " refreshed token of user $user->id.");
+		$this->logger->info('User ' . Session::get('username') . " refreshed token of user $user->id.");
+
+		$response->getBody()->write($token);
 
-		echo $token;
+		return $response;
 	}
 
-	public function getShareXconfigFile($id): void
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param $args
+	 * @return Response
+	 * @throws NotFoundException
+	 * @throws UnauthorizedException
+	 */
+	public function getShareXconfigFile(Request $request, Response $response, $args): Response
 	{
-		$this->checkLogin();
-
-		$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
+		$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $args['id'])->fetch();
 
 		if (!$user) {
-			Flight::halt(404);
-			return;
+			throw new NotFoundException($request, $response);
 		}
 
 		if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
-			Flight::halt(403);
-			return;
+			throw new UnauthorizedException();
 		}
 
-		$base_url = Flight::get('config')['base_url'];
+		$base_url = $this->settings['base_url'];
 		$json = [
 			'DestinationType' => 'ImageUploader, TextUploader, FileUploader',
 			'RequestURL' => "$base_url/upload",
@@ -319,18 +343,19 @@ class UserController extends Controller
 			'ThumbnailURL' => '$json:url$/raw',
 		];
 
-		Flight::response()->header('Content-Type', 'application/json');
-		Flight::response()->header('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"');
-		Flight::response()->sendHeaders();
-
-		echo json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
+		return $response
+			->withHeader('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"')
+			->withJson($json, 200, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
 	}
 
+	/**
+	 * @return string
+	 */
 	protected function generateNewToken(): string
 	{
 		do {
 			$token = 'token_' . md5(uniqid('', true));
-		} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
+		} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
 
 		return $token;
 	}

+ 13 - 15
app/Database/DB.php

@@ -21,7 +21,7 @@ class DB
 	protected $pdo;
 
 	/** @var string */
-	protected static $dsn = 'database.db';
+	protected static $dsn = 'sqlite:database.db';
 
 	/** @var string */
 	protected $currentDriver;
@@ -67,6 +67,15 @@ class DB
 		return $this->currentDriver;
 	}
 
+	public static function getInstance(): DB
+	{
+		if (self::$instance === null) {
+			self::$instance = new self(self::$dsn, self::$username, self::$password);
+		}
+
+		return self::$instance;
+	}
+
 	/**
 	 * Perform a query
 	 * @param string $query
@@ -76,11 +85,7 @@ class DB
 	public static function query(string $query, $parameters = [])
 	{
 
-		if (self::$instance === null) {
-			self::$instance = new self(self::$dsn, self::$username, self::$password);
-		}
-
-		return self::$instance->doQuery($query, $parameters);
+		return self::getInstance()->doQuery($query, $parameters);
 	}
 
 	/**
@@ -90,11 +95,7 @@ class DB
 	public static function driver(): string
 	{
 
-		if (self::$instance === null) {
-			self::$instance = new self(self::$dsn, self::$username, self::$password);
-		}
-
-		return self::$instance->getCurrentDriver();
+		return self::getInstance()->getCurrentDriver();
 	}
 
 	/**
@@ -103,11 +104,8 @@ class DB
 	 */
 	public static function raw(): PDO
 	{
-		if (self::$instance === null) {
-			self::$instance = new self(self::$dsn, self::$username, self::$password);
-		}
 
-		return self::$instance->getPdo();
+		return self::getInstance()->getPdo();
 	}
 
 	/**

+ 0 - 15
app/Exceptions/AuthenticationException.php

@@ -1,15 +0,0 @@
-<?php
-
-namespace App\Exceptions;
-
-
-use Exception;
-use Throwable;
-
-class AuthenticationException extends Exception
-{
-	public function __construct(string $message = 'Not Authorized', int $code = 401, Throwable $previous = null)
-	{
-		parent::__construct($message, $code, $previous);
-	}
-}

+ 0 - 15
app/Exceptions/NotFoundException.php

@@ -1,15 +0,0 @@
-<?php
-
-namespace App\Exceptions;
-
-
-use Exception;
-use Throwable;
-
-class NotFoundException extends Exception
-{
-	public function __construct(string $message = 'Not Found', int $code = 404, Throwable $previous = null)
-	{
-		parent::__construct($message, $code, $previous);
-	}
-}

+ 40 - 0
app/Middleware/AdminMiddleware.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Middleware;
+
+use App\Exceptions\AuthenticationException;
+use App\Exceptions\UnauthorizedException;
+use App\Web\Session;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class AdminMiddleware
+{
+	/** @var \Slim\Container */
+	private $container;
+
+	public function __construct($container)
+	{
+		$this->container = $container;
+	}
+
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param callable $next
+	 * @return Response
+	 * @throws UnauthorizedException
+	 */
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+		if (!$this->container->database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->is_admin) {
+			Session::alert('Your account is not admin anymore.', 'danger');
+			Session::set('admin', false);
+			Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
+			throw new UnauthorizedException();
+		}
+
+		return $next($request, $response);
+	}
+
+}

+ 44 - 0
app/Middleware/AuthMiddleware.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Middleware;
+
+use App\Exceptions\AuthenticationException;
+use App\Web\Session;
+use Slim\Http\Request;
+use Slim\Http\Response;
+
+class AuthMiddleware
+{
+
+	/** @var \Slim\Container */
+	private $container;
+
+	public function __construct($container)
+	{
+		$this->container = $container;
+	}
+
+	/**
+	 * @param Request $request
+	 * @param Response $response
+	 * @param callable $next
+	 * @return Response
+	 */
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+		if (!Session::get('logged', false)) {
+			Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
+			return $response->withRedirect('/login');
+		}
+
+		if (!$this->container->database->query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->active) {
+			Session::alert('Your account is not active anymore.', 'danger');
+			Session::set('logged', false);
+			Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
+			return $response->withRedirect('/login');
+		}
+
+		return $next($request, $response);
+	}
+
+}

+ 0 - 24
app/Traits/SingletonController.php

@@ -1,24 +0,0 @@
-<?php
-
-namespace App\Traits;
-
-
-use App\Controllers\Controller;
-
-trait SingletonController
-{
-	protected static $instance;
-
-	/**
-	 * Return the controller instance
-	 * @return Controller
-	 */
-	public static function instance(): Controller
-	{
-		if (static::$instance === null) {
-			static::$instance = new static();
-		}
-
-		return static::$instance;
-	}
-}

+ 0 - 68
app/Web/Log.php

@@ -1,68 +0,0 @@
-<?php
-
-namespace App\Web;
-
-use Monolog\Formatter\LineFormatter;
-use Monolog\Handler\RotatingFileHandler;
-use Monolog\Logger;
-
-class Log
-{
-	/** @var Logger */
-	protected $logger;
-
-	/** @var Log */
-	protected static $instance;
-
-	/**
-	 * @return Log
-	 * @throws \Exception
-	 */
-	public static function instance(): Log
-	{
-		if (static::$instance === null) {
-			static::$instance = new static();
-		}
-
-		return static::$instance;
-	}
-
-	/**
-	 * Log constructor.
-	 * @throws \Exception
-	 */
-	public function __construct()
-	{
-		$this->logger = new Logger(self::class);
-
-		$streamHandler = new RotatingFileHandler(__DIR__ . '/../../logs/log.txt', 10, Logger::DEBUG);
-		$streamHandler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true));
-
-		$this->logger->pushHandler($streamHandler);
-	}
-
-	public static function debug(string $message, array $context = [])
-	{
-		self::instance()->logger->debug($message, $context);
-	}
-
-	public static function info(string $message, array $context = [])
-	{
-		self::instance()->logger->info($message, $context);
-	}
-
-	public static function warning(string $message, array $context = [])
-	{
-		self::instance()->logger->warning($message, $context);
-	}
-
-	public static function error(string $message, array $context = [])
-	{
-		self::instance()->logger->error($message, $context);
-	}
-
-	public static function critical(string $message, array $context = [])
-	{
-		self::instance()->logger->critical($message, $context);
-	}
-}

+ 32 - 24
app/routes.php

@@ -1,29 +1,37 @@
 <?php
+// Auth routes
+$app->group('', function () {
+	$this->get('/', \App\Controllers\DashboardController::class . ':redirects');
+	$this->get('/home[/page/{page}]', \App\Controllers\DashboardController::class . ':home');
+	$this->get('/system', \App\Controllers\DashboardController::class . ':system')->add(\App\Middleware\AdminMiddleware::class);
 
-Flight::route('GET /login', [\App\Controllers\LoginController::instance(), 'show']);
-Flight::route('POST /login', [\App\Controllers\LoginController::instance(), 'login']);
-Flight::route('GET|POST /logout', [\App\Controllers\LoginController::instance(), 'logout']);
+	$this->group('', function () {
+		$this->get('/users[/page/{page}]', \App\Controllers\UserController::class . ':index');
+		$this->get('/user/create', \App\Controllers\UserController::class . ':create');
+		$this->post('/user/create', \App\Controllers\UserController::class . ':store');
+		$this->get('/user/{id}/edit', \App\Controllers\UserController::class . ':edit');
+		$this->post('/user/{id}', \App\Controllers\UserController::class . ':update');
+		$this->get('/user/{id}/delete', \App\Controllers\UserController::class . ':delete');
+	})->add(\App\Middleware\AdminMiddleware::class);
 
-Flight::route('GET /', [\App\Controllers\DashboardController::instance(), 'redirects']);
-Flight::route('GET /home(/page/@page)', [\App\Controllers\DashboardController::instance(), 'home']);
-Flight::route('GET /system', [\App\Controllers\DashboardController::instance(), 'system']);
+	$this->get('/profile', \App\Controllers\UserController::class . ':profile');
+	$this->post('/profile/{id}/edit', \App\Controllers\UserController::class . ':profileEdit');
+	$this->post('/user/{id}/refreshToken', \App\Controllers\UserController::class . ':refreshToken');
+	$this->get('/user/{id}/config/sharex', \App\Controllers\UserController::class . ':getShareXconfigFile');
 
-Flight::route('GET /users(/page/@page)', [\App\Controllers\UserController::instance(), 'index']);
-Flight::route('GET /user/add', [\App\Controllers\UserController::instance(), 'create']);
-Flight::route('POST /user/add', [\App\Controllers\UserController::instance(), 'store']);
-Flight::route('GET /user/@id/edit', [\App\Controllers\UserController::instance(), 'edit']);
-Flight::route('POST /user/@id', [\App\Controllers\UserController::instance(), 'update']);
-Flight::route('GET /user/@id/delete', [\App\Controllers\UserController::instance(), 'delete']);
-Flight::route('GET /profile', [\App\Controllers\UserController::instance(), 'profile']);
-Flight::route('POST /profile/@id/edit', [\App\Controllers\UserController::instance(), 'profileEdit']);
-Flight::route('POST /user/@id/refreshToken', [\App\Controllers\UserController::instance(), 'refreshToken']);
-Flight::route('GET /user/@id/config/sharex', [\App\Controllers\UserController::instance(), 'getShareXconfigFile']);
+	$this->post('/upload/{id}/publish', \App\Controllers\UploadController::class . ':togglePublish');
+	$this->post('/upload/{id}/unpublish', \App\Controllers\UploadController::class . ':togglePublish');
+	$this->get('/upload/{id}/raw', \App\Controllers\UploadController::class . ':getRawById')->add(\App\Middleware\AdminMiddleware::class);
+	$this->post('/upload/{id}/delete', \App\Controllers\UploadController::class . ':delete');
 
-Flight::route('POST /upload', [\App\Controllers\UploadController::instance(), 'upload']);
-Flight::route('POST /upload/@id/publish', [\App\Controllers\UploadController::instance(), 'togglePublish']);
-Flight::route('POST /upload/@id/unpublish', [\App\Controllers\UploadController::instance(), 'togglePublish']);
-Flight::route('GET /upload/@id/raw', [\App\Controllers\UploadController::instance(), 'getRawById']);
-Flight::route('POST /upload/@id/delete', [\App\Controllers\UploadController::instance(), 'delete']);
-Flight::route('GET /@userCode/@filename', [\App\Controllers\UploadController::instance(), 'show']);
-Flight::route('GET /@userCode/@filename/raw', [\App\Controllers\UploadController::instance(), 'showRaw']);
-Flight::route('GET /@userCode/@filename/download', [\App\Controllers\UploadController::instance(), 'download']);
+})->add(\App\Middleware\AuthMiddleware::class);
+
+$app->get('/login', \App\Controllers\LoginController::class . ':show');
+$app->post('/login', \App\Controllers\LoginController::class . ':login');
+$app->map(['GET', 'POST'], '/logout', \App\Controllers\LoginController::class . ':logout');
+
+$app->post('/upload', \App\Controllers\UploadController::class . ':upload');
+
+$app->get('/{userCode}/{mediaCode}', \App\Controllers\UploadController::class . ':show');
+$app->get('/{userCode}/{mediaCode}/raw', \App\Controllers\UploadController::class . ':showRaw');
+$app->get('/{userCode}/{mediaCode}/download', \App\Controllers\UploadController::class . ':download');

+ 65 - 59
bootstrap/app.php

@@ -1,13 +1,19 @@
 <?php
 
 use App\Database\DB;
+use App\Web\Session;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\RotatingFileHandler;
+use Monolog\Logger;
+use Slim\App;
+use Slim\Container;
 
 // Load the config
 $config = array_replace_recursive([
 	'app_name' => 'XBackBone',
 	'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'],
 	'storage_dir' => 'storage',
-	'debug' => false,
+	'displayErrorDetails' => false,
 	'db' => [
 		'connection' => 'sqlite',
 		'dsn' => 'resources/database/xbackbone.db',
@@ -16,68 +22,68 @@ $config = array_replace_recursive([
 	],
 ], require 'config.php');
 
-// Set flight parameters
-Flight::set('flight.base_url', $config['base_url']);
-Flight::set('flight.log_errors', false);
-Flight::set('config', $config);
+$container = new Container(['settings' => $config]);
+
+$container['logger'] = function ($container) {
+	$logger = new Logger('app');
+
+	$streamHandler = new RotatingFileHandler(__DIR__ . '/../logs/log.txt', 10, Logger::DEBUG);
+	$streamHandler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true));
+
+	$logger->pushHandler($streamHandler);
+
+	return $logger;
+};
+
 
 // Set the database dsn
 DB::setDsn($config['db']['connection'] . ':' . $config['db']['dsn'], $config['db']['username'], $config['db']['password']);
 
-// Register the Twig instance
-Flight::register('view', 'Twig_Environment',
-	[new Twig_Loader_Filesystem('resources/templates'),
-		[
-			'cache' => 'resources/cache',
-			'autoescape' => 'html',
-			'debug' => $config['debug'],
-			'auto_reload' => $config['debug'],
-		]
-	]
-);
-
-// Redirect back helper
-Flight::register('redirectBack', function () {
-	Flight::redirect(Flight::request()->referrer);
-});
-
-// Map the render call to the Twig view instance
-Flight::map('render', function (string $template, array $data = []) use (&$config) {
-	Flight::view()->addGlobal('config', $config);
-	Flight::view()->addGlobal('request', Flight::request());
-	Flight::view()->addGlobal('alerts', App\Web\Session::getAlert());
-	Flight::view()->addGlobal('session', App\Web\Session::all());
-	Flight::view()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
-	Flight::view()->display($template, $data);
-});
-
-// The application error handler
-Flight::map('error', function (Exception $exception) {
-	if ($exception instanceof \App\Exceptions\AuthenticationException) {
-		Flight::redirect('/login');
-		return;
-	}
-
-	if ($exception instanceof \App\Exceptions\UnauthorizedException) {
-		Flight::response()->status(403);
-		Flight::render('errors/403.twig');
-		return;
-	}
-
-	if ($exception instanceof \App\Exceptions\NotFoundException) {
-		Flight::response()->status(404);
-		Flight::render('errors/404.twig');
-		return;
-	}
-
-	Flight::response()->status(500);
-	\App\Web\Log::critical('Fatal error during app execution', [$exception->getTraceAsString()]);
-	Flight::render('errors/500.twig', ['exception' => $exception]);
-});
-
-Flight::map('notFound', function () {
-	Flight::render('errors/404.twig');
-});
+$container['database'] = function ($container) use (&$config) {
+	return DB::getInstance();
+};
+
+
+$container['view'] = function ($container) use (&$config) {
+	$view = new \Slim\Views\Twig('resources/templates', [
+		'cache' => 'resources/cache',
+		'autoescape' => 'html',
+		'debug' => $config['displayErrorDetails'],
+		'auto_reload' => $config['displayErrorDetails'],
+	]);
+
+	// Instantiate and add Slim specific extension
+	$router = $container->get('router');
+	$uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER));
+	$view->addExtension(new Slim\Views\TwigExtension($router, $uri));
+
+	$view->getEnvironment()->addGlobal('config', $config);
+	$view->getEnvironment()->addGlobal('request', $container->get('request'));
+	$view->getEnvironment()->addGlobal('alerts', Session::getAlert());
+	$view->getEnvironment()->addGlobal('session', Session::all());
+	$view->getEnvironment()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
+	return $view;
+};
+
+$container['errorHandler'] = function ($container) {
+	return function (\Slim\Http\Request $request, \Slim\Http\Response $response, $exception) use (&$container) {
+
+		if ($exception instanceof \App\Exceptions\UnauthorizedException) {
+			return $container->view->render($response->withStatus(403), 'errors/403.twig');
+		}
+
+		$container->logger->critical('Fatal error during app execution', [$exception, $exception->getTraceAsString()]);
+		return $container->view->render($response->withStatus(500), 'errors/500.twig', ['exception' => $exception]);
+	};
+};
+$container['notFoundHandler'] = function ($container) {
+	return function (\Slim\Http\Request $request, \Slim\Http\Response $response) use (&$container) {
+		$response->withStatus(404)->withHeader('Content-Type', 'text/html');
+		return $container->view->render($response, 'errors/404.twig');
+	};
+};
+
+$app = new App($container);
 
 // Load the application routes
 require 'app/routes.php';

+ 4 - 4
composer.json

@@ -1,14 +1,14 @@
 {
   "name": "sergix44/xbackbone",
-  "version": "1.3",
+  "version": "2.0",
   "description": "A lightweight ShareX PHP backend",
   "type": "project",
   "require": {
-    "mikecao/flight": "^1.3",
+    "php": ">=7.1",
+    "slim/slim": "^3.0",
+    "slim/twig-view": "^2.4",
     "league/flysystem": "^1.0.45",
-    "twig/twig": "~2.0",
     "monolog/monolog": "^1.23",
-    "php": ">=7.1",
     "ext-json": "*",
     "ext-gd": "*",
     "ext-pdo": "*"

+ 365 - 60
composer.lock

@@ -4,20 +4,51 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "b2d12fd3c7627ea2da10cbb0bc3cc35d",
+    "content-hash": "c1434c69fd6bdbfc70c7ca0b185ffdc9",
     "packages": [
+        {
+            "name": "container-interop/container-interop",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/container-interop/container-interop.git",
+                "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8",
+                "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8",
+                "shasum": ""
+            },
+            "require": {
+                "psr/container": "^1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Interop\\Container\\": "src/Interop/Container/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
+            "homepage": "https://github.com/container-interop/container-interop",
+            "time": "2017-02-14T19:40:03+00:00"
+        },
         {
             "name": "league/flysystem",
-            "version": "1.0.47",
+            "version": "1.0.48",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/flysystem.git",
-                "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c"
+                "reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a11e4a75f256bdacf99d20780ce42d3b8272975c",
-                "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a6ded5b2f6055e2db97b4b859fdfca2b952b78aa",
+                "reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa",
                 "shasum": ""
             },
             "require": {
@@ -88,63 +119,20 @@
                 "sftp",
                 "storage"
             ],
-            "time": "2018-09-14T15:30:29+00:00"
-        },
-        {
-            "name": "mikecao/flight",
-            "version": "v1.3.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/mikecao/flight.git",
-                "reference": "e146b8c0ddbc6384c5a66f23dea71743fc282289"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/mikecao/flight/zipball/e146b8c0ddbc6384c5a66f23dea71743fc282289",
-                "reference": "e146b8c0ddbc6384c5a66f23dea71743fc282289",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.3.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "~4.6"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "flight/autoload.php",
-                    "flight/Flight.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Mike Cao",
-                    "email": "mike@mikecao.com",
-                    "homepage": "http://www.mikecao.com/",
-                    "role": "Original Developer"
-                }
-            ],
-            "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.",
-            "homepage": "http://flightphp.com",
-            "time": "2017-10-20T01:46:56+00:00"
+            "time": "2018-10-15T13:53:10+00:00"
         },
         {
             "name": "monolog/monolog",
-            "version": "1.23.0",
+            "version": "1.24.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
+                "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
-                "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
+                "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
                 "shasum": ""
             },
             "require": {
@@ -209,7 +197,202 @@
                 "logging",
                 "psr-3"
             ],
-            "time": "2017-06-19T01:22:40+00:00"
+            "time": "2018-11-05T09:00:11+00:00"
+        },
+        {
+            "name": "nikic/fast-route",
+            "version": "v1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/FastRoute.git",
+                "reference": "181d480e08d9476e61381e04a71b34dc0432e812"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
+                "reference": "181d480e08d9476e61381e04a71b34dc0432e812",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35|~5.7"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "FastRoute\\": "src/"
+                },
+                "files": [
+                    "src/functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov",
+                    "email": "nikic@php.net"
+                }
+            ],
+            "description": "Fast request router for PHP",
+            "keywords": [
+                "router",
+                "routing"
+            ],
+            "time": "2018-02-13T20:26:39+00:00"
+        },
+        {
+            "name": "pimple/pimple",
+            "version": "v3.2.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/silexphp/Pimple.git",
+                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/container": "^1.0"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^3.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Pimple": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Pimple, a simple Dependency Injection Container",
+            "homepage": "http://pimple.sensiolabs.org",
+            "keywords": [
+                "container",
+                "dependency injection"
+            ],
+            "time": "2018-01-21T07:42:36+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "time": "2017-02-14T16:28:37+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
         },
         {
             "name": "psr/log",
@@ -258,9 +441,131 @@
             ],
             "time": "2016-10-10T12:19:37+00:00"
         },
+        {
+            "name": "slim/slim",
+            "version": "3.11.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/slimphp/Slim.git",
+                "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
+                "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
+                "shasum": ""
+            },
+            "require": {
+                "container-interop/container-interop": "^1.2",
+                "nikic/fast-route": "^1.0",
+                "php": ">=5.5.0",
+                "pimple/pimple": "^3.0",
+                "psr/container": "^1.0",
+                "psr/http-message": "^1.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0",
+                "squizlabs/php_codesniffer": "^2.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Slim\\": "Slim"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Rob Allen",
+                    "email": "rob@akrabat.com",
+                    "homepage": "http://akrabat.com"
+                },
+                {
+                    "name": "Josh Lockhart",
+                    "email": "hello@joshlockhart.com",
+                    "homepage": "https://joshlockhart.com"
+                },
+                {
+                    "name": "Gabriel Manricks",
+                    "email": "gmanricks@me.com",
+                    "homepage": "http://gabrielmanricks.com"
+                },
+                {
+                    "name": "Andrew Smith",
+                    "email": "a.smith@silentworks.co.uk",
+                    "homepage": "http://silentworks.co.uk"
+                }
+            ],
+            "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
+            "homepage": "https://slimframework.com",
+            "keywords": [
+                "api",
+                "framework",
+                "micro",
+                "router"
+            ],
+            "time": "2018-09-16T10:54:21+00:00"
+        },
+        {
+            "name": "slim/twig-view",
+            "version": "2.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/slimphp/Twig-View.git",
+                "reference": "78386c01a97f7870462b38fff759dad649da9efc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/78386c01a97f7870462b38fff759dad649da9efc",
+                "reference": "78386c01a97f7870462b38fff759dad649da9efc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.0",
+                "psr/http-message": "^1.0",
+                "twig/twig": "^1.18|^2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8|^5.7",
+                "slim/slim": "^3.10"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Slim\\Views\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Josh Lockhart",
+                    "email": "hello@joshlockhart.com",
+                    "homepage": "http://joshlockhart.com"
+                }
+            ],
+            "description": "Slim Framework 3 view helper built on top of the Twig 2 templating component",
+            "homepage": "http://slimframework.com",
+            "keywords": [
+                "framework",
+                "slim",
+                "template",
+                "twig",
+                "view"
+            ],
+            "time": "2018-05-07T10:54:29+00:00"
+        },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.9.0",
+            "version": "v1.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
@@ -318,16 +623,16 @@
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.9.0",
+            "version": "v1.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8"
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8",
-                "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
                 "shasum": ""
             },
             "require": {
@@ -373,7 +678,7 @@
                 "portable",
                 "shim"
             ],
-            "time": "2018-08-06T14:22:27+00:00"
+            "time": "2018-09-21T13:07:52+00:00"
         },
         {
             "name": "twig/twig",

+ 1 - 1
index.php

@@ -5,4 +5,4 @@ define('PLATFORM_VERSION', json_decode(file_get_contents('composer.json'))->vers
 
 require 'bootstrap/app.php';
 
-Flight::start();
+$app->run();

+ 1 - 1
resources/templates/errors/500.twig

@@ -12,7 +12,7 @@
                 </div>
             </div>
         </div>
-        {% if config.debug %}
+        {% if config.displayErrorDetails %}
 
             <div class="row">
                 <div class="col-md-12">

+ 1 - 1
resources/templates/user/create.twig

@@ -12,7 +12,7 @@
                 <div class="card">
                     <div class="card-header">Create User</div>
                     <div class="card-body">
-                        <form method="post" action="{{ config.base_url }}/user/add">
+                        <form method="post" action="{{ config.base_url }}/user/create">
                             <div class="form-group row">
                                 <label for="email" class="col-sm-2 col-form-label">Email</label>
                                 <div class="col-sm-10">

+ 1 - 1
resources/templates/user/index.twig

@@ -7,7 +7,7 @@
     <div class="container">
         {% include 'comp/alert.twig' %}
         <div class="text-right">
-            <a href="{{ config.base_url }}/user/add" class="btn btn-outline-success mb-3"><i class="fas fa-plus"></i>
+            <a href="{{ config.base_url }}/user/create" class="btn btn-outline-success mb-3"><i class="fas fa-plus"></i>
                 Add User</a>
         </div>
         <div class="table-responsive">