├── README.md ├── .gitignore ├── logo.png ├── favicon.png ├── public ├── sounds │ ├── no.wav │ └── yes.wav ├── images │ ├── image_0.png │ ├── image_1.png │ ├── image_2.png │ ├── image_3.png │ ├── image_4.png │ ├── image_5.png │ ├── image_6.png │ ├── image_7.png │ ├── image_8.png │ └── image_9.png ├── css │ ├── hangman.css │ └── style.css └── js │ ├── tables.js │ └── hangman.js ├── composer.json ├── app ├── views │ ├── index.view.php │ ├── partials │ │ ├── footer.php │ │ ├── nav.php │ │ └── head.php │ ├── currencies.view.php │ ├── game.view.php │ ├── history.view.php │ └── exchange.view.php ├── routes.php ├── providers │ └── NbpProvider.php ├── controllers │ └── CurrencyController.php └── services │ └── CurrencyService.php ├── core ├── error.php ├── Request.php ├── database │ ├── Connection.php │ └── QueryBuilder.php ├── bootstrap.php ├── App.php └── Router.php ├── functions.php ├── update_currencies.php ├── config.php ├── index.php ├── composer.lock ├── Dockerfile ├── docker-compose.yml ├── .htaccess └── db ├── exchange_history.sql └── exchange_rates.sql /README.md: -------------------------------------------------------------------------------- 1 | # currency_exchange -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /php 3 | /apache -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/logo.png -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/favicon.png -------------------------------------------------------------------------------- /public/sounds/no.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/sounds/no.wav -------------------------------------------------------------------------------- /public/sounds/yes.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/sounds/yes.wav -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoload": { 3 | "classmap": [ 4 | "./" 5 | ] 6 | } 7 | } -------------------------------------------------------------------------------- /public/images/image_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_0.png -------------------------------------------------------------------------------- /public/images/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_1.png -------------------------------------------------------------------------------- /public/images/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_2.png -------------------------------------------------------------------------------- /public/images/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_3.png -------------------------------------------------------------------------------- /public/images/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_4.png -------------------------------------------------------------------------------- /public/images/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_5.png -------------------------------------------------------------------------------- /public/images/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_6.png -------------------------------------------------------------------------------- /public/images/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_7.png -------------------------------------------------------------------------------- /public/images/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_8.png -------------------------------------------------------------------------------- /public/images/image_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalm57/currency_exchange/HEAD/public/images/image_9.png -------------------------------------------------------------------------------- /app/views/index.view.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /app/views/partials/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /core/error.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Not Found 5 | 6 | 7 |
8 |

404

9 |

Not Found

