From ea1ec391dd4407a93bf8231b61db8acd880feac5 Mon Sep 17 00:00:00 2001 From: LeonardoBizzoni Date: Wed, 4 May 2022 11:20:58 +0200 Subject: [PATCH] Login to existing account --- README.org | 24 +++++++-- www/controllers/AuthController.php | 22 +++++--- www/controllers/SiteController.php | 17 +++--- www/core/Application.php | 29 +++++++++- www/core/BaseModel.php | 28 +++++++--- www/core/DbModel.php | 18 ++++++- www/core/Response.php | 4 ++ www/core/Session.php | 23 ++++++++ www/core/forms/Field.php | 2 +- www/models/LoginForm.php | 43 +++++++++++++++ www/models/User.php | 21 +++++++- www/models/Vtubers.php | 85 +++++++++++++++++------------- www/pub/index.php | 1 + www/views/layouts/main.php | 9 ++++ www/views/live.php | 18 +++---- www/views/login.php | 22 +++----- 16 files changed, 272 insertions(+), 94 deletions(-) create mode 100644 www/core/Session.php create mode 100644 www/models/LoginForm.php diff --git a/README.org b/README.org index f662ed8..18a2ba2 100644 --- a/README.org +++ b/README.org @@ -243,15 +243,30 @@ Ogni migration class è formata da _almeno_ 2 metodi: ** Models Le classi "Model" gestiscono i dati presenti nel database e controlla che i dati seguano delle determinate regole. -Tutte le classi Model derivano dalla classe "BaseModel". +Tutte le classi Model derivano dalla classe "BaseModel" o "DbModel". -*** Registration model +*** Registration model - User model Il model di registrazione si occupa della gestione dei dati di nuovi utenti e dell'interazione con il database. Attraverso l'implementazione del metodo astratto "rules()" si possono impostare le regole che i campi della form dovranno seguire. -*Attenzione*: i nomi dei parametri, i nomi inseriti nella array resistituito da "rules()" ed i nomi dei [[./www/core/forms/Field.php][Field]] _devono_ essere uguali. -# TODO: register() +I nomi dei parametri di questa classe non devono necessariamente coincidere con i label visibili dall'utente. +Questo perchè utilizzando il metodo "labels()" è possibile mappare i nomi dei parametri ai label che appaiono all'utente. + +L'effettiva registrazione dell'utente attraverso l'inserimento dei valori all'interno del database viene eseguita dal metodo "save()". +"save()" è un metodo a cui tutti i model che estendono "DbModel" possono accedere, questo perchè sono la rappresentazione della tabella presente nel database. + +*** Login model - LoginForm model +Model molto semplice, non basato sul DbModel ma bensì sul BaseModel in quanto non deve interagire direttamente con il database. + +Inviata la POST request per effettuare il login esso cerca nel database un utente che abbia l'indirizzo email fornito dall'utente, se lo trova controlla la correttezza della password. + +*** Vtuber model +Model utilizzato per interagire con il database di vtuber. + +Simile al model di registrazione ma con 2 importanti metodi: +- *getVtuberInfo()*: controlla che il link inserito sia l'URL ad un canale twitch o youtube ed utilizzando i rispettivi API(Twitch o Google) recupara le informazioni della vtuber in questione +- *isLive()*: controlla se la vtuber in questione è live o no, utilizzando l'API Twitch o tramite curl request per canali YT ** Controllers Le classi "Controller" svolgono il ruolo di ponte. @@ -261,4 +276,3 @@ Esse permettono ai dati di apparire nella View richiesta dall'utente una volta p L'authentication controller ha 2 compiti fondamentali: - registrare nuovi utenti con l'aiuto della classe "RegisterModel" - permettere l'accesso ad utenti già registrati -# TODO diff --git a/www/controllers/AuthController.php b/www/controllers/AuthController.php index 4671a29..6456621 100644 --- a/www/controllers/AuthController.php +++ b/www/controllers/AuthController.php @@ -1,26 +1,36 @@ setLayout("auth"); - return $this->render("login"); + public function login(Request $req) { + $loginForm = new LoginForm; + + if ($req->getMethod() == "post"){ + $loginForm->loadData($req->getBody()); + + if ($loginForm->validate() && $loginForm->login()) { + Application::$app->res->redirect("/"); + return; + } + } + + return $this->render("login", [ "model" => $loginForm ]); } public function register(Request $req) { - // $this->setLayout("auth"); - $errors = []; $registerModel = new User; if ($req->getMethod() == "post") { $registerModel->loadData($req->getBody()); if ($registerModel->validate() && $registerModel->register()) { - return "Success"; + Application::$app->res->redirect("/"); } } return $this->render("register", [ "model" => $registerModel ]); diff --git a/www/controllers/SiteController.php b/www/controllers/SiteController.php index dbb3d96..cbf3fe1 100644 --- a/www/controllers/SiteController.php +++ b/www/controllers/SiteController.php @@ -21,29 +21,28 @@ class SiteController extends BaseController public function live(Request $req) { $params = []; + $statement = Application::$app->db->pdo->prepare("SELECT * FROM vtubers;"); $vtuberModel = new Vtubers; if ($req->getMethod() == "post") { $vtuberModel->loadData($req->getBody()); - $vtuberModel->getVtuberName(); + $vtuberModel->getVtuberInfo(); if ($vtuberModel->validate() && $vtuberModel->register()) { - return "Success"; + Application::$app->res->redirect("/"); } } else if ($req->getMethod() == "get") { - $statement = Application::$app->db->pdo->prepare("SELECT * FROM vtubers;"); + if (isset($_GET["id"])) { + $this->setLayout("live"); + $statement = Application::$app->db->pdo->prepare("SELECT * from vtubers where id={$_GET['id']}"); + } $statement->execute(); foreach ($statement->fetchAll() as $vtuber) { - $params[] = ["vtuber" => [ $vtuber, $vtuberModel->isLive($vtuber["login"], $vtuber["link"]) ]]; + $params[] = [ $vtuber, $vtuberModel->isLive($vtuber["login"], $vtuber["link"]) ]; } - // echo "
";
-                // var_dump($params);
-                // echo "setLayout("live");
         return $this->render("live", ["model" => $vtuberModel, $params]);
     }
 }
