From b1da73b743adbde56eb81681d39848cbdc57ca7e Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 30 Oct 2023 15:55:06 +0100 Subject: [PATCH 1/7] add PDO --- .gitignore | 8 +++++--- Documentation/how-to-dev.md | 3 +-- ci/.drone.yml | 2 +- composer.json | 4 +++- config.php | 4 ++++ profiles/dev-config-profile.php | 10 +++++++++- profiles/prod-config-profile.php | 6 ++++++ public/index.php | 2 ++ sql/database.php | 32 ++++++++++++++++++++++++++++++++ sql/setup-tables.sql | 8 ++++++++ 10 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 sql/database.php create mode 100644 sql/setup-tables.sql diff --git a/.gitignore b/.gitignore index b4c3368..48a117a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,13 @@ -.idea -.vs -.code +.* vendor + composer.lock *.phar /dist +# sqlite database files +*.sqlite + views-mappings.php # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. diff --git a/Documentation/how-to-dev.md b/Documentation/how-to-dev.md index 88e9d75..0af8da5 100644 --- a/Documentation/how-to-dev.md +++ b/Documentation/how-to-dev.md @@ -102,6 +102,7 @@ the file is replaced with `prod-config-file.php` by the CI when deploying to the The two profiles declares an `_asset(string $uri)` function, used by the `/config.php::asset` method, but with different implementations : ### Development profile + ```php $hostname = getHostName(); $front_url = "http://$hostname:5173"; @@ -110,8 +111,6 @@ function _asset(string $assetURI): string { global $front_url; return $front_url . "/" . $assetURI; } - - ``` The simplest profile, simply redirect all assets to the development server diff --git a/ci/.drone.yml b/ci/.drone.yml index cb1a90f..e4380db 100644 --- a/ci/.drone.yml +++ b/ci/.drone.yml @@ -48,5 +48,5 @@ steps: - echo "$SERVER_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 0600 ~/.ssh - chmod 0500 ~/.ssh/id_rsa* - - rsync -avz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:/server/nginx/IQBall/$DRONE_BRANCH + - rsync -rvz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:/server/nginx/IQBall/$DRONE_BRANCH diff --git a/composer.json b/composer.json index 7cfbb6b..c3bb579 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,8 @@ }, "require": { "altorouter/altorouter": "1.2.0", - "ext-json": "*" + "ext-json": "*", + "ext-pdo": "*", + "ext-pdo_sqlite": "*" } } \ No newline at end of file diff --git a/config.php b/config.php index 11e7c2c..592ee38 100644 --- a/config.php +++ b/config.php @@ -16,4 +16,8 @@ function asset(string $assetURI): string { return _asset($assetURI); } +global $_data_source_name; +$data_source_name = $_data_source_name; +const DATABASE_USER = _DATABASE_USER; +const DATABASE_PASSWORD = _DATABASE_PASSWORD; diff --git a/profiles/dev-config-profile.php b/profiles/dev-config-profile.php index 3ce4986..316ff44 100644 --- a/profiles/dev-config-profile.php +++ b/profiles/dev-config-profile.php @@ -4,9 +4,17 @@ $hostname = getHostName(); $front_url = "http://$hostname:5173"; const _SUPPORTS_FAST_REFRESH = true; +$_data_source_name = "sqlite:${_SERVER['DOCUMENT_ROOT']}/../dev-database.sqlite"; -function _asset(string $assetURI): string { +// no user and password needed for sqlite databases +const _DATABASE_USER = null; +const _DATABASE_PASSWORD = null; + +function _asset(string $assetURI): string +{ global $front_url; return $front_url . "/" . $assetURI; } + + diff --git a/profiles/prod-config-profile.php b/profiles/prod-config-profile.php index d76826c..dfd4d02 100644 --- a/profiles/prod-config-profile.php +++ b/profiles/prod-config-profile.php @@ -5,6 +5,12 @@ require "../views-mappings.php"; const _SUPPORTS_FAST_REFRESH = false; +$database_file = __DIR__ . "/../database.sqlite"; +$_data_source_name = "sqlite:/$database_file"; + +// no user and password needed for sqlite databases +const _DATABASE_USER = null; +const _DATABASE_PASSWORD = null; function _asset(string $assetURI): string { diff --git a/public/index.php b/public/index.php index 23737f5..71c24e3 100644 --- a/public/index.php +++ b/public/index.php @@ -2,6 +2,7 @@ require "../vendor/autoload.php"; require "../config.php"; +require "../sql/database.php"; use App\Controller\SampleFormController; @@ -21,6 +22,7 @@ function get_base_path() { } $basePath = get_base_path(); +$pdo = get_database(); // routes initialization $router = new AltoRouter(); diff --git a/sql/database.php b/sql/database.php new file mode 100644 index 0000000..00a9188 --- /dev/null +++ b/sql/database.php @@ -0,0 +1,32 @@ +query($req); + } + } + } + + touch(__DIR__ . "/.guard"); + + return $pdo; +} + + + diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql new file mode 100644 index 0000000..0c6fbe7 --- /dev/null +++ b/sql/setup-tables.sql @@ -0,0 +1,8 @@ + +-- drop tables here +DROP TABLE IF EXISTS FormEntries; + +CREATE TABLE FormEntries(name varchar, description varchar); + + + -- 2.36.3 From bd8d8a3f611701ae8302a3087667be6680cc1441 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 30 Oct 2023 16:49:50 +0100 Subject: [PATCH 2/7] persist in database sample form inputs and show the list of previously entered prompts when displaying form's result --- front/ViewRenderer.tsx | 2 ++ front/views/DisplayResults.tsx | 13 ++++++---- front/views/SampleForm.tsx | 10 ++++---- public/index.php | 5 ++-- src/Controller/SampleFormController.php | 19 ++++++++++++-- src/Gateway/FormResultGateway.php | 34 +++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 src/Gateway/FormResultGateway.php diff --git a/front/ViewRenderer.tsx b/front/ViewRenderer.tsx index ffaf886..17148dc 100644 --- a/front/ViewRenderer.tsx +++ b/front/ViewRenderer.tsx @@ -11,6 +11,8 @@ export function renderView(Component: FunctionComponent, args: {}) { document.getElementById('root') as HTMLElement ); + console.log(args) + root.render( diff --git a/front/views/DisplayResults.tsx b/front/views/DisplayResults.tsx index faf5e28..d6a88db 100644 --- a/front/views/DisplayResults.tsx +++ b/front/views/DisplayResults.tsx @@ -1,12 +1,15 @@ -import ReactDOM from "react-dom/client"; -import React from "react"; -export default function DisplayResults({password, username}: any) { +export default function DisplayResults({results}: { results: { name: string, description: string }[] }) { + const list = results.map(({name, description}) => +
+