10 |
11 | 12 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | '; 13 | die(var_dump($val)); 14 | echo ''; 15 | } 16 | -------------------------------------------------------------------------------- /update_currencies.php: -------------------------------------------------------------------------------- 1 | updateCurrencies(NbpProvider::TABLE_TYPE_A); 13 | $currencyController->updateCurrencies(NbpProvider::TABLE_TYPE_B); 14 | ?> -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'name' => 'currency_exchange', 6 | 'username' => 'currency_exchange_user', 7 | 'password' => 'password', 8 | 'connection' => 'mysql:host=db', 9 | 'options' => [ 10 | PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING, 11 | ], 12 | ], 13 | 'nbp_api' => [ 14 | 'exchange_rates_url' => 'http://api.nbp.pl/api/exchangerates/tables/:tableType/', 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | direct(Request::uri(), Request::method()); 20 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "d751713988987e9331980363e24189ce", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": [], 16 | "platform-dev": [], 17 | "plugin-api-version": "2.3.0" 18 | } 19 | -------------------------------------------------------------------------------- /core/Request.php: -------------------------------------------------------------------------------- 1 | get('', 'CurrencyController@home'); 12 | 13 | //Currencies 14 | $router->get('currencies', 'CurrencyController@currencies'); 15 | 16 | //Currrency exchange 17 | $router->get('exchange', 'CurrencyController@exchange'); 18 | $router->post('exchange/calculate-exchange', 'CurrencyController@calculateExchange'); 19 | 20 | //Exchange history 21 | $router->get('history', 'CurrencyController@history'); 22 | 23 | //Game 24 | $router->get('game', 'CurrencyController@game'); 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:apache 2 | 3 | RUN chown -R www-data:www-data /var/www/html 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends \ 7 | libpq-dev \ 8 | libzip-dev \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | RUN docker-php-ext-install pdo pdo_mysql mysqli 12 | 13 | RUN apt-get update \ 14 | && apt-get install -y --no-install-recommends \ 15 | libonig-dev \ 16 | && docker-php-ext-install mbstring 17 | 18 | RUN docker-php-ext-install zip 19 | 20 | RUN a2enmod rewrite 21 | 22 | COPY apache-config.conf /etc/apache2/sites-available/000-default.conf 23 | 24 | COPY . /var/www/html 25 | 26 | RUN chown -R www-data:www-data /var/www/html 27 | 28 | EXPOSE 80 29 | 30 | CMD ["apache2-foreground"] 31 | -------------------------------------------------------------------------------- /app/views/currencies.view.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
NameCodeRate
name); ?>code; ?>rate; ?>
21 |
22 | -------------------------------------------------------------------------------- /app/views/partials/nav.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | -------------------------------------------------------------------------------- /core/database/Connection.php: -------------------------------------------------------------------------------- 1 | getMessage()); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/bootstrap.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Currency Exchange 21 | 22 | 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | php: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | depends_on: 8 | - db 9 | www: 10 | build: 11 | context: . 12 | dockerfile: Dockerfile 13 | volumes: 14 | - "./:/var/www/html" 15 | ports: 16 | - 80:80 17 | - 443:443 18 | depends_on: 19 | - db 20 | links: 21 | - db 22 | environment: 23 | - DB_HOST=db 24 | db: 25 | image: mysql:latest 26 | environment: 27 | - MYSQL_DATABASE=currency_exchange 28 | - MYSQL_USER=currency_exchange_user 29 | - MYSQL_PASSWORD=password 30 | - MYSQL_ALLOW_EMPTY_PASSWORD=1 31 | volumes: 32 | - "./db:/docker-entrypoint-initdb.d" 33 | phpmyadmin: 34 | image: phpmyadmin/phpmyadmin 35 | ports: 36 | - 8080:80 37 | environment: 38 | - PMA_HOST=db 39 | - PMA_PORT=3306 40 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | # APACHE conf (.htaccess) 2 | 3 | 4 | Options -MultiViews -Indexes 5 | 6 | 7 | RewriteEngine On 8 | 9 | # Handle Authorization Header 10 | RewriteCond %{HTTP:Authorization} . 11 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 12 | 13 | # Redirect Trailing Slashes If Not A Folder... 14 | RewriteCond %{REQUEST_FILENAME} !-d 15 | RewriteCond %{REQUEST_URI} (.+)/$ 16 | RewriteRule ^ %1 [L,R=301] 17 | 18 | # Send Requests To Front Controller... 19 | RewriteCond %{REQUEST_FILENAME} !-d 20 | RewriteCond %{REQUEST_FILENAME} !-f 21 | RewriteRule ^ index.php [L] 22 | 23 | 24 | 25 | Order allow,deny 26 | deny from all 27 | ErrorDocument 403 /404.html 28 | 29 | 30 | 31 | Order allow,deny 32 | deny from all 33 | ErrorDocument 403 /404.html 34 | 35 | 36 | ErrorDocument 404 core/error.php -------------------------------------------------------------------------------- /app/views/game.view.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Hangman 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /core/App.php: -------------------------------------------------------------------------------- 1 | 3 |
4 | ' . $alert['message'] . '
'; 10 | } 11 | ?> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | > 24 | 27 | 30 | 33 | 34 | 35 | 36 | 37 |
SourceTargetDate
25 | amount . ' ' . $record->source_currency; ?> 26 | 28 | amount_after_conversion . ' ' . $record->target_currency; ?>
29 |
31 | created_at; ?> 32 |
38 | 39 | -------------------------------------------------------------------------------- /db/exchange_history.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 5.2.1 3 | -- https://www.phpmyadmin.net/ 4 | -- 5 | -- Host: db:3306 6 | -- Generation Time: Jun 10, 2023 at 09:23 AM 7 | -- Server version: 8.0.32 8 | -- PHP Version: 8.1.17 9 | 10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 11 | START TRANSACTION; 12 | SET time_zone = "+00:00"; 13 | 14 | 15 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 16 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 17 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 18 | /*!40101 SET NAMES utf8mb4 */; 19 | 20 | -- 21 | -- Database: `currency_exchange` 22 | -- 23 | 24 | -- -------------------------------------------------------- 25 | 26 | -- 27 | -- Table structure for table `exchange_history` 28 | -- 29 | 30 | CREATE TABLE `exchange_history` ( 31 | `id` int NOT NULL, 32 | `amount` float NOT NULL, 33 | `source_currency` varchar(255) NOT NULL, 34 | `target_currency` varchar(255) NOT NULL, 35 | `amount_after_conversion` float NOT NULL, 36 | `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 37 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 38 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 39 | 40 | -- 41 | -- Indexes for dumped tables 42 | -- 43 | 44 | -- 45 | -- Indexes for table `exchange_history` 46 | -- 47 | ALTER TABLE `exchange_history` 48 | ADD PRIMARY KEY (`id`); 49 | 50 | -- 51 | -- AUTO_INCREMENT for dumped tables 52 | -- 53 | 54 | -- 55 | -- AUTO_INCREMENT for table `exchange_history` 56 | -- 57 | ALTER TABLE `exchange_history` 58 | MODIFY `id` int NOT NULL AUTO_INCREMENT; 59 | COMMIT; 60 | 61 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 62 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 63 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 64 | -------------------------------------------------------------------------------- /app/providers/NbpProvider.php: -------------------------------------------------------------------------------- 1 | curlHandle = curl_init(); 26 | $this->tableType = $tableType; 27 | $this->url = str_replace(':tableType', $this->tableType, App::get('config')['nbp_api']['exchange_rates_url']); 28 | } 29 | 30 | /** 31 | * Get the response from the API. 32 | * 33 | * @return array|false Returns the decoded JSON response or false on error. 34 | */ 35 | public function getResponse() 36 | { 37 | curl_setopt($this->curlHandle, CURLOPT_URL, $this->url); 38 | curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true); 39 | 40 | $response = curl_exec($this->curlHandle); 41 | 42 | if ($error = curl_error($this->curlHandle)) { 43 | throw new Exception("Connection error: $error"); 44 | } 45 | 46 | curl_close($this->curlHandle); 47 | 48 | return json_decode($response, true); 49 | } 50 | 51 | /** 52 | * Get the exchange rates from the decoded API response. 53 | * 54 | * @param array $decodedData The decoded JSON response. 55 | * @return array The exchange rates. 56 | */ 57 | public function getRates($decodedData) 58 | { 59 | return $decodedData[0]['rates']; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/views/exchange.view.php: -------------------------------------------------------------------------------- 1 | 2 | ' . $alert['message'] . ''; 8 | } 9 | ?> 10 |
11 |
12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 | 27 |
28 | 29 |
30 | 31 | 39 |
40 | 41 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /db/exchange_rates.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 5.2.1 3 | -- https://www.phpmyadmin.net/ 4 | -- 5 | -- Host: db:3306 6 | -- Generation Time: Jun 10, 2023 at 10:02 AM 7 | -- Server version: 8.0.32 8 | -- PHP Version: 8.1.17 9 | 10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 11 | START TRANSACTION; 12 | SET time_zone = "+00:00"; 13 | 14 | 15 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 16 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 17 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 18 | /*!40101 SET NAMES utf8mb4 */; 19 | 20 | -- 21 | -- Database: `currency_exchange` 22 | -- 23 | 24 | -- -------------------------------------------------------- 25 | 26 | -- 27 | -- Table structure for table `exchange_rates` 28 | -- 29 | 30 | CREATE TABLE `exchange_rates` ( 31 | `id` int NOT NULL, 32 | `name` varchar(255) NOT NULL, 33 | `code` varchar(255) NOT NULL, 34 | `rate` float NOT NULL, 35 | `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, 36 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP 37 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 38 | 39 | -- 40 | -- Dumping data for table `exchange_rates` 41 | -- 42 | 43 | INSERT INTO `exchange_rates` (`id`, `name`, `code`, `rate`, `updated_at`, `created_at`) VALUES 44 | (1, 'złoty (Polska)', 'PLN', 1, '2023-06-10 10:02:55', '2023-06-10 10:02:55'); 45 | 46 | -- 47 | -- Indexes for dumped tables 48 | -- 49 | 50 | -- 51 | -- Indexes for table `exchange_rates` 52 | -- 53 | ALTER TABLE `exchange_rates` 54 | ADD PRIMARY KEY (`id`); 55 | 56 | -- 57 | -- AUTO_INCREMENT for dumped tables 58 | -- 59 | 60 | -- 61 | -- AUTO_INCREMENT for table `exchange_rates` 62 | -- 63 | ALTER TABLE `exchange_rates` 64 | MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; 65 | COMMIT; 66 | 67 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 68 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 69 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 70 | -------------------------------------------------------------------------------- /core/Router.php: -------------------------------------------------------------------------------- 1 | [], 12 | 'POST' => [], 13 | ]; 14 | 15 | /** 16 | * Load routes from a file and create a new Router instance. 17 | * 18 | * @param string $file The path to the file containing the routes. 19 | * @return Router The created Router instance. 20 | */ 21 | public static function load($file) 22 | { 23 | $router = new static; 24 | 25 | require $file; 26 | 27 | return $router; 28 | } 29 | 30 | /** 31 | * Register a GET route. 32 | * 33 | * @param string $uri The URI pattern to match. 34 | * @param string $controller The controller and action to be called. 35 | * @return void 36 | */ 37 | public function get($uri, $controller) 38 | { 39 | $this->routes['GET'][$uri] = $controller; 40 | } 41 | 42 | /** 43 | * Register a POST route. 44 | * 45 | * @param string $uri The URI pattern to match. 46 | * @param string $controller The controller and action to be called. 47 | * @return void 48 | */ 49 | public function post($uri, $controller) 50 | { 51 | $this->routes['POST'][$uri] = $controller; 52 | } 53 | 54 | /** 55 | * Dispatch the request to the appropriate controller and action. 56 | * 57 | * @param string $uri The current URI. 58 | * @param string $requestType The HTTP request method (GET, POST, etc.). 59 | * @return mixed The result of the called controller action. 60 | */ 61 | public function direct($uri, $requestType) 62 | { 63 | if (array_key_exists($uri, $this->routes[$requestType])) { 64 | 65 | return $this->callAction( 66 | ...explode('@', $this->routes[$requestType][$uri]) 67 | ); 68 | } 69 | 70 | http_response_code(404); 71 | include('error.php'); 72 | die(); 73 | } 74 | 75 | /** 76 | * Call the specified controller action. 77 | * 78 | * @param string $controller The fully qualified controller class name. 79 | * @param string $action The action method to call. 80 | * @return mixed The result of the called action. 81 | */ 82 | protected function callAction($controller, $action) 83 | { 84 | $controller = "App\\Controllers\\{$controller}"; 85 | 86 | $controller = new $controller; 87 | 88 | if (!method_exists($controller, $action)) { 89 | http_response_code(404); 90 | include('error.php'); 91 | die(); 92 | } 93 | 94 | return $controller->$action(); 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | header { 2 | background: #3b5998; 3 | color: #fff; 4 | padding: 2em; 5 | text-align: center; 6 | } 7 | 8 | body { 9 | background: #f7f7f7; 10 | color: #333; 11 | text-align: center; 12 | } 13 | 14 | nav { 15 | text-align: center; 16 | word-spacing: 30px; 17 | background-color: #f7f7f7; 18 | padding-bottom: 35px; 19 | padding-top: 35px; 20 | } 21 | 22 | ul { 23 | list-style-type: none; 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | .nav-li { 29 | display: inline-block; 30 | margin-right: 10px; 31 | } 32 | 33 | .nav-li a { 34 | text-decoration: none; 35 | color: #333; 36 | padding: 5px; 37 | } 38 | 39 | .nav-li a:hover { 40 | background-color: #ddd; 41 | border-radius: 5px; 42 | } 43 | 44 | .nav-li a.active { 45 | color: #5ca7de; 46 | } 47 | 48 | #currencies-table, 49 | #exchange-history-table { 50 | width: 100%; 51 | border-collapse: collapse; 52 | } 53 | 54 | #currencies-table th, 55 | #exchange-history-table th, 56 | #currencies-table td, 57 | #exchange-history-table td { 58 | padding: 8px; 59 | text-align: left; 60 | border-bottom: 1px solid #ccc; 61 | } 62 | 63 | #currencies-table th, 64 | #exchange-history-table th { 65 | background-color: #5ca7de; 66 | font-weight: bold; 67 | } 68 | 69 | #currencies-table tr:nth-child(even), 70 | #exchange-history-table tr:nth-child(even) { 71 | background-color: #e9ebee; 72 | } 73 | 74 | #exchange-history-table tr:first-child.first-row { 75 | background-color: #d4edda; 76 | animation: fadeOutBackground 10s ease forwards; 77 | } 78 | 79 | .dataTables_wrapper .dataTables_paginate { 80 | float: none !important; 81 | text-align: unset !important; 82 | padding-top: 2.25em !important; 83 | } 84 | 85 | .form-container { 86 | max-width: 500px; 87 | } 88 | 89 | .table-container { 90 | margin-left: 10%; 91 | margin-right: 10%; 92 | } 93 | 94 | .error-page-body { 95 | background-color: #f7f7f7; 96 | } 97 | 98 | .error-page-container { 99 | text-align: center; 100 | vertical-align: center; 101 | top: 50px; 102 | margin-top: 10%; 103 | font-family: cursive; 104 | color: #757a79; 105 | } 106 | 107 | #footer { 108 | position: fixed; 109 | bottom: 0; 110 | width: 100%; 111 | height: 60px; 112 | background: #6cf; 113 | } 114 | 115 | .alert { 116 | padding: 10px; 117 | margin-bottom: 10px; 118 | opacity: 1; 119 | animation: fadeOut 10s ease forwards; 120 | margin-left: 20%; 121 | margin-right: 20%; 122 | } 123 | 124 | .alert-success { 125 | background-color: #d4edda; 126 | color: #155724; 127 | border-color: #c3e6cb; 128 | } 129 | 130 | .alert-error { 131 | background-color: #f8d7da; 132 | color: #721c24; 133 | border-color: #f5c6cb; 134 | } 135 | 136 | @keyframes fadeOut { 137 | 0% { 138 | opacity: 1; 139 | } 140 | 141 | 100% { 142 | opacity: 0; 143 | } 144 | } 145 | 146 | @keyframes fadeOutBackground { 147 | 0% { 148 | background-color: #d4edda; 149 | } 150 | 151 | 100% { 152 | background-color: #e9ebee; 153 | } 154 | } -------------------------------------------------------------------------------- /app/controllers/CurrencyController.php: -------------------------------------------------------------------------------- 1 | currencyService = new CurrencyService; 21 | } 22 | 23 | /** 24 | * Home Page Action 25 | * 26 | * This function is responsible for handling the home page request. 27 | * 28 | * @return string The rendered view for the home page. 29 | */ 30 | public function home() 31 | { 32 | return view('index'); 33 | } 34 | 35 | /** 36 | * Currencies Page Action 37 | * 38 | * This function is responsible for handling the currencies page request. 39 | * It updates the currencies and retrieves all the currencies from the CurrencyService, passing them to the view. 40 | * 41 | * @return string The rendered view for the currencies page. 42 | */ 43 | public function currencies() 44 | { 45 | $currencies = $this->currencyService->getAllCurrencies(); 46 | 47 | return view('currencies', compact('currencies')); 48 | } 49 | 50 | /** 51 | * Exchange Page Action 52 | * 53 | * This function is responsible for handling the exchange page request. 54 | * It retrieves all the codes and values from the CurrencyService and passes them to the view. 55 | * 56 | * @param bool $validationRedirect Determines if there is a validation redirect. 57 | * @return string The rendered view for the exchange page. 58 | */ 59 | public function exchange($validationRedirect = false) 60 | { 61 | $codesValues = $this->currencyService->getAllCodes(); 62 | 63 | return view('exchange', [ 64 | 'codesValues' => $codesValues, 65 | 'validationRedirect' => $validationRedirect 66 | ]); 67 | } 68 | 69 | /** 70 | * Calculate Exchange 71 | * 72 | * This function is responsible for calculating exchanges. 73 | * It receives the exchange data, validates it, performs the exchange, and redirects to the history page. 74 | * 75 | * @return string The rendered view for the history page. 76 | */ 77 | public function calculateExchange() 78 | { 79 | $data = [ 80 | 'amount' => $_POST['amount'], 81 | 'source_currency_id' => $_POST['source_currency_id'], 82 | 'target_currency_id' => $_POST['target_currency_id'], 83 | ]; 84 | 85 | $validation = $this->currencyService->validateData($data); 86 | 87 | if ($validation['is_valid']) { 88 | $_SESSION['alert'] = [ 89 | 'type' => 'success', 90 | 'message' => $validation['message'], 91 | ]; 92 | 93 | $this->currencyService->exchange($data); 94 | 95 | return $this->history(true); 96 | } else { 97 | $_SESSION['alert'] = [ 98 | 'type' => 'error', 99 | 'message' => $validation['message'], 100 | ]; 101 | 102 | return $this->exchange(true); 103 | } 104 | } 105 | 106 | /** 107 | * Exchange History 108 | * 109 | * This function is responsible for retrieving the exchange history records. 110 | * 111 | * @param bool $exchangeRedirect Determines if there is an exchange redirect. 112 | * @return string The rendered view for the history page. 113 | */ 114 | public function history($exchangeRedirect = false) 115 | { 116 | $historyRecords = $this->currencyService->getHistory(); 117 | 118 | return view('history', [ 119 | 'historyRecords' => $historyRecords, 120 | 'exchangeRedirect' => $exchangeRedirect 121 | ]); 122 | } 123 | 124 | public function game() 125 | { 126 | return view('game'); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /public/js/hangman.js: -------------------------------------------------------------------------------- 1 | //Items and categories and variables 2 | 3 | let items = [ 4 | { word: "Better late than never", category: "Proverb" }, 5 | { word: "Lubie placki", category: "Phrase" }, 6 | { word: "Hello world", category: "Phrase" }, 7 | { word: "Type Script", category: "Programming Language" }, 8 | { word: "Java Script", category: "Programming Language" }, 9 | { word: "Kotlin", category: "Programming Language" }, 10 | { word: "Minecraft", category: "Game" }, 11 | { word: "Iphone", category: "Technology" }, 12 | { word: "Apple", category: "Fruit" } 13 | ]; 14 | 15 | let words = items[Math.floor(Math.random()*items.length)]; 16 | let category = words.category; 17 | 18 | words = words.word.toUpperCase(); 19 | 20 | let letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; 21 | 22 | let stringLength = words.length; 23 | 24 | let hiddenWords = ""; 25 | 26 | let countOfFails = 0; 27 | 28 | //Sounds 29 | 30 | let yesAudio = new Audio("public/sounds/yes.wav"); 31 | let noAudio = new Audio("public/sounds/no.wav"); 32 | 33 | for (let i = 0; i < stringLength; i++) { 34 | if (words.charAt(i) === " ") { 35 | hiddenWords += " "; 36 | } else { 37 | hiddenWords += "-"; 38 | } 39 | } 40 | 41 | // Functions 42 | 43 | function printWords() { 44 | document.getElementById('board').innerHTML = hiddenWords; 45 | } 46 | 47 | function showLetters() { 48 | let alphabetContent = ""; 49 | 50 | for (let i = 0; i <= 25; i++) { 51 | let element = "letter" + i; 52 | alphabetContent += '
' + letters[i] + '
'; 53 | if ((i + 1) % 7 === 0) { 54 | alphabetContent += '
' 55 | } 56 | } 57 | document.getElementById('category').innerHTML = "Category: " + category; 58 | document.getElementById('alphabet').innerHTML = alphabetContent; 59 | 60 | printWords(); 61 | } 62 | 63 | String.prototype.setChar = function (place, char) { 64 | if (place > this.length - 1) { 65 | return this.toString(); 66 | } else { 67 | return this.substr(0, place) + char + this.substr(place + 1); 68 | } 69 | } 70 | 71 | function checkLetter(number) { 72 | let correctLetter = false; 73 | 74 | for (let i = 0; i < stringLength; i++) { 75 | if (words.charAt(i) === letters[number]) { 76 | hiddenWords = hiddenWords.setChar(i, letters[number]); 77 | correctLetter = true; 78 | } 79 | } 80 | let element = "letter" + number; 81 | 82 | if (correctLetter) { 83 | yesAudio.play(); 84 | document.getElementById(element).style.background = "#003300"; 85 | document.getElementById(element).style.color = "#00C000"; 86 | document.getElementById(element).style.border = "3px solid #00C000"; 87 | document.getElementById(element).style.cursor = "default"; 88 | 89 | printWords(); 90 | } else { 91 | noAudio.play(); 92 | document.getElementById(element).style.background = "#330000"; 93 | document.getElementById(element).style.color = "#C00000"; 94 | document.getElementById(element).style.border = "3px solid #C00000"; 95 | document.getElementById(element).style.cursor = "default"; 96 | document.getElementById(element).setAttribute("onclick", ";"); 97 | 98 | //Fails 99 | countOfFails++; 100 | let image = "public/images/image_" + countOfFails + ".png"; 101 | document.getElementById("gallows").innerHTML = ''; 102 | } 103 | 104 | //Win 105 | if(words == hiddenWords){ 106 | document.getElementById("alphabet").innerHTML = "Congratulations! You guessed the proverb: " + words + '