diff --git a/www/core/Application.php b/www/core/Application.php
index dec27fb..7922f6d 100644
--- a/www/core/Application.php
+++ b/www/core/Application.php
@@ -4,11 +4,14 @@ namespace app\core;
 class Application {
     private BaseController $controller;
 
+    public $userClass;
+    public array $config;
     public Router $router;
     public Request $req;
     public Response $res;
     public Database $db;
-    public array $config;
+    public Session $session;
+    public ?DbModel $user = null;
 
     public static Application $app;
     public static string $ROOT_DIR;
@@ -17,11 +20,20 @@ class Application {
         self::$ROOT_DIR = $root;
         self::$app = $this;
 
+        $this->userClass = $config["userClass"];
         $this->config = $config;
         $this->req = new Request();
         $this->res = new Response();
         $this->router = new Router($this->req, $this->res);
+        $this->session = new Session;
+
         $this->db = new Database($config["db"]);
+
+        $primaryValue = $this->session->get("user");
+        if ($primaryValue) {
+            $primaryKey = $this->userClass::primaryKey();
+            $this->user = $this->userClass::findOne([$primaryKey => $primaryValue]);
+        }
     }
 
     public function run() {
@@ -35,5 +47,20 @@ class Application {
     public function setController(BaseController $controller) {
         $this->controller = $controller;
     }
+
+    public function login(DbModel $user) {
+        $this->user = $user;
+        $primaryKey = $user->primaryKey();
+        $primaryValue = $user->{$primaryKey};
+
+        $this->session->set("user", $primaryValue);
+
+        return true;
+    }
+
+    public function logout() {
+        $this->user = null;
+        $this->session->remove("user");
+    }
 }
 ?>
diff --git a/www/core/BaseModel.php b/www/core/BaseModel.php
index 9960ffe..158be02 100644
--- a/www/core/BaseModel.php
+++ b/www/core/BaseModel.php
@@ -21,6 +21,14 @@ abstract class BaseModel {
 
     abstract public function rules(): array;
 
+    public function labels(): array {
+        return [];
+    }
+
+    public function getLabel(string $attribute) {
+        return $this->labels()[$attribute] ?? $attribute;
+    }
+
     public function validate() {
         foreach ($this->rules() as $attribute => $rules) {
             $value = $this->{$attribute};
@@ -33,19 +41,19 @@ abstract class BaseModel {
                 }
 
                 if ($ruleName == self::RULE_REQUIRED && !$value) {
-                    $this->addError($attribute, self::RULE_REQUIRED);
+                    $this->internalAddError($attribute, self::RULE_REQUIRED);
                 }
                 if ($ruleName == self::RULE_EMAIL && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
-                    $this->addError($attribute, self::RULE_EMAIL);
+                    $this->internalAddError($attribute, self::RULE_EMAIL);
                 }
                 if ($ruleName == self::RULE_MIN && strlen($value) < $rule["min"]) {
-                    $this->addError($attribute, self::RULE_MIN, $rule);
+                    $this->internalAddError($attribute, self::RULE_MIN, $rule);
                 }
                 if ($ruleName == self::RULE_MAX && strlen($value) > $rule["max"]) {
-                    $this->addError($attribute, self::RULE_MAX, $rule);
+                    $this->internalAddError($attribute, self::RULE_MAX, $rule);
                 }
                 if ($ruleName == self::RULE_MATCH && $value != $this->{$rule["match"]}) {
-                    $this->addError($attribute, self::RULE_MATCH, $rule);
+                    $this->internalAddError($attribute, self::RULE_MATCH, $rule);
                 }
                 if ($ruleName == self::RULE_UNIQUE) {
                     $className = $rule["class"];
@@ -57,7 +65,7 @@ abstract class BaseModel {
                     $statement->execute();
 
                     if($statement->fetchObject()) {
-                        $this->addError($attribute, self::RULE_UNIQUE, ["field" => $attribute]);
+                        $this->internalAddError($attribute, self::RULE_UNIQUE, ["field" => $this->getLabel($attribute)]);
                     }
                 }
             }
@@ -66,14 +74,18 @@ abstract class BaseModel {
         return empty($this->errors);
     }
 
-    public function addError(string $attribute, string $rule, $params = []) {
+    private function internalAddError(string $attribute, string $rule, $params = []) {
         $message = $this->errorMessages()[$rule] ?? "";
         foreach ($params as $key => $value) {
-            $message = str_replace("{{$key}}", $value, $message);
+            $message = str_replace("{{$key}}", strtolower($this->getLabel($value)), $message);
         }
         $this->errors[$attribute][] = $message;
     }
 
+    public function addError(string $attribute, string $message) {
+        $this->errors[$attribute][] = $message;
+    }
+
     public function errorMessages() {
         return [
             self::RULE_REQUIRED => "This field is required",
diff --git a/www/core/DbModel.php b/www/core/DbModel.php
index 11d2869..5d90c04 100644
--- a/www/core/DbModel.php
+++ b/www/core/DbModel.php
@@ -4,7 +4,8 @@ namespace app\core;
 
 abstract class DbModel extends BaseModel
 {
-    abstract public function tableName(): string;
+    abstract public static function tableName(): string;
+    abstract public static function primaryKey(): string;
     abstract public function attributes(): array;
 
     public function save()
@@ -23,6 +24,21 @@ abstract class DbModel extends BaseModel
         return true;
     }
 
+    public static function findOne(array $where) {
+        $tableName = static::tableName();
+        $attributes = array_keys($where);
+
+        $sql = implode(" AND ", array_map(fn($attr) => "$attr = :$attr", $attributes));
+
+        $statement = self::prepare("SELECT * FROM $tableName WHERE $sql");
+        foreach ($where as $key => $value) {
+            $statement->bindValue(":$key", $value);
+        }
+        $statement->execute();
+
+        return $statement->fetchObject(static::class);
+    }
+
     public static function prepare(string $sql) {
         return  Application::$app->db->pdo->prepare($sql);
     }
diff --git a/www/core/Response.php b/www/core/Response.php
index 06c2295..e5dcf3e 100644
--- a/www/core/Response.php
+++ b/www/core/Response.php
@@ -5,5 +5,9 @@ class Response {
     public function setStatusCode(int $code) {
         http_response_code($code);
     }
+
+    public function redirect(string $url) {
+        header("Location: $url");
+    }
 }
 ?>
diff --git a/www/core/Session.php b/www/core/Session.php
new file mode 100644
index 0000000..f8d9884
--- /dev/null
+++ b/www/core/Session.php
@@ -0,0 +1,23 @@
+
diff --git a/www/core/forms/Field.php b/www/core/forms/Field.php
index af33968..997597b 100644
--- a/www/core/forms/Field.php
+++ b/www/core/forms/Field.php
@@ -26,7 +26,7 @@ class Field {
     
     
%s
', - ucfirst($this->attribute), + $this->model->getLabel($this->attribute), $this->attribute, $this->type, $this->model->{$this->attribute}, diff --git a/www/models/LoginForm.php b/www/models/LoginForm.php new file mode 100644 index 0000000..45cc7f3 --- /dev/null +++ b/www/models/LoginForm.php @@ -0,0 +1,43 @@ + [self::RULE_REQUIRED, self::RULE_EMAIL], + "password" => [self::RULE_REQUIRED] + ]; + } + + public function login() { + $user = User::findOne(["email" => $this->email]); + + if (!$user) { + $this->addError("email", "User does not exist!"); + return false; + } else { + if (!password_verify($this->password, $user->password)) { + $this->addError("password", "Password is incorrect"); + return false; + } + } + + return Application::$app->login($user); + } + + public function labels(): array + { + return [ + "email" => "Your account email", + "password" => "The super secret password to your epic account" + ]; + } +} diff --git a/www/models/User.php b/www/models/User.php index 4f3bb34..7d291d3 100644 --- a/www/models/User.php +++ b/www/models/User.php @@ -13,11 +13,16 @@ class User extends DbModel public string $password = ""; public string $confirm = ""; - public function tableName(): string + public static function tableName(): string { return "users"; } + public static function primaryKey(): string + { + return "id"; + } + public function attributes(): array { return [ "firstname", "lastname", "email", "username", "password" ]; } @@ -35,8 +40,20 @@ class User extends DbModel "lastname" => [self::RULE_REQUIRED], "username" => [self::RULE_REQUIRED], "email" => [self::RULE_REQUIRED, self::RULE_EMAIL, [self::RULE_UNIQUE, "class" => self::class ]], - "password" => [self::RULE_REQUIRED, [self::RULE_MIN, "min" => 20], [self::RULE_MAX, "max" => 100]], + "password" => [self::RULE_REQUIRED, [self::RULE_MIN, "min" => 1], [self::RULE_MAX, "max" => 100]], "confirm" => [self::RULE_REQUIRED, [self::RULE_MATCH, "match" => "password"]] ]; } + + public function labels(): array + { + return [ + "firstname" => "First name", + "lastname" => "Last name", + "username" => "Your username", + "email" => "Email", + "password" => "Your super secret password", + "confirm" => "Confirm your super secret password", + ]; + } } diff --git a/www/models/Vtubers.php b/www/models/Vtubers.php index 3ac8372..0f33808 100644 --- a/www/models/Vtubers.php +++ b/www/models/Vtubers.php @@ -15,16 +15,28 @@ class Vtubers extends DbModel public string $img = ""; public string $link = ""; - public function tableName(): string + public static function tableName(): string { return "vtubers"; } + public static function primaryKey(): string + { + return "id"; + } + public function attributes(): array { return ["username", "login", "img", "link"]; } + public function labels(): array + { + return [ + "link" => "Vtuber channel link", + ]; + } + public function register() { return $this->save(); @@ -40,7 +52,7 @@ class Vtubers extends DbModel ]; } - public function getVtuberName() + public function getVtuberInfo() { if (str_contains($this->link, "twitch.tv")) { $clientID = Application::$app->config["twitch"]["clientid"] ?? ""; @@ -77,53 +89,54 @@ class Vtubers extends DbModel $this->img = $response["items"][0]["snippet"]["thumbnails"]["default"]["url"]; return; } - $this->username = "i got you bro"; - $this->login = "i got you bro"; - $this->img = "i got you bro"; } public function isLive(string $login, string $link) { - if (str_contains($link, "twitch.tv")) { - $clientID = Application::$app->config["twitch"]["clientid"] ?? ""; - $token = Application::$app->config["twitch"]["token"] ?? ""; + // if (str_contains($link, "twitch.tv")) { + // $clientID = Application::$app->config["twitch"]["clientid"] ?? ""; + // $token = Application::$app->config["twitch"]["token"] ?? ""; - $url = "https://api.twitch.tv/helix/streams?user_login=$login"; + // $url = "https://api.twitch.tv/helix/streams?user_login=$login"; - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, array("Client-ID: $clientID", "Authorization: Bearer $token")); + // $ch = curl_init($url); + // curl_setopt($ch, CURLOPT_URL, $url); + // curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + // curl_setopt($ch, CURLOPT_HTTPHEADER, array("Client-ID: $clientID", "Authorization: Bearer $token")); - $result = get_object_vars(json_decode(curl_exec($ch))); - curl_close($ch); + // $result = get_object_vars(json_decode(curl_exec($ch))); + // curl_close($ch); - return count($result["data"]) ? $result["data"] : []; - } + // return count($result["data"]) ? $result["data"] : []; + // } - if (str_contains($link, "youtube.com")) { - $url = "https://www.youtube.com/channel/$login/live"; + // if (str_contains($link, "youtube.com")) { + // $url = "https://www.youtube.com/channel/$login/live"; - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + // $ch = curl_init($url); + // curl_setopt($ch, CURLOPT_URL, $url); + // curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - $result = curl_exec($ch); - curl_close($ch); + // $result = curl_exec($ch); + // curl_close($ch); - $doc = new DOMDocument(); - libxml_use_internal_errors(true); - $doc->loadHTML($result); - $length = $doc->getElementsByTagName('link')->length; + // $doc = new DOMDocument(); + // libxml_use_internal_errors(true); + // $doc->loadHTML($result); - for ($i = 0; $i < $length; $i++) { - $result = $doc->getElementsByTagName("link")->item($i)->getAttribute("href"); - if (str_contains($result, "https://www.youtube.com/watch?v=")) { - return [str_replace( "https://www.youtube.com/watch?v=", "", $result)]; - } - } + // $result = $doc->getElementsByTagName("link"); + // $length = $result->length; - return []; - } + // for ($i = 0; $i < $length; $i++) { + // $tag = $result->item($i)->getAttribute("href"); + // if (str_contains($tag, "https://www.youtube.com/watch?v=")) { + // return [str_replace("https://www.youtube.com/watch?v=", "", $tag)]; + // } + // } + + // unset($doc); + // } + + return []; } } diff --git a/www/pub/index.php b/www/pub/index.php index 28ab3df..ca1a395 100644 --- a/www/pub/index.php +++ b/www/pub/index.php @@ -8,6 +8,7 @@ $dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__)); $dotenv->load(); $config = [ + "userClass" => \app\models\User::class, "db" => [ "dsn" => $_ENV["DB_DSN"], "user" => $_ENV["DB_USER"], diff --git a/www/views/layouts/main.php b/www/views/layouts/main.php index 1f13f44..00a44c8 100644 --- a/www/views/layouts/main.php +++ b/www/views/layouts/main.php @@ -1,3 +1,12 @@ +"; +var_dump(Application::$app->user); +echo "
"; +?> + diff --git a/www/views/live.php b/www/views/live.php index 7450813..8a13a2c 100644 --- a/www/views/live.php +++ b/www/views/live.php @@ -10,27 +10,27 @@ if (!isset($_GET["id"])) { echo "

Currently live

"; echo ""; echo "

Currently offline

"; echo ""; } else { foreach ($params[0] as $vtuber) { - if ($_GET["id"] == $vtuber["vtuber"][0]["id"]) { + if ($_GET["id"] == $vtuber[0]["id"]) { echo "
@@ -38,10 +38,10 @@ if (!isset($_GET["id"])) {
"; - if (str_contains($vtuber["vtuber"][0]["link"], "twitch.tv")) { - echo ""; + if (str_contains($vtuber[0]["link"], "twitch.tv")) { + echo ""; } else { - echo ""; + echo ""; } echo "
"; diff --git a/www/views/login.php b/www/views/login.php index 5d48ec6..a4673b6 100644 --- a/www/views/login.php +++ b/www/views/login.php @@ -1,20 +1,10 @@

Login page

-
-
- - -
We'll never share your email with anyone else.
-
-
- - -
-
- - -
- -
+ + field($model, "email"); ?> + field($model, "password")->passwordField(); ?> + + +
-- 2.52.0