├── .github └── ISSUE_TEMPLATE │ ├── -проблем-с-проект.md │ ├── бъг.md │ └── желание-или-идея.md ├── README.md ├── books-js-client ├── .gitignore ├── app.js ├── index.html └── styles.css ├── books-server ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── booksserver │ │ │ ├── BooksServerApplication.java │ │ │ ├── init │ │ │ └── DBInit.java │ │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── AuthorDTO.java │ │ │ │ └── BookDTO.java │ │ │ └── entity │ │ │ │ ├── AuthorEntity.java │ │ │ │ └── BookEntity.java │ │ │ ├── repository │ │ │ ├── AuthorRepository.java │ │ │ └── BookRepository.java │ │ │ ├── service │ │ │ ├── BookService.java │ │ │ └── impl │ │ │ │ └── BookServiceImpl.java │ │ │ └── web │ │ │ └── BooksRestController.java │ └── resources │ │ └── application.yaml │ └── test │ └── java │ └── bg │ └── softuni │ └── booksserver │ └── BooksServerApplicationTests.java ├── boot-demo ├── .gitignore ├── build.gradle ├── deps.txt ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── softuni │ │ │ └── bootdemo │ │ │ ├── BootDemoApplication.java │ │ │ ├── util │ │ │ └── CLGreeter.java │ │ │ └── web │ │ │ └── HelloController.java │ └── resources │ │ ├── application.yaml │ │ └── templates │ │ └── test.html │ └── test │ └── java │ └── org │ └── softuni │ └── bootdemo │ └── BootDemoApplicationTests.java ├── cache-redis ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── cache │ │ │ └── redis │ │ │ ├── CacheRedisApplication.java │ │ │ ├── config │ │ │ └── RedisConfiguration.java │ │ │ ├── inti │ │ │ └── Init.java │ │ │ ├── model │ │ │ └── StudentDTO.java │ │ │ └── service │ │ │ └── StudentService.java │ └── resources │ │ └── application.yaml │ └── test │ └── java │ └── bg │ └── softuni │ └── cache │ └── redis │ └── CacheRedisApplicationTests.java ├── dep-inj ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── org │ │ └── softuni │ │ ├── Main.java │ │ ├── model │ │ └── User.java │ │ ├── repository │ │ ├── FileBasedUserRepository.java │ │ ├── InMemoryUserRepository.java │ │ └── UserRepository.java │ │ └── service │ │ ├── UserService.java │ │ └── UserServiceImpl.java │ └── resources │ └── users.csv ├── errors ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── errors │ │ │ ├── ErrorsApplication.java │ │ │ ├── config │ │ │ └── ErrorConfig.java │ │ │ ├── exception │ │ │ ├── ObjectNotFoundException.java │ │ │ └── ObjectNotFoundRestException.java │ │ │ └── web │ │ │ ├── GloabalNotFoundExceptionHandler.java │ │ │ ├── NotFound1Controller.java │ │ │ ├── NotFound2Controller.java │ │ │ ├── NotFound3Controller.java │ │ │ ├── TestRestController.java │ │ │ └── WhitelabelTestController.java │ └── resources │ │ ├── application.yaml │ │ └── templates │ │ ├── error │ │ └── 404.html │ │ ├── iae.html │ │ ├── notfound-global.html │ │ └── notfound1.html │ └── test │ └── java │ └── bg │ └── softuni │ └── errors │ └── ErrorsApplicationTests.java ├── html ├── .gitignore ├── home.html ├── index.html ├── simplejs.html └── test.requests.http ├── i18n ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── i18n │ │ │ ├── I18nApplication.java │ │ │ └── web │ │ │ ├── LangControllerCookies.java │ │ │ └── LangControllerSessions.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ ├── lang-view-cookie.html │ │ └── lang-view-session.html │ └── test │ └── java │ └── bg │ └── softuni │ └── i18n │ └── I18nApplicationTests.java ├── interview-questions ├── .gitignore ├── .idea │ └── .gitignore ├── advanced │ ├── 01-rest-api.md │ ├── 02-spring-security.md │ ├── 03-error-handling.md │ ├── 04-events.md │ ├── 05-testing.md │ ├── 06-deployment-monitoring.md │ ├── 07-aop.md │ └── 08-kafka.md └── fundamentals │ ├── 01-internet-explained.md │ ├── 02-http-protocol.md │ ├── 03-spring-boot.md │ ├── 04-spring-mvc.md │ ├── 06-state.md │ ├── 07-thymeleaf.md │ └── 08-spring-essentials.md ├── kafka-consumer ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── kafka │ │ │ ├── KafkaConsumerApplication.java │ │ │ ├── config │ │ │ └── KafkaConfig.java │ │ │ ├── consumer │ │ │ └── ExRatesConsumer.java │ │ │ └── model │ │ │ └── ExchangeRatesDTO.java │ └── resources │ │ └── application.yaml │ └── test │ └── java │ └── bg │ └── softuni │ └── kafka │ └── KafkaConsumerApplicationTests.java ├── kafka-producer ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── bg │ │ │ └── softuni │ │ │ └── kafka │ │ │ ├── KafkaApplication.java │ │ │ ├── config │ │ │ └── KafkaConfig.java │ │ │ ├── model │ │ │ └── ExchangeRatesDTO.java │ │ │ ├── service │ │ │ └── KafkaPublicationService.java │ │ │ └── web │ │ │ └── FakePublisherController.java │ └── resources │ │ └── application.yaml │ └── test │ └── java │ └── bg │ └── softuni │ └── kafka │ └── KafkaApplicationTests.java ├── local ├── docker-mysql-apple-m1.yaml └── prometheus.yml ├── mobilele ├── .gitignore ├── build.gradle ├── deployment │ └── Dockerfile ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample-docker-commands.md ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── softuni │ │ │ └── mobilele │ │ │ ├── MobileleApplication.java │ │ │ ├── config │ │ │ ├── AppConfig.java │ │ │ ├── MailConfiguration.java │ │ │ ├── OpenExchangeRateConfig.java │ │ │ ├── ReCaptchaConfig.java │ │ │ └── SecurityConfiguration.java │ │ │ ├── init │ │ │ └── RatesInit.java │ │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── BrandDTO.java │ │ │ │ ├── ConvertRequestDTO.java │ │ │ │ ├── CreateOfferDTO.java │ │ │ │ ├── ExchangeRatesDTO.java │ │ │ │ ├── ModelDTO.java │ │ │ │ ├── MoneyDTO.java │ │ │ │ ├── OfferDetailDTO.java │ │ │ │ ├── OfferSummaryDTO.java │ │ │ │ ├── ReCaptchaResponseDTO.java │ │ │ │ └── UserRegistrationDTO.java │ │ │ ├── entity │ │ │ │ ├── BaseEntity.java │ │ │ │ ├── BrandEntity.java │ │ │ │ ├── ExchangeRateEntity.java │ │ │ │ ├── ModelEntity.java │ │ │ │ ├── OfferEntity.java │ │ │ │ ├── UserActivationCodeEntity.java │ │ │ │ ├── UserEntity.java │ │ │ │ └── UserRoleEntity.java │ │ │ ├── enums │ │ │ │ ├── EngineEnum.java │ │ │ │ ├── ModelCategoryEnum.java │ │ │ │ ├── TransmissionEnum.java │ │ │ │ └── UserRoleEnum.java │ │ │ ├── events │ │ │ │ └── UserRegisteredEvent.java │ │ │ └── validation │ │ │ │ ├── FieldMatch.java │ │ │ │ ├── FieldMatchValidator.java │ │ │ │ ├── UniqueUserEmail.java │ │ │ │ ├── UniqueUserEmailValidator.java │ │ │ │ ├── YearNotInTheFuture.java │ │ │ │ └── YearNotInTheFutureValidator.java │ │ │ ├── repository │ │ │ ├── BrandRepository.java │ │ │ ├── ExchangeRateRepository.java │ │ │ ├── ModelRepository.java │ │ │ ├── OfferRepository.java │ │ │ ├── UserActivationCodeRepository.java │ │ │ ├── UserRepository.java │ │ │ └── UserRoleRepository.java │ │ │ ├── service │ │ │ ├── BrandService.java │ │ │ ├── CurrencyService.java │ │ │ ├── EmailService.java │ │ │ ├── MonitoringService.java │ │ │ ├── OfferService.java │ │ │ ├── ReCaptchaService.java │ │ │ ├── UserActivationService.java │ │ │ ├── UserService.java │ │ │ ├── aop │ │ │ │ ├── MonitoringAspect.java │ │ │ │ ├── Pointcuts.java │ │ │ │ └── WarnIfExecutionExceeds.java │ │ │ ├── exception │ │ │ │ └── ObjectNotFoundException.java │ │ │ ├── impl │ │ │ │ ├── BrandServiceImpl.java │ │ │ │ ├── CurrencyServiceImpl.java │ │ │ │ ├── EmailServiceImpl.java │ │ │ │ ├── MobileleUserDetailsService.java │ │ │ │ ├── MonitoringServiceImpl.java │ │ │ │ ├── OfferServiceImpl.java │ │ │ │ ├── ReCaptchaServiceImpl.java │ │ │ │ ├── UserActivationServiceImpl.java │ │ │ │ └── UserServiceImpl.java │ │ │ ├── oauth │ │ │ │ └── OAuthSuccessHandler.java │ │ │ └── schedulers │ │ │ │ └── ActivationLinkCleanupScheduler.java │ │ │ └── web │ │ │ ├── BrandController.java │ │ │ ├── CurrencyRestController.java │ │ │ ├── OfferController.java │ │ │ ├── OffersController.java │ │ │ ├── UserLoginController.java │ │ │ └── UserRegistrationController.java │ └── resources │ │ ├── application.yaml │ │ ├── data.sql │ │ ├── static │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ ├── main.css │ │ │ └── reset-css.css │ │ ├── favicon.ico │ │ ├── images │ │ │ ├── 1.jpg │ │ │ ├── broken-car.png │ │ │ ├── car-dealership-car-car-showroom.jpg │ │ │ ├── car.png │ │ │ └── user-avatar.svg │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ └── jquery-3.5.1.slim.min.js │ │ └── templates │ │ ├── auth-login.html │ │ ├── auth-register.html │ │ ├── brands.html │ │ ├── details.html │ │ ├── email │ │ └── registration-email.html │ │ ├── error │ │ ├── 404.html │ │ └── 500.html │ │ ├── fragments │ │ ├── head.html │ │ └── navbar.html │ │ ├── index.html │ │ ├── offer-add.html │ │ ├── offers.html │ │ └── update.html │ └── test │ ├── java │ └── org │ │ └── softuni │ │ └── mobilele │ │ ├── MobileleApplicationTests.java │ │ ├── service │ │ └── impl │ │ │ ├── CurrencyServiceImplTestIT.java │ │ │ └── MobileleUserDetailsServiceTest.java │ │ ├── testutils │ │ ├── DBInit.java │ │ ├── TestDataUtil.java │ │ └── UserTestDataUtil.java │ │ └── web │ │ ├── CurrencyRestControllerTestIT.java │ │ ├── OfferControllerTestIT.java │ │ └── UserRegistrationControllerTestIT.java │ └── resources │ └── application.yaml ├── mvcfn ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── softuni │ │ │ └── mvcfn │ │ │ ├── MvcfnApplication.java │ │ │ ├── config │ │ │ └── RoutesConfig.java │ │ │ └── web │ │ │ └── TestHandlers.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ └── test.html │ └── test │ └── java │ └── org │ └── softuni │ └── mvcfn │ └── MvcfnApplicationTests.java ├── plain-web-app ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── bg │ │ └── softuni │ │ └── plainwebapp │ │ ├── HelloFilter.java │ │ └── HelloServlet.java │ └── webapp │ ├── WEB-INF │ └── web.xml │ └── index.jsp ├── proxies ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ └── java │ └── bg │ └── softuni │ └── proxies │ ├── Cacheable.java │ ├── CacheableInvocationHandler.java │ ├── Main.java │ ├── StudentDTO.java │ ├── StudentService.java │ └── StudentServiceIfc.java └── thymeleaf-pure ├── .gitignore ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── org │ └── example │ └── Main.java └── resources └── test.html /.github/ISSUE_TEMPLATE/-проблем-с-проект.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: " Проблем с проект" 3 | about: Моля, използвайте при въпроси за вашите проекти 4 | title: '' 5 | labels: project-help 6 | assignees: luchob 7 | 8 | --- 9 | 10 | *Връзка към проекта:* 11 | 12 | _Поставете [връзка](https://github.com/) към проекта си тук._ 13 | 14 | *Кратко описание:* 15 | 16 | Възможно най-накратко опишете: 17 | 18 | 1. Какво очаквате да се случи 19 | 2. Какво всъщност се случва 20 | 3. Какво опитахте 21 | 22 | *Стъпки за репродуциране:* 23 | 24 | Опишете възможно най-лесните стъпки, с помощта на които може да се репродуцира проблемът. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/бъг.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Бъг 3 | about: Съобщете за бъг в нашите проекти 4 | title: '' 5 | labels: bug 6 | assignees: luchob 7 | 8 | --- 9 | 10 | **Опишете грешката** 11 | 12 | Опишете къде сме допуснали грешка. 13 | Какво очаквате да се случи, и какво се случва реално. 14 | 15 | **Стъпки за да се репродуцира** 16 | 17 | Ако описанието е недостатъчно, моля добавете тук. 18 | 19 | **Допълнителен контекст** 20 | Всякаква полезна информация - например OS, браузър и т.н. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/желание-или-идея.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Желание или идея 3 | about: Предложете нещо свежо за нашия курс 4 | title: '' 5 | labels: suggestion 6 | assignees: luchob 7 | 8 | --- 9 | 10 | Опишете накратко какво бихте искали да видите или да чуете в нашия курс. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | This repository contains exersices and examples from 4 | the lectures Spring Fundametals and Spring Advanced in 5 | SoftUni. 6 | 7 | If you have troubles please [submit an issue](https://github.com/luchob/softuni-sep-2023/issues). 8 | -------------------------------------------------------------------------------- /books-js-client/.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | .idea 3 | *.iws 4 | *.iml 5 | *.ipr 6 | -------------------------------------------------------------------------------- /books-js-client/app.js: -------------------------------------------------------------------------------- 1 | let reloadBooksButton = document.getElementById('reloadBooks'); 2 | 3 | reloadBooksButton.addEventListener('click', reloadBooks) 4 | 5 | function reloadBooks() { 6 | 7 | let booksContainer = document.getElementById('books-container'); 8 | booksContainer.innerHTML = '' 9 | 10 | fetch('http://localhost:8080/api/books') 11 | .then(response => response.json()) 12 | .then(json => json.forEach(book => { 13 | 14 | let bookRow = document.createElement('tr') 15 | 16 | let titleCol = document.createElement('td') 17 | let authorCol = document.createElement('td') 18 | let isbnCol = document.createElement('td') 19 | let actionCol= document.createElement('td') 20 | 21 | titleCol.textContent = book.title 22 | authorCol.textContent = book.author.name 23 | isbnCol.textContent = book.isbn 24 | 25 | let deleteBookBtn = document.createElement('button') 26 | deleteBookBtn.innerHTML = 'DELETE' 27 | deleteBookBtn.dataset.id = book.id 28 | deleteBookBtn.addEventListener('click', deleteBtnClicked) 29 | 30 | actionCol.appendChild(deleteBookBtn) 31 | 32 | bookRow.appendChild(titleCol) 33 | bookRow.appendChild(authorCol) 34 | bookRow.appendChild(isbnCol) 35 | bookRow.appendChild(actionCol) 36 | 37 | booksContainer.append(bookRow) 38 | })) 39 | 40 | } 41 | 42 | function deleteBtnClicked(event) { 43 | 44 | let bookId = event.target.dataset.id; 45 | let requestOptions = { 46 | method: 'DELETE' 47 | } 48 | 49 | fetch(`http://localhost:8080/api/books/${bookId}`, requestOptions) 50 | .then(_ => reloadBooks()) 51 | .catch(error => console.log('error', error)) 52 | } -------------------------------------------------------------------------------- /books-js-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Books 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
TitleAuthorIsbnAction
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /books-js-client/styles.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; 3 | font-size: 20px; 4 | } 5 | 6 | table{ 7 | border:1px solid #234465; 8 | padding: 1%; 9 | margin: 0 auto; 10 | width: 70%; 11 | border-radius: 4px; 12 | } 13 | table th{ 14 | text-decoration: underline; 15 | text-align: center; 16 | } 17 | table tbody td{ 18 | text-align: center; 19 | border-bottom:1px solid black; 20 | padding: 0.5%; 21 | } 22 | 23 | table tbody tr:nth-child(even){ 24 | background-color: #234465; 25 | color: white 26 | } 27 | 28 | table tbody tr:nth-child(odd){ 29 | color: #234465; 30 | border-bottom: 1px solid black; 31 | } 32 | 33 | table tbody tr:nth-child(odd) button{ 34 | background: #234465; 35 | color:white; 36 | } 37 | 38 | table tbody tr:nth-child(even) button{ 39 | background: white; 40 | color:#234465; 41 | } 42 | 43 | table tr td:not(:last-child):hover{ 44 | text-decoration: underline; 45 | } 46 | 47 | table button{ 48 | border: none; 49 | padding: 5px; 50 | border-radius: 5px; 51 | margin-left: 8%; 52 | } 53 | 54 | table button:hover{ 55 | text-decoration: underline; 56 | } 57 | 58 | button#reloadBooks{ 59 | margin: 0 auto; 60 | display:block; 61 | padding:1%; 62 | margin-top: 1%; 63 | margin-bottom: 1%; 64 | border-radius: 4px; 65 | } 66 | 67 | 68 | label, input{ 69 | display:block; 70 | margin: 0 auto; 71 | } 72 | 73 | form input{ 74 | margin-bottom: 1%; 75 | width: 25%; 76 | } 77 | 78 | form{ 79 | display: block; 80 | margin: 0 auto; 81 | text-align: center; 82 | } 83 | 84 | form button{ 85 | margin: 0 auto; 86 | display:block; 87 | margin-top: 1%; 88 | margin-bottom: 1%; 89 | border-radius: 4px; 90 | background-color: #234465; 91 | color:white; 92 | border: none; 93 | padding: 0.5%; 94 | } -------------------------------------------------------------------------------- /books-server/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /books-server/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.5' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | runtimeOnly 'com.mysql:mysql-connector-j' 22 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 23 | } 24 | 25 | tasks.named('test') { 26 | useJUnitPlatform() 27 | } 28 | -------------------------------------------------------------------------------- /books-server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/books-server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /books-server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /books-server/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'books-server' 2 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/BooksServerApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class BooksServerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(BooksServerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/init/DBInit.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.init; 2 | 3 | import bg.softuni.booksserver.model.entity.AuthorEntity; 4 | import bg.softuni.booksserver.model.entity.BookEntity; 5 | import bg.softuni.booksserver.repository.AuthorRepository; 6 | import bg.softuni.booksserver.repository.BookRepository; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.UUID; 10 | import org.springframework.boot.CommandLineRunner; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class DBInit implements CommandLineRunner { 15 | 16 | private final AuthorRepository authorRepository; 17 | private final BookRepository bookRepository; 18 | 19 | 20 | DBInit( 21 | AuthorRepository authorRepository, 22 | BookRepository bookRepository) { 23 | 24 | this.authorRepository = authorRepository; 25 | this.bookRepository = bookRepository; 26 | } 27 | 28 | @Override 29 | public void run(String... args) throws Exception { 30 | 31 | if (bookRepository.count() == 0 && authorRepository.count() == 0) { 32 | initJovkov(); 33 | initNikolaiHaitov(); 34 | initDimitarTalev(); 35 | initElinPelin(); 36 | initVazov(); 37 | } 38 | } 39 | 40 | private void initNikolaiHaitov() { 41 | initAuthor("Николай Хайтов", 42 | "Диви Разкази" 43 | ); 44 | } 45 | 46 | private void initDimitarTalev() { 47 | initAuthor("Димитър Димов", 48 | "Тютюн" 49 | ); 50 | } 51 | 52 | private void initElinPelin() { 53 | initAuthor("Елин Пелин", 54 | "Пижо и Пендо", 55 | "Ян Бибиян на луната", 56 | "Под манастирската лоза" 57 | ); 58 | } 59 | 60 | private void initVazov() { 61 | initAuthor("Иван Вазов", 62 | "Пряпорец и Гусла", 63 | "Под Игото", 64 | "Тъгите на България" 65 | ); 66 | } 67 | 68 | private void initJovkov() { 69 | 70 | initAuthor("Йордан Йовков", 71 | "Старопланински легенди", 72 | "Чифликът край границата"); 73 | } 74 | 75 | private void initAuthor(String authorName, String... books) { 76 | AuthorEntity author = new AuthorEntity(); 77 | author.setName(authorName); 78 | author = authorRepository.save(author); 79 | 80 | List allBooks = new ArrayList<>(); 81 | 82 | for (String book: books) { 83 | BookEntity aBook = new BookEntity(); 84 | aBook.setAuthor(author); 85 | aBook.setTitle(book); 86 | aBook.setIsbn(UUID.randomUUID().toString());//random string, not real, dummy 87 | allBooks.add(aBook); 88 | } 89 | 90 | author.setBooks(allBooks); 91 | authorRepository.save(author); 92 | 93 | bookRepository.saveAll(allBooks); 94 | } 95 | } -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/model/dto/AuthorDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.model.dto; 2 | 3 | public class AuthorDTO { 4 | private String name; 5 | 6 | public String getName() { 7 | return name; 8 | } 9 | 10 | public AuthorDTO setName(String name) { 11 | this.name = name; 12 | return this; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "AuthorDTO{" + 18 | "name='" + name + '\'' + 19 | '}'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/model/dto/BookDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.model.dto; 2 | 3 | public class BookDTO { 4 | 5 | private Long id; 6 | private String title; 7 | private String isbn; 8 | private AuthorDTO author; 9 | 10 | public Long getId() { 11 | return id; 12 | } 13 | 14 | public BookDTO setId(Long id) { 15 | this.id = id; 16 | return this; 17 | } 18 | 19 | public String getTitle() { 20 | return title; 21 | } 22 | 23 | public BookDTO setTitle(String title) { 24 | this.title = title; 25 | return this; 26 | } 27 | 28 | public String getIsbn() { 29 | return isbn; 30 | } 31 | 32 | public BookDTO setIsbn(String isbn) { 33 | this.isbn = isbn; 34 | return this; 35 | } 36 | 37 | public AuthorDTO getAuthor() { 38 | return author; 39 | } 40 | 41 | public BookDTO setAuthor(AuthorDTO author) { 42 | this.author = author; 43 | return this; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "BookDTO{" + 49 | "id=" + id + 50 | ", title='" + title + '\'' + 51 | ", isbn='" + isbn + '\'' + 52 | ", author=" + author + 53 | '}'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/model/entity/AuthorEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.model.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import jakarta.persistence.OneToMany; 8 | import jakarta.persistence.Table; 9 | import java.util.List; 10 | 11 | @Entity 12 | @Table(name = "authors") 13 | public class AuthorEntity { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | private String name; 19 | 20 | @OneToMany(mappedBy = "author") 21 | private List books; 22 | 23 | public Long getId() { 24 | return id; 25 | } 26 | 27 | public AuthorEntity setId(Long id) { 28 | this.id = id; 29 | return this; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public AuthorEntity setName(String name) { 37 | this.name = name; 38 | return this; 39 | } 40 | 41 | public List getBooks() { 42 | return books; 43 | } 44 | 45 | public AuthorEntity setBooks(List books) { 46 | this.books = books; 47 | return this; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "AuthorEntity{" + 53 | "id=" + id + 54 | ", name='" + name + '\'' + 55 | ", books=" + books + 56 | '}'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/model/entity/BookEntity.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.model.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import jakarta.persistence.ManyToOne; 8 | import jakarta.persistence.Table; 9 | 10 | @Entity 11 | @Table(name = "books") 12 | public class BookEntity { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private Long id; 17 | private String title; 18 | private String isbn; 19 | 20 | @ManyToOne 21 | private AuthorEntity author; 22 | 23 | public Long getId() { 24 | return id; 25 | } 26 | 27 | public BookEntity setId(Long id) { 28 | this.id = id; 29 | return this; 30 | } 31 | 32 | public String getTitle() { 33 | return title; 34 | } 35 | 36 | public BookEntity setTitle(String title) { 37 | this.title = title; 38 | return this; 39 | } 40 | 41 | public String getIsbn() { 42 | return isbn; 43 | } 44 | 45 | public BookEntity setIsbn(String isbn) { 46 | this.isbn = isbn; 47 | return this; 48 | } 49 | 50 | public AuthorEntity getAuthor() { 51 | return author; 52 | } 53 | 54 | public BookEntity setAuthor(AuthorEntity author) { 55 | this.author = author; 56 | return this; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "BookEntity{" + 62 | "id=" + id + 63 | ", title='" + title + '\'' + 64 | ", isbn='" + isbn + '\'' + 65 | ", author=" + (author != null ? author.getName() : null) + 66 | '}'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/repository/AuthorRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.repository; 2 | 3 | 4 | import bg.softuni.booksserver.model.entity.AuthorEntity; 5 | import java.util.Optional; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | @Repository 9 | public interface AuthorRepository extends JpaRepository { 10 | Optional findByName(String name); 11 | } -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/repository/BookRepository.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.repository; 2 | 3 | import bg.softuni.booksserver.model.entity.BookEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BookRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/service/BookService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.service; 2 | 3 | import bg.softuni.booksserver.model.dto.BookDTO; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | public interface BookService { 8 | 9 | List getAllBooks(); 10 | 11 | Optional findBookById(Long id); 12 | 13 | void deleteBookByID(Long id); 14 | 15 | Long createBook(BookDTO bookDTO); 16 | } 17 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/service/impl/BookServiceImpl.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.service.impl; 2 | 3 | import bg.softuni.booksserver.model.dto.AuthorDTO; 4 | import bg.softuni.booksserver.model.dto.BookDTO; 5 | import bg.softuni.booksserver.model.entity.AuthorEntity; 6 | import bg.softuni.booksserver.model.entity.BookEntity; 7 | import bg.softuni.booksserver.repository.AuthorRepository; 8 | import bg.softuni.booksserver.repository.BookRepository; 9 | import bg.softuni.booksserver.service.BookService; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import org.springframework.stereotype.Service; 13 | 14 | @Service 15 | public class BookServiceImpl implements BookService { 16 | 17 | private final BookRepository bookRepository; 18 | private final AuthorRepository authorRepository; 19 | 20 | public BookServiceImpl(BookRepository bookRepository, 21 | AuthorRepository authorRepository) { 22 | this.bookRepository = bookRepository; 23 | this.authorRepository = authorRepository; 24 | } 25 | 26 | @Override 27 | public List getAllBooks() { 28 | return bookRepository 29 | .findAll() 30 | .stream() 31 | .map(BookServiceImpl::mapBookToDTO) 32 | .toList(); 33 | } 34 | 35 | @Override 36 | public Optional findBookById(Long id) { 37 | return bookRepository 38 | .findById(id) 39 | .map(BookServiceImpl::mapBookToDTO); 40 | } 41 | 42 | @Override 43 | public void deleteBookByID(Long id) { 44 | this.bookRepository.deleteById(id); 45 | } 46 | 47 | @Override 48 | public Long createBook(BookDTO bookDTO) { 49 | 50 | Optional authorOpt = authorRepository 51 | .findByName(bookDTO.getAuthor().getName()); 52 | 53 | BookEntity newBook = new BookEntity() 54 | .setAuthor(authorOpt.orElseGet(() -> 55 | authorRepository.save(new AuthorEntity() 56 | .setName(bookDTO.getAuthor().getName())))) 57 | .setIsbn(bookDTO.getIsbn()) 58 | .setTitle(bookDTO.getTitle()); 59 | 60 | newBook = bookRepository.save(newBook); 61 | 62 | return newBook.getId(); 63 | } 64 | 65 | private static BookDTO mapBookToDTO(BookEntity bookEntity) { 66 | 67 | AuthorDTO authorDTO = new AuthorDTO() 68 | .setName(bookEntity.getAuthor().getName()); 69 | 70 | return new BookDTO() 71 | .setId(bookEntity.getId()) 72 | .setAuthor(authorDTO) 73 | .setTitle(bookEntity.getTitle()) 74 | .setIsbn(bookEntity.getIsbn()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /books-server/src/main/java/bg/softuni/booksserver/web/BooksRestController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver.web; 2 | 3 | import bg.softuni.booksserver.model.dto.BookDTO; 4 | import bg.softuni.booksserver.service.BookService; 5 | import java.util.List; 6 | import java.util.Optional; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.CrossOrigin; 9 | import org.springframework.web.bind.annotation.DeleteMapping; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import org.springframework.web.util.UriComponentsBuilder; 17 | 18 | @CrossOrigin("*") 19 | @RestController 20 | @RequestMapping("/api/books") 21 | public class BooksRestController { 22 | 23 | private final BookService bookService; 24 | 25 | public BooksRestController(BookService bookService) { 26 | this.bookService = bookService; 27 | } 28 | 29 | @GetMapping 30 | public ResponseEntity> getAllBooks() { 31 | return ResponseEntity.ok(bookService.getAllBooks()); 32 | } 33 | 34 | @GetMapping("/{id}") 35 | public ResponseEntity findBookById(@PathVariable("id") Long id) { 36 | Optional bookDTOOptional = bookService.findBookById(id); 37 | 38 | return bookDTOOptional 39 | .map(ResponseEntity::ok) 40 | .orElse(ResponseEntity.notFound().build()); 41 | } 42 | 43 | @DeleteMapping("/{id}") 44 | public ResponseEntity deleteBookByID(@PathVariable("id") Long id) { 45 | 46 | bookService.deleteBookByID(id); 47 | 48 | return ResponseEntity 49 | .noContent() 50 | .build(); 51 | } 52 | 53 | @PostMapping 54 | public ResponseEntity createBook( 55 | @RequestBody BookDTO bookDTO, 56 | UriComponentsBuilder uriComponentsBuilder) { 57 | 58 | long newBookID = bookService.createBook(bookDTO); 59 | 60 | return ResponseEntity.created( 61 | uriComponentsBuilder.path("/api/books/{id}").build(newBookID) 62 | ).build(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /books-server/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driverClassName: com.mysql.cj.jdbc.Driver 4 | url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/books?allowPublicKeyRetrieval=true&useSSL=false&createDatabaseIfNotExist=true&serverTimezone=UTC 5 | username: ${MYSQL_USER:root} 6 | password: ${MYSQL_PASSWORD:} 7 | jpa: 8 | database-platform: org.hibernate.dialect.MySQLDialect 9 | defer-datasource-initialization: true 10 | properties: 11 | hibernate: 12 | format_sql: true 13 | hibernate: 14 | ddl-auto: update 15 | 16 | logging: 17 | level: 18 | org.hibernate.SQL: DEBUG 19 | org.hibernate.orm.jdbc.bind: TRACE 20 | 21 | -------------------------------------------------------------------------------- /books-server/src/test/java/bg/softuni/booksserver/BooksServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.booksserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BooksServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /boot-demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /boot-demo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.3' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'org.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | implementation 'com.google.code.gson:gson' 22 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 23 | } 24 | 25 | tasks.named('test') { 26 | useJUnitPlatform() 27 | } 28 | -------------------------------------------------------------------------------- /boot-demo/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/boot-demo/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /boot-demo/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /boot-demo/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'boot-demo' 2 | -------------------------------------------------------------------------------- /boot-demo/src/main/java/org/softuni/bootdemo/BootDemoApplication.java: -------------------------------------------------------------------------------- 1 | package org.softuni.bootdemo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class BootDemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(BootDemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /boot-demo/src/main/java/org/softuni/bootdemo/util/CLGreeter.java: -------------------------------------------------------------------------------- 1 | package org.softuni.bootdemo.util; 2 | 3 | import com.google.gson.Gson; 4 | import org.springframework.boot.CommandLineRunner; 5 | import org.springframework.stereotype.Component; 6 | @Component 7 | public class CLGreeter implements CommandLineRunner { 8 | 9 | private final Gson gson; 10 | 11 | public CLGreeter(Gson gson) { 12 | this.gson = gson; 13 | } 14 | 15 | @Override 16 | public void run(String... args) throws Exception { 17 | var json = gson.toJson(new Point(1, 4)); 18 | System.out.println("My point: " + json); 19 | } 20 | 21 | record Point(int x, int y) { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /boot-demo/src/main/java/org/softuni/bootdemo/web/HelloController.java: -------------------------------------------------------------------------------- 1 | package org.softuni.bootdemo.web; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | 8 | @Controller 9 | public class HelloController { 10 | 11 | 12 | private final String greeting; 13 | 14 | public HelloController(@Value("${demo.greeting.message}") String greeting) { 15 | this.greeting = greeting; 16 | } 17 | 18 | @GetMapping("/test") 19 | public String test(Model model) { 20 | model.addAttribute("greeting", greeting); 21 | return "test"; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /boot-demo/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | demo: 2 | greeting: 3 | message: Hello, softuni 4 | -------------------------------------------------------------------------------- /boot-demo/src/main/resources/templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 |

Hello, world!

8 | 9 | 10 | -------------------------------------------------------------------------------- /boot-demo/src/test/java/org/softuni/bootdemo/BootDemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.softuni.bootdemo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BootDemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /cache-redis/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /cache-redis/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.5' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'bg.softuni.cache' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-cache' 20 | implementation 'org.springframework.boot:spring-boot-starter-data-redis' 21 | implementation 'org.springframework.boot:spring-boot-starter-web' 22 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 23 | } 24 | 25 | tasks.named('test') { 26 | useJUnitPlatform() 27 | } 28 | -------------------------------------------------------------------------------- /cache-redis/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/cache-redis/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /cache-redis/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /cache-redis/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'cache-redis' 2 | -------------------------------------------------------------------------------- /cache-redis/src/main/java/bg/softuni/cache/redis/CacheRedisApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.cache.redis; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class CacheRedisApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(CacheRedisApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /cache-redis/src/main/java/bg/softuni/cache/redis/config/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.cache.redis.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonTypeInfo.As; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; 6 | import java.time.Duration; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 10 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 11 | import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; 12 | import org.springframework.data.redis.serializer.StringRedisSerializer; 13 | 14 | @Configuration 15 | public class RedisConfiguration { 16 | 17 | @Bean 18 | public RedisCacheConfiguration cacheConfiguration(ObjectMapper objectMapper) { 19 | 20 | objectMapper = objectMapper.copy(); 21 | objectMapper.activateDefaultTyping( 22 | objectMapper.getPolymorphicTypeValidator(), 23 | DefaultTyping.NON_FINAL, 24 | As.PROPERTY 25 | ); 26 | 27 | return RedisCacheConfiguration.defaultCacheConfig() 28 | .entryTtl(Duration.ofMinutes(5)) 29 | .serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer())) 30 | .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper))); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cache-redis/src/main/java/bg/softuni/cache/redis/inti/Init.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.cache.redis.inti; 2 | 3 | import bg.softuni.cache.redis.service.StudentService; 4 | import org.springframework.boot.CommandLineRunner; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class Init implements CommandLineRunner { 9 | 10 | private final StudentService studentService; 11 | 12 | public Init(StudentService studentService) { 13 | this.studentService = studentService; 14 | } 15 | 16 | @Override 17 | public void run(String... args) throws Exception { 18 | studentService.getAllStudents(); 19 | studentService.getAllStudents(); 20 | studentService.getAllStudents(); 21 | studentService.getAllStudents().forEach( 22 | System.out::println 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cache-redis/src/main/java/bg/softuni/cache/redis/model/StudentDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.cache.redis.model; 2 | 3 | public record StudentDTO(String name, int age) { 4 | } 5 | -------------------------------------------------------------------------------- /cache-redis/src/main/java/bg/softuni/cache/redis/service/StudentService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.cache.redis.service; 2 | 3 | import bg.softuni.cache.redis.model.StudentDTO; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class StudentService { 11 | 12 | @Cacheable("students") 13 | public List getAllStudents() { 14 | 15 | System.out.println("Calculating students..."); 16 | 17 | List allStudents = new ArrayList<>(); 18 | allStudents.add(new StudentDTO("Pesho", 40)); 19 | allStudents.add(new StudentDTO("Anna", 39)); 20 | 21 | return allStudents; 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /cache-redis/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | redis: 4 | host: localhost 5 | port: 6379 6 | -------------------------------------------------------------------------------- /cache-redis/src/test/java/bg/softuni/cache/redis/CacheRedisApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.cache.redis; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class CacheRedisApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /dep-inj/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea 9 | .idea/modules.xml 10 | .idea/jarRepositories.xml 11 | .idea/compiler.xml 12 | .idea/libraries/ 13 | *.iws 14 | *.iml 15 | *.ipr 16 | out/ 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store -------------------------------------------------------------------------------- /dep-inj/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'org.example' 6 | version = '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | 14 | implementation("org.springframework:spring-context:6.0.11") 15 | 16 | testImplementation platform('org.junit:junit-bom:5.9.1') 17 | testImplementation 'org.junit.jupiter:junit-jupiter' 18 | } 19 | 20 | test { 21 | useJUnitPlatform() 22 | } -------------------------------------------------------------------------------- /dep-inj/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/dep-inj/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /dep-inj/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 18 18:43:33 EEST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /dep-inj/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dep-inj' 2 | 3 | -------------------------------------------------------------------------------- /dep-inj/src/main/java/org/softuni/Main.java: -------------------------------------------------------------------------------- 1 | package org.softuni; 2 | 3 | import org.softuni.service.UserService; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 | 7 | public class Main { 8 | 9 | public static void main(String[] args) { 10 | ApplicationContext context = new AnnotationConfigApplicationContext("org.softuni"); 11 | 12 | UserService userService1 = context.getBean(UserService.class); 13 | UserService userService2 = context.getBean(UserService.class); 14 | 15 | System.out.println(userService1 == userService2); 16 | } 17 | } -------------------------------------------------------------------------------- /dep-inj/src/main/java/org/softuni/model/User.java: -------------------------------------------------------------------------------- 1 | package org.softuni.model; 2 | 3 | public record User( 4 | String firstName, 5 | String lastName, 6 | int age) { 7 | 8 | public String description() { 9 | return firstName() + " " + lastName() + ", Age: " + age; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dep-inj/src/main/java/org/softuni/repository/FileBasedUserRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.repository; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedReader; 5 | import java.io.InputStreamReader; 6 | import java.util.List; 7 | import org.softuni.model.User; 8 | import org.springframework.stereotype.Repository; 9 | 10 | @Repository("fileRepo") 11 | public class FileBasedUserRepository implements UserRepository { 12 | 13 | @Override 14 | public List findAll() { 15 | return new BufferedReader( 16 | new InputStreamReader(ClassLoader.getSystemResourceAsStream("users.csv"))) 17 | .lines() 18 | .map(this::parseUser) 19 | .toList(); 20 | } 21 | 22 | private User parseUser(String line) { 23 | String[] tokens = line.split(","); 24 | return new User(tokens[0], tokens[1], Integer.parseInt(tokens[2])); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dep-inj/src/main/java/org/softuni/repository/InMemoryUserRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.repository; 2 | 3 | import java.util.List; 4 | import org.softuni.model.User; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository("memoryRepo") 8 | public class InMemoryUserRepository implements UserRepository { 9 | @Override 10 | public List findAll() { 11 | return List.of( 12 | new User("Ivan", "Ivanov", 20), 13 | new User("Georgi", "Georgiev", 30), 14 | new User("Pesho", "Petrov", 40) 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dep-inj/src/main/java/org/softuni/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.repository; 2 | 3 | import java.util.List; 4 | import org.softuni.model.User; 5 | 6 | public interface UserRepository { 7 | List findAll(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /dep-inj/src/main/java/org/softuni/service/UserService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.service; 2 | 3 | import java.util.Optional; 4 | import org.softuni.model.User; 5 | 6 | public interface UserService { 7 | Optional findYoungestUser(); 8 | } 9 | -------------------------------------------------------------------------------- /dep-inj/src/main/java/org/softuni/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.softuni.service; 2 | 3 | import java.util.Comparator; 4 | import java.util.Optional; 5 | import org.softuni.model.User; 6 | import org.softuni.repository.InMemoryUserRepository; 7 | import org.softuni.repository.UserRepository; 8 | import org.springframework.beans.factory.BeanNameAware; 9 | import org.springframework.beans.factory.annotation.Lookup; 10 | import org.springframework.beans.factory.annotation.Qualifier; 11 | import org.springframework.context.annotation.Scope; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | public class UserServiceImpl implements UserService, 17 | BeanNameAware { 18 | private final UserRepository userRepository; 19 | 20 | public UserServiceImpl( 21 | @Qualifier("fileRepo") 22 | UserRepository userRepository) { 23 | this.userRepository = userRepository; 24 | } 25 | 26 | @Override 27 | public Optional findYoungestUser() { 28 | return userRepository.findAll().stream() 29 | .min(Comparator.comparingInt(User::age)); 30 | } 31 | 32 | @Override 33 | public void setBeanName(String name) { 34 | System.out.println("My bean is named " + name); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /dep-inj/src/main/resources/users.csv: -------------------------------------------------------------------------------- 1 | Anna,Petrova,20 2 | Nina,Bozhinova,45 -------------------------------------------------------------------------------- /errors/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /errors/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.5' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 22 | } 23 | 24 | tasks.named('test') { 25 | useJUnitPlatform() 26 | } 27 | -------------------------------------------------------------------------------- /errors/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/errors/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /errors/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /errors/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'errors' 2 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/ErrorsApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ErrorsApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ErrorsApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/config/ErrorConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.config; 2 | 3 | import java.util.Properties; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; 7 | 8 | @Configuration 9 | public class ErrorConfig { 10 | 11 | @Bean 12 | public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { 13 | 14 | SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver(); 15 | 16 | Properties properties = new Properties(); 17 | properties.setProperty(IllegalArgumentException.class.getSimpleName(), "iae"); 18 | 19 | resolver.setExceptionMappings(properties); 20 | 21 | return resolver; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/exception/ObjectNotFoundException.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(code = HttpStatus.NOT_FOUND) 7 | public class ObjectNotFoundException extends RuntimeException{ 8 | 9 | private final String id; 10 | 11 | public ObjectNotFoundException(String message, String id) { 12 | super(message); 13 | this.id = id; 14 | } 15 | 16 | public String getId() { 17 | return id; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/exception/ObjectNotFoundRestException.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ObjectNotFoundRestException extends RuntimeException{ 8 | 9 | public ObjectNotFoundRestException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/web/GloabalNotFoundExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.web; 2 | 3 | import bg.softuni.errors.exception.ObjectNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | @ControllerAdvice 11 | public class GloabalNotFoundExceptionHandler { 12 | 13 | @ResponseStatus(HttpStatus.NOT_FOUND) 14 | @ExceptionHandler(ObjectNotFoundException.class) 15 | public ModelAndView handleNotFound(ObjectNotFoundException exception) { 16 | ModelAndView modelAndView = new ModelAndView("notfound-global"); 17 | modelAndView.addObject("id", exception.getId()); 18 | return modelAndView; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/web/NotFound1Controller.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.web; 2 | 3 | import bg.softuni.errors.exception.ObjectNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.servlet.ModelAndView; 11 | 12 | @Controller 13 | public class NotFound1Controller { 14 | 15 | @GetMapping("/not-found1/{id}") 16 | public String notFound1(@PathVariable("id") String id) { 17 | throw new ObjectNotFoundException("Object with id " + id + " was not found!", id); 18 | } 19 | 20 | @ResponseStatus(HttpStatus.NOT_FOUND) 21 | @ExceptionHandler(ObjectNotFoundException.class) 22 | public ModelAndView handleNotFound(ObjectNotFoundException exception) { 23 | ModelAndView modelAndView = new ModelAndView("notfound1"); 24 | modelAndView.addObject("id", exception.getId()); 25 | return modelAndView; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/web/NotFound2Controller.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.web; 2 | 3 | import bg.softuni.errors.exception.ObjectNotFoundException; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | 8 | @Controller 9 | public class NotFound2Controller { 10 | 11 | @GetMapping("/not-found2/{id}") 12 | public String notFound1(@PathVariable("id") String id) { 13 | throw new ObjectNotFoundException("Object with id " + id + " was not found!", id); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/web/NotFound3Controller.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.web; 2 | 3 | import bg.softuni.errors.exception.ObjectNotFoundException; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | 8 | @Controller 9 | public class NotFound3Controller { 10 | 11 | @GetMapping("/not-found3/{id}") 12 | public String notFound1(@PathVariable("id") String id) { 13 | throw new ObjectNotFoundException("Object with id " + id + " was not found!", id); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/web/TestRestController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.web; 2 | 3 | import bg.softuni.errors.exception.ObjectNotFoundException; 4 | import bg.softuni.errors.exception.ObjectNotFoundRestException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | @RestController 14 | public class TestRestController { 15 | 16 | @GetMapping("/test-rest") 17 | public ResponseEntity get() { 18 | throw new ObjectNotFoundRestException("Student not found"); 19 | } 20 | 21 | @ResponseStatus(HttpStatus.NOT_FOUND) 22 | @ExceptionHandler(ObjectNotFoundRestException.class) 23 | public ErrorInfo handleNotFound(ObjectNotFoundRestException exception) { 24 | return new ErrorInfo( 25 | "/test-rest", 26 | exception 27 | ); 28 | } 29 | 30 | } 31 | 32 | record Student(String name, int age){} 33 | 34 | class ErrorInfo { 35 | public final String url; 36 | public final String ex; 37 | public ErrorInfo(String url, Exception ex) { 38 | this.url = url; 39 | this.ex = ex.getMessage(); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /errors/src/main/java/bg/softuni/errors/web/WhitelabelTestController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors.web; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | @Controller 7 | public class WhitelabelTestController { 8 | 9 | @GetMapping("/test-whitelabel") 10 | public String testWhitelaabel() { 11 | throw new NullPointerException("Something went wrong!"); 12 | } 13 | 14 | @GetMapping("/iae") 15 | public String testIae() { 16 | throw new IllegalArgumentException("iae"); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /errors/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | error: 3 | include-message: always 4 | 5 | -------------------------------------------------------------------------------- /errors/src/main/resources/templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 12 | Uppps, custom 404 page! 13 | 14 | 15 | -------------------------------------------------------------------------------- /errors/src/main/resources/templates/iae.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 12 |

Illegal argument exception page!

13 | 14 | 15 | -------------------------------------------------------------------------------- /errors/src/main/resources/templates/notfound-global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 12 |

Hello, this my GLOBAL custom object not found page!

13 | The object that was not found has id ID. 14 | 15 | 16 | -------------------------------------------------------------------------------- /errors/src/main/resources/templates/notfound1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 12 |

Hello, this my custom object not found page!

13 | The object that was not found has id ID. 14 | 15 | 16 | -------------------------------------------------------------------------------- /errors/src/test/java/bg/softuni/errors/ErrorsApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.errors; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ErrorsApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /html/.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | .idea 3 | -------------------------------------------------------------------------------- /html/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome home! 5 | 6 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | First name:
6 | Age:
7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /html/simplejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /html/test.requests.http: -------------------------------------------------------------------------------- 1 | GET http://softuni.org -------------------------------------------------------------------------------- /i18n/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /i18n/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '2.7.16' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 22 | } 23 | 24 | tasks.named('test') { 25 | useJUnitPlatform() 26 | } 27 | -------------------------------------------------------------------------------- /i18n/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/i18n/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /i18n/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /i18n/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'i18n' 2 | -------------------------------------------------------------------------------- /i18n/src/main/java/bg/softuni/i18n/I18nApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.i18n; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class I18nApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(I18nApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /i18n/src/main/java/bg/softuni/i18n/web/LangControllerCookies.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.i18n.web; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletResponse; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.CookieValue; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | 12 | @Controller 13 | public class LangControllerCookies { 14 | 15 | 16 | @GetMapping("/cookie") 17 | public String getLang( 18 | @CookieValue( 19 | value = "lang", 20 | defaultValue = "bg") String lang, 21 | Model model) { 22 | 23 | model.addAttribute("lang", lang); 24 | 25 | return "lang-view-cookie"; 26 | } 27 | 28 | @PostMapping("/cookie") 29 | public String postLang(@RequestParam("lang") String lang, 30 | HttpServletResponse response) { 31 | 32 | Cookie cookie = new Cookie("lang", lang); 33 | response.addCookie(cookie); 34 | 35 | return "redirect:/cookie"; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /i18n/src/main/java/bg/softuni/i18n/web/LangControllerSessions.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.i18n.web; 2 | 3 | import javax.servlet.http.HttpSession; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | 10 | @Controller 11 | public class LangControllerSessions { 12 | 13 | @GetMapping("/session") 14 | public String getLang( 15 | HttpSession session, 16 | Model model) { 17 | 18 | var lang = session.getAttribute("lang"); 19 | if (lang == null) { 20 | lang = "bg"; 21 | } 22 | 23 | session.setAttribute("name", "Ivan"); 24 | 25 | model.addAttribute("lang", lang); 26 | 27 | return "lang-view-session"; 28 | } 29 | 30 | @PostMapping("/session") 31 | public String postLang(@RequestParam("lang") String lang, 32 | HttpSession session) { 33 | 34 | session.setAttribute("lang", lang); 35 | 36 | return "redirect:/session"; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /i18n/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /i18n/src/main/resources/templates/lang-view-cookie.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 |
13 | 14 | 17 | 25 | 26 | 27 |
28 | 29 | -------------------------------------------------------------------------------- /i18n/src/main/resources/templates/lang-view-session.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 |
13 | 14 | 17 | 25 | 26 | 27 |
28 | 29 |
30 | ----- 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /i18n/src/test/java/bg/softuni/i18n/I18nApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.i18n; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class I18nApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /interview-questions/.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | .idea 3 | -------------------------------------------------------------------------------- /interview-questions/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /interview-questions/advanced/01-rest-api.md: -------------------------------------------------------------------------------- 1 | # Lecture REST API 2 | 3 | 1. За какво се използва REST? 4 | 2. Какви са архитектурните REST принципи? Какво означава всяка един от тях? 5 | 3. Какви са HTTP verbs които познавате и за какво служат? 6 | 4. Какво е HTTP status code? Какви съществуват и какво означават? 7 | 5. Как се създава контролер който връща REST респонс? 8 | 6. Какво представлява `@RestController` анотацията и какво я отличава от `@Controller`? 9 | 7. За какво се използва `ResponseEntity`? 10 | 8. Какво е HATEOAS? -------------------------------------------------------------------------------- /interview-questions/advanced/02-spring-security.md: -------------------------------------------------------------------------------- 1 | # Lecture Spring security 2 | 3 | 1. За какво се използва spring security? 4 | 2. Как се инсталира спринг секюрити в servlet проект? 5 | 3. Какво е Filter и за какво се използва? 6 | 4. Каква е разликата между филтър и интерсептор? 7 | 5. Как се хешират паролите на потребителите, когато ги съхраняваме в базата данни? 8 | 6. При форм логин - кои са трите бийна, които трябва да се създадат? 9 | 7. Как управляваме достъпа до различните ресурси в приложението? 10 | 8. Какво представлява `@PreAuthorize` анотацията? 11 | 9. Каква е разликата между аутентикация и авторизация? -------------------------------------------------------------------------------- /interview-questions/advanced/03-error-handling.md: -------------------------------------------------------------------------------- 1 | # Lecture - error handling 2 | 3 | 1. Какво представлява Whitelabel Error Page? 4 | 2. Как може да я заменим? 5 | 3. Как може да направим при грешка даден HTTP status code да се визуализира в определен thymeleaf template? 6 | 4. Как може да обработваме ексепшъни в рамките на един контролер? 7 | 5. Как може да обработваме глобални ексепшъни? 8 | 6. Как може да върнем специфичен JSON при грешка в REST API? -------------------------------------------------------------------------------- /interview-questions/advanced/04-events.md: -------------------------------------------------------------------------------- 1 | # Events in spring 2 | 3 | 1. Кой е главния клас който подсугурява event handling в Spring? 4 | 2. Как се прави custom event? 5 | 3. Как може да обвържем event с транзакция? 6 | 4. Какво е scheduling? 7 | 5. Как може да имплементираме Scheduler в Spring? 8 | 6. Каква е разликата между cron, fix rate и fix delay? 9 | 7. Какво представлява spring cache? 10 | 8. Какво е прокси? 11 | 9. Какви проксита създава спринг? 12 | 10. Може ли в рамките на един сървис от нетранзакционен метод да извикаме транзакционен и ще работи ли това? Защо? -------------------------------------------------------------------------------- /interview-questions/advanced/05-testing.md: -------------------------------------------------------------------------------- 1 | # Lecture - testing 2 | 3 | 1. Какво представлява един unit тест? 4 | 2. Как се създават депендънситата на класа, който се тества? 5 | 3. Какво представлява mockito? 6 | 4. Познавате ли интеграция между mockito и Junit? 7 | 5. Как се създава интегрейшън тест в Spring? 8 | 6. Какво представлява `@SpringBootTest` анотацията? 9 | 7. Какво е mockMvc? 10 | 8. Как да подаваме CSRF токен в mockMvc? 11 | 9. Как да емулираме логнат потребител в spring integration test? 12 | 10. Разликата между TDD и code-first approach? -------------------------------------------------------------------------------- /interview-questions/advanced/06-deployment-monitoring.md: -------------------------------------------------------------------------------- 1 | # Deployment, hosting, monitoring 2 | 3 | 1. Какво представлява spring actuator? 4 | 2. Как може да се конфигурира actuator - да се разрешават или забраняват определени ендпойнти? 5 | 3. На същия порт като приложението ли работи actuator? 6 | 4. Какво представлява micrometer? 7 | 5. Как може да експортваме метрики с помощта на micrometer? Познавате ли подобни технологии? -------------------------------------------------------------------------------- /interview-questions/advanced/07-aop.md: -------------------------------------------------------------------------------- 1 | # AOP 2 | 3 | 1. Какво представлява AOP? 4 | 2. Какво е Aspect, Advise, Pointcut, Join Point? 5 | 3. Какво е таргет обект и AOP Proxy? 6 | 4. Използва ли сприг цялата функционалност на AspectJ? 7 | 5. Какви проксита създава Spring и как с тях се имплементира AOP? 8 | 6. Кога се предпочита CGLIB и кога dynamic JDK proxy? 9 | 7. Може ли да се слагат AOP анотации върху final методи, когато се ползва CGLIB? -------------------------------------------------------------------------------- /interview-questions/advanced/08-kafka.md: -------------------------------------------------------------------------------- 1 | # Kafka 2 | 3 | 1. Какво представлява Kafka? 4 | 2. Как се наричат сървърите в Kafka? 5 | 3. Какво е producer и consumer? 6 | 4. Къде се пазят съобщенията в кафка? 7 | 5. Как се разделя един topic? 8 | 6. Всички партишъни на един топик се съхраняват на един или на различни брокери? 9 | 7. Какво представлява consumer group? 10 | 8. Къде се пазят offset-ите на consumer-ите? -------------------------------------------------------------------------------- /interview-questions/fundamentals/01-internet-explained.md: -------------------------------------------------------------------------------- 1 | # Internet explained 2 | 3 | 1. Каква е целта на транспортния слой? 4 | 2. Опишете разликата между TCP и UDP. 5 | 3. Каква е ролята на IP протоколът? 6 | 4. IPv4 и IPv6 - какво е новото и защо искаме IPv6? 7 | 5. IPv4 класове срещу CIDR. Какво е новото и защо се е наложило? 8 | 6. Какво е TTL в IP? 9 | 7. Какво представлява OSI моделът? Какво е TCP/IP модел? 10 | -------------------------------------------------------------------------------- /interview-questions/fundamentals/02-http-protocol.md: -------------------------------------------------------------------------------- 1 | # HTTP Protocol 2 | 3 | 1. Какви HTTP "глаголи" познавате? 4 | 2. Каква е разликата между PUT, POST и PATCH? 5 | 3. Какво е предназначението на всяка от следните групи HTTP статуси - 1xx, 2xx, 3xx, 4xx и 5xx? 6 | 4. Избройте някои по-често срещани HTTP status-и? 7 | 5. Какво е HTTP/2 и с какво подобрява HTTP/1.1? 8 | 6. Какви недостатъци има HTTP/2? 9 | 7. Какво представлява HTTP/3? -------------------------------------------------------------------------------- /interview-questions/fundamentals/03-spring-boot.md: -------------------------------------------------------------------------------- 1 | # Lecture Spring Boot 2 | 3 | 1. Какво е IoC и DI? Какво е Spring IoC контейнер? 4 | 2. Има ли разлика между Spring Boot и Spring MVC? 5 | 3. Какво е Spring Boot Starter? 6 | 4. Какво е Spring Boot Autoconfiguration? 7 | 5. Какво е Spring Boot Actuator? 8 | 6. Как може да създадем един Spring Boot проект? 9 | 7. Дайте примери за няколко Spring Boot Starter-а. -------------------------------------------------------------------------------- /interview-questions/fundamentals/04-spring-mvc.md: -------------------------------------------------------------------------------- 1 | # Lecture Spring MVC 2 | 3 | 1. Какво е MVC? 4 | 2. Какво разбирате под "фронтенд контролер"? 5 | 2. Какво е DispatcherServlet? 6 | 3. Как може да предадем атрибути към едно view? 7 | 4. Слоеве в типичен spring application. 8 | 5. Как може да реализираме обработката на Get/Post и т.н. заявки в контролер? 9 | 6. Какво ще стане ако пратим POST заявка към ендпойнт, който може да обработва само GET заявки? -------------------------------------------------------------------------------- /interview-questions/fundamentals/06-state.md: -------------------------------------------------------------------------------- 1 | # Lecture state management 2 | 3 | 1. Какво означава cookie? 4 | 2. Какво е scope на едно cookie? 5 | 3. Какво е съдържанието на едно cookie? 6 | 4. Какво представлява HTTP сесията? 7 | 5. Как се създава и унищожава една HTTP сесия? 8 | 6. С помощта на кои класове и анотации работим със сесии в Spring MVC? 9 | 7. С помощта на кои класове и анотации работим със cookies в Spring MVC? 10 | 8. Какво е 3rd party cookie? -------------------------------------------------------------------------------- /interview-questions/fundamentals/07-thymeleaf.md: -------------------------------------------------------------------------------- 1 | # Lecture Thymeleaf 2 | 3 | 1. Какво представлява Thymeleaf engine? 4 | 2. Какви алтернативи на Thymeleaf познавате? 5 | 3. Какво добавя Spring към Thymeleaf? 6 | 4. Как се реализира if-else statement в Thymealeaf? 7 | 5. Как се реализират цикли в Thymeleaf? 8 | 6. Поддържа ли Thymeleaf elvis оператор? 9 | 7. Как се изкарва повтаряем markup в Thymeleaf? 10 | -------------------------------------------------------------------------------- /interview-questions/fundamentals/08-spring-essentials.md: -------------------------------------------------------------------------------- 1 | # Lecture Spring Essentials 2 | 3 | 1. За какво се използва `@ModelAttribute` анотацията? 4 | 2. Какво е CORS? 5 | 3. Как се управлява CORS в Spring MVC? 6 | 4. Ако имаме два бийна (например. Car и Bike), които имплементират общ интерфейс (напр. Vehicle) как може да инжектираме само 1 от тях? 7 | 5. Как се чете cookie? 8 | 6. Как се създава cookie? 9 | 7. Как се трие cookie? 10 | 8. Как се чете HTTP header? -------------------------------------------------------------------------------- /kafka-consumer/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /kafka-consumer/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.5' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-web' 20 | implementation 'org.springframework.kafka:spring-kafka' 21 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 22 | testImplementation 'org.springframework.kafka:spring-kafka-test' 23 | } 24 | 25 | tasks.named('bootBuildImage') { 26 | builder = 'paketobuildpacks/builder-jammy-base:latest' 27 | } 28 | 29 | tasks.named('test') { 30 | useJUnitPlatform() 31 | } 32 | -------------------------------------------------------------------------------- /kafka-consumer/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/kafka-consumer/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /kafka-consumer/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /kafka-consumer/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'kafka-consumer' 2 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/java/bg/softuni/kafka/KafkaConsumerApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class KafkaConsumerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(KafkaConsumerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/java/bg/softuni/kafka/config/KafkaConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.config; 2 | 3 | import bg.softuni.kafka.model.ExchangeRatesDTO; 4 | import org.springframework.boot.autoconfigure.kafka.KafkaProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 8 | import org.springframework.kafka.core.ConsumerFactory; 9 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 10 | 11 | @Configuration 12 | public class KafkaConfig { 13 | 14 | @Bean 15 | public ConsumerFactory consumerFactory(KafkaProperties kafkaProperties) { 16 | return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties()); 17 | } 18 | 19 | 20 | @Bean 21 | public ConcurrentKafkaListenerContainerFactory kafkaContainerFactory( 22 | ConsumerFactory consumerFactory 23 | ) { 24 | ConcurrentKafkaListenerContainerFactory containerFactory = 25 | new ConcurrentKafkaListenerContainerFactory<>(); 26 | containerFactory.setConsumerFactory(consumerFactory); 27 | return containerFactory; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/java/bg/softuni/kafka/consumer/ExRatesConsumer.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.consumer; 2 | 3 | import bg.softuni.kafka.model.ExchangeRatesDTO; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.kafka.annotation.KafkaListener; 7 | import org.springframework.kafka.support.KafkaHeaders; 8 | import org.springframework.messaging.handler.annotation.Header; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class ExRatesConsumer { 13 | 14 | private static Logger LOGGER = LoggerFactory.getLogger(ExRatesConsumer.class); 15 | 16 | @KafkaListener( 17 | containerFactory = "kafkaContainerFactory", 18 | topics = "exchange_rates", 19 | groupId = "softuni-consumer" 20 | ) 21 | public void readMessage( 22 | @Header(KafkaHeaders.RECEIVED_KEY) String key, 23 | ExchangeRatesDTO exRate) { 24 | 25 | LOGGER.info("We have consumed ex rate with key{} and value {}.", 26 | key, 27 | exRate); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/java/bg/softuni/kafka/model/ExchangeRatesDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.model; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.LocalDateTime; 5 | import java.util.Map; 6 | 7 | //{ 8 | // "base": "USD", 9 | // "rates": { 10 | // "BGN": 1.840515, 11 | // "EUR": 0.937668 12 | // } 13 | // } 14 | public record ExchangeRatesDTO(String base, 15 | long timestamp, 16 | Map rates) { 17 | } 18 | -------------------------------------------------------------------------------- /kafka-consumer/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | kafka: 3 | bootstrap-servers: localhost:9092 4 | consumer: 5 | key-deserializer: org.apache.kafka.common.serialization.StringDeserializer 6 | value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer 7 | auto-offset-reset: earliest 8 | enable-auto-commit: false 9 | properties: 10 | spring.json.trusted.packages: '*' 11 | listener: 12 | concurrency: 2 13 | server: 14 | port: 7070 -------------------------------------------------------------------------------- /kafka-consumer/src/test/java/bg/softuni/kafka/KafkaConsumerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class KafkaConsumerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /kafka-producer/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /kafka-producer/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.5' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'bg.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-web' 20 | implementation 'org.springframework.kafka:spring-kafka' 21 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 22 | testImplementation 'org.springframework.kafka:spring-kafka-test' 23 | } 24 | 25 | tasks.named('bootBuildImage') { 26 | builder = 'paketobuildpacks/builder-jammy-base:latest' 27 | } 28 | 29 | tasks.named('test') { 30 | useJUnitPlatform() 31 | } 32 | -------------------------------------------------------------------------------- /kafka-producer/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/kafka-producer/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /kafka-producer/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /kafka-producer/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'kafka' 2 | -------------------------------------------------------------------------------- /kafka-producer/src/main/java/bg/softuni/kafka/KafkaApplication.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.kafka.support.serializer.JsonSerializer; 6 | 7 | @SpringBootApplication 8 | public class KafkaApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(KafkaApplication.class, args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /kafka-producer/src/main/java/bg/softuni/kafka/config/KafkaConfig.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.config; 2 | 3 | import org.apache.kafka.clients.admin.NewTopic; 4 | import org.springframework.boot.autoconfigure.kafka.KafkaProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.kafka.config.TopicBuilder; 8 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 9 | import org.springframework.kafka.core.KafkaTemplate; 10 | import org.springframework.kafka.core.ProducerFactory; 11 | 12 | @Configuration 13 | public class KafkaConfig { 14 | 15 | public static String EXCHANGE_RATE_TOPIC = "exchange_rates"; 16 | 17 | @Bean 18 | public NewTopic exRateTopic() { 19 | return TopicBuilder.name("exchange_rates") 20 | .partitions(2) 21 | .build(); 22 | } 23 | 24 | @Bean 25 | public ProducerFactory producerFactory(KafkaProperties kafkaProperties) { 26 | return new DefaultKafkaProducerFactory<>( 27 | kafkaProperties.buildProducerProperties() 28 | ); 29 | } 30 | 31 | @Bean 32 | public KafkaTemplate jsonKafkaTemplate( 33 | ProducerFactory producerFactory) { 34 | return new KafkaTemplate<>(producerFactory); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /kafka-producer/src/main/java/bg/softuni/kafka/model/ExchangeRatesDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.model; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.LocalDateTime; 5 | import java.util.Map; 6 | 7 | //{ 8 | // "base": "USD", 9 | // "rates": { 10 | // "BGN": 1.840515, 11 | // "EUR": 0.937668 12 | // } 13 | // } 14 | public record ExchangeRatesDTO(String base, 15 | long timestamp, 16 | Map rates) { 17 | } 18 | -------------------------------------------------------------------------------- /kafka-producer/src/main/java/bg/softuni/kafka/service/KafkaPublicationService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.service; 2 | 3 | import static bg.softuni.kafka.config.KafkaConfig.EXCHANGE_RATE_TOPIC; 4 | 5 | import bg.softuni.kafka.model.ExchangeRatesDTO; 6 | import java.util.UUID; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.kafka.core.KafkaTemplate; 10 | import org.springframework.stereotype.Service; 11 | 12 | @Service 13 | public class KafkaPublicationService { 14 | 15 | private Logger LOGGER = LoggerFactory.getLogger(KafkaPublicationService.class); 16 | private final KafkaTemplate kafkaTemplate; 17 | 18 | public KafkaPublicationService(KafkaTemplate kafkaTemplate) { 19 | this.kafkaTemplate = kafkaTemplate; 20 | } 21 | 22 | public void publishExchangeRate(ExchangeRatesDTO exchangeRatesDTO) { 23 | kafkaTemplate. 24 | send(EXCHANGE_RATE_TOPIC, UUID.randomUUID().toString(), exchangeRatesDTO). 25 | whenComplete( 26 | (res, ex) -> { 27 | if (ex == null) { 28 | LOGGER.info( 29 | "Kafka message successfully send to topic {}/partition {}/ offset {}. Key = {}.", 30 | res.getRecordMetadata().topic(), 31 | res.getRecordMetadata().partition(), 32 | res.getRecordMetadata().offset(), 33 | res.getProducerRecord().key() 34 | ); 35 | } else { 36 | LOGGER.error("Problem with the publication to kafka.", ex); 37 | } 38 | } 39 | 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kafka-producer/src/main/java/bg/softuni/kafka/web/FakePublisherController.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka.web; 2 | 3 | import bg.softuni.kafka.model.ExchangeRatesDTO; 4 | import bg.softuni.kafka.service.KafkaPublicationService; 5 | import java.math.BigDecimal; 6 | import java.time.LocalDateTime; 7 | import java.util.Map; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class FakePublisherController { 13 | 14 | private final KafkaPublicationService kafkaPublicationService; 15 | 16 | public FakePublisherController(KafkaPublicationService kafkaPublicationService) { 17 | this.kafkaPublicationService = kafkaPublicationService; 18 | } 19 | 20 | @GetMapping("/publish") 21 | public String publish() { 22 | var toPublish = new ExchangeRatesDTO( 23 | "USD", 24 | System.currentTimeMillis(), 25 | Map.of("BGN", BigDecimal.valueOf(1.840515), 26 | "EUR", BigDecimal.valueOf(0.937668) 27 | ) 28 | ); 29 | 30 | kafkaPublicationService.publishExchangeRate(toPublish); 31 | 32 | return "OK"; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /kafka-producer/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | kafka: 3 | producer: 4 | key-serializer: org.apache.kafka.common.serialization.StringSerializer 5 | value-serializer: org.springframework.kafka.support.serializer.JsonSerializer 6 | bootstrap-servers: localhost:9092 7 | -------------------------------------------------------------------------------- /kafka-producer/src/test/java/bg/softuni/kafka/KafkaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.kafka; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class KafkaApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /local/docker-mysql-apple-m1.yaml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | db: 4 | image: arm64v8/mysql:oracle 5 | ports: 6 | - "3306:3306" 7 | command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_bin', '--default-authentication-plugin=mysql_native_password'] 8 | environment: 9 | - MYSQL_ALLOW_EMPTY_PASSWORD="yes" 10 | - MYSQL_DATABASE=mobilele 11 | mailhog: 12 | image: mailhog/mailhog 13 | logging: 14 | driver: 'none' # disable saving logs 15 | ports: 16 | - "1025:1025" # smtp server 17 | - "8025:8025" # web ui 18 | # redis: 19 | # image: redis 20 | # ports: 21 | # - "6379:6379" 22 | promtool: 23 | image: prom/prometheus:v2.43.0 24 | ports: 25 | - 9090:9090 26 | volumes: 27 | - type: bind 28 | source: ./prometheus.yml 29 | target: /etc/prometheus/prometheus.yml 30 | grafana: 31 | image: grafana/grafana:8.5.22 32 | ports: 33 | - 3000:3000 34 | zookeeper: 35 | image: wurstmeister/zookeeper 36 | ports: 37 | - "2181:2181" 38 | kafka: 39 | image: wurstmeister/kafka 40 | ports: 41 | - "9092:9092" 42 | environment: 43 | KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 44 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 45 | volumes: 46 | - /var/run/docker.sock:/var/run/docker.sock -------------------------------------------------------------------------------- /local/prometheus.yml: -------------------------------------------------------------------------------- 1 | # check sample config here 2 | # https://github.com/prometheus/prometheus/blob/release-2.1/config/testdata/conf.good.yml 3 | 4 | # my global config 5 | global: 6 | scrape_interval: 5s 7 | evaluation_interval: 15s 8 | 9 | scrape_configs: 10 | - job_name: rest-service 11 | 12 | honor_labels: true 13 | # scrape_interval is defined by the configured global (15s). 14 | # scrape_timeout is defined by the global default (10s). 15 | 16 | # metrics_path defaults to '/metrics' 17 | # scheme defaults to 'http'. 18 | 19 | metrics_path: /actuator/prometheus 20 | scheme: http 21 | 22 | static_configs: 23 | - targets: ['host.docker.internal:8081'] -------------------------------------------------------------------------------- /mobilele/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /mobilele/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.3' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'org.softuni' 8 | 9 | java { 10 | sourceCompatibility = '17' 11 | } 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | 19 | implementation 'org.springframework.boot:spring-boot-starter-security' 20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 21 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 22 | implementation 'org.springframework.boot:spring-boot-starter-mail' 23 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 24 | implementation 'org.springframework.boot:spring-boot-starter-cache' 25 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 26 | implementation 'io.micrometer:micrometer-registry-prometheus' 27 | implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' 28 | implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' 29 | 30 | implementation 'org.springframework.boot:spring-boot-starter-validation' 31 | implementation 'org.springframework.boot:spring-boot-starter-web' 32 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 33 | runtimeOnly 'com.mysql:mysql-connector-j' 34 | testImplementation 'com.icegreen:greenmail:2.0.0' 35 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 36 | testImplementation 'org.springframework.security:spring-security-test' 37 | testImplementation 'org.hsqldb:hsqldb' 38 | } 39 | 40 | tasks.named('test') { 41 | useJUnitPlatform() 42 | } 43 | -------------------------------------------------------------------------------- /mobilele/deployment/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:17.0.6 2 | 3 | COPY build/libs/mobilele.jar app.jar 4 | 5 | ENTRYPOINT ["java", "-jar", "/app.jar"] -------------------------------------------------------------------------------- /mobilele/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/mobilele/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mobilele/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /mobilele/sample-docker-commands.md: -------------------------------------------------------------------------------- 1 | ``` 2 | docker network create softuni 3 | 4 | docker run \ 5 | -e MYSQL_ALLOW_EMPTY_PASSWORD='yes' \ 6 | -e MYSQL_DATABASE='mobilele' \ 7 | --hostname=db \ 8 | --network=softuni \ 9 | -p 3306:3306 \ 10 | arm64v8/mysql:oracle --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --default-authentication-plugin=mysql_native_password 11 | 12 | docker run \ 13 | -e MYSQL_HOST='db' \ 14 | -e MYSQL_USER='root' \ 15 | -e MYSQL_PASSWORD='' \ 16 | -p 8080:8080 \ 17 | --hostname=mobilele \ 18 | --network=softuni \ 19 | luchob/mobilele 20 | ``` -------------------------------------------------------------------------------- /mobilele/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mobilele' 2 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/MobileleApplication.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele; 2 | 3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 4 | import io.swagger.v3.oas.annotations.info.Info; 5 | import io.swagger.v3.oas.annotations.servers.Server; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.cache.annotation.EnableCaching; 9 | import org.springframework.scheduling.annotation.EnableScheduling; 10 | 11 | @OpenAPIDefinition( 12 | info = @Info( 13 | title = "Mobilele", 14 | version = "0.0.1", 15 | description = "The REST API of mobilele" 16 | ), 17 | servers = { 18 | @Server( 19 | url = "http://localhost:8080", 20 | description = "Local server" 21 | ) 22 | } 23 | ) 24 | @EnableScheduling 25 | @EnableCaching 26 | @SpringBootApplication 27 | public class MobileleApplication { 28 | 29 | public static void main(String[] args) { 30 | SpringApplication.run(MobileleApplication.class, args); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.config; 2 | 3 | import org.springframework.boot.web.client.RestTemplateBuilder; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; 10 | import org.springframework.web.client.RestTemplate; 11 | import org.springframework.web.reactive.function.client.WebClient; 12 | 13 | @Configuration 14 | public class AppConfig { 15 | 16 | @Bean 17 | public WebClient formPostWebClient() { 18 | return WebClient 19 | .builder() 20 | .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) 21 | .build(); 22 | } 23 | 24 | @Bean 25 | public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { 26 | return restTemplateBuilder 27 | .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/config/MailConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.config; 2 | 3 | import java.util.Properties; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.mail.javamail.JavaMailSender; 8 | import org.springframework.mail.javamail.JavaMailSenderImpl; 9 | 10 | @Configuration 11 | public class MailConfiguration { 12 | 13 | @Bean 14 | public JavaMailSender javaMailSender( 15 | @Value("${mail.host}") String host, 16 | @Value("${mail.port}") int port, 17 | @Value("${mail.username}") String username, 18 | @Value("${mail.password}") String password) { 19 | JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); 20 | 21 | javaMailSender.setHost(host); 22 | javaMailSender.setPort(port); 23 | javaMailSender.setUsername(username); 24 | javaMailSender.setPassword(password); 25 | javaMailSender.setDefaultEncoding("UTF-8"); 26 | javaMailSender.setJavaMailProperties(mailProperties()); 27 | 28 | return javaMailSender; 29 | } 30 | 31 | private Properties mailProperties() { 32 | Properties properties = new Properties(); 33 | 34 | properties.setProperty("mail.smtp.auth", "true"); 35 | properties.setProperty("mail.transport.protocol", "smtp"); 36 | 37 | return properties; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/config/OpenExchangeRateConfig.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.config; 2 | 3 | import java.util.List; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @ConfigurationProperties(prefix = "open.exchange.rates") 9 | public class OpenExchangeRateConfig { 10 | 11 | private String appId; 12 | private List symbols; 13 | private String host; 14 | private String schema; 15 | private String path; 16 | private boolean enabled; 17 | 18 | public String getPath() { 19 | return path; 20 | } 21 | 22 | public OpenExchangeRateConfig setPath(String path) { 23 | this.path = path; 24 | return this; 25 | } 26 | 27 | public String getAppId() { 28 | return appId; 29 | } 30 | 31 | public OpenExchangeRateConfig setAppId(String appId) { 32 | this.appId = appId; 33 | return this; 34 | } 35 | 36 | public List getSymbols() { 37 | return symbols; 38 | } 39 | 40 | public boolean isEnabled() { 41 | return enabled; 42 | } 43 | 44 | public OpenExchangeRateConfig setEnabled(boolean enabled) { 45 | this.enabled = enabled; 46 | return this; 47 | } 48 | 49 | public OpenExchangeRateConfig setSymbols(List symbols) { 50 | this.symbols = symbols; 51 | return this; 52 | } 53 | 54 | public String getHost() { 55 | return host; 56 | } 57 | 58 | public OpenExchangeRateConfig setHost(String host) { 59 | this.host = host; 60 | return this; 61 | } 62 | 63 | public String getSchema() { 64 | return schema; 65 | } 66 | 67 | public OpenExchangeRateConfig setSchema(String schema) { 68 | this.schema = schema; 69 | return this; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "OpenExchangeRateConfig{" + 75 | "appId='" + appId + '\'' + 76 | ", symbols=" + symbols + 77 | ", host='" + host + '\'' + 78 | ", schema='" + schema + '\'' + 79 | ", path='" + path + '\'' + 80 | ", enabled=" + enabled + 81 | '}'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/config/ReCaptchaConfig.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ConfigurationProperties(prefix = "google.recaptcha") 8 | public class ReCaptchaConfig { 9 | 10 | private String site; 11 | private String secret; 12 | 13 | public String getSite() { 14 | return site; 15 | } 16 | 17 | public ReCaptchaConfig setSite(String site) { 18 | this.site = site; 19 | return this; 20 | } 21 | 22 | public String getSecret() { 23 | return secret; 24 | } 25 | 26 | public ReCaptchaConfig setSecret(String secret) { 27 | this.secret = secret; 28 | return this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/init/RatesInit.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.init; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | import org.softuni.mobilele.config.OpenExchangeRateConfig; 7 | import org.softuni.mobilele.model.dto.ExchangeRatesDTO; 8 | import org.softuni.mobilele.service.CurrencyService; 9 | import org.springframework.boot.CommandLineRunner; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | @Component 14 | public class RatesInit implements CommandLineRunner { 15 | 16 | private final RestTemplate restTemplate; 17 | private final OpenExchangeRateConfig openExchangeRateConfig; 18 | 19 | private final CurrencyService currencyService; 20 | 21 | public RatesInit( 22 | RestTemplate restTemplate, 23 | OpenExchangeRateConfig openExchangeRateConfig, 24 | CurrencyService currencyService) { 25 | this.restTemplate = restTemplate; 26 | this.openExchangeRateConfig = openExchangeRateConfig; 27 | this.currencyService = currencyService; 28 | } 29 | 30 | @Override 31 | public void run(String... args) throws Exception { 32 | if (openExchangeRateConfig.isEnabled()) { 33 | String openExchangeRateUrlTemplate = 34 | openExchangeRateConfig.getSchema() 35 | + "://" 36 | + openExchangeRateConfig.getHost() 37 | + openExchangeRateConfig.getPath() 38 | + "?app_id={app_id}&symbols={symbols}"; 39 | 40 | Map requestParams = Map.of( 41 | "app_id", openExchangeRateConfig.getAppId(), 42 | "symbols", String.join(",", openExchangeRateConfig.getSymbols()) 43 | ); 44 | 45 | ExchangeRatesDTO exchangeRatesDTO = restTemplate 46 | .getForObject(openExchangeRateUrlTemplate, 47 | ExchangeRatesDTO.class, 48 | requestParams 49 | ); 50 | 51 | currencyService.refreshRates(exchangeRatesDTO); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/BrandDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import java.util.List; 4 | 5 | public record BrandDTO(String name, List models) { 6 | } 7 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/ConvertRequestDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import jakarta.validation.constraints.NotEmpty; 4 | import jakarta.validation.constraints.NotNull; 5 | import jakarta.validation.constraints.Positive; 6 | import java.math.BigDecimal; 7 | 8 | public record ConvertRequestDTO(@NotEmpty String target, @NotNull @Positive BigDecimal amount) { 9 | } 10 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/CreateOfferDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Positive; 7 | import jakarta.validation.constraints.Size; 8 | import org.softuni.mobilele.model.enums.EngineEnum; 9 | import org.softuni.mobilele.model.enums.TransmissionEnum; 10 | import org.softuni.mobilele.model.validation.YearNotInTheFuture; 11 | 12 | public record CreateOfferDTO(@NotEmpty @Size(min = 5, max = 512) String description, 13 | @Positive @NotNull Long modelId, @NotNull EngineEnum engine, 14 | @NotNull TransmissionEnum transmission, @NotEmpty String imageUrl, 15 | @Positive @NotNull Integer mileage, 16 | @Positive @NotNull Integer price, 17 | @YearNotInTheFuture(message = "The year should not be in the future!") 18 | @NotNull(message = "Year must be provided!") 19 | @Min(1930) 20 | Integer year) { 21 | 22 | public static CreateOfferDTO empty() { 23 | return new CreateOfferDTO(null, null, null, null, null, null, null, null); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/ExchangeRatesDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Map; 5 | 6 | //{ 7 | // "disclaimer": "Usage subject to terms: https://openexchangerates.org/terms", 8 | // "license": "https://openexchangerates.org/license", 9 | // "timestamp": 1698080400, 10 | // "base": "USD", 11 | // "rates": { 12 | // "BGN": 1.840515, 13 | // "EUR": 0.937668 14 | // } 15 | // } 16 | public record ExchangeRatesDTO(String base, Map rates) { 17 | } 18 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/ModelDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | public record ModelDTO(long id, String name) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/MoneyDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record MoneyDTO(String currency, BigDecimal amount) { 6 | } 7 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/OfferDetailDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | import org.softuni.mobilele.model.enums.EngineEnum; 5 | import org.softuni.mobilele.model.enums.TransmissionEnum; 6 | 7 | public record OfferDetailDTO( 8 | String id, 9 | String brand, 10 | String model, 11 | int year, 12 | int mileage, 13 | BigDecimal price, 14 | EngineEnum engine, 15 | TransmissionEnum transmission, 16 | String imageUrl, 17 | String seller, 18 | boolean viewerIsOwner 19 | ) { 20 | public String summary() { 21 | return brand + " " + model + ", " + year; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/OfferSummaryDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | import org.softuni.mobilele.model.enums.EngineEnum; 5 | import org.softuni.mobilele.model.enums.TransmissionEnum; 6 | 7 | public record OfferSummaryDTO( 8 | String id, 9 | String brand, 10 | String model, 11 | int year, 12 | int mileage, 13 | BigDecimal price, 14 | EngineEnum engine, 15 | TransmissionEnum transmission, 16 | String imageUrl 17 | ) { 18 | public String summary() { 19 | return brand + " " + model + ", " + year; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/ReCaptchaResponseDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 5 | import java.util.List; 6 | 7 | @JsonInclude(Include.NON_NULL) 8 | public class ReCaptchaResponseDTO { 9 | 10 | private boolean success; 11 | private List errorCodes; 12 | 13 | public boolean isSuccess() { 14 | return success; 15 | } 16 | 17 | public ReCaptchaResponseDTO setSuccess(boolean success) { 18 | this.success = success; 19 | return this; 20 | } 21 | 22 | public List getErrorCodes() { 23 | return errorCodes; 24 | } 25 | 26 | public ReCaptchaResponseDTO setErrorCodes(List errorCodes) { 27 | this.errorCodes = errorCodes; 28 | return this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/dto/UserRegistrationDTO.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.dto; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.NotNull; 6 | import org.softuni.mobilele.model.validation.FieldMatch; 7 | import org.softuni.mobilele.model.validation.UniqueUserEmail; 8 | 9 | @FieldMatch( 10 | first = "password", 11 | second = "confirmPassword", 12 | message = "Passwords should match." 13 | ) 14 | public record UserRegistrationDTO(@NotEmpty String firstName, 15 | @NotEmpty String lastName, 16 | @NotNull @Email @UniqueUserEmail String email, 17 | String password, 18 | String confirmPassword) { 19 | 20 | public String fullName() { 21 | return firstName + " " + lastName; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.GeneratedValue; 4 | import jakarta.persistence.GenerationType; 5 | import jakarta.persistence.Id; 6 | import jakarta.persistence.MappedSuperclass; 7 | 8 | @MappedSuperclass 9 | public class BaseEntity { 10 | @Id 11 | @GeneratedValue(strategy = GenerationType.IDENTITY) 12 | private Long id; 13 | 14 | public Long getId() { 15 | return id; 16 | } 17 | 18 | public BaseEntity setId(Long id) { 19 | this.id = id; 20 | return this; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/entity/BrandEntity.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.CascadeType; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.FetchType; 7 | import jakarta.persistence.NamedAttributeNode; 8 | import jakarta.persistence.NamedEntityGraph; 9 | import jakarta.persistence.OneToMany; 10 | import jakarta.persistence.Table; 11 | import java.util.List; 12 | 13 | @Entity 14 | @Table(name = "brands") 15 | @NamedEntityGraph( 16 | name = "brandWithModels", 17 | attributeNodes = @NamedAttributeNode("models") 18 | ) 19 | public class BrandEntity extends BaseEntity { 20 | @Column(unique = true, nullable = false) 21 | private String name; 22 | 23 | @OneToMany( 24 | fetch = FetchType.LAZY, 25 | cascade = CascadeType.ALL, 26 | mappedBy = "brand" 27 | ) 28 | private List models; 29 | 30 | public BrandEntity setName(String name) { 31 | this.name = name; 32 | return this; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public List getModels() { 40 | return models; 41 | } 42 | 43 | public BrandEntity setModels(List models) { 44 | this.models = models; 45 | return this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/entity/ExchangeRateEntity.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.Id; 5 | import jakarta.persistence.Table; 6 | import jakarta.validation.constraints.NotNull; 7 | import java.math.BigDecimal; 8 | 9 | @Entity 10 | @Table(name = "exchange_rates") 11 | public class ExchangeRateEntity { 12 | 13 | @Id 14 | @NotNull 15 | private String currency; 16 | 17 | @NotNull 18 | private BigDecimal rate; 19 | 20 | public String getCurrency() { 21 | return currency; 22 | } 23 | 24 | public ExchangeRateEntity setCurrency(String currency) { 25 | this.currency = currency; 26 | return this; 27 | } 28 | 29 | public BigDecimal getRate() { 30 | return rate; 31 | } 32 | 33 | public ExchangeRateEntity setRate(BigDecimal rate) { 34 | this.rate = rate; 35 | return this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/entity/ModelEntity.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.EnumType; 5 | import jakarta.persistence.Enumerated; 6 | import jakarta.persistence.ManyToOne; 7 | import jakarta.persistence.Table; 8 | import org.softuni.mobilele.model.enums.ModelCategoryEnum; 9 | 10 | @Entity 11 | @Table(name="models") 12 | public class ModelEntity extends BaseEntity { 13 | 14 | private String name; 15 | 16 | @Enumerated(EnumType.STRING) 17 | private ModelCategoryEnum category; 18 | 19 | @ManyToOne 20 | private BrandEntity brand; 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public ModelEntity setName(String name) { 27 | this.name = name; 28 | return this; 29 | } 30 | 31 | public ModelCategoryEnum getCategory() { 32 | return category; 33 | } 34 | 35 | public ModelEntity setCategory(ModelCategoryEnum category) { 36 | this.category = category; 37 | return this; 38 | } 39 | 40 | public BrandEntity getBrand() { 41 | return brand; 42 | } 43 | 44 | public ModelEntity setBrand(BrandEntity brand) { 45 | this.brand = brand; 46 | return this; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "ModelEntity{" + 52 | "name='" + name + '\'' + 53 | ", category=" + category + 54 | ", brand=" + brand + 55 | '}'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/entity/UserActivationCodeEntity.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.ManyToOne; 5 | import jakarta.persistence.Table; 6 | import java.time.Instant; 7 | import java.time.LocalDateTime; 8 | 9 | @Entity 10 | @Table(name = "user_activation_codes") 11 | public class UserActivationCodeEntity extends BaseEntity { 12 | 13 | private String activationCode; 14 | 15 | private Instant created; 16 | 17 | @ManyToOne 18 | private UserEntity user; 19 | 20 | public String getActivationCode() { 21 | return activationCode; 22 | } 23 | 24 | public UserActivationCodeEntity setActivationCode(String activationCode) { 25 | this.activationCode = activationCode; 26 | return this; 27 | } 28 | 29 | public Instant getCreated() { 30 | return created; 31 | } 32 | 33 | public UserActivationCodeEntity setCreated(Instant created) { 34 | this.created = created; 35 | return this; 36 | } 37 | 38 | public UserEntity getUser() { 39 | return user; 40 | } 41 | 42 | public UserActivationCodeEntity setUser(UserEntity user) { 43 | this.user = user; 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.FetchType; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.JoinColumn; 10 | import jakarta.persistence.JoinTable; 11 | import jakarta.persistence.ManyToMany; 12 | import jakarta.persistence.Table; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @Entity 17 | @Table(name = "users") 18 | public class UserEntity extends BaseEntity{ 19 | 20 | @Column(unique = true) 21 | private String email; 22 | 23 | @ManyToMany(fetch = FetchType.EAGER) 24 | @JoinTable( 25 | name = "users_roles", 26 | joinColumns = @JoinColumn(name = "user_id"), 27 | inverseJoinColumns = @JoinColumn(name = "role_id")) 28 | private List roles = new ArrayList<>(); 29 | 30 | private String password; 31 | 32 | private String firstName; 33 | 34 | private String lastName; 35 | 36 | private boolean active; 37 | 38 | public String getEmail() { 39 | return email; 40 | } 41 | 42 | public UserEntity setEmail(String email) { 43 | this.email = email; 44 | return this; 45 | } 46 | 47 | public String getPassword() { 48 | return password; 49 | } 50 | 51 | public UserEntity setPassword(String password) { 52 | this.password = password; 53 | return this; 54 | } 55 | 56 | public String getFirstName() { 57 | return firstName; 58 | } 59 | 60 | public UserEntity setFirstName(String firstName) { 61 | this.firstName = firstName; 62 | return this; 63 | } 64 | 65 | public String getLastName() { 66 | return lastName; 67 | } 68 | 69 | public UserEntity setLastName(String lastName) { 70 | this.lastName = lastName; 71 | return this; 72 | } 73 | 74 | public boolean isActive() { 75 | return active; 76 | } 77 | 78 | public UserEntity setActive(boolean active) { 79 | this.active = active; 80 | return this; 81 | } 82 | 83 | public List getRoles() { 84 | return roles; 85 | } 86 | 87 | public UserEntity setRoles(List roles) { 88 | this.roles = roles; 89 | return this; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/entity/UserRoleEntity.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.EnumType; 5 | import jakarta.persistence.Enumerated; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | import org.softuni.mobilele.model.enums.UserRoleEnum; 11 | 12 | @Table(name = "roles") 13 | @Entity 14 | public class UserRoleEntity { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | @Enumerated(EnumType.STRING) 21 | private UserRoleEnum role; 22 | 23 | public Long getId() { 24 | return id; 25 | } 26 | 27 | public UserRoleEntity setId(Long id) { 28 | this.id = id; 29 | return this; 30 | } 31 | 32 | public UserRoleEnum getRole() { 33 | return role; 34 | } 35 | 36 | public UserRoleEntity setRole(UserRoleEnum role) { 37 | this.role = role; 38 | return this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/enums/EngineEnum.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.enums; 2 | 3 | public enum EngineEnum { 4 | PETROL, 5 | DIESEL, 6 | ELECTRIC; 7 | } 8 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/enums/ModelCategoryEnum.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.enums; 2 | 3 | public enum ModelCategoryEnum { 4 | CAR, 5 | TRUCK, 6 | MOTORCYCLE; 7 | } 8 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/enums/TransmissionEnum.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.enums; 2 | 3 | public enum TransmissionEnum { 4 | MANUAL, 5 | AUTOMATIC; 6 | } 7 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/enums/UserRoleEnum.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.enums; 2 | 3 | public enum UserRoleEnum { 4 | ADMIN, 5 | USER; 6 | } 7 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/events/UserRegisteredEvent.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.events; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class UserRegisteredEvent extends ApplicationEvent { 6 | 7 | private final String userEmail; 8 | private final String userNames; 9 | 10 | public UserRegisteredEvent(Object source, 11 | String userEmail, 12 | String userNames) { 13 | super(source); 14 | this.userEmail = userEmail; 15 | this.userNames = userNames; 16 | } 17 | 18 | public String getUserEmail() { 19 | return userEmail; 20 | } 21 | 22 | public String getUserNames() { 23 | return userNames; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/validation/FieldMatch.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.TYPE) 12 | @Constraint(validatedBy = UniqueUserEmailValidator.class)//todo 13 | public @interface FieldMatch { 14 | 15 | String first(); 16 | 17 | String second(); 18 | 19 | String message() default "Fields should match"; 20 | 21 | Class[] groups() default {}; 22 | 23 | Class[] payload() default {}; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/validation/FieldMatchValidator.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.validation; 2 | 3 | import jakarta.validation.ConstraintValidator; 4 | import jakarta.validation.ConstraintValidatorContext; 5 | import java.util.Objects; 6 | import org.springframework.beans.BeanWrapper; 7 | import org.springframework.beans.PropertyAccessException; 8 | import org.springframework.beans.PropertyAccessorFactory; 9 | 10 | public class FieldMatchValidator implements ConstraintValidator { 11 | 12 | private String first; 13 | private String second; 14 | private String message; 15 | 16 | @Override 17 | public void initialize(FieldMatch constraintAnnotation) { 18 | this.first = constraintAnnotation.first(); 19 | this.second = constraintAnnotation.second(); 20 | this.message = constraintAnnotation.message(); 21 | } 22 | 23 | @Override 24 | public boolean isValid(Object value, ConstraintValidatorContext context) { 25 | BeanWrapper beanWrapper = PropertyAccessorFactory 26 | .forBeanPropertyAccess(value); 27 | 28 | Object firstProperty = beanWrapper.getPropertyValue(this.first); 29 | Object secondProperty = beanWrapper.getPropertyValue(this.second); 30 | 31 | boolean isValid = Objects.equals(firstProperty, secondProperty); 32 | 33 | if (!isValid) { 34 | context 35 | .buildConstraintViolationWithTemplate(message) 36 | .addPropertyNode(second) 37 | .addConstraintViolation() 38 | .disableDefaultConstraintViolation(); 39 | } 40 | 41 | return isValid; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/validation/UniqueUserEmail.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.FIELD) 12 | @Constraint(validatedBy = UniqueUserEmailValidator.class) 13 | public @interface UniqueUserEmail { 14 | 15 | String message() default "The user email should be unique"; 16 | 17 | Class[] groups() default {}; 18 | 19 | Class[] payload() default {}; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/validation/UniqueUserEmailValidator.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.validation; 2 | 3 | import jakarta.validation.ConstraintValidator; 4 | import jakarta.validation.ConstraintValidatorContext; 5 | import org.softuni.mobilele.repository.UserRepository; 6 | 7 | public class UniqueUserEmailValidator implements ConstraintValidator { 8 | 9 | private final UserRepository userRepository; 10 | 11 | public UniqueUserEmailValidator(UserRepository userRepository) { 12 | this.userRepository = userRepository; 13 | } 14 | 15 | @Override 16 | public boolean isValid(String value, ConstraintValidatorContext context) { 17 | return userRepository 18 | .findByEmail(value) 19 | .isEmpty(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/validation/YearNotInTheFuture.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.FIELD) 12 | @Constraint(validatedBy = YearNotInTheFutureValidator.class) 13 | public @interface YearNotInTheFuture { 14 | 15 | String message() default "must not be in the future"; 16 | 17 | Class[] groups() default {}; 18 | 19 | Class[] payload() default {}; 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/model/validation/YearNotInTheFutureValidator.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.model.validation; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.ConstraintValidator; 5 | import jakarta.validation.ConstraintValidatorContext; 6 | import java.time.Year; 7 | 8 | public class YearNotInTheFutureValidator implements ConstraintValidator { 9 | 10 | @Override 11 | public boolean isValid(Number value, ConstraintValidatorContext context) { 12 | 13 | if (value == null) { 14 | return true; 15 | } else { 16 | 17 | int currentYear = Year.now().getValue(); 18 | 19 | return value.intValue() <= currentYear; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/repository/BrandRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.repository; 2 | 3 | import java.util.List; 4 | import org.softuni.mobilele.model.entity.BrandEntity; 5 | import org.softuni.mobilele.model.entity.OfferEntity; 6 | import org.springframework.data.jpa.repository.EntityGraph; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.stereotype.Repository; 10 | 11 | @Repository 12 | public interface BrandRepository extends JpaRepository { 13 | 14 | // @Query("SELECT b FROM BrandEntity b " 15 | // + "JOIN FETCH b.models ") 16 | @EntityGraph( 17 | value = "brandWithModels", 18 | attributePaths = "models" 19 | ) 20 | @Query("SELECT b FROM BrandEntity b") 21 | List getAllBrands(); 22 | 23 | } -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/repository/ExchangeRateRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.repository; 2 | 3 | import org.softuni.mobilele.model.entity.ExchangeRateEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface ExchangeRateRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/repository/ModelRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.repository; 2 | 3 | import org.softuni.mobilele.model.entity.ModelEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface ModelRepository extends JpaRepository { 11 | 12 | 13 | List findAllByBrandId (Long id); 14 | } 15 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/repository/OfferRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.repository; 2 | 3 | import java.util.Optional; 4 | import java.util.UUID; 5 | import org.softuni.mobilele.model.entity.OfferEntity; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public interface OfferRepository extends JpaRepository { 11 | Optional findByUuid(UUID uuid); 12 | 13 | void deleteByUuid(UUID uuid); 14 | } 15 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/repository/UserActivationCodeRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.repository; 2 | 3 | import org.softuni.mobilele.model.entity.UserActivationCodeEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface UserActivationCodeRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.repository; 2 | 3 | import java.util.Optional; 4 | import org.softuni.mobilele.model.entity.UserEntity; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | @Repository 8 | public interface UserRepository extends JpaRepository { 9 | Optional findByEmail(String email); 10 | } 11 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/repository/UserRoleRepository.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.repository; 2 | 3 | import java.util.List; 4 | import org.softuni.mobilele.model.entity.UserRoleEntity; 5 | import org.softuni.mobilele.model.enums.UserRoleEnum; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | public interface UserRoleRepository extends JpaRepository { 9 | 10 | List findAllByRoleIn(List roles); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/BrandService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | import java.util.List; 4 | import org.softuni.mobilele.model.dto.BrandDTO; 5 | 6 | public interface BrandService { 7 | 8 | List getAllBrands(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/CurrencyService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | import org.softuni.mobilele.model.dto.ConvertRequestDTO; 4 | import org.softuni.mobilele.model.dto.ExchangeRatesDTO; 5 | import org.softuni.mobilele.model.dto.MoneyDTO; 6 | 7 | public interface CurrencyService { 8 | 9 | void refreshRates(ExchangeRatesDTO exchangeRatesDTO); 10 | 11 | MoneyDTO convert(ConvertRequestDTO convertRequestDTO); 12 | } 13 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/EmailService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | public interface EmailService { 4 | void sendRegistrationEmail( 5 | String userEmail, 6 | String userName, 7 | String activationCode); 8 | } 9 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/MonitoringService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | public interface MonitoringService { 4 | void logOfferSearch(); 5 | } 6 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/OfferService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | 4 | import java.util.Optional; 5 | import java.util.UUID; 6 | import org.softuni.mobilele.model.dto.CreateOfferDTO; 7 | import org.softuni.mobilele.model.dto.OfferDetailDTO; 8 | import org.softuni.mobilele.model.dto.OfferSummaryDTO; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | 13 | public interface OfferService { 14 | 15 | UUID createOffer(CreateOfferDTO createOfferDTO, UserDetails seller); 16 | 17 | Page getAllOffers(Pageable pageable); 18 | 19 | Optional getOfferDetail(UUID offerUUID, UserDetails viewer); 20 | 21 | void deleteOffer(UUID offerUUID); 22 | 23 | boolean isOwner(UUID uuid, String userName); 24 | } 25 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/ReCaptchaService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | import java.util.Optional; 4 | import org.softuni.mobilele.model.dto.ReCaptchaResponseDTO; 5 | 6 | public interface ReCaptchaService { 7 | Optional verify(String token); 8 | } 9 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/UserActivationService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | import org.softuni.mobilele.model.events.UserRegisteredEvent; 4 | 5 | public interface UserActivationService { 6 | 7 | void userRegistered(UserRegisteredEvent event); 8 | 9 | void cleanUpObsoleteActivationLinks(); 10 | 11 | String createActivationCode(String userEmail); 12 | } 13 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/UserService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service; 2 | 3 | import org.softuni.mobilele.model.dto.UserRegistrationDTO; 4 | import org.springframework.security.core.Authentication; 5 | 6 | public interface UserService { 7 | 8 | void registerUser(UserRegistrationDTO userRegistrationDTO); 9 | 10 | void createUserIfNotExist(String email, String names); 11 | 12 | Authentication login(String email); 13 | } 14 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/aop/MonitoringAspect.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.aop; 2 | 3 | import java.lang.reflect.Method; 4 | import org.aspectj.lang.ProceedingJoinPoint; 5 | import org.aspectj.lang.annotation.Around; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.aspectj.lang.annotation.Before; 8 | import org.aspectj.lang.reflect.MethodSignature; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.softuni.mobilele.service.MonitoringService; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.StopWatch; 14 | 15 | @Aspect 16 | @Component 17 | public class MonitoringAspect { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(MonitoringAspect.class); 20 | private final MonitoringService monitoringService; 21 | 22 | public MonitoringAspect(MonitoringService monitoringService) { 23 | this.monitoringService = monitoringService; 24 | } 25 | 26 | @Before("Pointcuts.trackOfferSearch()") 27 | public void logOfferSearch() { 28 | monitoringService.logOfferSearch(); 29 | } 30 | 31 | @Around("Pointcuts.warnIfExecutionExceeds()") 32 | public Object logExecutionTime(ProceedingJoinPoint pjp) throws Throwable { 33 | 34 | WarnIfExecutionExceeds annotation = getAnnotation(pjp); 35 | long timeout = annotation.timeInMillis(); 36 | 37 | StopWatch stopWatch = new StopWatch(); 38 | stopWatch.start(); 39 | 40 | var returnValue = pjp.proceed(); 41 | 42 | stopWatch.stop(); 43 | 44 | if (stopWatch.getLastTaskTimeMillis() > timeout) { 45 | LOGGER.warn("The method {} ran for {} millis which is more than the " + 46 | "expected {} millis.", 47 | pjp.getSignature(), 48 | stopWatch.getLastTaskTimeMillis(), 49 | timeout 50 | ); 51 | } 52 | 53 | return returnValue; 54 | } 55 | 56 | private static WarnIfExecutionExceeds getAnnotation(ProceedingJoinPoint pjp) { 57 | 58 | Method method = ((MethodSignature)pjp.getSignature()).getMethod(); 59 | 60 | try { 61 | return pjp 62 | .getTarget() 63 | .getClass() 64 | .getMethod(method.getName(), method.getParameterTypes()) 65 | .getAnnotation(WarnIfExecutionExceeds.class); 66 | } catch (NoSuchMethodException e) { 67 | throw new RuntimeException(e); 68 | } 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/aop/Pointcuts.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.aop; 2 | 3 | import org.aspectj.lang.annotation.Pointcut; 4 | 5 | public class Pointcuts { 6 | @Pointcut("execution(* org.softuni.mobilele.service.OfferService.getAllOffers(..))") 7 | public void trackOfferSearch(){} 8 | 9 | @Pointcut("@annotation(WarnIfExecutionExceeds)") 10 | public void warnIfExecutionExceeds(){} 11 | } 12 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/aop/WarnIfExecutionExceeds.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.aop; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface WarnIfExecutionExceeds { 11 | long timeInMillis(); 12 | } 13 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/exception/ObjectNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ObjectNotFoundException extends RuntimeException{ 8 | public ObjectNotFoundException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/impl/BrandServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.impl; 2 | 3 | import java.util.*; 4 | import java.util.stream.Collectors; 5 | 6 | import org.softuni.mobilele.model.dto.BrandDTO; 7 | import org.softuni.mobilele.model.dto.ModelDTO; 8 | import org.softuni.mobilele.repository.BrandRepository; 9 | import org.softuni.mobilele.repository.ModelRepository; 10 | import org.softuni.mobilele.service.BrandService; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class BrandServiceImpl implements BrandService { 15 | private final BrandRepository brandRepository; 16 | 17 | public BrandServiceImpl(BrandRepository brandRepository) { 18 | this.brandRepository = brandRepository; 19 | } 20 | 21 | @Override 22 | public List getAllBrands() { 23 | 24 | return brandRepository.getAllBrands().stream() 25 | .map(brand -> new BrandDTO( 26 | brand.getName(), 27 | brand.getModels().stream() 28 | .map(model -> new ModelDTO(model.getId(), model.getName())) 29 | .sorted(Comparator.comparing(ModelDTO::name)) 30 | .collect(Collectors.toList()) 31 | )) 32 | .sorted(Comparator.comparing(BrandDTO::name)) 33 | .collect(Collectors.toList()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/impl/EmailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.impl; 2 | 3 | import jakarta.mail.MessagingException; 4 | import jakarta.mail.internet.MimeMessage; 5 | import org.softuni.mobilele.service.EmailService; 6 | import org.softuni.mobilele.service.aop.WarnIfExecutionExceeds; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.mail.javamail.JavaMailSender; 9 | import org.springframework.mail.javamail.JavaMailSenderImpl; 10 | import org.springframework.mail.javamail.MimeMessageHelper; 11 | import org.springframework.stereotype.Service; 12 | import org.thymeleaf.TemplateEngine; 13 | import org.thymeleaf.context.Context; 14 | 15 | @Service 16 | public class EmailServiceImpl implements EmailService { 17 | 18 | private final TemplateEngine templateEngine; 19 | private final JavaMailSender javaMailSender; 20 | private final String mobileleEmail; 21 | 22 | public EmailServiceImpl( 23 | TemplateEngine templateEngine, 24 | JavaMailSender javaMailSender, 25 | @Value("${mail.mobilele}") String mobileleEmail) { 26 | this.templateEngine = templateEngine; 27 | this.javaMailSender = javaMailSender; 28 | this.mobileleEmail = mobileleEmail; 29 | } 30 | 31 | @Override 32 | @WarnIfExecutionExceeds(timeInMillis = 600) 33 | public void sendRegistrationEmail(String userEmail, String userName, String activationCode) { 34 | MimeMessage mimeMessage = javaMailSender.createMimeMessage(); 35 | 36 | MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage); 37 | 38 | try { 39 | 40 | mimeMessageHelper.setTo(userEmail); 41 | mimeMessageHelper.setFrom(mobileleEmail); 42 | mimeMessageHelper.setReplyTo(mobileleEmail); 43 | mimeMessageHelper.setSubject("Welcome to mobilele!"); 44 | mimeMessageHelper.setText(generateRegistrationEmailBody(userName, activationCode), true); 45 | 46 | javaMailSender.send(mimeMessageHelper.getMimeMessage()); 47 | 48 | } catch (MessagingException e) { 49 | throw new RuntimeException(e); 50 | } 51 | } 52 | 53 | private String generateRegistrationEmailBody(String userName, String activationCode) { 54 | 55 | Context context = new Context(); 56 | context.setVariable("username", userName); 57 | context.setVariable("activation_code", activationCode); 58 | 59 | return templateEngine.process("email/registration-email", context); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/impl/MobileleUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.impl; 2 | 3 | import java.util.List; 4 | import org.softuni.mobilele.model.entity.UserEntity; 5 | import org.softuni.mobilele.model.entity.UserRoleEntity; 6 | import org.softuni.mobilele.repository.UserRepository; 7 | import org.springframework.security.core.GrantedAuthority; 8 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 9 | import org.springframework.security.core.userdetails.User; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 13 | 14 | public class MobileleUserDetailsService implements UserDetailsService { 15 | 16 | private final UserRepository userRepository; 17 | 18 | public MobileleUserDetailsService(UserRepository userRepository){ 19 | this.userRepository = userRepository; 20 | } 21 | 22 | @Override 23 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { 24 | return userRepository 25 | .findByEmail(email) 26 | .map(MobileleUserDetailsService::map) 27 | .orElseThrow(() -> new UsernameNotFoundException("User " + email + " not found!")); 28 | } 29 | 30 | private static UserDetails map(UserEntity userEntity) { 31 | return User 32 | .withUsername(userEntity.getEmail()) 33 | .password(userEntity.getPassword()) 34 | .authorities(userEntity.getRoles().stream().map(MobileleUserDetailsService::map).toList()) 35 | .build(); 36 | } 37 | 38 | private static GrantedAuthority map(UserRoleEntity userRoleEntity) { 39 | return new SimpleGrantedAuthority( 40 | "ROLE_" + userRoleEntity.getRole().name() 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/impl/MonitoringServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.impl; 2 | 3 | import io.micrometer.core.instrument.Counter; 4 | import io.micrometer.core.instrument.MeterRegistry; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.softuni.mobilele.service.MonitoringService; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class MonitoringServiceImpl implements MonitoringService { 12 | 13 | private Logger LOGGER = LoggerFactory.getLogger(MonitoringServiceImpl.class); 14 | 15 | private final Counter offerSearches; 16 | 17 | public MonitoringServiceImpl(MeterRegistry meterRegistry) { 18 | offerSearches = Counter 19 | .builder("offer_search_cnt") 20 | .description("How many offer searched we have performed") 21 | .register(meterRegistry); 22 | } 23 | 24 | @Override 25 | public void logOfferSearch() { 26 | LOGGER.info("Offer search was performed."); 27 | offerSearches.increment(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/impl/ReCaptchaServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.impl; 2 | 3 | import java.net.URI; 4 | import java.util.Optional; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.softuni.mobilele.config.ReCaptchaConfig; 8 | import org.softuni.mobilele.model.dto.ReCaptchaResponseDTO; 9 | import org.softuni.mobilele.service.ReCaptchaService; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.web.reactive.function.BodyInserters; 12 | import org.springframework.web.reactive.function.client.WebClient; 13 | import org.springframework.web.util.UriBuilder; 14 | 15 | @Service 16 | public class ReCaptchaServiceImpl implements ReCaptchaService { 17 | 18 | private static final Logger LOGGER = 19 | LoggerFactory.getLogger(ReCaptchaServiceImpl.class); 20 | private final WebClient webClient; 21 | private final ReCaptchaConfig reCaptchaConfig; 22 | 23 | public ReCaptchaServiceImpl( 24 | WebClient webClient, 25 | ReCaptchaConfig reCaptchaConfig) { 26 | this.webClient = webClient; 27 | this.reCaptchaConfig = reCaptchaConfig; 28 | } 29 | 30 | @Override 31 | public Optional verify(String token) { 32 | ReCaptchaResponseDTO response = webClient 33 | .post() 34 | .uri(this::buildRecaptchaURI) 35 | .body(BodyInserters 36 | .fromFormData("secret", reCaptchaConfig.getSecret()) 37 | .with("response", token)) 38 | .retrieve() 39 | .bodyToMono(ReCaptchaResponseDTO.class) 40 | .doOnError(t -> LOGGER.error("Error fetching google reponse...", t)) 41 | .onErrorComplete() 42 | .block(); 43 | 44 | return Optional.ofNullable(response); 45 | } 46 | 47 | private URI buildRecaptchaURI(UriBuilder uriBuilder) { 48 | // REST endpoint for google verification. 49 | // https://www.google.com/recaptcha/api/siteverify 50 | return uriBuilder 51 | .scheme("https") 52 | .host("www.google.com") 53 | .path("/recaptcha/api/siteverify") 54 | .build(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/oauth/OAuthSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.oauth; 2 | 3 | import jakarta.servlet.ServletException; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import java.io.IOException; 7 | import org.softuni.mobilele.service.UserService; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 10 | import org.springframework.security.oauth2.core.user.OAuth2User; 11 | import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | public class OAuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { 16 | 17 | private final UserService userService; 18 | 19 | public OAuthSuccessHandler(UserService userService) { 20 | this.userService = userService; 21 | } 22 | 23 | @Override 24 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 25 | Authentication authentication) throws ServletException, IOException { 26 | 27 | if (authentication instanceof OAuth2AuthenticationToken token) { 28 | 29 | OAuth2User user = token.getPrincipal(); 30 | 31 | String email = user 32 | .getAttribute("email"); 33 | String name = user 34 | .getAttribute("name"); 35 | 36 | userService.createUserIfNotExist(email, name); 37 | authentication = userService.login(email); 38 | } 39 | 40 | super.onAuthenticationSuccess(request, response, authentication); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/service/schedulers/ActivationLinkCleanupScheduler.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.service.schedulers; 2 | 3 | import java.time.LocalDateTime; 4 | import org.softuni.mobilele.service.UserActivationService; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class ActivationLinkCleanupScheduler { 10 | 11 | private final UserActivationService userActivationService; 12 | 13 | public ActivationLinkCleanupScheduler(UserActivationService userActivationService) { 14 | this.userActivationService = userActivationService; 15 | } 16 | 17 | //@Scheduled(cron = "*/10 * * * * *") 18 | @Scheduled(fixedRate = 10_000) 19 | public void cleanUp() { 20 | //System.out.println("Trigger cleanup of activation links. " + LocalDateTime.now()); 21 | // TODO. 22 | userActivationService.cleanUpObsoleteActivationLinks(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/web/BrandController.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.web; 2 | 3 | 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | 7 | @Controller 8 | public class BrandController { 9 | 10 | @GetMapping("/brands") 11 | public String allBrands() { 12 | return "brands"; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/web/CurrencyRestController.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.web; 2 | 3 | import io.swagger.v3.oas.annotations.Operation; 4 | import io.swagger.v3.oas.annotations.Parameter; 5 | import io.swagger.v3.oas.annotations.media.Content; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 8 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 9 | import jakarta.validation.Valid; 10 | import org.softuni.mobilele.model.dto.ConvertRequestDTO; 11 | import org.softuni.mobilele.model.dto.MoneyDTO; 12 | import org.softuni.mobilele.service.CurrencyService; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | @RestController 17 | public class CurrencyRestController { 18 | 19 | private final CurrencyService currencyService; 20 | 21 | public CurrencyRestController(CurrencyService currencyService) { 22 | this.currencyService = currencyService; 23 | } 24 | 25 | @Operation(summary = "Converts BGN to a target currency.") 26 | @ApiResponses(value = { 27 | @ApiResponse( 28 | responseCode = "200", 29 | description = "Returned when we successfully converted the currency.", 30 | content = { 31 | @Content( 32 | mediaType = "application/json", 33 | schema = @Schema(implementation = MoneyDTO.class) 34 | ) 35 | } 36 | ), 37 | @ApiResponse( 38 | responseCode = "404", 39 | description = "There is no information about this currency", 40 | content = @Content 41 | ) 42 | }) 43 | @Parameter(name = "target", description = "The targer currency", required = true) 44 | @Parameter(name = "amount", description = "The amount to be converted", required = true) 45 | @GetMapping("/api/currency/convert") 46 | public MoneyDTO convert(@Valid ConvertRequestDTO convertRequestDTO) { 47 | return currencyService.convert(convertRequestDTO); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/web/OffersController.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.web; 2 | 3 | import org.softuni.mobilele.model.dto.OfferSummaryDTO; 4 | import org.softuni.mobilele.service.OfferService; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.web.PageableDefault; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | 13 | @Controller 14 | @RequestMapping("/offers") 15 | public class OffersController { 16 | 17 | private final OfferService offerService; 18 | 19 | public OffersController(OfferService offerService) { 20 | this.offerService = offerService; 21 | } 22 | 23 | @GetMapping("/all") 24 | public String all(Model model, 25 | @PageableDefault( 26 | size = 3, 27 | sort = "uuid" 28 | ) Pageable pageable) { 29 | 30 | Page allOffers = offerService.getAllOffers(pageable); 31 | 32 | model.addAttribute("offers", allOffers); 33 | 34 | return "offers"; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/web/UserLoginController.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.web; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.ModelAttribute; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | 9 | @Controller 10 | public class UserLoginController { 11 | 12 | @GetMapping("/users/login") 13 | public String login() { 14 | return "auth-login"; 15 | } 16 | 17 | @PostMapping("/users/login-error") 18 | public String onFailure( 19 | @ModelAttribute("email") String email, 20 | Model model) { 21 | 22 | model.addAttribute("email", email); 23 | model.addAttribute("bad_credentials", "true"); 24 | 25 | return "auth-login"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mobilele/src/main/java/org/softuni/mobilele/web/UserRegistrationController.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.web; 2 | 3 | import org.softuni.mobilele.model.dto.ReCaptchaResponseDTO; 4 | import org.softuni.mobilele.model.dto.UserRegistrationDTO; 5 | import org.softuni.mobilele.service.ReCaptchaService; 6 | import org.softuni.mobilele.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | 15 | @RequestMapping("/users") 16 | @Controller 17 | public class UserRegistrationController { 18 | 19 | private final UserService userService; 20 | private final ReCaptchaService reCaptchaService; 21 | 22 | public UserRegistrationController( 23 | UserService userService, 24 | ReCaptchaService reCaptchaService) { 25 | this.userService = userService; 26 | this.reCaptchaService = reCaptchaService; 27 | } 28 | 29 | @GetMapping("/register") 30 | public String register() { 31 | return "auth-register"; 32 | } 33 | 34 | @PostMapping("/register") 35 | public String register(UserRegistrationDTO userRegistrationDTO, 36 | @RequestParam("g-recaptcha-response") String reCaptchaResponse) { 37 | 38 | boolean isBot = !reCaptchaService 39 | .verify(reCaptchaResponse) 40 | .map(ReCaptchaResponseDTO::isSuccess) 41 | .orElse(false); 42 | 43 | if (isBot) { 44 | return "redirect:/"; 45 | } 46 | 47 | userService.registerUser(userRegistrationDTO); 48 | 49 | return "redirect:/"; 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driverClassName: com.mysql.cj.jdbc.Driver 4 | url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/mobilele?allowPublicKeyRetrieval=true&useSSL=false&createDatabaseIfNotExist=true&serverTimezone=UTC 5 | username: ${MYSQL_USER:root} 6 | password: ${MYSQL_PASSWORD:} 7 | jpa: 8 | database-platform: org.hibernate.dialect.MySQLDialect 9 | defer-datasource-initialization: true 10 | properties: 11 | hibernate: 12 | format_sql: true 13 | hibernate: 14 | ddl-auto: update 15 | sql: 16 | init: 17 | mode: never 18 | security: 19 | oauth2: 20 | client: 21 | registration: 22 | github: 23 | client-id: ${GITHUB_CLIENT:} 24 | client-secret: ${GITHUB_SECRET:} 25 | scope: user:email 26 | mvc: 27 | hiddenmethod: 28 | filter: 29 | enabled: true 30 | 31 | mobilele: 32 | default.admin.pass: ${DEFAULT_ADMIN_PASS:topsecret} 33 | remember.me.key: ${REMEBER_ME_KEY:topsecret} 34 | 35 | 36 | logging: 37 | level: 38 | org.hibernate.SQL: DEBUG 39 | org.hibernate.orm.jdbc.bind: TRACE 40 | 41 | 42 | open.exchange.rates: 43 | schema: https 44 | host: openexchangerates.org 45 | path: /api/latest.json 46 | app_id: ${APP_ID:test} 47 | symbols: EUR,BGN 48 | enabled: false 49 | 50 | google.recaptcha: 51 | site: ${RECAPTCHA_SITE_KEY:test} 52 | secret: ${RECAPTCHA_SITE_SECRET:test} 53 | 54 | 55 | mail: 56 | mobilele: "mobilele@example.com" 57 | host: localhost 58 | port: 1025 59 | username: ${MAIL_USER:softuni} 60 | password: ${MAIL_PASSWORD:softuni} 61 | 62 | management: 63 | server: 64 | port: 8081 65 | endpoints: 66 | web: 67 | base-path: /actuator 68 | exposure: 69 | include: "*" 70 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO users (id, active, email, first_name, last_name, password) 2 | VALUES 3 | (1, 1, 'admin@example.com', 'Admin', 'Adminov', '95c1933b8ffe84f085f2839899d1673260be58dbd9c2c787ac35515805502c996417596dae9a92880aaa50a046fc7151'), 4 | (2, 1, 'user@example.com', 'User', 'Userov', '95c1933b8ffe84f085f2839899d1673260be58dbd9c2c787ac35515805502c996417596dae9a92880aaa50a046fc7151'), 5 | (3, 1, 'lachezar.balev@gmail.com', 'Lachezar', 'Balev', '95c1933b8ffe84f085f2839899d1673260be58dbd9c2c787ac35515805502c996417596dae9a92880aaa50a046fc7151'); 6 | 7 | 8 | INSERT INTO roles (`id`, `role`) 9 | VALUES 10 | (1, 'ADMIN'), 11 | (2, 'USER'); 12 | 13 | INSERT INTO users_roles(`user_id`, `role_id`) 14 | VALUES 15 | (1, 1), 16 | (1, 2), 17 | (2, 2); 18 | 19 | 20 | INSERT INTO `brands` (`id`, `name`) 21 | VALUES 22 | (1, 'Toyota'), 23 | (2, 'Ford'), 24 | (3, 'Trabant'); 25 | 26 | INSERT INTO `models` (`id`, `category`, `brand_id`, `name`) 27 | VALUES 28 | (1, 'CAR', 1, 'Camry'), 29 | (2, 'CAR', 1, 'Corolla'), 30 | (3, 'CAR', 2, 'Focus'), 31 | (4, 'CAR', 2, 'Fiesta'), 32 | (5, 'CAR', 3, '601'); 33 | 34 | INSERT INTO `offers` (`id`, `description`, `engine`, `image_url`, `mileage`, `price`, `transmission`, `uuid`, `year`, `model_id`, `seller_id`) 35 | VALUES 36 | (1, 'Top Trabi 1!', 'PETROL', 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG/1200px-AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG', 24000, 2223, 'MANUAL', 'b72e6550-e365-43bf-aab2-b57cafc2db7c', 1985, 5, 1), 37 | (2, 'Top Trabi 1!', 'PETROL', 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG/1200px-AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG', 24000, 2225, 'MANUAL', 'b72e6550-e365-43bf-aab2-b57cafc2db71', 1986, 5, 1), 38 | (3, 'Top Trabi 2!', 'PETROL', 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG/1200px-AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG', 24000, 2227, 'MANUAL', 'b72e6550-e365-43bf-aab2-b57cafc2db72', 1987, 5, 2), 39 | (4, 'Top Trabi 2!', 'PETROL', 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG/1200px-AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG', 24000, 2221, 'MANUAL', 'b72e6550-e365-43bf-aab2-b57cafc2db73', 1988, 5, 2), 40 | (5, 'Top Trabi 2!', 'PETROL', 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG/1200px-AWZ_Trabant_601S%2C_Verkehrszentrum_des_Deutschen_Museums.JPG', 24000, 2220, 'MANUAL', 'b72e6550-e365-43bf-aab2-b57cafc2db74', 1989, 5, 2); 41 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: url('/images/1.jpg'); 3 | background-position: center center; 4 | background-repeat: no-repeat; 5 | background-attachment: fixed; 6 | background-size: cover; 7 | background-color: #464646; 8 | } 9 | 10 | .navbar .logo { 11 | max-height: 50px; 12 | padding-right: 100px; 13 | } 14 | 15 | .container, .container-fluid { 16 | padding-top: 90px; 17 | } 18 | 19 | .bg-text { 20 | background-color: rgb(0,0,0); /* Fallback color */ 21 | background-color: rgba(0,0,0, 0.4); /* Black w/opacity/see-through */ 22 | color: white; 23 | font-weight: bold; 24 | border: 3px solid #f1f1f1; 25 | z-index: 2; 26 | width: 80%; 27 | padding: 20px; 28 | text-align: center; 29 | } 30 | 31 | .brand-section { 32 | background-image: linear-gradient(to bottom right, rgba(220, 220, 220, 0.9), rgba(255, 255, 255, 0.9)); 33 | } 34 | 35 | td, th { 36 | padding: 3px 8px; 37 | } 38 | 39 | .card.offer, .offer-details .list-group-item { 40 | background-image: linear-gradient(to bottom right, rgba(220, 220, 220, 0.9), rgba(255, 255, 255, 0.9)); 41 | } 42 | .card-img-top-wrapper { 43 | height: 150px; 44 | overflow:hidden; 45 | } 46 | 47 | form.main-form { 48 | background-image: linear-gradient(to bottom right, rgba(70, 70, 70, 0.8), rgba(10, 10, 10, 0.8)); 49 | padding:10px 30px; 50 | border-radius: 20px; 51 | } 52 | 53 | .field-error { 54 | background: white url('data:image/svg+xml;utf8, ') no-repeat top right/30px 30px; 55 | } 56 | 57 | .logged-user { 58 | color: white; 59 | } 60 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/css/reset-css.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/mobilele/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/mobilele/src/main/resources/static/images/1.jpg -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/images/broken-car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/mobilele/src/main/resources/static/images/broken-car.png -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/images/car-dealership-car-car-showroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/mobilele/src/main/resources/static/images/car-dealership-car-car-showroom.jpg -------------------------------------------------------------------------------- /mobilele/src/main/resources/static/images/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/mobilele/src/main/resources/static/images/car.png -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/auth-login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 | 10 |
11 |

Login

12 |
15 |
16 | 17 | 23 |
24 |
25 | 26 | 31 | 32 |
33 | 34 |
35 | 36 | 40 | 41 |
42 | 43 |

44 | Invalid username or password. 45 |

46 | 47 |
48 |
49 |
50 | 51 |
52 |
53 |
54 |
55 |
56 | 57 | 64 | 65 |
66 |
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/details.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 |
15 |

Details

16 |
17 |
18 | 19 |
20 |
21 |
22 |
    23 |
  • 24 | 25 |
    26 | • Mileage 27 | 12345 28 |
    29 |
    30 | • Price 31 | 123 32 |
    33 |
    34 | • Engine type 35 | PETROL 36 |
    37 |
    38 | • Transmission type 39 | TRANSMISSION 40 |
    41 | 42 |
    • Seller 43 | Pesho Petrov 44 |
    45 |
  • 46 |
47 |
48 |
50 | 51 |
52 |
53 |
54 |
55 | Car image 56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/email/registration-email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Hello 9 | 10 | 11 |

Hello, Pesho!

12 | Welcome to Mobilele! 13 | 14 | Please follow the link in order to activate your account. 15 | 16 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 |
15 |
Upps, the thingy that you are looking for is no longer here!
16 |
We are veeeery sorry.
17 | Broken car 18 |
19 | Go home... 20 |
21 | 22 |
23 |
24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/error/500.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 |
15 |
Upps, something went terribly wrong!
16 |
We are veeeery sorry and we are working to resolve the problem.
17 | Broken car 18 |
19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/fragments/head.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | MobiLeLeLe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/fragments/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /mobilele/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 |
15 |
Welcome to MobiLeLeLe, 16 | 17 | 18 | 19 | 20 | Anonymous! 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mobilele/src/test/java/org/softuni/mobilele/MobileleApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MobileleApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mobilele/src/test/java/org/softuni/mobilele/testutils/DBInit.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.testutils; 2 | 3 | import java.util.List; 4 | import org.softuni.mobilele.model.entity.UserRoleEntity; 5 | import org.softuni.mobilele.model.enums.UserRoleEnum; 6 | import org.softuni.mobilele.repository.UserRoleRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class DBInit implements CommandLineRunner { 13 | 14 | @Autowired 15 | private UserRoleRepository userRoleRepository; 16 | 17 | @Override 18 | public void run(String... args) throws Exception { 19 | if (userRoleRepository.count() == 0) { 20 | userRoleRepository.saveAll(List.of( 21 | new UserRoleEntity().setRole(UserRoleEnum.USER), 22 | new UserRoleEntity().setRole(UserRoleEnum.ADMIN) 23 | )); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mobilele/src/test/java/org/softuni/mobilele/testutils/TestDataUtil.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.testutils; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.List; 5 | import java.util.UUID; 6 | import org.softuni.mobilele.model.entity.BrandEntity; 7 | import org.softuni.mobilele.model.entity.ExchangeRateEntity; 8 | import org.softuni.mobilele.model.entity.ModelEntity; 9 | import org.softuni.mobilele.model.entity.OfferEntity; 10 | import org.softuni.mobilele.model.entity.UserEntity; 11 | import org.softuni.mobilele.model.entity.UserRoleEntity; 12 | import org.softuni.mobilele.model.enums.EngineEnum; 13 | import org.softuni.mobilele.model.enums.TransmissionEnum; 14 | import org.softuni.mobilele.model.enums.UserRoleEnum; 15 | import org.softuni.mobilele.repository.BrandRepository; 16 | import org.softuni.mobilele.repository.ExchangeRateRepository; 17 | import org.softuni.mobilele.repository.OfferRepository; 18 | import org.softuni.mobilele.repository.UserRepository; 19 | import org.softuni.mobilele.repository.UserRoleRepository; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.stereotype.Component; 22 | 23 | @Component 24 | public class TestDataUtil { 25 | 26 | @Autowired 27 | private ExchangeRateRepository exchangeRateRepository; 28 | 29 | @Autowired 30 | private OfferRepository offerRepository; 31 | 32 | @Autowired 33 | private BrandRepository brandRepository; 34 | 35 | public void createExchangeRate(String currency, BigDecimal rate) { 36 | exchangeRateRepository.save( 37 | new ExchangeRateEntity().setCurrency(currency).setRate(rate) 38 | ); 39 | } 40 | 41 | 42 | public OfferEntity createTestOffer(UserEntity owner) { 43 | 44 | // create test brand 45 | BrandEntity brandEntity = brandRepository.save(new BrandEntity() 46 | .setName("Test Brand") 47 | .setModels(List.of( 48 | new ModelEntity().setName("Test Model"), 49 | new ModelEntity().setName("Test Model1") 50 | ))); 51 | 52 | // create test offer 53 | OfferEntity offer = new OfferEntity() 54 | .setModel(brandEntity.getModels().get(0)) 55 | .setImageUrl("https://www.google.com") 56 | .setPrice(BigDecimal.valueOf(1000)) 57 | .setYear(2020) 58 | .setUuid(UUID.randomUUID()) 59 | .setDescription("Test Description") 60 | .setEngine(EngineEnum.PETROL) 61 | .setMileage(10000) 62 | .setTransmission(TransmissionEnum.MANUAL) 63 | .setSeller(owner); 64 | 65 | return offerRepository.save(offer); 66 | } 67 | // 68 | public void cleanUp() { 69 | exchangeRateRepository.deleteAll(); 70 | offerRepository.deleteAll(); 71 | brandRepository.deleteAll(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /mobilele/src/test/java/org/softuni/mobilele/testutils/UserTestDataUtil.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.testutils; 2 | 3 | import java.util.List; 4 | import org.softuni.mobilele.model.entity.UserEntity; 5 | import org.softuni.mobilele.model.enums.UserRoleEnum; 6 | import org.softuni.mobilele.repository.UserRepository; 7 | import org.softuni.mobilele.repository.UserRoleRepository; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class UserTestDataUtil { 13 | 14 | @Autowired 15 | private UserRepository userRepository; 16 | 17 | @Autowired 18 | private UserRoleRepository userRoleRepository; 19 | 20 | public UserEntity createTestUser(String email) { 21 | return createUser(email, List.of(UserRoleEnum.USER)); 22 | } 23 | 24 | public UserEntity createTestAdmin(String email) { 25 | return createUser(email, List.of(UserRoleEnum.ADMIN)); 26 | } 27 | 28 | private UserEntity createUser(String email, List roles) { 29 | 30 | var roleEntities = userRoleRepository.findAllByRoleIn(roles); 31 | 32 | UserEntity newUser = new UserEntity() 33 | .setActive(true) 34 | .setEmail(email) 35 | .setFirstName("Test user first") 36 | .setLastName("Test user last") 37 | .setRoles( 38 | roleEntities 39 | ); 40 | 41 | return userRepository.save(newUser); 42 | } 43 | 44 | public void cleanUp() { 45 | userRepository.deleteAll(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /mobilele/src/test/java/org/softuni/mobilele/web/CurrencyRestControllerTestIT.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.web; 2 | 3 | import static org.hamcrest.Matchers.is; 4 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 5 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 6 | 7 | import java.math.BigDecimal; 8 | import org.junit.jupiter.api.Test; 9 | import org.softuni.mobilele.testutils.TestDataUtil; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 15 | 16 | @SpringBootTest 17 | @AutoConfigureMockMvc 18 | class CurrencyRestControllerTestIT { 19 | 20 | @Autowired 21 | private MockMvc mockMvc; 22 | 23 | @Autowired 24 | private TestDataUtil testDataUtil; 25 | 26 | @Test 27 | public void testConvert() throws Exception { 28 | 29 | testDataUtil.createExchangeRate("EUR", BigDecimal.valueOf(0.54)); 30 | 31 | this.mockMvc.perform(MockMvcRequestBuilders.get("/api/currency/convert") 32 | .param("target", "EUR") 33 | .param("amount", "1000")). 34 | andExpect(status().isOk()). 35 | andExpect(jsonPath("$.currency", is("EUR"))). 36 | andExpect(jsonPath("$.amount", is(540.0))) 37 | ; 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /mobilele/src/test/java/org/softuni/mobilele/web/UserRegistrationControllerTestIT.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mobilele.web; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 5 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; 7 | 8 | import com.icegreen.greenmail.util.GreenMail; 9 | import com.icegreen.greenmail.util.ServerSetup; 10 | import jakarta.mail.internet.MimeMessage; 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.Assertions; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 18 | import org.springframework.boot.test.context.SpringBootTest; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 21 | 22 | @SpringBootTest 23 | @AutoConfigureMockMvc 24 | class UserRegistrationControllerTestIT { 25 | 26 | @Autowired 27 | private MockMvc mockMvc; 28 | 29 | @Value("${mail.port}") 30 | private int port; 31 | 32 | @Value("${mail.host}") 33 | private String host; 34 | 35 | @Value("${mail.username}") 36 | private String username; 37 | 38 | @Value("${mail.password}") 39 | private String password; 40 | 41 | private GreenMail greenMail; 42 | 43 | @BeforeEach 44 | void setUp() { 45 | greenMail = new GreenMail(new ServerSetup(port, host,"smtp")); 46 | greenMail.start(); 47 | greenMail.setUser(username, password); 48 | } 49 | 50 | @AfterEach 51 | void tearDown() { 52 | greenMail.stop(); 53 | } 54 | 55 | @Test 56 | void testRegistration() throws Exception { 57 | mockMvc.perform( 58 | MockMvcRequestBuilders.post("/users/register") 59 | .param("email", "pesho@softuni.bg") 60 | .param("firstName", "Pesho") 61 | .param("lastName", "Petrov") 62 | .param("password", "topsecret") 63 | .param("confirmPassword", "topsecret") 64 | .with(csrf()) 65 | ).andExpect(status().is3xxRedirection()) 66 | .andExpect(view().name("redirect:/")); 67 | 68 | greenMail.waitForIncomingEmail(1); 69 | MimeMessage[] receivedMessages = greenMail.getReceivedMessages(); 70 | 71 | Assertions.assertEquals(1, receivedMessages.length); 72 | MimeMessage registrationMessage = receivedMessages[0]; 73 | 74 | Assertions.assertTrue(registrationMessage.getContent().toString().contains("Pesho")); 75 | Assertions.assertEquals(1, registrationMessage.getAllRecipients().length); 76 | Assertions.assertEquals("pesho@softuni.bg", registrationMessage.getAllRecipients()[0].toString()); 77 | } 78 | } -------------------------------------------------------------------------------- /mobilele/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | jpa: 3 | hibernate: 4 | ddl-auto: create-drop 5 | defer-datasource-initialization: true 6 | sql: 7 | init: 8 | mode: never 9 | mvc: 10 | hiddenmethod: 11 | filter: 12 | enabled: true 13 | 14 | mobilele: 15 | default.admin.pass: topsecret 16 | remember.me.key: topsecret 17 | 18 | 19 | logging: 20 | level: 21 | org.hibernate.SQL: DEBUG 22 | org.hibernate.orm.jdbc.bind: TRACE 23 | 24 | 25 | open.exchange.rates: 26 | schema: https 27 | host: dummy 28 | path: /api/latest.json 29 | app_id: test 30 | symbols: EUR,BGN 31 | enabled: false 32 | 33 | 34 | mail: 35 | mobilele: "mobilele@example.com" 36 | host: localhost 37 | port: 3333 38 | username: test@example.com 39 | password: topsecret 40 | -------------------------------------------------------------------------------- /mvcfn/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /mvcfn/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.3' 4 | id 'io.spring.dependency-management' version '1.1.3' 5 | } 6 | 7 | group = 'org.softuni' 8 | version = '0.0.1-SNAPSHOT' 9 | 10 | java { 11 | sourceCompatibility = '17' 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 20 | implementation 'org.springframework.boot:spring-boot-starter-web' 21 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 22 | } 23 | 24 | tasks.named('test') { 25 | useJUnitPlatform() 26 | } 27 | -------------------------------------------------------------------------------- /mvcfn/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/mvcfn/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /mvcfn/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /mvcfn/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mvcfn' 2 | -------------------------------------------------------------------------------- /mvcfn/src/main/java/org/softuni/mvcfn/MvcfnApplication.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mvcfn; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MvcfnApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MvcfnApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mvcfn/src/main/java/org/softuni/mvcfn/config/RoutesConfig.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mvcfn.config; 2 | 3 | import static org.springframework.web.servlet.function.RouterFunctions.route; 4 | 5 | import org.softuni.mvcfn.web.TestHandlers; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.servlet.function.RouterFunction; 9 | import org.springframework.web.servlet.function.ServerResponse; 10 | 11 | @Configuration 12 | public class RoutesConfig { 13 | 14 | @Bean 15 | public RouterFunction routingFunction(TestHandlers testHandlers) { 16 | return route() 17 | .GET("/test", testHandlers::test) 18 | .build(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /mvcfn/src/main/java/org/softuni/mvcfn/web/TestHandlers.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mvcfn.web; 2 | 3 | import java.util.Map; 4 | import org.apache.catalina.Server; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.servlet.function.ServerRequest; 7 | import org.springframework.web.servlet.function.ServerResponse; 8 | 9 | @Component 10 | public class TestHandlers { 11 | public ServerResponse test(ServerRequest serverRequest) { 12 | return ServerResponse.ok().render("test", 13 | Map.of("message", "Hello, Pesho!")); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mvcfn/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mvcfn/src/main/resources/templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 12 |

The message here

13 | 14 | 15 | -------------------------------------------------------------------------------- /mvcfn/src/test/java/org/softuni/mvcfn/MvcfnApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.softuni.mvcfn; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MvcfnApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /plain-web-app/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | .idea 13 | *.iws 14 | *.iml 15 | *.ipr 16 | out/ 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store -------------------------------------------------------------------------------- /plain-web-app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'war' 4 | } 5 | 6 | group 'bg.softuni' 7 | version '1.0-SNAPSHOT' 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | ext { 14 | junitVersion = '5.9.2' 15 | } 16 | 17 | sourceCompatibility = '11' 18 | targetCompatibility = '11' 19 | 20 | tasks.withType(JavaCompile) { 21 | options.encoding = 'UTF-8' 22 | } 23 | 24 | dependencies { 25 | compileOnly('jakarta.servlet:jakarta.servlet-api:5.0.0') 26 | 27 | testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") 28 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") 29 | } 30 | 31 | test { 32 | useJUnitPlatform() 33 | } -------------------------------------------------------------------------------- /plain-web-app/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/plain-web-app/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /plain-web-app/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /plain-web-app/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "plain-web-app" -------------------------------------------------------------------------------- /plain-web-app/src/main/java/bg/softuni/plainwebapp/HelloFilter.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.plainwebapp; 2 | 3 | import jakarta.servlet.Filter; 4 | import jakarta.servlet.FilterChain; 5 | import jakarta.servlet.ServletException; 6 | import jakarta.servlet.ServletRequest; 7 | import jakarta.servlet.ServletResponse; 8 | import jakarta.servlet.annotation.WebFilter; 9 | import java.io.IOException; 10 | 11 | @WebFilter( 12 | urlPatterns = "/hello-servlet" 13 | ) 14 | public class HelloFilter implements Filter { 15 | // ---> Filter1 --> Filter2 ... --> Servlet -> Browser 16 | @Override 17 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 18 | throws IOException, ServletException { 19 | request.setAttribute("username", "Pesho"); 20 | chain.doFilter(request, response); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plain-web-app/src/main/java/bg/softuni/plainwebapp/HelloServlet.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.plainwebapp; 2 | 3 | import java.io.*; 4 | import jakarta.servlet.http.*; 5 | import jakarta.servlet.annotation.*; 6 | 7 | @WebServlet(name = "helloServlet", value = "/hello-servlet") 8 | public class HelloServlet extends HttpServlet { 9 | 10 | public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 11 | response.setContentType("text/html"); 12 | 13 | // Hello 14 | PrintWriter out = response.getWriter(); 15 | out.println(""); 16 | out.println("

" + request.getAttribute("username") + "

"); 17 | out.println(""); 18 | } 19 | } -------------------------------------------------------------------------------- /plain-web-app/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /plain-web-app/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 2 | 3 | 4 | 5 | JSP - Hello World 6 | 7 | 8 |

<%= "Hello World!" %> 9 |

10 |
11 | Hello Servlet 12 | 13 | -------------------------------------------------------------------------------- /proxies/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea 9 | .idea/modules.xml 10 | .idea/jarRepositories.xml 11 | .idea/compiler.xml 12 | .idea/libraries/ 13 | *.iws 14 | *.iml 15 | *.ipr 16 | out/ 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store -------------------------------------------------------------------------------- /proxies/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'org.example' 6 | version = '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testImplementation platform('org.junit:junit-bom:5.9.1') 14 | testImplementation 'org.junit.jupiter:junit-jupiter' 15 | } 16 | 17 | test { 18 | useJUnitPlatform() 19 | } -------------------------------------------------------------------------------- /proxies/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/proxies/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /proxies/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 06 21:04:36 EET 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /proxies/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'proxies' 2 | 3 | -------------------------------------------------------------------------------- /proxies/src/main/java/bg/softuni/proxies/Cacheable.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.proxies; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface Cacheable { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /proxies/src/main/java/bg/softuni/proxies/CacheableInvocationHandler.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.proxies; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | public class CacheableInvocationHandler implements InvocationHandler { 9 | 10 | private final Map cache = new ConcurrentHashMap<>(); 11 | private final Object realObject; 12 | 13 | public CacheableInvocationHandler(Object realObject) { 14 | this.realObject = realObject; 15 | } 16 | 17 | @Override 18 | public Object invoke( 19 | Object proxy, 20 | Method method, 21 | Object[] args) throws Throwable { 22 | 23 | Cacheable cacheableAnnotation = 24 | realObject 25 | .getClass() 26 | .getMethod(method.getName(), method.getParameterTypes()) 27 | .getAnnotation(Cacheable.class); 28 | 29 | if (cacheableAnnotation != null) { 30 | String cacheKey = cacheableAnnotation.value(); 31 | if (cache.containsKey(cacheKey)) { 32 | return cache.get(cacheKey); 33 | } else { 34 | Object value = method.invoke(realObject, args); 35 | cache.put(cacheKey, value); 36 | return value; 37 | } 38 | } else { 39 | return method.invoke(realObject, args); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /proxies/src/main/java/bg/softuni/proxies/Main.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.proxies; 2 | 3 | import java.lang.reflect.Proxy; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | 9 | StudentServiceIfc studentServiceIfc = createStudentService(); 10 | studentServiceIfc.getAllStudents(); 11 | studentServiceIfc.getAllStudents(); 12 | studentServiceIfc.getAllStudents(); 13 | } 14 | 15 | public static StudentServiceIfc createStudentService() { 16 | return (StudentServiceIfc)Proxy.newProxyInstance( 17 | Main.class.getClassLoader(), 18 | new Class[]{StudentServiceIfc.class}, 19 | new CacheableInvocationHandler(new StudentService()) 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /proxies/src/main/java/bg/softuni/proxies/StudentDTO.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.proxies; 2 | 3 | public record StudentDTO(String name, int age) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /proxies/src/main/java/bg/softuni/proxies/StudentService.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.proxies; 2 | 3 | import java.util.List; 4 | 5 | public class StudentService implements StudentServiceIfc { 6 | @Override 7 | @Cacheable("students") 8 | public List getAllStudents() { 9 | 10 | System.out.println("CALCULATING ALL STUDENTS"); 11 | 12 | return List.of( 13 | new StudentDTO("Pesho", 21), 14 | new StudentDTO("Anna", 21) 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /proxies/src/main/java/bg/softuni/proxies/StudentServiceIfc.java: -------------------------------------------------------------------------------- 1 | package bg.softuni.proxies; 2 | 3 | import java.util.List; 4 | 5 | public interface StudentServiceIfc { 6 | List getAllStudents(); 7 | } 8 | -------------------------------------------------------------------------------- /thymeleaf-pure/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/ 9 | .idea/modules.xml 10 | .idea/jarRepositories.xml 11 | .idea/compiler.xml 12 | .idea/libraries/ 13 | *.iws 14 | *.iml 15 | *.ipr 16 | out/ 17 | !**/src/main/**/out/ 18 | !**/src/test/**/out/ 19 | 20 | ### Eclipse ### 21 | .apt_generated 22 | .classpath 23 | .factorypath 24 | .project 25 | .settings 26 | .springBeans 27 | .sts4-cache 28 | bin/ 29 | !**/src/main/**/bin/ 30 | !**/src/test/**/bin/ 31 | 32 | ### NetBeans ### 33 | /nbproject/private/ 34 | /nbbuild/ 35 | /dist/ 36 | /nbdist/ 37 | /.nb-gradle/ 38 | 39 | ### VS Code ### 40 | .vscode/ 41 | 42 | ### Mac OS ### 43 | .DS_Store -------------------------------------------------------------------------------- /thymeleaf-pure/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group = 'org.example' 6 | version = '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | 14 | implementation "org.thymeleaf:thymeleaf:3.1.2.RELEASE" 15 | 16 | testImplementation platform('org.junit:junit-bom:5.9.1') 17 | testImplementation 'org.junit.jupiter:junit-jupiter' 18 | } 19 | 20 | test { 21 | useJUnitPlatform() 22 | } -------------------------------------------------------------------------------- /thymeleaf-pure/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luchob/softuni-sep-2023/c640aaad1a3e34eba81af8ba5ed433ea3899f529/thymeleaf-pure/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /thymeleaf-pure/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Oct 02 19:00:53 EEST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /thymeleaf-pure/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'thymeleaf-pure' 2 | 3 | -------------------------------------------------------------------------------- /thymeleaf-pure/src/main/java/org/example/Main.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.thymeleaf.ITemplateEngine; 4 | import org.thymeleaf.TemplateEngine; 5 | import org.thymeleaf.context.Context; 6 | import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; 7 | 8 | public class Main { 9 | 10 | public static void main(String[] args) { 11 | ITemplateEngine templateEngine = createThymeleafTemplateEngine(); 12 | 13 | Context ctx = new Context(); 14 | ctx.setVariable("name", "Pesho"); 15 | 16 | String html = templateEngine.process("test.html", ctx); 17 | System.out.println(html); 18 | } 19 | 20 | private static ITemplateEngine createThymeleafTemplateEngine() { 21 | 22 | TemplateEngine engine = new TemplateEngine(); 23 | 24 | engine.setTemplateResolver(new ClassLoaderTemplateResolver()); 25 | 26 | return engine; 27 | } 28 | } -------------------------------------------------------------------------------- /thymeleaf-pure/src/main/resources/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 |

Hello,

12 |

13 | 14 | --------------------------------------------------------------------------------