username: {name}

+

description: {description}

+
+ ) return (
-

username: {username}

-

password: {password}

+ {list}
) } diff --git a/front/views/SampleForm.tsx b/front/views/SampleForm.tsx index 5108697..c8f1ca4 100644 --- a/front/views/SampleForm.tsx +++ b/front/views/SampleForm.tsx @@ -5,12 +5,12 @@ export default function SampleForm() { return (

Hello, this is a sample form made in react !

-
+ - - - - + + + +
) diff --git a/public/index.php b/public/index.php index 71c24e3..813bf9e 100644 --- a/public/index.php +++ b/public/index.php @@ -5,6 +5,7 @@ require "../config.php"; require "../sql/database.php"; use App\Controller\SampleFormController; +use App\Gateway\FormResultGateway; /** * relative path of the index.php's directory from the server's document root. @@ -28,9 +29,9 @@ $pdo = get_database(); $router = new AltoRouter(); $router->setBasePath($basePath); -$sampleFormController = new SampleFormController(); +$sampleFormController = new SampleFormController(new FormResultGateway($pdo)); $router->map("GET", "/", fn() => $sampleFormController->displayForm()); -$router->map("POST", "/result", fn() => $sampleFormController->displayResults($_POST)); +$router->map("POST", "/submit", fn() => $sampleFormController->submitForm($_POST)); $match = $router->match(); diff --git a/src/Controller/SampleFormController.php b/src/Controller/SampleFormController.php index 5c4dfbd..57625e9 100644 --- a/src/Controller/SampleFormController.php +++ b/src/Controller/SampleFormController.php @@ -3,13 +3,28 @@ namespace App\Controller; require_once __DIR__ . "/../react-display.php"; +use App\Gateway\FormResultGateway; class SampleFormController { + + private FormResultGateway $gateway; + + /** + * @param FormResultGateway $gateway + */ + public function __construct(FormResultGateway $gateway) + { + $this->gateway = $gateway; + } + + public function displayForm() { send_react_front("views/SampleForm.tsx", []); } - public function displayResults(array $request) { - send_react_front("views/DisplayResults.tsx", $request); + public function submitForm(array $request) { + $this->gateway->insert($request["name"], $request["description"]); + $results = ["results" => $this->gateway->listResults()]; + send_react_front("views/DisplayResults.tsx", $results); } } \ No newline at end of file diff --git a/src/Gateway/FormResultGateway.php b/src/Gateway/FormResultGateway.php new file mode 100644 index 0000000..f34ca3a --- /dev/null +++ b/src/Gateway/FormResultGateway.php @@ -0,0 +1,34 @@ +insertion_stmnt = $pdo->prepare("INSERT INTO FormEntries VALUES (:name, :description)"); + $this->list_stmnt = $pdo->prepare("SELECT * FROM FormEntries"); + } + + + function insert(string $username, string $description) { + $this->insertion_stmnt->bindValue(":name", $username, PDO::PARAM_STR); + $this->insertion_stmnt->bindValue(":description", $description, PDO::PARAM_STR); + + $this->insertion_stmnt->execute(); + } + + function listResults(): array { + $this->list_stmnt->execute(); + return $this->list_stmnt->fetchAll(); + } +} \ No newline at end of file -- 2.36.3 From 0b6c0a734cf77ecd0509ff330807c19b3338528d Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 30 Oct 2023 17:46:33 +0100 Subject: [PATCH 3/7] add sql folder to CI --- ci/.drone.yml | 2 +- sql/database.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/.drone.yml b/ci/.drone.yml index e4380db..f3d2b4f 100644 --- a/ci/.drone.yml +++ b/ci/.drone.yml @@ -32,7 +32,7 @@ steps: - sed -iE 's/\\/\\*PROFILE_FILE\\*\\/\\s*".*"/"profiles\\/prod-config-profile.php"/' config.php - composer install && composer update - rm profiles/dev-config-profile.php - - mv src config.php profiles vendor /outputs/ + - mv src config.php sql profiles vendor /outputs/ - image: eeacms/rsync:latest name: Deliver on staging server branch diff --git a/sql/database.php b/sql/database.php index 00a9188..8f445bc 100644 --- a/sql/database.php +++ b/sql/database.php @@ -23,6 +23,8 @@ function get_database() } } + //FIXME Server will need to explicitly set permissions to the `sql` folder + // in order for the touch to work touch(__DIR__ . "/.guard"); return $pdo; -- 2.36.3 From f9d25a40a7d8adfe173de36c285d9fce656f97e9 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 1 Nov 2023 19:19:11 +0100 Subject: [PATCH 4/7] use 'Connexion' class instead of storing prepared statements --- public/index.php | 3 ++- src/Connexion.php | 35 +++++++++++++++++++++++++++++++ src/Gateway/FormResultGateway.php | 25 +++++++++++----------- 3 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 src/Connexion.php diff --git a/public/index.php b/public/index.php index 813bf9e..b3e6bb8 100644 --- a/public/index.php +++ b/public/index.php @@ -4,6 +4,7 @@ require "../vendor/autoload.php"; require "../config.php"; require "../sql/database.php"; +use App\Connexion; use App\Controller\SampleFormController; use App\Gateway\FormResultGateway; @@ -23,7 +24,7 @@ function get_base_path() { } $basePath = get_base_path(); -$pdo = get_database(); +$pdo = new Connexion(get_database()); // routes initialization $router = new AltoRouter(); diff --git a/src/Connexion.php b/src/Connexion.php new file mode 100644 index 0000000..20526a3 --- /dev/null +++ b/src/Connexion.php @@ -0,0 +1,35 @@ +pdo = $pdo; + } + + public function exec(string $query, array $args) { + $stmnt = $this->pdo->prepare($query); + foreach ($args as $name => $value) { + $stmnt->bindValue($name, $value[0], $value[1]); + } + $stmnt->execute(); + } + + public function fetch(string $query, array $args): array { + $stmnt = $this->pdo->prepare($query); + foreach ($args as $name => $value) { + $stmnt->bindValue($name, $value[0], $value[1]); + } + $stmnt->execute(); + return $stmnt->fetchAll(); + } + +} \ No newline at end of file diff --git a/src/Gateway/FormResultGateway.php b/src/Gateway/FormResultGateway.php index f34ca3a..fe0c601 100644 --- a/src/Gateway/FormResultGateway.php +++ b/src/Gateway/FormResultGateway.php @@ -3,32 +3,31 @@ namespace App\Gateway; use PDO; -use PDOStatement; +use App\Connexion; /** * A sample gateway, that stores the sample form's result. */ class FormResultGateway { - private PDOStatement $insertion_stmnt; - private PDOStatement $list_stmnt; + private Connexion $con; - public function __construct(PDO $pdo) - { - $this->insertion_stmnt = $pdo->prepare("INSERT INTO FormEntries VALUES (:name, :description)"); - $this->list_stmnt = $pdo->prepare("SELECT * FROM FormEntries"); + public function __construct(Connexion $con) { + $this->con = $con; } function insert(string $username, string $description) { - $this->insertion_stmnt->bindValue(":name", $username, PDO::PARAM_STR); - $this->insertion_stmnt->bindValue(":description", $description, PDO::PARAM_STR); - - $this->insertion_stmnt->execute(); + $this->con->exec( + "INSERT INTO FormEntries VALUES (:name, :description)", + [ + ":name" => [$username, PDO::PARAM_STR], + "description" => [$description, PDO::PARAM_STR] + ] + ); } function listResults(): array { - $this->list_stmnt->execute(); - return $this->list_stmnt->fetchAll(); + return $this->con->fetch("SELECT * FROM FormEntries", []); } } \ No newline at end of file -- 2.36.3 From a7ffeb845b08af687ce45b150254b4149a644ba4 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Thu, 2 Nov 2023 11:44:48 +0100 Subject: [PATCH 5/7] fix ci --- ci/.drone.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/.drone.yml b/ci/.drone.yml index f3d2b4f..7885e92 100644 --- a/ci/.drone.yml +++ b/ci/.drone.yml @@ -48,5 +48,6 @@ steps: - echo "$SERVER_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 0600 ~/.ssh - chmod 0500 ~/.ssh/id_rsa* - - rsync -rvz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:/server/nginx/IQBall/$DRONE_BRANCH - + - rsync -avz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:/server/nginx/IQBall/$DRONE_BRANCH + - + - echo "cd /server/nginx/IQBall/$DRONE_BRANCH && chmod 777 . sql" | ssh -p 80 -o 'StrictHostKeyChecking=no' iqball@maxou.dev '/usr/bin/bash' \ No newline at end of file -- 2.36.3 From 341a460d3cc47945161a289f3137e1fe425b0640 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Thu, 2 Nov 2023 18:48:02 +0100 Subject: [PATCH 6/7] add documentation --- public/index.php | 4 ++-- sql/database.php | 9 +++++++-- src/Connexion.php | 12 ++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index b3e6bb8..14b0756 100644 --- a/public/index.php +++ b/public/index.php @@ -24,13 +24,13 @@ function get_base_path() { } $basePath = get_base_path(); -$pdo = new Connexion(get_database()); +$con = new Connexion(get_database()); // routes initialization $router = new AltoRouter(); $router->setBasePath($basePath); -$sampleFormController = new SampleFormController(new FormResultGateway($pdo)); +$sampleFormController = new SampleFormController(new FormResultGateway($con)); $router->map("GET", "/", fn() => $sampleFormController->displayForm()); $router->map("POST", "/submit", fn() => $sampleFormController->submitForm($_POST)); diff --git a/sql/database.php b/sql/database.php index 8f445bc..0830ed8 100644 --- a/sql/database.php +++ b/sql/database.php @@ -1,7 +1,10 @@ pdo = $pdo; } + /** + * execute a request + * @param string $query + * @param array $args + * @return void + */ public function exec(string $query, array $args) { $stmnt = $this->pdo->prepare($query); foreach ($args as $name => $value) { @@ -23,6 +29,12 @@ class Connexion { $stmnt->execute(); } + /** + * Execute a request, and return the returned rows + * @param string $query the SQL request + * @param array $args an array containing the arguments label, value and type: ex: `[":label" => [$value, PDO::PARAM_TYPE]` + * @return array the returned rows of the request + */ public function fetch(string $query, array $args): array { $stmnt = $this->pdo->prepare($query); foreach ($args as $name => $value) { -- 2.36.3 From 29dd65536ea650a3259907b6fea5cab1b7202012 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Sun, 5 Nov 2023 20:54:09 +0100 Subject: [PATCH 7/7] apply suggestions --- ci/.drone.yml | 2 -- front/views/DisplayResults.tsx | 8 ++++++-- front/views/SampleForm.tsx | 3 +-- sql/database.php | 15 +++------------ 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/ci/.drone.yml b/ci/.drone.yml index 7885e92..8a28ad7 100644 --- a/ci/.drone.yml +++ b/ci/.drone.yml @@ -49,5 +49,3 @@ steps: - chmod 0600 ~/.ssh - chmod 0500 ~/.ssh/id_rsa* - rsync -avz -e "ssh -p 80 -o 'StrictHostKeyChecking=no'" --delete /outputs/* iqball@maxou.dev:/server/nginx/IQBall/$DRONE_BRANCH - - - - echo "cd /server/nginx/IQBall/$DRONE_BRANCH && chmod 777 . sql" | ssh -p 80 -o 'StrictHostKeyChecking=no' iqball@maxou.dev '/usr/bin/bash' \ No newline at end of file diff --git a/front/views/DisplayResults.tsx b/front/views/DisplayResults.tsx index d6a88db..c4bbd1b 100644 --- a/front/views/DisplayResults.tsx +++ b/front/views/DisplayResults.tsx @@ -1,7 +1,11 @@ +interface DisplayResultsProps { + results: readonly { name: string, description: string}[] +} -export default function DisplayResults({results}: { results: { name: string, description: string }[] }) { - const list = results.map(({name, description}) => +export default function DisplayResults({results}: DisplayResultsProps) { + const list = results + .map(({name, description}) =>