Play again?'; 107 | } 108 | 109 | //Lose 110 | if(countOfFails >= 9){ 111 | document.getElementById("alphabet").innerHTML = "Game Over! Correct proverb is: " + words + '

Play again?'; 112 | } 113 | 114 | 115 | } 116 | 117 | window.onload = showLetters; -------------------------------------------------------------------------------- /core/database/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | pdo = $pdo; 18 | } 19 | 20 | /** 21 | * Select All Query 22 | * 23 | * Retrieves all rows from the specified table. 24 | * 25 | * @param string $table The name of the table to select from. 26 | * @param string|null $orderBy Optional. The column and sorting direction to use for ordering the results (e.g., "column_name ASC"). 27 | * @return array An array of objects representing the selected rows. 28 | */ 29 | public function selectAll($table, $orderBy = null) 30 | { 31 | $query = "SELECT * FROM {$table}"; 32 | 33 | if ($orderBy) { 34 | $query .= " ORDER BY {$orderBy}"; 35 | } 36 | 37 | $statement = $this->pdo->prepare($query); 38 | $statement->execute(); 39 | 40 | return $statement->fetchAll(PDO::FETCH_CLASS); 41 | } 42 | 43 | /** 44 | * Insert Query 45 | * 46 | * Inserts a new row into the specified table. 47 | * 48 | * @param string $table The name of the table to insert into. 49 | * @param array $parameters An associative array of column-value pairs to be inserted. 50 | * @return void 51 | */ 52 | public function insert($table, $parameters) 53 | { 54 | $sql = sprintf( 55 | 'INSERT INTO %s (%s) VALUES (%s)', 56 | $table, 57 | implode(', ', array_keys($parameters)), 58 | ':' . implode(', :', array_keys($parameters)), 59 | ); 60 | 61 | try { 62 | $statement = $this->pdo->prepare($sql); 63 | $statement->execute($parameters); 64 | } catch (Exception $e) { 65 | die('Whoops, something wents wrong!'); 66 | } 67 | } 68 | 69 | /** 70 | * Update or Insert Query for a single unique column 71 | * 72 | * Updates an existing row based on a unique column value or inserts a new row if it doesn't exist. 73 | * 74 | * @param string $table The name of the table to update or insert into. 75 | * @param array $data An associative array of column-value pairs to be updated or inserted. 76 | * @param string $uniqueColumn The column name that makes a record unique. 77 | * @return void 78 | */ 79 | public function updateOrInsert($table, $data, $uniqueColumn) 80 | { 81 | $uniqueValue = $data[$uniqueColumn]; 82 | unset($data[$uniqueColumn]); 83 | 84 | $selectSql = "SELECT COUNT(*) as count FROM $table WHERE $uniqueColumn = :$uniqueColumn"; 85 | $selectStatement = $this->pdo->prepare($selectSql); 86 | $selectStatement->execute([$uniqueColumn => $uniqueValue]); 87 | $result = $selectStatement->fetch(PDO::FETCH_ASSOC); 88 | 89 | if ($result['count'] > 0) { 90 | $updateColumns = []; 91 | foreach ($data as $column => $value) { 92 | $updateColumns[] = $column . ' = :' . $column; 93 | } 94 | $updateColumns = implode(', ', $updateColumns); 95 | 96 | $sql = "UPDATE $table SET $updateColumns WHERE $uniqueColumn = :$uniqueColumn"; 97 | 98 | try { 99 | $updateStatement = $this->pdo->prepare($sql); 100 | $updateStatement->execute(array_merge([$uniqueColumn => $uniqueValue], $data)); 101 | } catch (Exception $e) { 102 | die('Whoops, something went wrong!'); 103 | } 104 | } else { 105 | $columns = implode(', ', array_keys($data)); 106 | $placeholders = ':' . implode(', :', array_keys($data)); 107 | 108 | $sql = "INSERT INTO $table ($uniqueColumn, $columns) VALUES (:$uniqueColumn, $placeholders)"; 109 | 110 | try { 111 | $insertStatement = $this->pdo->prepare($sql); 112 | $insertStatement->execute(array_merge([$uniqueColumn => $uniqueValue], $data)); 113 | } catch (Exception $e) { 114 | die('Whoops, something went wrong!'); 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * Select Query with WHERE clause 121 | * 122 | * Retrieves rows from the specified table based on the given condition. 123 | * 124 | * @param string $table The name of the table to select from. 125 | * @param string $condition The WHERE condition to apply. 126 | * @param array $params An array of parameters to bind to the prepared statement. 127 | * @return array An array of objects representing the selected rows. 128 | */ 129 | public function selectWhere($table, $condition, $params = array()) 130 | { 131 | $query = $this->pdo->prepare("SELECT * FROM {$table} WHERE {$condition}"); 132 | $query->execute($params); 133 | return $query->fetchAll(PDO::FETCH_CLASS); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/services/CurrencyService.php: -------------------------------------------------------------------------------- 1 | selectAll('exchange_rates'); 18 | } 19 | 20 | /** 21 | * Update the currencies in the database using the NbpProvider. 22 | * 23 | * @param string $tableType The type of table for which the exchange rates are retrieved. 24 | */ 25 | public function updateCurrencies($tableType) 26 | { 27 | $nbpProvider = new NbpProvider($tableType); 28 | 29 | $data = $nbpProvider->getResponse(); 30 | 31 | $rates = $nbpProvider->getRates($data); 32 | 33 | foreach ($rates as $rate) { 34 | App::get('database')->updateOrInsert( 35 | 'exchange_rates', 36 | [ 37 | 'name' => $rate['currency'], 38 | 'code' => $rate['code'], 39 | 'rate' => $rate['mid'], 40 | ], 41 | 'code' 42 | ); 43 | } 44 | } 45 | 46 | /** 47 | * Get all currency codes with details. 48 | * 49 | * @return array The array of currency codes with details. 50 | */ 51 | public function getAllCodes() 52 | { 53 | $currencies = $this->getAllCurrencies(); 54 | 55 | $codesArray = []; 56 | foreach ($currencies as $currency) { 57 | $codesArray[$currency->id] = $currency->code . ' - ' . $currency->name; 58 | } 59 | 60 | asort($codesArray); 61 | 62 | return $codesArray; 63 | } 64 | 65 | /** 66 | * Convert currency and save data to exchange_history table. 67 | * 68 | * @param array $data An array of exchange data. 69 | * @return float The converted amount. 70 | */ 71 | public function exchange($data) 72 | { 73 | $convertedAmount = $this->convertCurrency($data['amount'], $data['source_currency_id'], $data['target_currency_id']); 74 | 75 | $this->updateExchangeHistory($data['amount'], $convertedAmount, $data['source_currency_id'], $data['target_currency_id']); 76 | 77 | return $convertedAmount; 78 | } 79 | 80 | /** 81 | * Get the currency rate by ID. 82 | * 83 | * @param int $id The ID of the currency rate. 84 | * @return float|null The currency rate or null if not found. 85 | */ 86 | public function getCurrencyRateById($id) 87 | { 88 | $condition = "id = :id"; 89 | $params = [':id' => $id]; 90 | $currency = App::get('database')->selectWhere('exchange_rates', $condition, $params); 91 | 92 | if (!empty($currency)) { 93 | return (float) $currency[0]->rate; 94 | } 95 | 96 | return null; 97 | } 98 | 99 | /** 100 | * Get the currency code by ID. 101 | * 102 | * @param int $id The ID of the currency. 103 | * @return string|null The currency code or null if not found. 104 | */ 105 | public function getCurrencyCodeById($id) 106 | { 107 | $condition = "id = :id"; 108 | $params = [':id' => $id]; 109 | $currency = App::get('database')->selectWhere('exchange_rates', $condition, $params); 110 | 111 | if (!empty($currency)) { 112 | return $currency[0]->code; 113 | } 114 | 115 | return null; 116 | } 117 | 118 | /** 119 | * Convert currency based on given data. 120 | * 121 | * @param float $amount The amount to be converted. 122 | * @param float $sourceRateId The ID of the source currency rate. 123 | * @param float $targetRateId The ID of the target currency rate. 124 | * @return float The converted amount. 125 | */ 126 | public function convertCurrency($amount, $sourceRateId, $targetRateId) 127 | { 128 | $sourceRate = $this->getCurrencyRateById($sourceRateId); 129 | $targetRate = $this->getCurrencyRateById($targetRateId); 130 | 131 | $convertedAmount = $amount * ($sourceRate / $targetRate); 132 | 133 | return $convertedAmount; 134 | } 135 | 136 | /** 137 | * Update exchange_history table. 138 | * 139 | * @param float $amount The amount to be converted. 140 | * @param float $convertedAmount The converted amount. 141 | * @param float $sourceRateId The ID of the source currency rate. 142 | * @param float $targetRateId The ID of the target currency rate. 143 | */ 144 | public function updateExchangeHistory($amount, $convertedAmount, $sourceRateId, $targetRateId) 145 | { 146 | $sourceCode = $this->getCurrencyCodeById($sourceRateId); 147 | $targetCode = $this->getCurrencyCodeById($targetRateId); 148 | 149 | App::get('database')->insert('exchange_history', [ 150 | 'amount' => $amount, 151 | 'source_currency' => $sourceCode, 152 | 'target_currency' => $targetCode, 153 | 'amount_after_conversion' => $convertedAmount, 154 | ]); 155 | } 156 | 157 | /** 158 | * Get exchange history from the 'exchange_history' table. 159 | * 160 | * @return array The array of exchange history. 161 | */ 162 | public function getHistory() 163 | { 164 | return App::get('database')->selectAll('exchange_history', 'created_at DESC'); 165 | } 166 | 167 | /** 168 | * Validate exchange data. 169 | * 170 | * @param array $data An array of exchange data. 171 | * @return array The validation result. 172 | */ 173 | public function validateData($data) 174 | { 175 | if (!is_numeric($data['amount']) || $data['amount'] < 1 || $data['amount'] > 1000000000) { 176 | return [ 177 | 'is_valid' => false, 178 | 'message' => 'The value should be numeric and between 1-1000000000.', 179 | ]; 180 | } 181 | 182 | $sourceRate = $this->getCurrencyRateById($data['source_currency_id']); 183 | $targetRate = $this->getCurrencyRateById($data['target_currency_id']); 184 | if (is_null($sourceRate) || is_null($targetRate)) { 185 | return [ 186 | 'is_valid' => false, 187 | 'message' => 'The source currency or target currency is incorrect.', 188 | ]; 189 | } 190 | 191 | if ($data['source_currency_id'] === $data['target_currency_id']) { 192 | return [ 193 | 'is_valid' => false, 194 | 'message' => 'You cannot convert the same currencies.', 195 | ]; 196 | } 197 | 198 | return [ 199 | 'is_valid' => true, 200 | 'message' => 'The currency exchange operation was successful.', 201 | ]; 202 | } 203 | } 204 | --------------------------------------------------------------------------------