diff --git a/bin/doctrine b/bin/doctrine
index b9ea0d1..653b679 100644
--- a/bin/doctrine
+++ b/bin/doctrine
@@ -15,7 +15,8 @@ require_once __DIR__ . '/../src/dependencies.php';
global $container;
$commands = [
- $container->get(TestFeed::class),
+ $container->get(TestFeed::class),
+ $container->get(\Lewisdale\App\Tools\Console\CreateUser::class),
];
ConsoleRunner::run(
diff --git a/src/Controllers/FeedController.php b/src/Controllers/FeedController.php
index 553816a..94b5867 100644
--- a/src/Controllers/FeedController.php
+++ b/src/Controllers/FeedController.php
@@ -30,6 +30,11 @@ class FeedController
{
$this->logger->info("FeedController::get_feed() called");
$feed = $this->feedRepository->find($request->getAttribute('id'));
+
+ if (empty($feed)) {
+ return $response->withStatus(404);
+ }
+
$filtered = $feed->get_filtered_feed();
$body = $response->getBody();
diff --git a/src/Controllers/LoginController.php b/src/Controllers/LoginController.php
new file mode 100644
index 0000000..bc71f4a
--- /dev/null
+++ b/src/Controllers/LoginController.php
@@ -0,0 +1,37 @@
+view->render($response, 'login/index.twig.html', []);
+ }
+
+ public function login(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface {
+ $body = $request->getParsedBody();
+ $email = $body['email'];
+ $password = $body['password'];
+
+ $user = $this->users->validateCredentials($email, $password);
+
+ if (!$user) {
+ return $this->view->render($response, 'login/index.twig.html', ['error' => 'Invalid email or password']);
+ }
+
+ $_SESSION['user'] = $user->id;
+
+ return $response->withStatus(302)->withHeader('Location', '/feed');
+ }
+}
\ No newline at end of file
diff --git a/src/Models/Data/User.php b/src/Models/Data/User.php
index c47748c..c4d8ae2 100644
--- a/src/Models/Data/User.php
+++ b/src/Models/Data/User.php
@@ -18,7 +18,7 @@ class User
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
- private ?int $id;
+ public ?int $id;
#[ORM\Column(type: 'string')]
private string $password;
#[ORM\Column(type: 'string')]
diff --git a/src/Models/Repositories/UserRepository.php b/src/Models/Repositories/UserRepository.php
new file mode 100644
index 0000000..70dd522
--- /dev/null
+++ b/src/Models/Repositories/UserRepository.php
@@ -0,0 +1,41 @@
+ */
+class UserRepository extends EntityRepository {
+ private readonly LoggerInterface $logger;
+
+ public function __construct(EntityManager $em, LoggerInterface $logger)
+ {
+ parent::__construct($em, $em->getClassMetadata(User::class));
+ $this->logger = $logger;
+ }
+
+ public function findByEmail(string $email): ?User {
+ return $this->findOneBy(['email' => $email]);
+ }
+
+ public function validateCredentials(string $email, string $password): ?User {
+ $user = $this->findByEmail($email);
+ if (!$user || !$user->validate($password)) {
+ $this->logger->warning('Invalid credentials.', ['email' => $email, 'password' => $password, 'user' => $user, 'valid' => $user?->validate($password)]);
+ return null;
+ }
+
+ return $user;
+ }
+
+ public function save(User $user): void {
+ $this->_em->persist($user);
+ $this->_em->flush();
+ }
+}
\ No newline at end of file
diff --git a/src/Session/LoginMiddleware.php b/src/Session/LoginMiddleware.php
new file mode 100644
index 0000000..d46b060
--- /dev/null
+++ b/src/Session/LoginMiddleware.php
@@ -0,0 +1,37 @@
+withHeader('Location', '/account/login');
+ }
+
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+ {
+ if (!isset($_SESSION['user'])) {
+ return $this->redirectToLogin();
+ }
+
+ $user = $this->users->find($_SESSION['user']);
+
+ if (!$user) {
+ return $this->redirectToLogin();
+ }
+
+ return $handler->handle($request->withAttribute('user', $user));
+ }
+}
\ No newline at end of file
diff --git a/src/Tools/Console/CreateUser.php b/src/Tools/Console/CreateUser.php
new file mode 100644
index 0000000..a3d6279
--- /dev/null
+++ b/src/Tools/Console/CreateUser.php
@@ -0,0 +1,48 @@
+addArgument('email', InputArgument::REQUIRED, "The user's email address")
+ ->addArgument('password', InputArgument::REQUIRED, "The user's password");
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $email = $input->getArgument('email');
+ $password = $input->getArgument('password');
+
+ $user = $this->userRepository->findOneBy(['email' => $email]);
+ if ($user !== null) {
+ $output->writeln('