username: {name}

description: {description}

diff --git a/front/views/SampleForm.tsx b/front/views/SampleForm.tsx index c8f1ca4..604e362 100644 --- a/front/views/SampleForm.tsx +++ b/front/views/SampleForm.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; + export default function SampleForm() { return ( diff --git a/sql/database.php b/sql/database.php index 0830ed8..6fae3ea 100644 --- a/sql/database.php +++ b/sql/database.php @@ -6,10 +6,10 @@ function get_database(): PDO { // defined by profiles. global $data_source_name; - // The presence of the .guard file says that the database has already been initialized. - $database_exists = file_exists(__DIR__ . "/.guard"); $pdo = new PDO($data_source_name, DATABASE_USER, DATABASE_PASSWORD); + $database_exists = $pdo->query("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'")->fetchColumn() > 0; + if ($database_exists) { return $pdo; } @@ -18,19 +18,10 @@ function get_database(): PDO { if (preg_match("/.*\.sql$/i", $file)) { $content = file_get_contents(__DIR__ . "/" . $file); - foreach (preg_split("/;\s*/", $content) as $req) { - if ($req === "") - break; - $pdo->query($req); - } + $pdo->exec($content); } } - //FIXME Server will need to explicitly set permissions to the `sql` folder - // in order for the touch to work - // - // Workaround in CI by setting permissions to 777 to the folder - touch(__DIR__ . "/.guard"); return $pdo; } -- 2.36.3