diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml index 86ca9b3..a2fb50f 100644 --- a/.docker/docker-compose.yml +++ b/.docker/docker-compose.yml @@ -11,6 +11,14 @@ services: ports: - "80:80" + node: + build: + dockerfile: node/Dockerfile + working_dir: /var/www/html + volumes: + - ../:/var/www/html + command: [ "tail", "-f", "/dev/null" ] + mysql: image: mysql:8.0.29 environment: @@ -29,11 +37,3 @@ services: - "1000:8080" depends_on: - mysql - - node: - build: - dockerfile: node/Dockerfile - working_dir: /var/www/html - volumes: - - ../:/var/www/html - command: [ "tail", "-f", "/dev/null" ] diff --git a/.docker/node/Dockerfile b/.docker/node/Dockerfile index 5155ee9..1c84547 100644 --- a/.docker/node/Dockerfile +++ b/.docker/node/Dockerfile @@ -1,3 +1 @@ FROM node:18.17.0 - -WORKDIR /var/www/html diff --git a/.docker/php/Dockerfile b/.docker/php/Dockerfile index ea20f4a..3306d0f 100644 --- a/.docker/php/Dockerfile +++ b/.docker/php/Dockerfile @@ -1,20 +1,27 @@ FROM php:8.2-apache +# Change default Apache config ENV APACHE_DOCUMENT_ROOT /var/www/html/www RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf +# Enable .htaccess RUN a2enmod rewrite -WORKDIR /var/www/html - +# For managing PHP packages COPY --from=composer /usr/bin/composer /usr/bin/composer +# Install base (from Docker) extensions RUN docker-php-ext-install \ + # For accessing databases in PHP pdo \ pdo_mysql \ mysqli +# Install extra (from Debian) extensions RUN apt-get update && apt-get -y install \ - git + # For repository actions + git \ + # For reading ZIP compressed archives (required by nextras/migrations) + zip diff --git a/Makefile b/Makefile index 6d70ab7..fd72c44 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,25 @@ +# run environment scripts +init\:env: + cd config && touch local.neon + make up + docker compose -f .docker/docker-compose.yml exec php composer install + docker compose -f .docker/docker-compose.yml exec php bin/console migration:reset + docker compose -f .docker/docker-compose.yml exec node npm install + docker compose -f .docker/docker-compose.yml exec node npm run build + +# run containers scripts up: - docker-compose -f .docker/docker-compose.yml up -d + docker compose -f .docker/docker-compose.yml up -d build: - docker-compose -f .docker/docker-compose.yml up -d --build + docker compose -f .docker/docker-compose.yml up -d --build down: - docker-compose -f .docker/docker-compose.yml down + docker compose -f .docker/docker-compose.yml down +# join container scripts php: - docker-compose -f .docker/docker-compose.yml exec php bash; + docker compose -f .docker/docker-compose.yml exec php bash; node: - docker-compose -f .docker/docker-compose.yml exec node bash; + docker compose -f .docker/docker-compose.yml exec node bash; diff --git a/README.md b/README.md index 0a2d2eb..36254b2 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,19 @@ -# Snyt +## Snyt -website for restaurant with small CMS +Website for restaurant with small CMS -> more tech-info find in project's Wiki (tab in Github repository) +### Used technology -## Start project +- `Layouts` are designed in Adobe XDesign +- `Devops` is runned by [Docker](https://www.docker.com/) & Apache virtual server +- `Frontend` is coded in HTML stylesheet [Latte](https://latte.nette.org/cs/) +- `Backend` is programmed in [PHP](https://www.php.net/) framework [Nette](https://nette.org/cs/) +- `Database` is build by MYSQL with [Adminer](https://www.adminer.org/cs/) interface & [Dibi](https://dibiphp.com/cs/) (database layer) -- start containers -``` -docker-compse up -d -``` +### Start project -- join php container -``` -docker exec php bash -``` +Run this command in Terminal & open `localhost:80` in Browser -- install dependencies ``` -composer install +make init:env ``` - -- add `local.neon` file to `config` folder -```neon -parameters: - -database: - dsn: 'mysql:host=127.0.0.1;dbname=test' - user: - password: -``` - -- run `localhost:80` in your browser \ No newline at end of file diff --git a/app/Model/BaseRepository.php b/app/Model/BaseRepository.php deleted file mode 100644 index 756de3b..0000000 --- a/app/Model/BaseRepository.php +++ /dev/null @@ -1,19 +0,0 @@ -database; - } -} diff --git a/app/Model/Factory/LoginFactory.php b/app/Model/Factory/LoginFactory.php new file mode 100644 index 0000000..cd9abd5 --- /dev/null +++ b/app/Model/Factory/LoginFactory.php @@ -0,0 +1,12 @@ +getShortName()); + + $this->tableName = strtolower($repositoryName); + } + + /** + * @return T + */ + public function find(mixed $id): Row|null + { + $result = $this->dibi->query(" + SELECT * FROM {$this->tableName} WHERE id=? + ", + $id + ); + + return $result->fetch(); + } + + /** + * @return T + */ + public function findOneBy(array $criteria, array|null $orderBy = null): Row|null + { + $query = "SELECT * FROM {$this->tableName} WHERE %and"; + + if ($orderBy) { + $query = $query . " ORDER BY %by"; + } + + $arguments = [ + $query, + $criteria + ]; + + if ($orderBy) { + $arguments[] = $orderBy; + } + + $result = $this->dibi->query(...$arguments); + + return $result->fetch(); + } + + /** + * @return array + */ + public function findAll(): array + { + $result = $this->dibi->query(" + SELECT * FROM {$this->tableName} + "); + + return $result->fetchAll(); + } + + public function getAllCount(): int + { + $result = $this->dibi->query(" + SELECT * FROM {$this->tableName} + "); + + return $result->getRowCount(); + } +} diff --git a/app/Model/Repository/EventRepository.php b/app/Model/Repository/EventRepository.php index a54c192..2d42384 100644 --- a/app/Model/Repository/EventRepository.php +++ b/app/Model/Repository/EventRepository.php @@ -4,37 +4,23 @@ namespace App\Model\Repository; -use App\Model\BaseRepository; +use App\Model\Mapper\Event; +/** + * @extends BaseRepository + */ class EventRepository extends BaseRepository { - private const TABLE = 'event'; - - public function findAll(): array - { - $result = $this->getDibiConnection() - ->select('*') - ->from(self::TABLE); - - return $result->fetchAssoc('id'); - } - - public function getAllCount(): int - { - $result = $this->getDibiConnection() - ->select('COUNT(id)') - ->from(self::TABLE); - - return $result->fetchSingle(); - } - public function findAllForPaginator(int $limit, int $offset): array { - $result = $this->getDibiConnection() - ->select('*') - ->from(self::TABLE) - ->limit($limit) - ->offset($offset); + $result = $this->dibi->query(" + SELECT * FROM {$this->tableName} + LIMIT ? + OFFSET ? + ", + $limit, + $offset + ); return $result->fetchAssoc('id'); } diff --git a/app/Model/Repository/UserRepository.php b/app/Model/Repository/UserRepository.php new file mode 100644 index 0000000..ff04c71 --- /dev/null +++ b/app/Model/Repository/UserRepository.php @@ -0,0 +1,14 @@ + + */ +class UserRepository extends BaseRepository +{ +} diff --git a/app/Model/Service/AuthenticationService.php b/app/Model/Service/AuthenticationService.php new file mode 100644 index 0000000..62bb8ea --- /dev/null +++ b/app/Model/Service/AuthenticationService.php @@ -0,0 +1,43 @@ +userRepository->findOneBy(['email' => $user]); + + if (!$user) { + throw new AuthenticationException('Uzivatel nebyl nalezen'); + } + if (!$this->passwords->verify($password, $user->password)) { + throw new AuthenticationException('Nepsravne heslo'); + } + + return new SimpleIdentity( + $user->id, + null, + [ + 'email' => $user->email + ] + ); + } +} \ No newline at end of file diff --git a/app/Model/Service/AuthorizationService.php b/app/Model/Service/AuthorizationService.php new file mode 100644 index 0000000..638332a --- /dev/null +++ b/app/Model/Service/AuthorizationService.php @@ -0,0 +1,29 @@ +netteSecurityUser->isLoggedIn(); + } + + /** + * @throws AuthenticationException + */ + public function login(string $email, string $password): void + { + $this->netteSecurityUser->login($email, $password); + } +} diff --git a/app/Module/BasePresenter.php b/app/Module/BasePresenter.php index 76cc261..406c4c4 100644 --- a/app/Module/BasePresenter.php +++ b/app/Module/BasePresenter.php @@ -4,8 +4,38 @@ namespace App\Module; +use App\Model\Factory\LoginFactory; +use App\Model\Service\AuthorizationService; +use App\Module\components\Login\LoginControl; use Nette\Application\UI\Presenter; +use Nette\Bridges\ApplicationLatte\Template; +use stdClass; -abstract class BasePresenter extends Presenter +/** + * @property-read Template|stdClass $template + */ +class BasePresenter extends Presenter { + public function __construct( + private AuthorizationService $authorizationService, + private LoginFactory $loginFactory + ) + { + parent::__construct(); + } + + protected function beforeRender(): void + { + $this->template->isLogged = $this->authorizationService->isLoggedIn(); + } + + protected function createComponentLogin(): LoginControl + { + return $this->loginFactory->create(); + } + + public function findLayoutTemplateFile(): ?string + { + return __DIR__ . '/templates/@layout.latte'; + } } diff --git a/app/Module/ModulePresenter.php b/app/Module/ModulePresenter.php deleted file mode 100644 index a0e5e1e..0000000 --- a/app/Module/ModulePresenter.php +++ /dev/null @@ -1,13 +0,0 @@ -authorizationService, + $this->loginFactory + ); } public function actionDefault(int $page = 1): void @@ -23,8 +34,23 @@ public function actionDefault(int $page = 1): void $paginator->setItemCount($eventAllCount); $paginator->setItemsPerPage(6); $paginator->setPage($page); + $this->paginator = $paginator; $this->template->events = $this->eventRepository->findAllForPaginator($paginator->getLength(), $paginator->getOffset()); $this->template->paginator = $paginator; + + $this->redrawControl("event"); + } + + public function handleChangePage(int $page = 1): void + { + if ($this->isAjax()) { + $this->paginator->setPage($page); + + $this->template->paginator = $this->paginator; + $this->template->events = $this->eventRepository->findAllForPaginator($this->paginator->getLength(), $this->paginator->getOffset()); + + $this->redrawControl("event"); + } } } diff --git a/app/Module/User/Event/templates/other/event.latte b/app/Module/User/Event/templates/other/event.latte index a94cbae..a2e54e6 100644 --- a/app/Module/User/Event/templates/other/event.latte +++ b/app/Module/User/Event/templates/other/event.latte @@ -1,5 +1,7 @@ {varType Nette\Utils\Paginator $paginator} -{varType array $events} +{varType object $iterator} +{varType array[App\Module\User\Event] $events} +{varType string $basePath} {block event}
@@ -41,12 +43,12 @@
- {var $isLastItemInRow = $iterator->counter % 3 === 0} - {var $isEventToShow = array_key_last($events) !== $key} - {if $isLastItemInRow && $isEventToShow} + {var $isLastItemInRow = $iterator->counter % 3 === 0} + {var $isEventToShow = array_key_last($events) !== $key} + {if $isLastItemInRow && $isEventToShow}
  • - {/if} + {/if} {/foreach}
  • @@ -55,7 +57,7 @@ {var $iamOnLastPage = $paginator->isLast()} {var $currentPage = $paginator->getPage()} {var $pageCount = $paginator->getPageCount()} - + @@ -66,55 +68,55 @@ {var $areMoreThanTwoPagesBeforeMe = ($pageCount - $currentPage) > 2} {var $iamNotOnLastOrOneBeforeLastPage = !$iamOnLastPage && ($currentPage !== ($pageCount - 1))} - {if !$iamOnFirstOrSecondPage} + {if !$iamOnFirstOrSecondPage}
  • - + 1
  • {/if} - {if $iamOnMoreThanThirdPage} + {if $iamOnMoreThanThirdPage}
  • ...
  • {/if} - {if !$iamOnFirstPage} + {if !$iamOnFirstPage}
  • - + {$currentPage - 1}
  • {/if} -
  • - {$currentPage} -
  • +
  • + {$currentPage} +
  • - {if !$iamOnLastPage} + {if !$iamOnLastPage}
  • - + {$currentPage + 1}
  • {/if} - {if $areMoreThanTwoPagesBeforeMe} + {if $areMoreThanTwoPagesBeforeMe}
  • ...
  • {/if} - {if $iamNotOnLastOrOneBeforeLastPage} + {if $iamNotOnLastOrOneBeforeLastPage}
  • - + {$pageCount}
  • {/if} - + diff --git a/app/Module/User/Register/RegisterPresenter.php b/app/Module/User/Register/RegisterPresenter.php new file mode 100644 index 0000000..ed1b91d --- /dev/null +++ b/app/Module/User/Register/RegisterPresenter.php @@ -0,0 +1,15 @@ +addEmail('email')->setRequired(); + $form->addPassword('password')->setRequired('test'); + $form->addSubmit('submit'); + $form->onSubmit[] = [$this, 'actionLogin']; + + return $form; + } + + public function actionLogin(Form $form): void + { + if ($this->presenter->isAjax()) { + try { + $values = $form->getValues(); + $this->authorizationService->login($values->email, $values->password); + + $this->flashMessage('LoginControl was successful'); + $this->redirect('Profile:'); + } catch (AuthenticationException $e) { + $form->addError($e->getMessage()); + $this->redrawControl("login"); + } + } + } + + public function render(): void + { + $this->template->setFile(__DIR__ . '/templates/login.latte'); + $this->template->render(); + } +} diff --git a/app/Module/components/Login/templates/login.latte b/app/Module/components/Login/templates/login.latte new file mode 100644 index 0000000..30278d7 --- /dev/null +++ b/app/Module/components/Login/templates/login.latte @@ -0,0 +1,44 @@ + diff --git a/app/Module/templates/@layout.latte b/app/Module/templates/@layout.latte index fb22d0e..80f77f1 100644 --- a/app/Module/templates/@layout.latte +++ b/app/Module/templates/@layout.latte @@ -21,8 +21,16 @@ {include footer} {block scripts} - + + + + + {/block} diff --git a/app/Module/templates/header.latte b/app/Module/templates/header.latte index e0732b1..781685a 100644 --- a/app/Module/templates/header.latte +++ b/app/Module/templates/header.latte @@ -1,5 +1,9 @@ {block header} +{varType bool $isLogged} + +{control login} +
    @@ -53,11 +57,19 @@