├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── client ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── account │ │ │ ├── account-routing.module.ts │ │ │ ├── account.module.ts │ │ │ ├── account.service.ts │ │ │ ├── login │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.scss │ │ │ │ └── login.component.ts │ │ │ └── register │ │ │ │ ├── register.component.html │ │ │ │ ├── register.component.scss │ │ │ │ └── register.component.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── basket │ │ │ ├── basket-routing.module.ts │ │ │ ├── basket.component.html │ │ │ ├── basket.component.scss │ │ │ ├── basket.component.ts │ │ │ ├── basket.module.ts │ │ │ └── basket.service.ts │ │ ├── checkout │ │ │ ├── address │ │ │ │ ├── address.component.html │ │ │ │ ├── address.component.scss │ │ │ │ └── address.component.ts │ │ │ ├── checkout-routing.module.ts │ │ │ ├── checkout.component.html │ │ │ ├── checkout.component.scss │ │ │ ├── checkout.component.ts │ │ │ ├── checkout.module.ts │ │ │ ├── checkout.service.ts │ │ │ ├── review │ │ │ │ ├── review.component.html │ │ │ │ ├── review.component.scss │ │ │ │ └── review.component.ts │ │ │ └── shipment │ │ │ │ ├── shipment.component.html │ │ │ │ ├── shipment.component.scss │ │ │ │ └── shipment.component.ts │ │ ├── core │ │ │ ├── core.module.ts │ │ │ ├── guards │ │ │ │ └── auth.guard.ts │ │ │ ├── interceptors │ │ │ │ ├── error.interceptor.ts │ │ │ │ └── loading.interceptor.ts │ │ │ ├── nav-bar │ │ │ │ ├── nav-bar.component.html │ │ │ │ ├── nav-bar.component.scss │ │ │ │ └── nav-bar.component.ts │ │ │ ├── not-found │ │ │ │ ├── not-found.component.html │ │ │ │ ├── not-found.component.scss │ │ │ │ └── not-found.component.ts │ │ │ ├── section-header │ │ │ │ ├── section-header.component.html │ │ │ │ ├── section-header.component.scss │ │ │ │ └── section-header.component.ts │ │ │ ├── server-error │ │ │ │ ├── server-error.component.html │ │ │ │ ├── server-error.component.scss │ │ │ │ └── server-error.component.ts │ │ │ └── services │ │ │ │ └── loading.service.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.ts │ │ │ └── home.module.ts │ │ ├── shared │ │ │ ├── components │ │ │ │ ├── order-summary │ │ │ │ │ ├── order-summary.component.html │ │ │ │ │ ├── order-summary.component.scss │ │ │ │ │ └── order-summary.component.ts │ │ │ │ ├── pagination-header │ │ │ │ │ ├── pagination-header.component.html │ │ │ │ │ ├── pagination-header.component.scss │ │ │ │ │ └── pagination-header.component.ts │ │ │ │ └── pagination │ │ │ │ │ ├── pagination.component.html │ │ │ │ │ ├── pagination.component.scss │ │ │ │ │ └── pagination.component.ts │ │ │ ├── models │ │ │ │ ├── address.ts │ │ │ │ ├── basket.ts │ │ │ │ ├── brand.ts │ │ │ │ ├── deliveryOptions.ts │ │ │ │ ├── product.ts │ │ │ │ ├── productData.ts │ │ │ │ ├── storeData.ts │ │ │ │ ├── type.ts │ │ │ │ └── user.ts │ │ │ └── shared.module.ts │ │ └── store │ │ │ ├── product-details │ │ │ ├── product-details.component.html │ │ │ ├── product-details.component.scss │ │ │ └── product-details.component.ts │ │ │ ├── product-item │ │ │ ├── product-item.component.html │ │ │ ├── product-item.component.scss │ │ │ └── product-item.component.ts │ │ │ ├── store-routing.module.ts │ │ │ ├── store.component.html │ │ │ ├── store.component.scss │ │ │ ├── store.component.ts │ │ │ ├── store.model.service.ts │ │ │ ├── store.module.ts │ │ │ └── store.service.ts │ ├── assets │ │ ├── .gitkeep │ │ └── images │ │ │ ├── bslide1.jpg │ │ │ ├── bslide2.jpg │ │ │ ├── bslide3.jpg │ │ │ ├── connection-refused.png │ │ │ ├── hero1.jpg │ │ │ ├── hero2.jpg │ │ │ ├── hero3.jpg │ │ │ ├── hero4.jpg │ │ │ ├── hero5.jpg │ │ │ ├── hero6.jpg │ │ │ ├── logo.png │ │ │ ├── page-not-found.png │ │ │ ├── products │ │ │ ├── Nike-Football-1.png │ │ │ ├── Nike-Football-2.png │ │ │ ├── Nike-Football-3.png │ │ │ ├── adidas_football-1.png │ │ │ ├── adidas_football-2.png │ │ │ ├── adidas_football-3.png │ │ │ ├── adidas_shoe-1.png │ │ │ ├── adidas_shoe-2.png │ │ │ ├── adidas_shoe-3.png │ │ │ ├── asics_shoe-1.png │ │ │ ├── asics_shoe-2.png │ │ │ ├── asics_shoe-3.png │ │ │ ├── babolat-kitback-1.png │ │ │ ├── babolat-kitback-2.png │ │ │ ├── babolat-kitback-3.png │ │ │ ├── babolat-racket-1.png │ │ │ ├── babolat-racket-2.png │ │ │ ├── babolat-racket-3.png │ │ │ ├── babolat.png │ │ │ ├── babolat_shoe-1.png │ │ │ ├── babolat_shoe-2.png │ │ │ ├── babolat_shoe-3.png │ │ │ ├── puma_shoe-1.png │ │ │ ├── puma_shoe-2.png │ │ │ ├── puma_shoe-3.png │ │ │ ├── victor-racket-1.png │ │ │ ├── victor-racket-2.png │ │ │ ├── victor-racket-3.png │ │ │ ├── victor_shoe-1.png │ │ │ ├── victor_shoe-2.png │ │ │ ├── yonex-badminton-1.png │ │ │ ├── yonex-badminton-2.png │ │ │ ├── yonex-badminton-3.png │ │ │ ├── yonex-kitback-1.png │ │ │ ├── yonex-kitback-2.png │ │ │ ├── yonex-kitback-3.png │ │ │ ├── yonex-racket-1.png │ │ │ ├── yonex-racket-2.png │ │ │ ├── yonex-racket-3.png │ │ │ ├── yonex_shoe-1.png │ │ │ ├── yonex_shoe-2.png │ │ │ └── yonex_shoe-3.png │ │ │ └── server-error.png │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ └── styles.scss ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json ├── docker ├── data.sql └── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── readme.md └── src ├── main ├── java │ └── com │ │ └── ecoomerce │ │ └── sportscenter │ │ ├── SportscenterApplication.java │ │ ├── config │ │ ├── CorsConfig.java │ │ ├── MyConfig.java │ │ ├── SecurityConfig.java │ │ └── WebConfig.java │ │ ├── controller │ │ ├── AuthConroller.java │ │ ├── BasketController.java │ │ └── ProductController.java │ │ ├── entity │ │ ├── Basket.java │ │ ├── BasketItem.java │ │ ├── Brand.java │ │ ├── Product.java │ │ └── Type.java │ │ ├── exceptions │ │ ├── CustomExceptionHandler.java │ │ └── ProductNotFoundException.java │ │ ├── model │ │ ├── BasketItemResponse.java │ │ ├── BasketResponse.java │ │ ├── BrandResponse.java │ │ ├── CustomErrorResponse.java │ │ ├── JwtRequest.java │ │ ├── JwtResponse.java │ │ ├── ProductResponse.java │ │ └── TypeResponse.java │ │ ├── repository │ │ ├── BasketRepository.java │ │ ├── BrandRepository.java │ │ ├── ProductRepository.java │ │ └── TypeRepository.java │ │ ├── security │ │ ├── JwtAuthenticationEntryPoint.java │ │ ├── JwtAuthenticationFilter.java │ │ └── JwtHelper.java │ │ └── service │ │ ├── BasketService.java │ │ ├── BasketServiceImpl.java │ │ ├── BrandService.java │ │ ├── BrandServiceImpl.java │ │ ├── ProductService.java │ │ ├── ProductServiceImpl.java │ │ ├── TypeService.java │ │ └── TypeServiceImpl.java └── resources │ └── application.yaml └── test └── java └── com └── ecoomerce └── sportscenter └── SportscenterApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Building FullStack ECommerce App using SpringBoot & Angular 2 | 3 | ## Introduction: 4 | Are you ready to embark on a transformative journey into the world of full-stack e-commerce development? 🔥 Brace yourself for an exhilarating adventure, where you'll harness the dynamic duo of Java21 and SpringBoot 3.2.0 to craft cutting-edge online stores that redefine the digital shopping experience! 🌐💻 5 | 6 | ## 🌟 Java21: The Future of Java 7 | Java21, the latest incarnation of the Java programming language, has taken the tech world by storm. With its enhanced performance, feature-rich capabilities, and improved developer productivity, it's the perfect foundation for your journey into e-commerce mastery. 💪💼 8 | 9 | ## 🚀 SpringBoot 3.2.0: Turbocharge Your Development 10 | SpringBoot 3.2.0, the latest iteration of the industry-favorite SpringBoot framework, is your turbocharger for full-stack e-commerce development. Its robust back-end capabilities, seamless integration with Java21, and extensive libraries make it the go-to choice for building scalable and secure e-commerce platforms. 🌐🔒 11 | 12 | ## Course Highlights: 13 | 📚 In this immersive 16+ hour course, you'll dive headfirst into a world of technical marvels and industry best practices. Here's a sneak peek at what awaits you: 14 | 1. 🧠 Mastery of SpringBoot Essentials and Advanced Features: Unleash the full potential of SpringBoot with in-depth coverage of its essential features and advanced functionalities. 15 | 2. 📦 Efficient Data Access with Spring Data JPA: Learn how to handle data like a pro, using Spring Data JPA to streamline your data access processes. 16 | 3. 🔐 Secure User Authentication and Authorization: Ensure your e-commerce platform's security with Spring Security's state-of-the-art user authentication and authorization mechanisms. 17 | 4. 🌐 Creating RESTful APIs with Spring Boot: Seamlessly communicate data between your back-end and front-end using RESTful APIs. 18 | 5. 🅰️ Angular Best Practices: Master Angular's dynamic front-end framework, including routing, lazy loading, and reactive forms, for a flawless user experience. 19 | 6. 🎨 Styling with Bootstrap and Font Awesome: Elevate your platform's aesthetics with Bootstrap and Font Awesome, creating a polished user interface. 20 | 21 | ## Who Should Enroll? 22 | This course caters to tech enthusiasts at every level of expertise: 23 | - 🌱 Interns: Lay a strong foundation for your tech career by applying academic knowledge to real-world e-commerce projects. 24 | - 👨‍💻 Junior Developers: Upgrade your skill set with advanced full-stack e-commerce development practices. 25 | - 👩‍💻 Senior Developers: Lead the way in e-commerce application development with cutting-edge Java21 and SpringBoot 3.2.0. 26 | - 🚀 Tech Leads: Direct high-impact projects with confidence, leveraging the power of SpringBoot and Angular. 27 | - 🏗️ Architects: Infuse innovation into your design strategies for scalable and dynamic e-commerce solutions. 28 | - 🌟 Senior Architects: Pioneer robust architecture, leading transformative projects with your expertise. 29 | Course Stats and Benefits: 30 | 31 | 🏁 Let's take a closer look at what you'll gain from this course: 32 | 🚀 Fast-Track Your Learning: Dive into an engaging course designed for maximum comprehension and superior learning outcomes. 33 | - 📚 15 Comprehensive Sections: Explore the depths of full-stack e-commerce development, from fundamentals to advanced design patterns. 34 | - 📹 185+ In-Depth Videos: Each video is a stepping stone, providing clear explanations, step-by-step instructions, and real-world applications. 35 | - ⏱️ 16+ Hours of Content: Immerse yourself in a comprehensive curriculum that fits your schedule, accessible anytime, anywhere. 36 | - 📝 Multiple Choice Questions: Solidify your knowledge with quizzes carefully designed to reinforce and test your learning. 37 | - 🔄 Yearly Updates: Stay in sync with the latest trends and best practices in SpringBoot and Angular as technology evolves. 38 | - 🔄 Lifetime Access: Your one-time enrollment grants you unrestricted access to all current and future course content - forever. 39 | 40 | ## Enroll here:- 41 | 42 | ## Solution Structure - Server Side 43 | 44 | ![1st](https://github.com/rahulsahay19/Blog-Images/assets/3886381/3a62b6c5-6d93-464d-b839-fe59e029f5e0) 45 | 46 | Ignore the typo in the solution name. ecoomerce → ecommerce. 47 | 48 | ## Solution Structure - Client Side 49 | 50 | ![2nd](https://github.com/rahulsahay19/Blog-Images/assets/3886381/828864a9-2828-49be-bb29-81d1a3a7bef2) 51 | 52 | ## Application Flow 53 | 54 | ![3rd](https://github.com/rahulsahay19/Blog-Images/assets/3886381/07216dbf-893e-44d5-8bb0-08279e2ad7d1) 55 | 56 | ![4th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/ae496c3d-9a2f-4b28-888e-d2529153c0e6) 57 | 58 | ![5th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/c172e9d1-ef2c-48a5-b39b-43d061302494) 59 | 60 | ![6th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/8ba3203b-180e-4bdc-bf95-9d0244807626) 61 | 62 | ![7th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/67759f82-82c6-441c-a9f9-82504b14b3ee) 63 | 64 | ![8th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/3efeaaae-f61f-4775-8d0e-a4621e2e2e83) 65 | 66 | ![9th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/7d90509f-3697-430b-9040-0782bb80edfb) 67 | 68 | ![10th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/9481896f-3fa2-4114-9173-df523444fa6b) 69 | 70 | ![11th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/c486930a-2928-4305-968c-134d9479c321) 71 | 72 | ![12th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/b12573a7-bac8-47de-9ca5-c7747273b0e7) 73 | 74 | ![13th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/f9b42116-2782-4f14-8e41-e08ae0a45a6f) 75 | 76 | ![14th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/1b29163b-480a-4b9e-a8e3-c362874bb720) 77 | 78 | ## Enroll here:- 79 | 80 | Thank You for Exploring This Course! 81 | 82 | We're thrilled you've taken the time to explore what this comprehensive course on SpringBoot & Angular has to offer. Your interest in advancing your skills and knowledge in the FullStack field is commendable, and we're excited to be a part of your learning journey. -------------------------------------------------------------------------------- /client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "client": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/client", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": [ 24 | "zone.js" 25 | ], 26 | "tsConfig": "tsconfig.app.json", 27 | "inlineStyleLanguage": "scss", 28 | "assets": [ 29 | "src/favicon.ico", 30 | "src/assets" 31 | ], 32 | "styles": [ 33 | "node_modules/bootstrap/dist/css/bootstrap.min.css", 34 | "node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", 35 | "bootswatch/dist/united/bootstrap.min.css", 36 | "node_modules/font-awesome/css/font-awesome.min.css", 37 | "node_modules/ngx-toastr/toastr.css", 38 | "node_modules/ngx-spinner/animations/square-jelly-box.css", 39 | "src/styles.scss" 40 | ], 41 | "scripts": [] 42 | }, 43 | "configurations": { 44 | "production": { 45 | "budgets": [ 46 | { 47 | "type": "initial", 48 | "maximumWarning": "500kb", 49 | "maximumError": "1mb" 50 | }, 51 | { 52 | "type": "anyComponentStyle", 53 | "maximumWarning": "2kb", 54 | "maximumError": "4kb" 55 | } 56 | ], 57 | "outputHashing": "all" 58 | }, 59 | "development": { 60 | "buildOptimizer": false, 61 | "optimization": false, 62 | "vendorChunk": true, 63 | "extractLicenses": false, 64 | "sourceMap": true, 65 | "namedChunks": true 66 | } 67 | }, 68 | "defaultConfiguration": "production" 69 | }, 70 | "serve": { 71 | "builder": "@angular-devkit/build-angular:dev-server", 72 | "configurations": { 73 | "production": { 74 | "browserTarget": "client:build:production" 75 | }, 76 | "development": { 77 | "browserTarget": "client:build:development" 78 | } 79 | }, 80 | "defaultConfiguration": "development" 81 | }, 82 | "extract-i18n": { 83 | "builder": "@angular-devkit/build-angular:extract-i18n", 84 | "options": { 85 | "browserTarget": "client:build" 86 | } 87 | }, 88 | "test": { 89 | "builder": "@angular-devkit/build-angular:karma", 90 | "options": { 91 | "polyfills": [ 92 | "zone.js", 93 | "zone.js/testing" 94 | ], 95 | "tsConfig": "tsconfig.spec.json", 96 | "inlineStyleLanguage": "scss", 97 | "assets": [ 98 | "src/favicon.ico", 99 | "src/assets" 100 | ], 101 | "styles": [ 102 | "src/styles.scss" 103 | ], 104 | "scripts": [] 105 | } 106 | } 107 | } 108 | } 109 | }, 110 | "cli": { 111 | "analytics": "e018a195-8ddc-4f4e-b1d5-632c7dfc4e6f" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^16.0.0", 14 | "@angular/common": "^16.0.0", 15 | "@angular/compiler": "^16.0.0", 16 | "@angular/core": "^16.0.0", 17 | "@angular/forms": "^16.0.0", 18 | "@angular/material": "^16.0.0", 19 | "@angular/platform-browser": "^16.0.0", 20 | "@angular/platform-browser-dynamic": "^16.0.0", 21 | "@angular/router": "^16.0.0", 22 | "@paralleldrive/cuid2": "^2.2.2", 23 | "bootstrap": "^5.2.3", 24 | "bootswatch": "^5.3.2", 25 | "font-awesome": "^4.7.0", 26 | "ngx-bootstrap": "^11.0.2", 27 | "ngx-spinner": "^16.0.2", 28 | "ngx-toastr": "^18.0.0", 29 | "rxjs": "~7.8.0", 30 | "tslib": "^2.3.0", 31 | "xng-breadcrumb": "^10.0.1", 32 | "zone.js": "~0.13.0" 33 | }, 34 | "devDependencies": { 35 | "@angular-devkit/build-angular": "^16.0.0", 36 | "@angular/cli": "~16.0.0", 37 | "@angular/compiler-cli": "^16.0.0", 38 | "@types/jasmine": "~4.3.0", 39 | "jasmine-core": "~4.6.0", 40 | "karma": "~6.4.0", 41 | "karma-chrome-launcher": "~3.2.0", 42 | "karma-coverage": "~2.2.0", 43 | "karma-jasmine": "~5.1.0", 44 | "karma-jasmine-html-reporter": "~2.0.0", 45 | "typescript": "~5.0.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/src/app/account/account-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { LoginComponent } from './login/login.component'; 5 | import { RegisterComponent } from './register/register.component'; 6 | 7 | const routes: Routes = [ 8 | {path: 'login', component: LoginComponent, data:{breadcrumb: 'Login'}}, 9 | {path: 'register', component: RegisterComponent, data:{breadcrumb: 'Register'}} 10 | ] 11 | 12 | @NgModule({ 13 | declarations: [], 14 | imports: [ 15 | RouterModule.forChild(routes) 16 | ] 17 | }) 18 | export class AccountRoutingModule { } 19 | -------------------------------------------------------------------------------- /client/src/app/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LoginComponent } from './login/login.component'; 4 | import { RegisterComponent } from './register/register.component'; 5 | import { AccountRoutingModule } from './account-routing.module'; 6 | import { SharedModule } from '../shared/shared.module'; 7 | 8 | 9 | 10 | @NgModule({ 11 | declarations: [ 12 | LoginComponent, 13 | RegisterComponent 14 | ], 15 | imports: [ 16 | CommonModule, 17 | AccountRoutingModule, 18 | SharedModule 19 | ] 20 | }) 21 | export class AccountModule { } 22 | -------------------------------------------------------------------------------- /client/src/app/account/account.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, map } from 'rxjs'; 3 | import { User } from '../shared/models/user'; 4 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 5 | import { Router } from '@angular/router'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class AccountService { 11 | redirectUrl: string | null = null; 12 | apiUrl = 'http://localhost:8080/auth'; 13 | private currentUserSource = new BehaviorSubject(null); 14 | currentUser$ = this.currentUserSource.asObservable(); 15 | 16 | constructor(private http: HttpClient, private router: Router) { } 17 | 18 | isAuthenticated(): boolean{ 19 | //whether user is authenticated or not 20 | const token = localStorage.getItem('token'); 21 | return !!token; 22 | } 23 | loadUser() { 24 | const token = localStorage.getItem('token'); 25 | if (token) { 26 | const headers = new HttpHeaders({ 27 | Authorization: `Bearer ${token}` 28 | }); 29 | 30 | this.http 31 | .get(`${this.apiUrl}/user`, { headers }) 32 | .subscribe({ 33 | next: (user) => { 34 | this.currentUserSource.next(user); 35 | }, 36 | error: (error) => { 37 | console.error('Error decoding JWT token:', token); 38 | } 39 | }); 40 | } 41 | } 42 | 43 | login(values: any){ 44 | return this.http.post(this.apiUrl + '/login', values).pipe( 45 | map(user=>{ 46 | localStorage.setItem('token', user.token); 47 | this.currentUserSource.next(user); 48 | }) 49 | ) 50 | } 51 | register(values: any){ 52 | return this.http.post(this.apiUrl + '/register', values).pipe( 53 | map(user=>{ 54 | localStorage.setItem('token', user.token); 55 | this.currentUserSource.next(user); 56 | }) 57 | ) 58 | } 59 | logout() { 60 | localStorage.removeItem('token'); 61 | this.currentUserSource.next(null); 62 | this.router.navigateByUrl('/'); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /client/src/app/account/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

Login

7 |
8 |
9 |
10 |
11 | 12 | 13 |
14 | UserName is required 15 |
16 |
17 |
18 | 19 | 20 |
21 | Password is required 22 |
23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /client/src/app/account/login/login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/account/login/login.component.scss -------------------------------------------------------------------------------- /client/src/app/account/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { AccountService } from '../account.service'; 4 | import { ToastrService } from 'ngx-toastr'; 5 | import { Router } from '@angular/router'; 6 | 7 | @Component({ 8 | selector: 'app-login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.scss'] 11 | }) 12 | export class LoginComponent { 13 | loginForm: FormGroup; 14 | 15 | constructor( 16 | private formsBuilder: FormBuilder, 17 | private accountService: AccountService, 18 | private router: Router, 19 | private toastService: ToastrService 20 | ){ 21 | this.loginForm = this.formsBuilder.group({ 22 | username: ['', [Validators.required]], 23 | password: ['', Validators.required], 24 | rememberMe: [false] 25 | }); 26 | } 27 | onSubmit(){ 28 | this.accountService.login(this.loginForm.value).subscribe({ 29 | next: user => { 30 | const redirect = this.accountService.redirectUrl ? this.accountService.redirectUrl : '/store'; 31 | this.router.navigateByUrl(redirect); 32 | this.accountService.redirectUrl = null; //clearing the redirect url post navigation 33 | this.toastService.success('Successfully Logged In'); 34 | 35 | }, 36 | error: () =>{ 37 | this.toastService.error('Error Occurred during Login'); 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/src/app/account/register/register.component.html: -------------------------------------------------------------------------------- 1 |

register works!

2 | -------------------------------------------------------------------------------- /client/src/app/account/register/register.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/account/register/register.component.scss -------------------------------------------------------------------------------- /client/src/app/account/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-register', 5 | templateUrl: './register.component.html', 6 | styleUrls: ['./register.component.scss'] 7 | }) 8 | export class RegisterComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /client/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { HomeComponent } from './home/home.component'; 4 | import { NotFoundComponent } from './core/not-found/not-found.component'; 5 | import { ServerErrorComponent } from './core/server-error/server-error.component'; 6 | 7 | const routes: Routes = [ 8 | {path:'', component: HomeComponent, data:{breadcrumb: 'Home'}}, 9 | {path:'store', loadChildren:()=>import('./store/store.module').then(m=>m.StoreModule)}, 10 | {path:'basket', loadChildren:()=>import('./basket/basket.module').then(m=>m.BasketModule)}, 11 | {path:'account', loadChildren:()=>import('./account/account.module').then(m=>m.AccountModule)}, 12 | {path:'checkout', loadChildren:()=>import('./checkout/checkout.module').then(m=>m.CheckoutModule)}, 13 | {path:'not-found', component: NotFoundComponent}, 14 | {path:'server-error', component: ServerErrorComponent}, 15 | {path:'**', redirectTo: '', pathMatch:'full'} 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [RouterModule.forRoot(routes)], 20 | exports: [RouterModule] 21 | }) 22 | export class AppRoutingModule { } 23 | -------------------------------------------------------------------------------- /client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 6 |

Loading...

7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/app.component.scss -------------------------------------------------------------------------------- /client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(() => TestBed.configureTestingModule({ 7 | imports: [RouterTestingModule], 8 | declarations: [AppComponent] 9 | })); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have as title 'client'`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app.title).toEqual('client'); 21 | }); 22 | 23 | it('should render title', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.nativeElement as HTMLElement; 27 | expect(compiled.querySelector('.content span')?.textContent).toContain('client app is running!'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { BasketService } from './basket/basket.service'; 3 | import { AccountService } from './account/account.service'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.scss'], 9 | }) 10 | export class AppComponent implements OnInit { 11 | title = 'Sports Center'; 12 | 13 | constructor( 14 | private basketService: BasketService, 15 | private accountService: AccountService 16 | ) {} 17 | 18 | ngOnInit() { 19 | this.loadUser(); 20 | this.loadBasket(); 21 | } 22 | 23 | loadBasket(){ 24 | const basketId = localStorage.getItem('basket_id'); 25 | if (basketId) this.basketService.getBasket(basketId); 26 | } 27 | 28 | loadUser(){ 29 | this.accountService.loadUser(); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | import { CoreModule } from './core/core.module'; 8 | import { HomeModule } from './home/home.module'; 9 | import { ErrorInterceptor } from './core/interceptors/error.interceptor'; 10 | import { LoadingInterceptor } from './core/interceptors/loading.interceptor'; 11 | import { NgxSpinnerModule } from 'ngx-spinner'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent 16 | ], 17 | imports: [ 18 | BrowserModule, 19 | AppRoutingModule, 20 | BrowserAnimationsModule, 21 | HttpClientModule, 22 | CoreModule, 23 | HomeModule, 24 | NgxSpinnerModule.forRoot({type: 'square-jelly-box'}) 25 | ], 26 | providers: [ 27 | { 28 | provide: HTTP_INTERCEPTORS, 29 | useClass: ErrorInterceptor, 30 | multi: true 31 | }, 32 | { 33 | provide: HTTP_INTERCEPTORS, 34 | useClass: LoadingInterceptor, 35 | multi: true 36 | } 37 | ], 38 | bootstrap: [AppComponent] 39 | }) 40 | export class AppModule { } 41 | -------------------------------------------------------------------------------- /client/src/app/basket/basket-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { BasketComponent } from './basket.component'; 5 | 6 | const routes: Routes =[ 7 | {path: '', component: BasketComponent} 8 | ] 9 | 10 | @NgModule({ 11 | declarations: [], 12 | imports: [ 13 | RouterModule.forChild(routes) 14 | ], 15 | exports: [ 16 | RouterModule 17 | ] 18 | }) 19 | export class BasketRoutingModule { } 20 | -------------------------------------------------------------------------------- /client/src/app/basket/basket.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Your Cart

3 |
4 | Your cart is empty. 5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | 32 | 33 | 38 | 39 | 40 |
ImageProductQuantityPriceTotal
{{ item.name }} 23 | 26 | {{ item.quantity }} 27 | 30 | {{ item.price | currency:"INR" }}{{ (item.quantity * item.price) | currency:"INR" }} 34 | 37 |
41 |
42 |
43 |
44 |
45 | 46 |
47 | 50 |
51 |
52 |
53 | 54 | -------------------------------------------------------------------------------- /client/src/app/basket/basket.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/basket/basket.component.scss -------------------------------------------------------------------------------- /client/src/app/basket/basket.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { BasketService } from './basket.service'; 3 | import { Basket, BasketItem } from '../shared/models/basket'; 4 | 5 | @Component({ 6 | selector: 'app-basket', 7 | templateUrl: './basket.component.html', 8 | styleUrls: ['./basket.component.scss'] 9 | }) 10 | export class BasketComponent implements OnInit { 11 | basket: Basket | null = new Basket(); 12 | constructor(public basketService: BasketService){} 13 | 14 | ngOnInit() { 15 | this.basketService.basketSource$.subscribe((basket)=>{ 16 | this.basket = basket; 17 | }); 18 | } 19 | 20 | extractImageName(item: BasketItem): string | null{ 21 | if(item && item.pictureUrl){ 22 | const parts = item.pictureUrl.split('/'); 23 | if(parts.length>0){ 24 | return parts[parts.length-1]; 25 | } 26 | } 27 | return null; 28 | } 29 | 30 | incrementQuantity(itemId: number){ 31 | this.basketService.incrementItemQuantity(itemId); 32 | } 33 | 34 | decrementQuantity(itemId: number){ 35 | this.basketService.decrementItemQuantity(itemId); 36 | } 37 | 38 | removeItem(itemId: number){ 39 | this.basketService.remove(itemId); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/src/app/basket/basket.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { BasketComponent } from './basket.component'; 4 | import { BasketRoutingModule } from './basket-routing.module'; 5 | import { SharedModule } from '../shared/shared.module'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | BasketComponent 10 | ], 11 | imports: [ 12 | CommonModule, 13 | BasketRoutingModule, 14 | SharedModule 15 | ] 16 | }) 17 | export class BasketModule { } 18 | -------------------------------------------------------------------------------- /client/src/app/basket/basket.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import { Basket, BasketItem, BasketTotals } from '../shared/models/basket'; 4 | import { HttpClient } from '@angular/common/http'; 5 | import { Product } from '../shared/models/product'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class BasketService { 11 | apiUrl = 'http://localhost:8080/api/baskets'; 12 | private basketSource = new BehaviorSubject(null); 13 | basketSource$ = this.basketSource.asObservable(); 14 | private basketTotalSource = new BehaviorSubject({ 15 | subtotal: 0, 16 | shipping: 0, 17 | total: 0 18 | }); 19 | basketTotalSource$ = this.basketTotalSource.asObservable(); 20 | 21 | constructor(private http: HttpClient) { 22 | // Check if there's a basket in local storage when the service is initialized 23 | const storedBasket = localStorage.getItem('basket'); 24 | if (storedBasket) { 25 | const parsedBasket = JSON.parse(storedBasket); 26 | this.basketSource.next(parsedBasket); 27 | this.calculateTotals(); 28 | } 29 | } 30 | 31 | updateShippingPrice(shippingPrice: number): void { 32 | // Update the shipping price in the basketTotalSubject 33 | const updatedBasketTotal = this.basketTotalSource.value; 34 | updatedBasketTotal.shipping = shippingPrice; 35 | updatedBasketTotal.total = updatedBasketTotal.subtotal + shippingPrice; 36 | this.basketTotalSource.next(updatedBasketTotal); 37 | } 38 | clearBasket(){ 39 | this.basketSource.next(null); 40 | localStorage.removeItem('basket_id'); 41 | localStorage.removeItem('basket'); 42 | } 43 | getBasket(id: string){ 44 | return this.http.get(this.apiUrl+'/'+id).subscribe({ 45 | next: basket => { 46 | this.basketSource.next(basket); 47 | this.calculateTotals(); 48 | // Update the localStorage with the latest basket data 49 | localStorage.setItem('basket', JSON.stringify(basket)); 50 | } 51 | }) 52 | } 53 | 54 | setBasket(basket: Basket){ 55 | return this.http.post(this.apiUrl, basket).subscribe({ 56 | next: basket => { 57 | this.basketSource.next(basket); 58 | this.calculateTotals(); 59 | // Update the localStorage with the latest basket data 60 | localStorage.setItem('basket', JSON.stringify(basket)); 61 | } 62 | }) 63 | } 64 | 65 | getCurrentBasket(){ 66 | return this.basketSource.value; 67 | } 68 | 69 | addItemToBasket(item: Product, quantity = 1){ 70 | const itemToAdd = this.mapProductToBasket(item); 71 | const basket = this.getCurrentBasket() ?? this.createBasket(); 72 | basket.items = this.upsertItems(basket.items, itemToAdd, quantity); 73 | this.setBasket(basket); 74 | } 75 | upsertItems(items: BasketItem[], itemToAdd: BasketItem, quantity: number): BasketItem[]{ 76 | const item = items.find(x=>x.id === itemToAdd.id); 77 | if(item){ 78 | item.quantity += quantity; 79 | }else{ 80 | itemToAdd.quantity = quantity; 81 | items.push(itemToAdd); 82 | } 83 | return items; 84 | } 85 | 86 | createBasket(): Basket{ 87 | const basket = new Basket(); 88 | localStorage.setItem('basket_id', basket.id); 89 | return basket; 90 | } 91 | 92 | //Basket Methods 93 | incrementItemQuantity(itemId: number, quantity: number = 1){ 94 | const basket = this.getCurrentBasket(); 95 | if(basket){ 96 | const item = basket.items.find((p)=>p.id === itemId); 97 | if(item){ 98 | item.quantity += quantity; 99 | if(item.quantity<1){ 100 | item.quantity = 1; //Preventing -ve quantity 101 | } 102 | this.setBasket(basket); 103 | } 104 | } 105 | } 106 | 107 | decrementItemQuantity(itemId: number, quantity: number = 1){ 108 | const basket = this.getCurrentBasket(); 109 | if(basket){ 110 | const item = basket.items.find((p)=>p.id === itemId); 111 | if(item && item.quantity > 1){ 112 | item.quantity -= quantity; 113 | this.setBasket(basket); 114 | } 115 | } 116 | } 117 | 118 | remove(itemId: number){ 119 | const basket = this.getCurrentBasket(); 120 | if(basket){ 121 | const itemIndex = basket.items.findIndex((p)=>p.id === itemId); 122 | if(itemIndex !==-1){ 123 | basket.items.splice(itemIndex, 1); 124 | this.setBasket(basket); 125 | } 126 | //check if the basket is empty after removing the item 127 | if(basket.items.length === 0){ 128 | //clear the basket from local storage 129 | this.basketSource.next(null); 130 | localStorage.removeItem('basket_id'); 131 | localStorage.removeItem('basket'); 132 | } 133 | } 134 | } 135 | 136 | private calculateTotals(){ 137 | const basket = this.getCurrentBasket(); 138 | if(!basket) return; 139 | const shipping = 0; 140 | const subTotal = basket.items.reduce((x, y) =>(y.price * y.quantity) + x, 0); 141 | const total = subTotal + shipping; 142 | this.basketTotalSource.next({shipping, subtotal: subTotal, total}); 143 | } 144 | private mapProductToBasket(item: Product): BasketItem { 145 | return { 146 | id: item.id, 147 | name: item.name, 148 | price: item.price, 149 | description: item.description, 150 | quantity: 0, 151 | pictureUrl: item.pictureUrl, 152 | productBrand: item.productBrand, 153 | productType: item.productType 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /client/src/app/checkout/address/address.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | First Name is required. 7 | 8 |
9 |
10 | 11 | 12 | 13 | Last Name is required. 14 | 15 |
16 |
17 | 18 | 19 | 20 | Street is required. 21 | 22 |
23 |
24 | 25 | 26 | 27 | City is required. 28 | 29 |
30 |
31 | 32 | 33 | 34 | State is required. 35 | 36 |
37 |
38 | 39 | 40 | 41 | Zip Code is required. 42 | 43 | 44 | Invalid ZIP code. 45 | 46 |
47 | 50 |
51 | -------------------------------------------------------------------------------- /client/src/app/checkout/address/address.component.scss: -------------------------------------------------------------------------------- 1 | .error-message{ 2 | color: red; 3 | } -------------------------------------------------------------------------------- /client/src/app/checkout/address/address.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { CheckoutComponent } from '../checkout.component'; 5 | import { Address } from 'src/app/shared/models/address'; 6 | 7 | @Component({ 8 | selector: 'app-address', 9 | templateUrl: './address.component.html', 10 | styleUrls: ['./address.component.scss'] 11 | }) 12 | export class AddressComponent { 13 | addressForm: FormGroup; 14 | constructor( 15 | private formBuilder: FormBuilder, 16 | private router: Router, 17 | private checkoutComponent: CheckoutComponent 18 | ){ 19 | this.addressForm = this.formBuilder.group({ 20 | Fname: ['', Validators.required], 21 | Lname: ['', Validators.required], 22 | Street: ['', Validators.required], 23 | City: ['', Validators.required], 24 | State: ['', Validators.required], 25 | ZipCode: ['', [Validators.required, Validators.pattern(/^\d{6}$/)]] 26 | }); 27 | } 28 | onSubmit(){ 29 | if(this.addressForm.valid){ 30 | const addressData: Address = this.addressForm.value; 31 | console.log('Submitted Address:', addressData); 32 | } 33 | } 34 | goToNextStep(){ 35 | if(this.addressForm.valid){ 36 | //Navigate to shipment page 37 | this.router.navigate(['/checkout/shipment']); 38 | //set the current step to Shipment 39 | this.checkoutComponent.setCurrentStep('shipment'); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/app/checkout/checkout-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { CheckoutComponent } from './checkout.component'; 4 | import { canActivate } from '../core/guards/auth.guard'; 5 | import { AddressComponent } from './address/address.component'; 6 | import { ShipmentComponent } from './shipment/shipment.component'; 7 | import { ReviewComponent } from './review/review.component'; 8 | 9 | const routes: Routes = [ 10 | { 11 | path:'', 12 | component: CheckoutComponent, 13 | canActivate: [canActivate], 14 | children: [ 15 | {path: 'address', component: AddressComponent, data:{breadcrumb: 'Address'}}, 16 | {path: 'shipment', component: ShipmentComponent, data:{breadcrumb: 'Shipment'}}, 17 | {path: 'review', component: ReviewComponent, data:{breadcrumb: 'Review'}}, 18 | {path: '', redirectTo: 'address', pathMatch: 'full'} //deafulting to address step 19 | ] 20 | } 21 | ] 22 | 23 | @NgModule({ 24 | declarations: [], 25 | imports: [ 26 | RouterModule.forChild(routes) 27 | ], 28 | exports: [RouterModule] 29 | }) 30 | export class CheckoutRoutingModule { } 31 | -------------------------------------------------------------------------------- /client/src/app/checkout/checkout.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /client/src/app/checkout/checkout.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/checkout/checkout.component.scss -------------------------------------------------------------------------------- /client/src/app/checkout/checkout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-checkout', 5 | templateUrl: './checkout.component.html', 6 | styleUrls: ['./checkout.component.scss'] 7 | }) 8 | export class CheckoutComponent { 9 | currentStep: 'address' | 'shipment' | 'review' = 'address'; 10 | setCurrentStep(step: 'address' | 'shipment' | 'review'){ 11 | this.currentStep = step; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client/src/app/checkout/checkout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { CheckoutComponent } from './checkout.component'; 4 | import { AddressComponent } from './address/address.component'; 5 | import { ShipmentComponent } from './shipment/shipment.component'; 6 | import { ReviewComponent } from './review/review.component'; 7 | import { CheckoutRoutingModule } from './checkout-routing.module'; 8 | import { SharedModule } from '../shared/shared.module'; 9 | 10 | 11 | 12 | @NgModule({ 13 | declarations: [ 14 | CheckoutComponent, 15 | AddressComponent, 16 | ShipmentComponent, 17 | ReviewComponent 18 | ], 19 | imports: [ 20 | CommonModule, 21 | CheckoutRoutingModule, 22 | SharedModule 23 | ] 24 | }) 25 | export class CheckoutModule { } 26 | -------------------------------------------------------------------------------- /client/src/app/checkout/checkout.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class CheckoutService { 7 | 8 | constructor() { } 9 | } 10 | -------------------------------------------------------------------------------- /client/src/app/checkout/review/review.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Your Items

3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
ImageProductQuantityPriceTotal
{{ item.name }}{{ item.quantity }}{{ item.price | currency:"INR" }}{{ (item.quantity * item.price) | currency:"INR" }}
24 |
25 | 26 | 27 |
28 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /client/src/app/checkout/review/review.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/checkout/review/review.component.scss -------------------------------------------------------------------------------- /client/src/app/checkout/review/review.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { BasketService } from 'src/app/basket/basket.service'; 4 | import { Basket, BasketItem } from 'src/app/shared/models/basket'; 5 | 6 | @Component({ 7 | selector: 'app-review', 8 | templateUrl: './review.component.html', 9 | styleUrls: ['./review.component.scss'] 10 | }) 11 | export class ReviewComponent implements OnInit { 12 | basket: Basket | null = new Basket(); 13 | 14 | constructor(public basketService: BasketService, private router: Router){} 15 | 16 | ngOnInit(): void { 17 | this.basketService.basketSource$.subscribe((basket) =>{ 18 | this.basket = basket; 19 | }) 20 | } 21 | 22 | extractImageName(item: BasketItem): string | null{ 23 | if(item && item.pictureUrl){ 24 | const parts = item.pictureUrl.split('/'); 25 | if(parts.length>0){ 26 | return parts[parts.length-1]; 27 | } 28 | } 29 | return null; 30 | } 31 | submitOrder(){ 32 | this.basketService.clearBasket(); 33 | this.router.navigate(['/store']); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /client/src/app/checkout/shipment/shipment.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Select Delivery Method

3 |
4 |
5 |
6 |
7 |
{{ option.name }}
8 |

{{ option.description }}

9 |

10 | Delivery Time: {{ option.deliveryTime }} 11 |

12 |

13 | Price: {{ option.price | currency: 'INR' }} 14 |

15 |
16 | 25 | 26 |
27 |
28 |
29 |
30 |
31 | 34 |
35 | -------------------------------------------------------------------------------- /client/src/app/checkout/shipment/shipment.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/checkout/shipment/shipment.component.scss -------------------------------------------------------------------------------- /client/src/app/checkout/shipment/shipment.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { BasketService } from 'src/app/basket/basket.service'; 5 | import { DeliveryOption } from 'src/app/shared/models/deliveryOptions'; 6 | import { CheckoutComponent } from '../checkout.component'; 7 | 8 | @Component({ 9 | selector: 'app-shipment', 10 | templateUrl: './shipment.component.html', 11 | styleUrls: ['./shipment.component.scss'], 12 | }) 13 | export class ShipmentComponent { 14 | deliveryOptions: DeliveryOption[] = [ 15 | { 16 | id: 1, 17 | name: 'Fedex', 18 | deliveryTime: '2 days', 19 | description: 'Fast delivery', 20 | price: 300, 21 | }, 22 | { 23 | id: 2, 24 | name: 'DTDC', 25 | deliveryTime: '3 days', 26 | description: 'Reliable delivery', 27 | price: 200, 28 | }, 29 | { 30 | id: 3, 31 | name: 'First Flight', 32 | deliveryTime: '4 days', 33 | description: 'Economical delivery', 34 | price: 150, 35 | }, 36 | { 37 | id: 4, 38 | name: 'Normal', 39 | deliveryTime: '7 days', 40 | description: 'Standard delivery', 41 | price: 100, 42 | }, 43 | ]; 44 | 45 | selectedOption: number | undefined; 46 | shipmentForm: FormGroup; 47 | constructor( 48 | private basketService: BasketService, 49 | private formBuilder: FormBuilder, 50 | private router: Router, 51 | private checkoutComponent: CheckoutComponent 52 | ) { 53 | this.shipmentForm = this.formBuilder.group({ 54 | selectedOption: [this.selectedOption, Validators.required], 55 | }); 56 | 57 | //Initialize this selected option 58 | this.selectedOption = this.deliveryOptions[0].id; 59 | this.updateShipmentPrice(); 60 | } 61 | updateShipmentPrice(){ 62 | const selectedDeliveryOption = this.deliveryOptions.find( 63 | (option)=>option.id === this.selectedOption 64 | ); 65 | if(selectedDeliveryOption){ 66 | this.basketService.updateShippingPrice(selectedDeliveryOption.price); 67 | } 68 | } 69 | goToNextStep(){ 70 | this.updateShipmentPrice(); 71 | //Navigate to review page 72 | this.router.navigate(['/checkout/review']); 73 | //set the current step to review 74 | this.checkoutComponent.setCurrentStep('review'); 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /client/src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NavBarComponent } from './nav-bar/nav-bar.component'; 4 | import { RouterModule } from '@angular/router'; 5 | import { NotFoundComponent } from './not-found/not-found.component'; 6 | import { ServerErrorComponent } from './server-error/server-error.component'; 7 | import { ToastrModule } from 'ngx-toastr'; 8 | import { SectionHeaderComponent } from './section-header/section-header.component'; 9 | import { BreadcrumbModule } from 'xng-breadcrumb'; 10 | 11 | 12 | @NgModule({ 13 | declarations: [ 14 | NavBarComponent, 15 | NotFoundComponent, 16 | ServerErrorComponent, 17 | SectionHeaderComponent 18 | ], 19 | imports: [ 20 | CommonModule, 21 | RouterModule, 22 | ToastrModule.forRoot({ 23 | positionClass: 'toast-bottom-right', 24 | preventDuplicates: true 25 | }), 26 | BreadcrumbModule 27 | ], 28 | exports:[ 29 | NavBarComponent, 30 | NotFoundComponent, 31 | ServerErrorComponent, 32 | SectionHeaderComponent 33 | ] 34 | }) 35 | export class CoreModule { } 36 | -------------------------------------------------------------------------------- /client/src/app/core/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router'; 3 | import { AccountService } from 'src/app/account/account.service'; 4 | 5 | export const canActivate: CanActivateFn = ( 6 | route: ActivatedRouteSnapshot, 7 | state: RouterStateSnapshot 8 | ) => { 9 | const accountService = inject(AccountService); 10 | const router = inject(Router); 11 | if(accountService.isAuthenticated()){ 12 | return true; 13 | } else{ 14 | // Store the attempted URL for redirection 15 | accountService.redirectUrl = state.url; 16 | // Redirect to the Login page with the Redirect URL 17 | return router.createUrlTree(['/account/login'],{ 18 | queryParams: {returnUrl: state.url} 19 | }) 20 | } 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /client/src/app/core/interceptors/error.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | HttpRequest, 4 | HttpHandler, 5 | HttpEvent, 6 | HttpInterceptor 7 | } from '@angular/common/http'; 8 | import { Observable, catchError } from 'rxjs'; 9 | import { Router } from '@angular/router'; 10 | import { ToastrService } from 'ngx-toastr'; 11 | 12 | @Injectable() 13 | export class ErrorInterceptor implements HttpInterceptor { 14 | 15 | constructor(private router: Router, private toastr: ToastrService) {} 16 | 17 | intercept( 18 | request: HttpRequest, 19 | next: HttpHandler): Observable> { 20 | return next.handle(request).pipe( 21 | catchError((error)=>{ 22 | if(error.status === 404){ 23 | this.toastr.error('404 error happened'); 24 | this.router.navigate(['/not-found']); 25 | } 26 | else if(error.status === 500){ 27 | this.toastr.error("500 error happened"); 28 | this.router.navigate(['/server-error']); 29 | } 30 | //Passing the error along to the next error handling middleware 31 | throw error; 32 | }) 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/src/app/core/interceptors/loading.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | HttpRequest, 4 | HttpHandler, 5 | HttpEvent, 6 | HttpInterceptor 7 | } from '@angular/common/http'; 8 | import { Observable, delay, finalize } from 'rxjs'; 9 | import { LoadingService } from '../services/loading.service'; 10 | 11 | @Injectable() 12 | export class LoadingInterceptor implements HttpInterceptor { 13 | constructor(private loadingService: LoadingService) {} 14 | 15 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 16 | this.loadingService.loading(); 17 | 18 | return next.handle(request) 19 | .pipe(delay(1000), 20 | finalize(()=> this.loadingService.idle()) 21 | ); 22 | } 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /client/src/app/core/nav-bar/nav-bar.component.html: -------------------------------------------------------------------------------- 1 | 62 | -------------------------------------------------------------------------------- /client/src/app/core/nav-bar/nav-bar.component.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | background-color: #fff; /* Background color */ 3 | border-bottom: 1px solid #ddd; /* Bottom border */ 4 | /* Ensure the navbar is fixed at the top */ 5 | position: fixed; 6 | top: 0; 7 | width: 100%; 8 | z-index: 1000; 9 | } 10 | 11 | /* Center the brand name */ 12 | .navbar-brand { 13 | font-weight: bold; 14 | align-items: center; 15 | font-weight: bold; 16 | } 17 | 18 | /* Style the navigation links */ 19 | .navbar-nav .nav-item { 20 | margin-right: 10px; /* Space between nav items */ 21 | } 22 | 23 | .navbar-nav .nav-link { 24 | display: flex; 25 | align-items: center; 26 | color: #333; 27 | cursor: pointer; 28 | position: relative; /* Add this line */ 29 | } 30 | 31 | /* Style Font Awesome icons */ 32 | .navbar-nav .nav-link .fa { 33 | margin-right: 5px; /* Space between icon and text */ 34 | } 35 | 36 | /* Style the active link */ 37 | .nav-item.active .nav-link { 38 | color: #007bff; /* Active link text color */ 39 | font-weight: bold; /* Active link text weight */ 40 | } 41 | 42 | .cart-badge { 43 | background-color: rgb(210, 74, 74); 44 | color: white; 45 | border-radius: 50%; 46 | padding: 4px 8px; 47 | font-size: 12px; 48 | position: absolute; /* Use absolute positioning within the relative parent */ 49 | top: -2px; /* Adjust this value to move the badge up */ 50 | right: 2px; /* Adjust this value as needed to align it above the cart icon */ 51 | transform: translateX( 52 | 50% 53 | ); /* Use transform to precisely position the badge */ 54 | z-index: 1; /* Ensure the badge is displayed above other content */ 55 | } 56 | 57 | /* Target the cart icon specifically */ 58 | .navbar-nav .nav-link .fa-shopping-cart { 59 | margin-top: 5px; 60 | margin-bottom: auto; 61 | } 62 | 63 | /* Correct positioning for mobile view and collapsing */ 64 | .navbar-toggler { 65 | border: none; 66 | } 67 | 68 | .navbar-collapse { 69 | justify-content: flex-end; 70 | } 71 | 72 | @media (max-width: 991px) { 73 | .navbar-nav .nav-link { 74 | padding-left: 0.5rem; 75 | padding-right: 0.5rem; 76 | } 77 | } 78 | 79 | /* Ensure the navbar doesn't overlap content */ 80 | body { 81 | padding-top: 56px; /* Adjust to the height of your navbar */ 82 | } -------------------------------------------------------------------------------- /client/src/app/core/nav-bar/nav-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { AccountService } from 'src/app/account/account.service'; 4 | import { BasketService } from 'src/app/basket/basket.service'; 5 | import { Basket, BasketItem } from 'src/app/shared/models/basket'; 6 | import { User } from 'src/app/shared/models/user'; 7 | 8 | @Component({ 9 | selector: 'app-nav-bar', 10 | templateUrl: './nav-bar.component.html', 11 | styleUrls: ['./nav-bar.component.scss'], 12 | }) 13 | export class NavBarComponent implements OnInit { 14 | currentUser$?: Observable; 15 | constructor( 16 | public basketService: BasketService, 17 | public accountService: AccountService 18 | ) {} 19 | 20 | ngOnInit(): void { 21 | this.currentUser$ = this.accountService.currentUser$; 22 | } 23 | 24 | getItemsCount(items: BasketItem[]) { 25 | return items.reduce((sum, item) => sum + item.quantity, 0); 26 | } 27 | logout() { 28 | this.accountService.logout(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client/src/app/core/not-found/not-found.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Page Not Found

5 |

Sorry, the page you are looking for does not exist.

6 | Page Not Found Illustration 7 |
8 | 9 |
10 |
11 |
12 |
-------------------------------------------------------------------------------- /client/src/app/core/not-found/not-found.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/core/not-found/not-found.component.scss -------------------------------------------------------------------------------- /client/src/app/core/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-not-found', 5 | templateUrl: './not-found.component.html', 6 | styleUrls: ['./not-found.component.scss'] 7 | }) 8 | export class NotFoundComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /client/src/app/core/section-header/section-header.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |

{{breadcrumbs[breadcrumbs.length-1].label}}

7 |
8 |
9 | 10 | 11 | {{ breadcrumb | titlecase }} 12 | 13 | 14 |
15 |
16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /client/src/app/core/section-header/section-header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/core/section-header/section-header.component.scss -------------------------------------------------------------------------------- /client/src/app/core/section-header/section-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { BreadcrumbService } from 'xng-breadcrumb'; 3 | 4 | @Component({ 5 | selector: 'app-section-header', 6 | templateUrl: './section-header.component.html', 7 | styleUrls: ['./section-header.component.scss'] 8 | }) 9 | export class SectionHeaderComponent { 10 | constructor(public breadCrumbService: BreadcrumbService){} 11 | } 12 | -------------------------------------------------------------------------------- /client/src/app/core/server-error/server-error.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Server Error

5 |

Oops! Something went wrong. We apologize for the inconvenience.

6 | Server Error Illustration 7 |
8 | 9 |
10 |
11 |
12 |
-------------------------------------------------------------------------------- /client/src/app/core/server-error/server-error.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/core/server-error/server-error.component.scss -------------------------------------------------------------------------------- /client/src/app/core/server-error/server-error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-server-error', 5 | templateUrl: './server-error.component.html', 6 | styleUrls: ['./server-error.component.scss'] 7 | }) 8 | export class ServerErrorComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /client/src/app/core/services/loading.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { NgxSpinnerService } from 'ngx-spinner'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class LoadingService { 8 | loadingReqCount = 0; 9 | 10 | constructor(private spinnerService: NgxSpinnerService) { } 11 | 12 | loading(){ 13 | this.loadingReqCount++; 14 | this.spinnerService.show(); 15 | } 16 | idle(){ 17 | this.loadingReqCount--; 18 | if(this.loadingReqCount<=0){ 19 | this.loadingReqCount = 0; 20 | this.spinnerService.hide(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | first slide 4 | 5 | 6 | second slide 7 | 8 | 9 | third slide 10 | 11 | 12 | third slide 13 | 14 | 15 | third slide 16 | 17 | 18 | third slide 19 | 20 | 21 |
22 |
23 |

Welcome to Sports Center!!!

24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 |

First featurette heading. It’ll blow your mind.

33 |

Some players prefer to have the comfort, power and flexibility to choose a combination of features on a single string. For this, few tennis brands offer Hybrid Strings, which have one string for Mains and a different string for Crosses. Now, YOU can also create your very own hybrid string! 34 | 35 | To avail of this service at Sportsjam.in, add your favourite racquet in your order, along with Tennis String for Mains and Tennis String for Crosses (Hybrid Stringing). Please note that a good understanding of string features are required to achieve an optimum balance between comfort, durability, control and power

36 |
37 |
38 | first slide 39 |
40 |
41 | 42 |
43 | 44 |
45 |
46 |

Oh yeah, it’s that good. See for yourself.

47 |

Looking for Hiking Boots, Walking Shoes, Sandals or Outdoor Wear? Choose from a large range today.

48 |
49 |
50 | first slide 51 |
52 |
53 | 54 |
55 | 56 |
57 |
58 |

And lastly, this one. Checkmate.

59 |

A huge collection of running shoes and sports equipments

60 |
61 |
62 | first slide 63 |
64 |
65 | 66 |
-------------------------------------------------------------------------------- /client/src/app/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/home/home.component.scss -------------------------------------------------------------------------------- /client/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /client/src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HomeComponent } from './home.component'; 4 | import { SharedModule } from '../shared/shared.module'; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | HomeComponent 9 | ], 10 | imports: [ 11 | CommonModule, 12 | SharedModule 13 | ], 14 | exports: [ 15 | HomeComponent 16 | ] 17 | }) 18 | export class HomeModule { } 19 | -------------------------------------------------------------------------------- /client/src/app/shared/components/order-summary/order-summary.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Order Summary

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
Subtotal:{{ basketTotal.subtotal | currency:"INR" }}
Shipping:{{ basketTotal.shipping | currency:"INR" }}
Total:{{ basketTotal.total | currency:"INR" }}
19 |
20 | -------------------------------------------------------------------------------- /client/src/app/shared/components/order-summary/order-summary.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/shared/components/order-summary/order-summary.component.scss -------------------------------------------------------------------------------- /client/src/app/shared/components/order-summary/order-summary.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { BasketService } from 'src/app/basket/basket.service'; 3 | 4 | @Component({ 5 | selector: 'app-order-summary', 6 | templateUrl: './order-summary.component.html', 7 | styleUrls: ['./order-summary.component.scss'] 8 | }) 9 | export class OrderSummaryComponent { 10 | constructor(public basketService: BasketService){} 11 | } 12 | -------------------------------------------------------------------------------- /client/src/app/shared/components/pagination-header/pagination-header.component.html: -------------------------------------------------------------------------------- 1 |
2 | Showing 3 | {{(currentPage - 1) * pageSize + 1}} 4 | - {{ 5 | currentPage * pageSize 6 | > totalElements 7 | ? totalElements 8 | : currentPage * pageSize 9 | }} 10 | of {{totalElements}} Results 11 | 12 | There are 0 Results 13 |
-------------------------------------------------------------------------------- /client/src/app/shared/components/pagination-header/pagination-header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/shared/components/pagination-header/pagination-header.component.scss -------------------------------------------------------------------------------- /client/src/app/shared/components/pagination-header/pagination-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-pagination-header', 5 | templateUrl: './pagination-header.component.html', 6 | styleUrls: ['./pagination-header.component.scss'] 7 | }) 8 | export class PaginationHeaderComponent { 9 | @Input() totalElements: number = 0; 10 | @Input() currentPage: number = 1; 11 | @Input() pageSize: number = 10; 12 | } 13 | -------------------------------------------------------------------------------- /client/src/app/shared/components/pagination/pagination.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 10 |
11 |
12 |
-------------------------------------------------------------------------------- /client/src/app/shared/components/pagination/pagination.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/shared/components/pagination/pagination.component.scss -------------------------------------------------------------------------------- /client/src/app/shared/components/pagination/pagination.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-pagination', 5 | templateUrl: './pagination.component.html', 6 | styleUrls: ['./pagination.component.scss'] 7 | }) 8 | export class PaginationComponent { 9 | @Input() totalElements: number = 0; 10 | @Input() pageSize: number = 10; 11 | @Input() currentPage: number = 1; 12 | @Output() pageChanged = new EventEmitter(); 13 | } 14 | -------------------------------------------------------------------------------- /client/src/app/shared/models/address.ts: -------------------------------------------------------------------------------- 1 | export interface Address { 2 | fname: String; 3 | lname: String; 4 | street: String; 5 | city: String; 6 | state: String; 7 | zipcode: String 8 | } -------------------------------------------------------------------------------- /client/src/app/shared/models/basket.ts: -------------------------------------------------------------------------------- 1 | import { createId } from "@paralleldrive/cuid2" 2 | 3 | export interface Basket { 4 | id: string 5 | items: BasketItem[] 6 | } 7 | 8 | export interface BasketItem { 9 | id: number 10 | name: string 11 | description: string 12 | price: number 13 | pictureUrl: string 14 | productBrand: string 15 | productType: string 16 | quantity: number 17 | } 18 | 19 | export class Basket implements Basket{ 20 | id = createId(); 21 | items: BasketItem[] = []; 22 | } 23 | 24 | export interface BasketTotals{ 25 | shipping: number; 26 | subtotal: number; 27 | total: number; 28 | } -------------------------------------------------------------------------------- /client/src/app/shared/models/brand.ts: -------------------------------------------------------------------------------- 1 | export interface Brand{ 2 | id: number; 3 | name: string; 4 | } -------------------------------------------------------------------------------- /client/src/app/shared/models/deliveryOptions.ts: -------------------------------------------------------------------------------- 1 | export interface DeliveryOption { 2 | id: number; 3 | name: string; 4 | deliveryTime: string; 5 | description: string; 6 | price: number; 7 | } -------------------------------------------------------------------------------- /client/src/app/shared/models/product.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: number 3 | name: string 4 | description: string 5 | price: number 6 | pictureUrl: string 7 | productType: string 8 | productBrand: string 9 | } 10 | -------------------------------------------------------------------------------- /client/src/app/shared/models/productData.ts: -------------------------------------------------------------------------------- 1 | import { Product } from "./product"; 2 | 3 | export interface ProductData { 4 | content: Product[]; 5 | pageable: { 6 | pageNumber: number; 7 | pageSize: number; 8 | }; 9 | totalElements: number; 10 | } -------------------------------------------------------------------------------- /client/src/app/shared/models/storeData.ts: -------------------------------------------------------------------------------- 1 | import { Brand } from "./brand"; 2 | import { Product } from "./product"; 3 | import { Type } from "./type"; 4 | 5 | export interface StoreData { 6 | products: Product[]; 7 | brands: Brand[]; 8 | types: Type[]; 9 | selectedBrand: Brand | null; 10 | selectedType: Type | null; 11 | selectedSort: string; 12 | search: string; 13 | currentPage: number; 14 | page?: number; 15 | pageable: any; 16 | totalElements: number; 17 | pageSize: number; 18 | } -------------------------------------------------------------------------------- /client/src/app/shared/models/type.ts: -------------------------------------------------------------------------------- 1 | export interface Type{ 2 | id: number; 3 | name: string; 4 | } -------------------------------------------------------------------------------- /client/src/app/shared/models/user.ts: -------------------------------------------------------------------------------- 1 | export interface User{ 2 | username: string; 3 | token: string; 4 | } -------------------------------------------------------------------------------- /client/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { MatInputModule } from '@angular/material/input'; 3 | import { CommonModule } from '@angular/common'; 4 | import { PaginationModule } from 'ngx-bootstrap/pagination'; 5 | import { PaginationHeaderComponent } from './components/pagination-header/pagination-header.component'; 6 | import { PaginationComponent } from './components/pagination/pagination.component'; 7 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 8 | import { CarouselModule } from 'ngx-bootstrap/carousel'; 9 | import { OrderSummaryComponent } from './components/order-summary/order-summary.component'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | PaginationHeaderComponent, 14 | PaginationComponent, 15 | OrderSummaryComponent 16 | ], 17 | imports: [ 18 | CommonModule, 19 | FormsModule, 20 | ReactiveFormsModule, 21 | CarouselModule.forRoot(), 22 | PaginationModule.forRoot(), 23 | MatInputModule 24 | ],exports:[ 25 | PaginationModule, 26 | PaginationHeaderComponent, 27 | PaginationComponent, 28 | CarouselModule, 29 | OrderSummaryComponent, 30 | FormsModule, 31 | ReactiveFormsModule, 32 | MatInputModule 33 | ] 34 | }) 35 | export class SharedModule { } 36 | -------------------------------------------------------------------------------- /client/src/app/store/product-details/product-details.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 |
8 |
9 |
10 | 11 |

{{ product.name }}

12 |

{{ product.description }}

13 |
14 |
15 | Price: 16 | {{ product.price | currency: 'INR' }} 17 |
18 |
19 | Brand: 20 | {{ product.productBrand }} 21 |
22 |
23 | Type: 24 | {{ product.productType }} 25 |
26 | 27 |
28 | Quantity: 29 | 32 | {{ quantity }} 33 | 36 |
37 |
38 | 39 | 40 | 41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /client/src/app/store/product-details/product-details.component.scss: -------------------------------------------------------------------------------- 1 | .product-image-container { 2 | text-align: center; 3 | } 4 | 5 | .product-image { 6 | max-width: 100%; 7 | border: 1px solid #ddd; 8 | border-radius: 5px; 9 | padding: 10px; 10 | } 11 | 12 | .product-title { 13 | font-size: 24px; 14 | font-weight: bold; 15 | margin-bottom: 10px; 16 | } 17 | 18 | .product-description { 19 | font-size: 16px; 20 | margin-bottom: 20px; 21 | } 22 | 23 | .product-info { 24 | margin-bottom: 20px; 25 | } 26 | 27 | .product-info-item { 28 | display: flex; 29 | align-items: center; 30 | margin-bottom: 10px; 31 | 32 | strong { 33 | margin-right: 10px; 34 | } 35 | } 36 | 37 | .product-price { 38 | font-weight: bold; 39 | font-size: 18px; 40 | } 41 | 42 | .quantity-control { 43 | display: flex; 44 | align-items: center; 45 | 46 | strong { 47 | margin-right: 10px; 48 | } 49 | 50 | .quantity-button { 51 | background: #007bff; 52 | color: #fff; 53 | border: none; 54 | padding: 5px 10px; 55 | border-radius: 5px; 56 | cursor: pointer; 57 | font-size: 18px; 58 | 59 | i { 60 | font-size: 16px; 61 | } 62 | } 63 | 64 | .quantity { 65 | font-size: 18px; 66 | font-weight: bold; 67 | margin: 0 10px; 68 | } 69 | } 70 | 71 | .add-to-cart-button { 72 | background: #28a745; 73 | color: #fff; 74 | border: none; 75 | border-radius: 5px; 76 | padding: 10px 20px; 77 | font-size: 18px; 78 | cursor: pointer; 79 | margin-right: 10px; 80 | 81 | &:hover { 82 | background: #218838; 83 | } 84 | } 85 | 86 | .go-back-button { 87 | background: #6c757d; 88 | color: #fff; 89 | border: none; 90 | border-radius: 5px; 91 | padding: 10px 20px; 92 | font-size: 18px; 93 | cursor: pointer; 94 | 95 | &:hover { 96 | background: #5a6268; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /client/src/app/store/product-details/product-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Product } from 'src/app/shared/models/product'; 3 | import { StoreService } from '../store.service'; 4 | import { ActivatedRoute } from '@angular/router'; 5 | import { BreadcrumbService } from 'xng-breadcrumb'; 6 | import { BasketService } from 'src/app/basket/basket.service'; 7 | import { ToastrService } from 'ngx-toastr'; 8 | 9 | @Component({ 10 | selector: 'app-product-details', 11 | templateUrl: './product-details.component.html', 12 | styleUrls: ['./product-details.component.scss'] 13 | }) 14 | export class ProductDetailsComponent implements OnInit { 15 | product?: Product; 16 | quantity: number = 1; 17 | 18 | constructor( 19 | private storeService: StoreService, 20 | private activateRoute: ActivatedRoute, 21 | private breadcrumb: BreadcrumbService, 22 | private basketService: BasketService, 23 | private toastr: ToastrService){} 24 | 25 | ngOnInit(): void { 26 | this.loadProduct(); 27 | } 28 | 29 | loadProduct(){ 30 | const id = this.activateRoute.snapshot.paramMap.get('id'); 31 | if(id){ 32 | this.storeService.getProduct(+id).subscribe({ 33 | next: product=> { 34 | this.product = product 35 | this.breadcrumb.set('@productName', product.name); 36 | }, 37 | error: error => console.log(error) 38 | }); 39 | } 40 | } 41 | addToCart() { 42 | if (this.product) { 43 | this.basketService.addItemToBasket(this.product, this.quantity); 44 | this.toastr.success('Item added to cart'); 45 | } 46 | } 47 | extractImageName(): string | null{ 48 | if(this.product && this.product.pictureUrl){ 49 | const parts = this.product.pictureUrl.split('/'); 50 | if(parts.length>0){ 51 | return parts[parts.length-1]; // fetch the last part after / 52 | } 53 | } 54 | return null; 55 | } 56 | incrementQuantity(){ 57 | this.quantity++; 58 | } 59 | decrementQuantity(){ 60 | if(this.quantity>1){ 61 | this.quantity--; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /client/src/app/store/product-item/product-item.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
{{ product.name }}
6 |

Price: {{ product.price | currency:"INR" }}

7 | 8 | 9 |
10 | 11 | 12 |
13 |
14 |
15 |
-------------------------------------------------------------------------------- /client/src/app/store/product-item/product-item.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/store/product-item/product-item.component.scss -------------------------------------------------------------------------------- /client/src/app/store/product-item/product-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { BasketService } from 'src/app/basket/basket.service'; 3 | import { Product } from 'src/app/shared/models/product'; 4 | 5 | @Component({ 6 | selector: 'app-product-item', 7 | templateUrl: './product-item.component.html', 8 | styleUrls: ['./product-item.component.scss'] 9 | }) 10 | export class ProductItemComponent { 11 | @Input() product:Product | null = null; 12 | 13 | constructor(private basketService: BasketService){} 14 | 15 | additemToBasket(){ 16 | this.product && this.basketService.addItemToBasket(this.product); 17 | } 18 | //Method to extract the image name from pictureUrl 19 | extractImageName(): string | null{ 20 | if(this.product && this.product.pictureUrl){ 21 | const parts = this.product.pictureUrl.split('/'); 22 | if(parts.length>0){ 23 | return parts[parts.length-1]; //It will return the last part 24 | } 25 | } 26 | return null; //if its invalid, then return null 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/app/store/store-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { StoreComponent } from './store.component'; 4 | import { ProductDetailsComponent } from './product-details/product-details.component'; 5 | 6 | const routes: Routes = [ 7 | {path:'', component: StoreComponent}, 8 | {path:':id', component: ProductDetailsComponent, data:{breadcrumb:{alias:'productName'}}}, 9 | ] 10 | @NgModule({ 11 | declarations: [], 12 | imports: [ 13 | RouterModule.forChild(routes) 14 | ], 15 | exports: [ 16 | RouterModule 17 | ] 18 | }) 19 | export class StoreRoutingModule { } 20 | -------------------------------------------------------------------------------- /client/src/app/store/store.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Welcome to Sports Center

5 |

Your one-stop destination for all your shopping needs.

6 |
7 | 13 | 18 | 19 |
20 | 21 | 22 |
Sort
23 | 31 | 32 |
Brands
33 |
    34 |
  • 45 | {{ brand.name }} 46 |
  • 47 |
48 |
Types
49 |
    50 |
  • 61 | {{ type.name }} 62 |
  • 63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 |
71 | 72 | 73 |
74 | 81 | 84 | 87 |
88 |
89 |
90 |
91 |
95 | 96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | 109 | -------------------------------------------------------------------------------- /client/src/app/store/store.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/app/store/store.component.scss -------------------------------------------------------------------------------- /client/src/app/store/store.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { StoreService } from './store.service'; 3 | import { Product } from '../shared/models/product'; 4 | import { Brand } from '../shared/models/brand'; 5 | import { Type } from '../shared/models/type'; 6 | import { PageChangedEvent } from 'ngx-bootstrap/pagination'; 7 | import { StoreModelService } from './store.model.service'; 8 | import { ToastrService } from 'ngx-toastr'; 9 | 10 | @Component({ 11 | selector: 'app-store', 12 | templateUrl: './store.component.html', 13 | styleUrls: ['./store.component.scss'], 14 | }) 15 | export class StoreComponent implements OnInit { 16 | constructor( 17 | private storeService: StoreService, 18 | public storeData: StoreModelService, 19 | private toastr: ToastrService 20 | ) {} 21 | 22 | @Input() title: string = ''; 23 | ngOnInit() { 24 | // Initialize selected brand and type to "All" 25 | this.storeData.selectedBrand = { id: 0, name: 'All' }; 26 | this.storeData.selectedType = { id: 0, name: 'All' }; 27 | 28 | // Check if both selectedBrand and selectedType are "All" before making the initial fetch 29 | if (this.storeData.selectedBrand.id === 0 && this.storeData.selectedType.id === 0) { 30 | this.fetchProducts(); // Fetch all records without brand and type filtering 31 | } else { 32 | // Fetch products with the selected brand and type 33 | this.fetchProducts(); 34 | } 35 | this.getBrands(); 36 | this.getTypes(); 37 | } 38 | 39 | pageChanged(event: PageChangedEvent): void { 40 | // Check if the page has actually changed 41 | if (event.page !== this.storeData.currentPage) { 42 | this.storeData.currentPage = event.page; 43 | this.fetchProducts(this.storeData.currentPage); 44 | } 45 | } 46 | 47 | 48 | fetchProducts(page: number = 1) { 49 | // Calculate the backend page (subtract 1) 50 | const backendPage = page - 1; 51 | 52 | // Pass the selected brand/type ids 53 | const brandId = this.storeData.selectedBrand?.id; 54 | const typeId = this.storeData.selectedType?.id; 55 | 56 | // Construct the base URL 57 | let url = `${this.storeService.apiUrl}?`; 58 | 59 | // Check the brand and type 60 | if (brandId && brandId !== 0) { 61 | url += `brandId=${brandId}&`; 62 | } 63 | 64 | if (typeId && typeId !== 0) { 65 | url += `typeId=${typeId}&`; 66 | } 67 | 68 | // Search 69 | if (this.storeData.search) { 70 | url += `keyword=${this.storeData.search}&`; 71 | } 72 | 73 | // Append backendPage and size parameters to the URL 74 | url += `page=${backendPage}&size=${this.storeData.pageSize}`; 75 | 76 | // Include sorting parameters only when selectedSort is not empty 77 | if (this.storeData.selectedSort !== 'asc') { 78 | url += `&sort=name&order=${this.storeData.selectedSort}`; 79 | } 80 | 81 | this.storeService.getProducts(brandId, typeId, url).subscribe({ 82 | next: (data) => { 83 | this.storeData.products = data.content; 84 | this.storeData.pageable = data.pageable; 85 | this.storeData.totalElements = data.totalElements; 86 | this.storeData.currentPage = data.pageable.pageNumber + 1; // Adjust the currentPage 87 | this.toastr.success('Products Fetched!!!'); 88 | }, 89 | error: (error) => { 90 | this.toastr.error('Error fetching data:'); 91 | console.log(error); 92 | }, 93 | }); 94 | } 95 | 96 | 97 | getBrands(){ 98 | this.storeService.getBrands().subscribe({ 99 | next:(response)=>(this.storeData.brands = [{id: 0, name:'All'}, ...response]), 100 | error:(error) =>console.log(error) 101 | }); 102 | } 103 | getTypes(){ 104 | this.storeService.getTypes().subscribe({ 105 | next:(response)=>(this.storeData.types = [{id: 0, name:'All'}, ...response]), 106 | error:(error) =>console.log(error) 107 | }); 108 | } 109 | 110 | selectBrand(brand: Brand){ 111 | //update the selected brand and fetch the products 112 | this.storeData.selectedBrand = brand; 113 | this.fetchProducts(); 114 | } 115 | 116 | selectType(type: Type){ 117 | //update the selected brand and fetch the products 118 | this.storeData.selectedType = type; 119 | this.fetchProducts(); 120 | } 121 | onSortChange(){ 122 | this.fetchProducts(); 123 | } 124 | onSearch(){ 125 | this.fetchProducts(); 126 | } 127 | onReset(){ 128 | this.storeData.search = ''; 129 | this.fetchProducts(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /client/src/app/store/store.model.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Brand } from '../shared/models/brand'; 3 | import { Product } from '../shared/models/product'; 4 | import { StoreData } from '../shared/models/storeData'; 5 | import { Type } from '../shared/models/type'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class StoreModelService implements StoreData { 11 | products: Product[] = []; 12 | brands: Brand[] = []; 13 | types: Type[] = []; 14 | selectedBrand: Brand | null = null; 15 | selectedType: Type | null = null; 16 | selectedSort = 'asc'; //default value 17 | search = ''; 18 | currentPage = 1; 19 | page?: number; 20 | pageable: any; // This will hold pagination information 21 | totalElements: number = 70; // Total number of elements 22 | pageSize: number = 10; // Number of items per page 23 | } 24 | -------------------------------------------------------------------------------- /client/src/app/store/store.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { StoreComponent } from './store.component'; 4 | import { ProductItemComponent } from './product-item/product-item.component'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { SharedModule } from '../shared/shared.module'; 7 | import { ProductDetailsComponent } from './product-details/product-details.component'; 8 | import { RouterModule } from '@angular/router'; 9 | import { StoreRoutingModule } from './store-routing.module'; 10 | 11 | 12 | @NgModule({ 13 | declarations: [ 14 | StoreComponent, 15 | ProductItemComponent, 16 | ProductDetailsComponent 17 | ], 18 | imports: [ 19 | CommonModule, 20 | StoreRoutingModule, 21 | FormsModule, 22 | RouterModule, 23 | SharedModule 24 | ], 25 | exports:[ 26 | StoreComponent 27 | ] 28 | }) 29 | export class StoreModule { } 30 | -------------------------------------------------------------------------------- /client/src/app/store/store.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, of } from 'rxjs'; 3 | import { ProductData } from '../shared/models/productData'; 4 | import { HttpClient } from '@angular/common/http'; 5 | import { Brand } from '../shared/models/brand'; 6 | import { Product } from '../shared/models/product'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class StoreService { 12 | 13 | constructor(private http: HttpClient) { } 14 | public apiUrl = 'http://localhost:8080/api/products'; 15 | 16 | getProducts(brandId?: number, typeId?: number, url?: string): Observable{ 17 | // Construct the base URL 18 | const apiUrl = url || this.apiUrl; 19 | 20 | return this.http.get(apiUrl); 21 | } 22 | 23 | getProduct(id: number){ 24 | return this.http.get(this.apiUrl + "/"+ id); 25 | } 26 | 27 | getBrands() { 28 | const url = `${this.apiUrl}/brands` 29 | return this.http.get(url); 30 | } 31 | 32 | getTypes() { 33 | const url = `${this.apiUrl}/types` 34 | return this.http.get(url); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/.gitkeep -------------------------------------------------------------------------------- /client/src/assets/images/bslide1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/bslide1.jpg -------------------------------------------------------------------------------- /client/src/assets/images/bslide2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/bslide2.jpg -------------------------------------------------------------------------------- /client/src/assets/images/bslide3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/bslide3.jpg -------------------------------------------------------------------------------- /client/src/assets/images/connection-refused.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/connection-refused.png -------------------------------------------------------------------------------- /client/src/assets/images/hero1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/hero1.jpg -------------------------------------------------------------------------------- /client/src/assets/images/hero2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/hero2.jpg -------------------------------------------------------------------------------- /client/src/assets/images/hero3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/hero3.jpg -------------------------------------------------------------------------------- /client/src/assets/images/hero4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/hero4.jpg -------------------------------------------------------------------------------- /client/src/assets/images/hero5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/hero5.jpg -------------------------------------------------------------------------------- /client/src/assets/images/hero6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/hero6.jpg -------------------------------------------------------------------------------- /client/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/logo.png -------------------------------------------------------------------------------- /client/src/assets/images/page-not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/page-not-found.png -------------------------------------------------------------------------------- /client/src/assets/images/products/Nike-Football-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/Nike-Football-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/Nike-Football-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/Nike-Football-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/Nike-Football-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/Nike-Football-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/adidas_football-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/adidas_football-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/adidas_football-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/adidas_football-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/adidas_football-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/adidas_football-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/adidas_shoe-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/adidas_shoe-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/adidas_shoe-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/adidas_shoe-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/adidas_shoe-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/adidas_shoe-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/asics_shoe-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/asics_shoe-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/asics_shoe-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/asics_shoe-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/asics_shoe-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/asics_shoe-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat-kitback-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat-kitback-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat-kitback-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat-kitback-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat-kitback-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat-kitback-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat-racket-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat-racket-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat-racket-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat-racket-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat-racket-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat-racket-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat_shoe-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat_shoe-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat_shoe-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat_shoe-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/babolat_shoe-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/babolat_shoe-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/puma_shoe-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/puma_shoe-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/puma_shoe-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/puma_shoe-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/puma_shoe-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/puma_shoe-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/victor-racket-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/victor-racket-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/victor-racket-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/victor-racket-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/victor-racket-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/victor-racket-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/victor_shoe-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/victor_shoe-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/victor_shoe-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/victor_shoe-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-badminton-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-badminton-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-badminton-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-badminton-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-badminton-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-badminton-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-kitback-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-kitback-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-kitback-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-kitback-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-kitback-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-kitback-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-racket-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-racket-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-racket-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-racket-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex-racket-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex-racket-3.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex_shoe-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex_shoe-1.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex_shoe-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex_shoe-2.png -------------------------------------------------------------------------------- /client/src/assets/images/products/yonex_shoe-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/products/yonex_shoe-3.png -------------------------------------------------------------------------------- /client/src/assets/images/server-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulsahay19/JavaFullstackEcommerce/eab770d02bb20f9a081fc3f0efd3cc70a3306448/client/src/assets/images/server-error.png -------------------------------------------------------------------------------- /client/src/favicon.ico: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR?�~� pHYs  ��~�fIDATH��WKLQ���̔�uG�� e�n�. 6qcb�l?���D`�F#� Ku�F 1Qc� 4 | ��!���� ��C�P�|B?$���ܱ3����I&}��}�̽s�[*�ɀU�A��K��yx�gY�Ajq��3L Š���˫�OD�4��3Ϗ:X�3��o�P J�ğo#IH�a����,{>1/�2$�R AR]�)w��?�sZw^��q�Y�m_��e���r[8�^� 5 | �&p��-���A}c��- ������!����2_) E�)㊪j���v�m��ZOi� g�nW�{�n.|�e2�a&�0aŸ����be�̀��C�fˤE%-{�ֺ��׮C��N��jXi�~c�C,t��T�����r�{� �L)s��V��6%�(�#ᤙ!�]��H�ҐH$R���^R�A�61(?Y舚�>���(Z����Qm�L2�K�ZIc�� 6 | ���̧�C��2!⅄�(����"�Go��>�q��=��$%�z`ѯ��T�&����PHh�Z!=���z��O��������,*VVV�1�f*CJ�]EE��K�k��d�#5���`2yT!�}7���߈~�,���zs�����y�T��V������D��C2�G��@%̑72Y�޾{oJ�"@��^h�~ ��fĬ�!a�D��6���Ha|�3��� [>�����]7U2п���]�ė 7 | ��PU� �.Wejq�in�g��+p<ߺQH����總j[������.��� Q���p _�K�� 1(��+��bB8�\ra 8 | �́�v.l���(���ǽ�w���L��w�8�C��IEND�B`� -------------------------------------------------------------------------------- /client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); 8 | -------------------------------------------------------------------------------- /client/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | label.xng-breadcrumb-trail{ 4 | color: blue; 5 | } -------------------------------------------------------------------------------- /client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "ES2022", 20 | "module": "ES2022", 21 | "useDefineForClassFields": false, 22 | "lib": [ 23 | "ES2022", 24 | "dom" 25 | ] 26 | }, 27 | "angularCompilerOptions": { 28 | "enableI18nLegacyMessageIdFormat": false, 29 | "strictInjectionParameters": true, 30 | "strictInputAccessModifiers": true, 31 | "strictTemplates": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | ecommerce-mysql: 5 | image: mysql 6 | restart: always 7 | environment: 8 | MYSQL_ROOT_PASSWORD: 'password' 9 | ports: 10 | - "3306:3306" 11 | volumes: 12 | - ./data.sql:/docker-entrypoint-initdb.d/data.sql 13 | - mysql-data:/var/lib/mysql 14 | 15 | ecommerce-redis: 16 | image: "redis:latest" 17 | container_name: "my-redis-container" 18 | ports: 19 | - "6379:6379" 20 | volumes: 21 | - redis-data:/data 22 | 23 | volumes: 24 | redis-data: 25 | mysql-data: 26 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.0 9 | 10 | 11 | com.ecoomerce 12 | sportscenter 13 | 0.0.1-SNAPSHOT 14 | sportscenter 15 | Sports center website 16 | 17 | 21 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-jpa 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | com.mysql 31 | mysql-connector-j 32 | runtime 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | true 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-data-redis 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-security 48 | 49 | 50 | 51 | io.jsonwebtoken 52 | jjwt-api 53 | 0.11.5 54 | 55 | 56 | io.jsonwebtoken 57 | jjwt-impl 58 | 0.11.5 59 | runtime 60 | 61 | 62 | io.jsonwebtoken 63 | jjwt-jackson 64 | 0.11.5 65 | runtime 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-test 71 | test 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-maven-plugin 80 | 81 | 82 | 83 | org.projectlombok 84 | lombok 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Building FullStack ECommerce App using SpringBoot & Angular 2 | 3 | ## Introduction: 4 | Are you ready to embark on a transformative journey into the world of full-stack e-commerce development? 🔥 Brace yourself for an exhilarating adventure, where you'll harness the dynamic duo of Java21 and SpringBoot 3.2.0 to craft cutting-edge online stores that redefine the digital shopping experience! 🌐💻 5 | 6 | ## 🌟 Java21: The Future of Java 7 | Java21, the latest incarnation of the Java programming language, has taken the tech world by storm. With its enhanced performance, feature-rich capabilities, and improved developer productivity, it's the perfect foundation for your journey into e-commerce mastery. 💪💼 8 | 9 | ## 🚀 SpringBoot 3.2.0: Turbocharge Your Development 10 | SpringBoot 3.2.0, the latest iteration of the industry-favorite SpringBoot framework, is your turbocharger for full-stack e-commerce development. Its robust back-end capabilities, seamless integration with Java21, and extensive libraries make it the go-to choice for building scalable and secure e-commerce platforms. 🌐🔒 11 | 12 | ## Course Highlights: 13 | 📚 In this immersive 16+ hour course, you'll dive headfirst into a world of technical marvels and industry best practices. Here's a sneak peek at what awaits you: 14 | 1. 🧠 Mastery of SpringBoot Essentials and Advanced Features: Unleash the full potential of SpringBoot with in-depth coverage of its essential features and advanced functionalities. 15 | 2. 📦 Efficient Data Access with Spring Data JPA: Learn how to handle data like a pro, using Spring Data JPA to streamline your data access processes. 16 | 3. 🔐 Secure User Authentication and Authorization: Ensure your e-commerce platform's security with Spring Security's state-of-the-art user authentication and authorization mechanisms. 17 | 4. 🌐 Creating RESTful APIs with Spring Boot: Seamlessly communicate data between your back-end and front-end using RESTful APIs. 18 | 5. 🅰️ Angular Best Practices: Master Angular's dynamic front-end framework, including routing, lazy loading, and reactive forms, for a flawless user experience. 19 | 6. 🎨 Styling with Bootstrap and Font Awesome: Elevate your platform's aesthetics with Bootstrap and Font Awesome, creating a polished user interface. 20 | 21 | ## Who Should Enroll? 22 | This course caters to tech enthusiasts at every level of expertise: 23 | - 🌱 Interns: Lay a strong foundation for your tech career by applying academic knowledge to real-world e-commerce projects. 24 | - 👨‍💻 Junior Developers: Upgrade your skill set with advanced full-stack e-commerce development practices. 25 | - 👩‍💻 Senior Developers: Lead the way in e-commerce application development with cutting-edge Java21 and SpringBoot 3.2.0. 26 | - 🚀 Tech Leads: Direct high-impact projects with confidence, leveraging the power of SpringBoot and Angular. 27 | - 🏗️ Architects: Infuse innovation into your design strategies for scalable and dynamic e-commerce solutions. 28 | - 🌟 Senior Architects: Pioneer robust architecture, leading transformative projects with your expertise. 29 | Course Stats and Benefits: 30 | 31 | 🏁 Let's take a closer look at what you'll gain from this course: 32 | 🚀 Fast-Track Your Learning: Dive into an engaging course designed for maximum comprehension and superior learning outcomes. 33 | - 📚 15 Comprehensive Sections: Explore the depths of full-stack e-commerce development, from fundamentals to advanced design patterns. 34 | - 📹 185+ In-Depth Videos: Each video is a stepping stone, providing clear explanations, step-by-step instructions, and real-world applications. 35 | - ⏱️ 16+ Hours of Content: Immerse yourself in a comprehensive curriculum that fits your schedule, accessible anytime, anywhere. 36 | - 📝 Multiple Choice Questions: Solidify your knowledge with quizzes carefully designed to reinforce and test your learning. 37 | - 🔄 Yearly Updates: Stay in sync with the latest trends and best practices in SpringBoot and Angular as technology evolves. 38 | - 🔄 Lifetime Access: Your one-time enrollment grants you unrestricted access to all current and future course content - forever. 39 | 40 | ## Enroll here:- https://www.udemy.com/course/building-fullstack-ecommerce-app-using-springboot-angular/?couponCode=OFFER-PRICE 41 | 42 | ## Solution Structure - Server Side 43 | 44 | ![1st](https://github.com/rahulsahay19/Blog-Images/assets/3886381/3a62b6c5-6d93-464d-b839-fe59e029f5e0) 45 | 46 | Ignore the typo in the solution name. ecoomerce → ecommerce. 47 | 48 | ## Solution Structure - Client Side 49 | 50 | ![2nd](https://github.com/rahulsahay19/Blog-Images/assets/3886381/828864a9-2828-49be-bb29-81d1a3a7bef2) 51 | 52 | ## Application Flow 53 | 54 | ![3rd](https://github.com/rahulsahay19/Blog-Images/assets/3886381/07216dbf-893e-44d5-8bb0-08279e2ad7d1) 55 | 56 | ![4th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/ae496c3d-9a2f-4b28-888e-d2529153c0e6) 57 | 58 | ![5th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/c172e9d1-ef2c-48a5-b39b-43d061302494) 59 | 60 | ![6th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/8ba3203b-180e-4bdc-bf95-9d0244807626) 61 | 62 | ![7th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/67759f82-82c6-441c-a9f9-82504b14b3ee) 63 | 64 | ![8th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/3efeaaae-f61f-4775-8d0e-a4621e2e2e83) 65 | 66 | ![9th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/7d90509f-3697-430b-9040-0782bb80edfb) 67 | 68 | ![10th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/9481896f-3fa2-4114-9173-df523444fa6b) 69 | 70 | ![11th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/c486930a-2928-4305-968c-134d9479c321) 71 | 72 | ![12th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/b12573a7-bac8-47de-9ca5-c7747273b0e7) 73 | 74 | ![13th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/f9b42116-2782-4f14-8e41-e08ae0a45a6f) 75 | 76 | ![14th](https://github.com/rahulsahay19/Blog-Images/assets/3886381/1b29163b-480a-4b9e-a8e3-c362874bb720) 77 | 78 | ## Enroll here:- https://www.udemy.com/course/building-fullstack-ecommerce-app-using-springboot-angular/?couponCode=OFFER-PRICE 79 | 80 | Thank You for Exploring This Course! 81 | 82 | We're thrilled you've taken the time to explore what this comprehensive course on SpringBoot & Angular has to offer. Your interest in advancing your skills and knowledge in the FullStack field is commendable, and we're excited to be a part of your learning journey. -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/SportscenterApplication.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SportscenterApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SportscenterApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | @EnableWebMvc 10 | public class CorsConfig implements WebMvcConfigurer { 11 | @Override 12 | public void addCorsMappings(CorsRegistry registry) { 13 | registry.addMapping("/*/**") 14 | .allowedOrigins("*") 15 | .allowedMethods("GET", "POST", "PUT", "DELETE") 16 | .allowedHeaders("*"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/config/MyConfig.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.core.userdetails.User; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 11 | 12 | @Configuration 13 | public class MyConfig { 14 | @Bean 15 | public UserDetailsService userDetailsService(){ 16 | UserDetails userDetails = User.builder() 17 | .username("rahul") 18 | .password(passwordEncoder().encode("Password")) 19 | .roles("admin") 20 | .build(); 21 | return new InMemoryUserDetailsManager(userDetails); 22 | } 23 | @Bean 24 | public PasswordEncoder passwordEncoder(){ 25 | return new BCryptPasswordEncoder(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.config; 2 | 3 | import com.ecoomerce.sportscenter.security.JwtAuthenticationEntryPoint; 4 | import com.ecoomerce.sportscenter.security.JwtAuthenticationFilter; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 11 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 12 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 13 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 14 | import org.springframework.security.config.http.SessionCreationPolicy; 15 | import org.springframework.security.web.SecurityFilterChain; 16 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 17 | 18 | @Configuration 19 | @EnableMethodSecurity() 20 | public class SecurityConfig { 21 | private final JwtAuthenticationEntryPoint entryPoint; 22 | private final JwtAuthenticationFilter filter; 23 | 24 | @Autowired 25 | private AuthenticationManagerBuilder authenticationManagerBuilder; 26 | 27 | public SecurityConfig(JwtAuthenticationEntryPoint entryPoint, JwtAuthenticationFilter filter) { 28 | this.entryPoint = entryPoint; 29 | this.filter = filter; 30 | } 31 | 32 | @Bean 33 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ 34 | http.csrf(AbstractHttpConfigurer::disable) 35 | .authorizeHttpRequests((requests)-> requests 36 | .requestMatchers("/products").authenticated() 37 | .requestMatchers("/auth/login").permitAll() 38 | .anyRequest().permitAll()) 39 | .exceptionHandling(ex -> ex.authenticationEntryPoint(entryPoint)) 40 | .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); 41 | http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); 42 | return http.build(); 43 | 44 | } 45 | @Bean 46 | public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { 47 | return authenticationManagerBuilder.getObject(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | //package com.ecoomerce.sportscenter.config; 2 | // 3 | //import org.springframework.context.annotation.Bean; 4 | //import org.springframework.context.annotation.Configuration; 5 | //import org.springframework.data.web.PageableHandlerMethodArgumentResolver; 6 | //import org.springframework.data.web.config.EnableSpringDataWebSupport; 7 | //import org.springframework.web.method.support.HandlerMethodArgumentResolver; 8 | //import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | // 10 | //import java.util.List; 11 | // 12 | //@Configuration 13 | //@EnableSpringDataWebSupport 14 | //public class WebConfig implements WebMvcConfigurer { 15 | // @Bean 16 | // public PageableHandlerMethodArgumentResolver customPageableResolver(){ 17 | // PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(){ 18 | // @Override 19 | // protected String getPageParameterName(){ 20 | // return super.getPageParameterName(); 21 | // } 22 | // }; 23 | // //This sets the default page number to 1 24 | // resolver.setOneIndexedParameters(true); 25 | // return resolver; 26 | // } 27 | // 28 | // @Override 29 | // public void addArgumentResolvers(List resolvers) { 30 | // resolvers.add(customPageableResolver()); 31 | // } 32 | //} 33 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/controller/AuthConroller.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.controller; 2 | 3 | import com.ecoomerce.sportscenter.model.JwtRequest; 4 | import com.ecoomerce.sportscenter.model.JwtResponse; 5 | import com.ecoomerce.sportscenter.security.JwtHelper; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.authentication.BadCredentialsException; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | import org.springframework.security.core.userdetails.UserDetailsService; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | @RestController 16 | @RequestMapping("/auth") 17 | public class AuthConroller { 18 | private final UserDetailsService userDetailsService; 19 | private final AuthenticationManager manager; 20 | private final JwtHelper jwtHelper; 21 | public AuthConroller(UserDetailsService userDetailsService, AuthenticationManager manager, JwtHelper jwtHelper) { 22 | this.userDetailsService = userDetailsService; 23 | this.manager = manager; 24 | this.jwtHelper = jwtHelper; 25 | } 26 | @PostMapping("/login") 27 | public ResponseEntity login(@RequestBody JwtRequest request){ 28 | this.authenticate(request.getUsername(), request.getPassword()); 29 | UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername()); 30 | String token = this.jwtHelper.generateToken(userDetails); 31 | JwtResponse response = JwtResponse.builder() 32 | .username(userDetails.getUsername()) 33 | .token(token) 34 | .build(); 35 | return new ResponseEntity<>(response, HttpStatus.OK); 36 | } 37 | 38 | @GetMapping("/user") 39 | public ResponseEntity getUserDetails(@RequestHeader("Authorization") String tokenHeader) { 40 | String token = extractTokenFromHeader(tokenHeader); 41 | if (token != null) { 42 | String username = jwtHelper.getUserNameFromToken(token); 43 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 44 | return new ResponseEntity<>(userDetails, HttpStatus.OK); 45 | } else { 46 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 47 | } 48 | } 49 | 50 | private String extractTokenFromHeader(String tokenHeader) { 51 | if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) { 52 | return tokenHeader.substring(7); // Remove "Bearer " prefix 53 | } 54 | return null; 55 | } 56 | 57 | private void authenticate(String username, String password) { 58 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); 59 | try{ 60 | manager.authenticate(authenticationToken); 61 | }catch(BadCredentialsException e){ 62 | throw new BadCredentialsException("Invalid Username or Password"); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/controller/BasketController.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.controller; 2 | 3 | import com.ecoomerce.sportscenter.entity.Basket; 4 | import com.ecoomerce.sportscenter.entity.BasketItem; 5 | import com.ecoomerce.sportscenter.model.BasketItemResponse; 6 | import com.ecoomerce.sportscenter.model.BasketResponse; 7 | import com.ecoomerce.sportscenter.service.BasketService; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.access.prepost.PreAuthorize; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | @RestController 17 | @RequestMapping("/api/baskets") 18 | public class BasketController { 19 | private final BasketService basketService; 20 | public BasketController(BasketService basketService) { 21 | this.basketService = basketService; 22 | } 23 | 24 | @GetMapping 25 | public List getAllBaskets(){ 26 | return basketService.getAllBaskets(); 27 | } 28 | 29 | @GetMapping("/{basketId}") 30 | public BasketResponse getBasketById(@PathVariable String basketId){ 31 | return basketService.getBasketById(basketId); 32 | } 33 | @DeleteMapping("/{basketId}") 34 | public void deleteBasketById(@PathVariable String basketId){ 35 | basketService.deleteBasketById(basketId); 36 | } 37 | 38 | @PostMapping 39 | public ResponseEntity createBasket(@RequestBody BasketResponse basketResponse){ 40 | //Convert this Basket Response to Basket Entity 41 | Basket basket = convertToBasketEntity(basketResponse); 42 | //Call the service method to create the Basket 43 | BasketResponse createdBasket = basketService.createBasket(basket); 44 | //Return the created basket 45 | return new ResponseEntity<>(createdBasket, HttpStatus.CREATED); 46 | } 47 | 48 | private Basket convertToBasketEntity(BasketResponse basketResponse) { 49 | Basket basket = new Basket(); 50 | basket.setId(basketResponse.getId()); 51 | basket.setItems(mapBasketItemResponsesToEntities(basketResponse.getItems())); 52 | return basket; 53 | } 54 | 55 | private List mapBasketItemResponsesToEntities(List itemResponses) { 56 | return itemResponses.stream() 57 | .map(this::convertToBasketItemEntity) 58 | .collect(Collectors.toList()); 59 | } 60 | 61 | private BasketItem convertToBasketItemEntity(BasketItemResponse itemResponse) { 62 | BasketItem basketItem = new BasketItem(); 63 | basketItem.setId(itemResponse.getId()); 64 | basketItem.setName(itemResponse.getName()); 65 | basketItem.setDescription(itemResponse.getDescription()); 66 | basketItem.setPrice(itemResponse.getPrice()); 67 | basketItem.setPictureUrl(itemResponse.getPictureUrl()); 68 | basketItem.setProductBrand(itemResponse.getProductBrand()); 69 | basketItem.setProductType(itemResponse.getProductType()); 70 | basketItem.setQuantity(itemResponse.getQuantity()); 71 | return basketItem; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.controller; 2 | 3 | import com.ecoomerce.sportscenter.model.BrandResponse; 4 | import com.ecoomerce.sportscenter.model.ProductResponse; 5 | import com.ecoomerce.sportscenter.model.TypeResponse; 6 | import com.ecoomerce.sportscenter.service.BrandService; 7 | import com.ecoomerce.sportscenter.service.ProductService; 8 | import com.ecoomerce.sportscenter.service.TypeService; 9 | import org.springframework.data.domain.*; 10 | import org.springframework.data.web.PageableDefault; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.access.prepost.PreAuthorize; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import java.util.List; 17 | 18 | @RestController 19 | @RequestMapping("/api/products") 20 | public class ProductController { 21 | private final ProductService productService; 22 | private final TypeService typeService; 23 | private final BrandService brandService; 24 | 25 | public ProductController(ProductService productService, TypeService typeService, BrandService brandService) { 26 | this.productService = productService; 27 | this.typeService = typeService; 28 | this.brandService = brandService; 29 | } 30 | @GetMapping("/{id}") 31 | public ResponseEntity getProductById(@PathVariable("id") Integer productId){ 32 | ProductResponse productResponse = productService.getProductById(productId); 33 | return new ResponseEntity<>(productResponse, HttpStatus.OK); 34 | } 35 | 36 | @GetMapping() 37 | public ResponseEntity> getProducts( 38 | @PageableDefault(size = 10)Pageable pageable, 39 | @RequestParam(name="keyword", required = false) String keyword, 40 | @RequestParam(name="brandId", required = false) Integer brandId, 41 | @RequestParam(name="typeId", required = false) Integer typeId, 42 | @RequestParam(name="sort", defaultValue = "name") String sort, 43 | @RequestParam(name = "order", defaultValue = "asc") String order 44 | ){ 45 | Page productResponsePage; 46 | if(brandId!=null && typeId!=null && keyword!=null && !keyword.isEmpty()) { 47 | //search by brand, type and keyword 48 | List productResponses = productService.searchProductsByBrandTypeAndName(brandId, typeId, keyword); 49 | productResponsePage = new PageImpl<>(productResponses, pageable, productResponses.size()); 50 | } 51 | else if(brandId!=null && typeId!=null) { 52 | //search by brand and type 53 | List productResponses = productService.searchProductsByBrandandType(brandId, typeId); 54 | productResponsePage = new PageImpl<>(productResponses, pageable, productResponses.size()); 55 | } 56 | else if(brandId!=null) { 57 | //search by brand 58 | List productResponses = productService.searchProductsByBrand(brandId); 59 | productResponsePage = new PageImpl<>(productResponses, pageable, productResponses.size()); 60 | } 61 | else if(typeId!=null) { 62 | //search by type 63 | List productResponses = productService.searchProductsByType(typeId); 64 | productResponsePage = new PageImpl<>(productResponses, pageable, productResponses.size()); 65 | } 66 | else if(keyword!=null && !keyword.isEmpty()){ 67 | List productResponses = productService.searchProductsByName(keyword); 68 | productResponsePage = new PageImpl<>(productResponses, pageable, productResponses.size()); 69 | }else{ 70 | //If no search criteria, then retrieve based on sorting options 71 | Sort.Direction direction = "asc".equalsIgnoreCase(order)?Sort.Direction.ASC : Sort.Direction.DESC; 72 | Sort sorting = Sort.by(direction, sort); 73 | 74 | productResponsePage = productService.getProducts(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sorting)); 75 | } 76 | return new ResponseEntity<>(productResponsePage, HttpStatus.OK); 77 | } 78 | @GetMapping("/brands") 79 | public ResponseEntity> getBrands(){ 80 | List brandResponses = brandService.getAllBrands(); 81 | return new ResponseEntity<>(brandResponses, HttpStatus.OK); 82 | } 83 | @GetMapping("/types") 84 | public ResponseEntity> getTypes(){ 85 | List typeResponses = typeService.getAllTypes(); 86 | return new ResponseEntity<>(typeResponses, HttpStatus.OK); 87 | } 88 | 89 | @GetMapping("/search") 90 | public ResponseEntity> searchProducts(@RequestParam("keyword") String keyword){ 91 | List productResponses = productService.searchProductsByName(keyword); 92 | return new ResponseEntity<>(productResponses, HttpStatus.OK); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/entity/Basket.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.entity; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.redis.core.RedisHash; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @RedisHash("Basket") 14 | public class Basket { 15 | @Id 16 | private String id; 17 | private List items = new ArrayList<>(); 18 | public Basket(String id){ 19 | this.id = id; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/entity/BasketItem.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.entity; 2 | import lombok.Data; 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.redis.core.RedisHash; 5 | 6 | @Data 7 | @RedisHash("BasketItem") 8 | public class BasketItem { 9 | @Id 10 | private Integer id; 11 | private String name; 12 | private String description; 13 | private Long price; 14 | private String pictureUrl; 15 | private String productBrand; 16 | private String productType; 17 | private Integer quantity; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/entity/Brand.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Entity 12 | @Table(name="Brand") 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Builder 17 | public class Brand { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | @Column(name="Id") 21 | private Integer id; 22 | @Column(name="Name") 23 | private String name; 24 | 25 | @OneToMany(mappedBy = "brand", fetch = FetchType.LAZY) 26 | private List products; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Entity 10 | @Table(name="Product") 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | public class Product { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @Column(name="Id") 19 | private Integer id; 20 | @Column(name="Name") 21 | private String name; 22 | @Column(name="Description") 23 | private String description; 24 | @Column(name="Price") 25 | private Long price; 26 | @Column(name="PictureUrl") 27 | private String pictureUrl; 28 | 29 | @ManyToOne(fetch = FetchType.LAZY) 30 | @JoinColumn(name= "ProductBrandId", referencedColumnName = "Id") 31 | private Brand brand; 32 | 33 | @ManyToOne(fetch = FetchType.LAZY) 34 | @JoinColumn(name= "ProductTypeId", referencedColumnName = "Id") 35 | private Type type; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/entity/Type.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Entity 12 | @Table(name="Type") 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Builder 17 | public class Type { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | @Column(name="Id") 21 | private Integer id; 22 | @Column(name="Name") 23 | private String name; 24 | 25 | @OneToMany(mappedBy = "type", fetch = FetchType.LAZY) 26 | private List products; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/exceptions/CustomExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.exceptions; 2 | 3 | import com.ecoomerce.sportscenter.model.CustomErrorResponse; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.context.request.WebRequest; 9 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 10 | 11 | @ControllerAdvice 12 | public class CustomExceptionHandler extends ResponseEntityExceptionHandler { 13 | @ExceptionHandler(ProductNotFoundException.class) 14 | public ResponseEntity handleProductNotFoundException(ProductNotFoundException ex, WebRequest request){ 15 | //Custom Exception 16 | CustomErrorResponse customErrorResponse = new CustomErrorResponse(HttpStatus.NOT_FOUND, "Product not found", ex.getMessage()); 17 | return new ResponseEntity<>(customErrorResponse, HttpStatus.NOT_FOUND); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/exceptions/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.exceptions; 2 | 3 | public class ProductNotFoundException extends RuntimeException { 4 | public ProductNotFoundException(String message){ 5 | super(message); 6 | } 7 | public ProductNotFoundException(String message, Throwable cause){ 8 | super(message, cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/BasketItemResponse.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class BasketItemResponse { 13 | private Integer id; 14 | private String name; 15 | private String description; 16 | private Long price; 17 | private String pictureUrl; 18 | private String productBrand; 19 | private String productType; 20 | private Integer quantity; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/BasketResponse.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class BasketResponse { 15 | private String id; 16 | private List items; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/BrandResponse.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class BrandResponse { 13 | private Integer id; 14 | private String name; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/CustomErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.http.HttpStatus; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class CustomErrorResponse { 12 | private HttpStatus status; 13 | private String error; 14 | private String message; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/JwtRequest.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Builder 12 | public class JwtRequest { 13 | private String username; 14 | private String password; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/JwtResponse.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Builder 12 | public class JwtResponse { 13 | private String username; 14 | private String token; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/ProductResponse.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class ProductResponse { 13 | private Integer id; 14 | private String name; 15 | private String description; 16 | private Long price; 17 | private String pictureUrl; 18 | private String productType; 19 | private String productBrand; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/model/TypeResponse.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class TypeResponse { 13 | private Integer id; 14 | private String name; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/repository/BasketRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.repository; 2 | 3 | import com.ecoomerce.sportscenter.entity.Basket; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BasketRepository extends CrudRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/repository/BrandRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.repository; 2 | 3 | import com.ecoomerce.sportscenter.entity.Brand; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BrandRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.repository; 2 | 3 | import com.ecoomerce.sportscenter.entity.Product; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.repository.query.Param; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | @Repository 12 | public interface ProductRepository extends JpaRepository { 13 | @Query("SELECT p FROM Product p where p.name LIKE %:keyword%") 14 | List searchByName(@Param("keyword") String keyword); 15 | 16 | @Query("SELECT p FROM Product p WHERE p.brand.id = :brandId") 17 | List searchByBrand(@Param("brandId") Integer brandId); 18 | @Query("SELECT p FROM Product p WHERE p.type.id = :typeId") 19 | List searchByType(@Param("typeId") Integer typeId); 20 | @Query("SELECT p FROM Product p WHERE p.brand.id = :brandId AND p.type.id = :typeId") 21 | List searchByBrandAndType(@Param("brandId") Integer brandId, @Param("typeId") Integer typeId); 22 | 23 | @Query("SELECT p FROM Product p WHERE p.brand.id = :brandId AND p.type.id = :typeId AND p.name LIKE %:keyword%") 24 | List searchByBrandTypeAndName(@Param("brandId") Integer brandId, @Param("typeId") Integer typeId, @Param("keyword") String keyword); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/repository/TypeRepository.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.repository; 2 | 3 | import com.ecoomerce.sportscenter.entity.Type; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface TypeRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/security/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.security; 2 | 3 | import jakarta.servlet.ServletException; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.springframework.security.core.AuthenticationException; 7 | import org.springframework.security.web.AuthenticationEntryPoint; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.IOException; 11 | import java.io.PrintWriter; 12 | 13 | @Component 14 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 15 | @Override 16 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { 17 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 18 | PrintWriter writer = response.getWriter(); 19 | writer.println("Access Denied: " + authException.getMessage()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/security/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.security; 2 | 3 | import io.jsonwebtoken.ExpiredJwtException; 4 | import io.jsonwebtoken.MalformedJwtException; 5 | import jakarta.servlet.FilterChain; 6 | import jakarta.servlet.ServletException; 7 | import jakarta.servlet.http.HttpServletRequest; 8 | import jakarta.servlet.http.HttpServletResponse; 9 | import lombok.extern.log4j.Log4j2; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.web.filter.OncePerRequestFilter; 17 | 18 | import java.io.IOException; 19 | 20 | @Component 21 | @Log4j2 22 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 23 | private final JwtHelper jwtHelper; 24 | private final UserDetailsService userDetailsService; 25 | public JwtAuthenticationFilter(JwtHelper jwtHelper, UserDetailsService userDetailsService) { 26 | this.jwtHelper = jwtHelper; 27 | this.userDetailsService = userDetailsService; 28 | } 29 | 30 | @Override 31 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 32 | String requestHeader = request.getHeader("Authorization"); 33 | log.info("Header: {}", requestHeader); 34 | String userName = null; 35 | String token = null; 36 | if(requestHeader!=null && requestHeader.startsWith("Bearer")){ 37 | token = requestHeader.substring(7); 38 | try{ 39 | userName = this.jwtHelper.getUserNameFromToken(token); 40 | } catch(IllegalArgumentException | ExpiredJwtException | MalformedJwtException e){ 41 | log.info("Jwt Token processing error"); 42 | e.printStackTrace(); 43 | } 44 | }else { 45 | log.warn("JWT token doesn't begin with Bearer String"); 46 | } 47 | if(userName!=null && SecurityContextHolder.getContext().getAuthentication() == null){ 48 | // fetch user details 49 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName); 50 | Boolean validateToken = this.jwtHelper.validateToken(token, userDetails); 51 | if(validateToken){ 52 | //set the authentication 53 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 54 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 55 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 56 | }else{ 57 | log.info("Validation fails"); 58 | } 59 | } 60 | filterChain.doFilter(request, response); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/security/JwtHelper.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.security; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.crypto.spec.SecretKeySpec; 10 | import java.nio.charset.StandardCharsets; 11 | import java.security.Key; 12 | import java.util.Date; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.function.Function; 16 | 17 | @Component 18 | public class JwtHelper { 19 | public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; 20 | private String secret = "f27dacd186810e78c0fd8ba65ecf3f1524ff087c5e86773d5172d424b3fd201f"; 21 | 22 | //retrieve username 23 | public String getUserNameFromToken(String token) { 24 | return getClaimFromToken(token, Claims::getSubject); 25 | } 26 | 27 | //retrieve expiration date from jwt token 28 | public Date getExpirationDateFromToken(String token) { 29 | return getClaimFromToken(token, Claims::getExpiration); 30 | } 31 | 32 | private T getClaimFromToken(String token, Function claimsResolver) { 33 | final Claims claims = getAllClaimsFromToken(token); 34 | return claimsResolver.apply(claims); 35 | } 36 | 37 | private Claims getAllClaimsFromToken(String token) { 38 | Key hmacKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); 39 | return Jwts.parserBuilder() 40 | .setSigningKey(hmacKey) 41 | .build() 42 | .parseClaimsJws(token) 43 | .getBody(); 44 | } 45 | 46 | //check if the token has expired 47 | private Boolean isTokenExpired(String token) { 48 | final Date expiration = getExpirationDateFromToken(token); 49 | return expiration.before(new Date()); 50 | } 51 | 52 | //generate token for user 53 | public String generateToken(UserDetails userDetails) { 54 | Map claims = new HashMap<>(); 55 | return generateToken(claims, userDetails.getUsername()); 56 | } 57 | 58 | private String generateToken(Map claims, String subject) { 59 | // Convert the secret string key into a Key object 60 | Key hmacKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), 61 | SignatureAlgorithm.HS512.getJcaName()); 62 | 63 | return Jwts.builder() 64 | .setClaims(claims) 65 | .setSubject(subject) 66 | .setIssuedAt(new Date(System.currentTimeMillis())) 67 | .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) 68 | .signWith(hmacKey, SignatureAlgorithm.HS512) 69 | .compact(); 70 | } 71 | 72 | public Boolean validateToken(String token, UserDetails userDetails) { 73 | final String username = getUserNameFromToken(token); 74 | return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/BasketService.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.entity.Basket; 4 | import com.ecoomerce.sportscenter.model.BasketResponse; 5 | 6 | import java.util.List; 7 | 8 | public interface BasketService { 9 | List getAllBaskets(); 10 | BasketResponse getBasketById(String basketId); 11 | void deleteBasketById(String basketId); 12 | BasketResponse createBasket(Basket basket); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/BasketServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.entity.Basket; 4 | import com.ecoomerce.sportscenter.entity.BasketItem; 5 | import com.ecoomerce.sportscenter.model.BasketItemResponse; 6 | import com.ecoomerce.sportscenter.model.BasketResponse; 7 | import com.ecoomerce.sportscenter.repository.BasketRepository; 8 | import lombok.extern.log4j.Log4j2; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.stream.Collectors; 14 | 15 | @Service 16 | @Log4j2 17 | public class BasketServiceImpl implements BasketService{ 18 | private final BasketRepository basketRepository; 19 | 20 | public BasketServiceImpl(BasketRepository basketRepository) { 21 | this.basketRepository = basketRepository; 22 | } 23 | 24 | @Override 25 | public List getAllBaskets() { 26 | log.info("Fetching all Baskets"); 27 | List basketList = (List) basketRepository.findAll(); 28 | //now we will use stream operator to map with response 29 | List basketResponses = basketList.stream() 30 | .map(this::convertToBasketResponse) 31 | .collect(Collectors.toList()); 32 | log.info("Fetched all Baskets"); 33 | return basketResponses; 34 | } 35 | 36 | @Override 37 | public BasketResponse getBasketById(String basketId) { 38 | log.info("Fetching Basket by Id: {}", basketId); 39 | Optional basketOptional = basketRepository.findById(basketId); 40 | if(basketOptional.isPresent()){ 41 | Basket basket = basketOptional.get(); 42 | log.info("Fetched Basket by Id: {}", basketId); 43 | return convertToBasketResponse(basket); 44 | }else{ 45 | log.info("Basket not found by Id: {}", basketId); 46 | return null; 47 | } 48 | } 49 | 50 | @Override 51 | public void deleteBasketById(String basketId) { 52 | log.info("Deleting Basket by Id: {}", basketId); 53 | basketRepository.deleteById(basketId); 54 | log.info("Deleted Basket by Id: {}", basketId); 55 | } 56 | 57 | @Override 58 | public BasketResponse createBasket(Basket basket) { 59 | log.info("Creating Basket"); 60 | Basket savedBasket = basketRepository.save(basket); 61 | log.info("Basket created by Id : {}", savedBasket.getId()); 62 | return convertToBasketResponse(savedBasket); 63 | } 64 | 65 | private BasketResponse convertToBasketResponse(Basket basket) { 66 | if(basket == null){ 67 | return null; 68 | } 69 | List itemResponses = basket.getItems().stream() 70 | .map(this::convertToBasketItemResponse) 71 | .collect(Collectors.toList()); 72 | return BasketResponse.builder() 73 | .id(basket.getId()) 74 | .items(itemResponses) 75 | .build(); 76 | } 77 | 78 | private BasketItemResponse convertToBasketItemResponse(BasketItem basketItem) { 79 | return BasketItemResponse.builder() 80 | .id(basketItem.getId()) 81 | .name(basketItem.getName()) 82 | .description(basketItem.getDescription()) 83 | .price(basketItem.getPrice()) 84 | .pictureUrl(basketItem.getPictureUrl()) 85 | .productBrand(basketItem.getProductBrand()) 86 | .productType(basketItem.getProductType()) 87 | .quantity(basketItem.getQuantity()) 88 | .build(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/BrandService.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.model.BrandResponse; 4 | 5 | import java.util.List; 6 | 7 | public interface BrandService { 8 | List getAllBrands(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/BrandServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.entity.Brand; 4 | import com.ecoomerce.sportscenter.model.BrandResponse; 5 | import com.ecoomerce.sportscenter.repository.BrandRepository; 6 | import lombok.extern.log4j.Log4j2; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @Service 13 | @Log4j2 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 | log.info("Fetching all Brands!!!"); 24 | //Fetch brands 25 | List brandList = brandRepository.findAll(); 26 | //now use stream operator to map with response 27 | List brandResponses = brandList.stream() 28 | .map(this::convertToBrandResponse) 29 | .collect(Collectors.toList()); 30 | log.info("Fetched all Brands"); 31 | return brandResponses; 32 | 33 | } 34 | private BrandResponse convertToBrandResponse(Brand brand){ 35 | return BrandResponse.builder() 36 | .id(brand.getId()) 37 | .name(brand.getName()) 38 | .build(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.model.ProductResponse; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | 7 | import java.util.List; 8 | 9 | public interface ProductService { 10 | ProductResponse getProductById(Integer productId); 11 | Page getProducts(Pageable pageable); 12 | List searchProductsByName(String keyword); 13 | List searchProductsByBrand(Integer brandId); 14 | List searchProductsByType(Integer typeId); 15 | List searchProductsByBrandandType(Integer brandId, Integer typeId); 16 | List searchProductsByBrandTypeAndName(Integer brandId, Integer typeId, String keyword); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/ProductServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.entity.Product; 4 | import com.ecoomerce.sportscenter.exceptions.ProductNotFoundException; 5 | import com.ecoomerce.sportscenter.model.ProductResponse; 6 | import com.ecoomerce.sportscenter.repository.ProductRepository; 7 | import lombok.extern.log4j.Log4j2; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | @Service 16 | @Log4j2 17 | public class ProductServiceImpl implements ProductService { 18 | private final ProductRepository productRepository; 19 | 20 | public ProductServiceImpl(ProductRepository productRepository) { 21 | this.productRepository = productRepository; 22 | } 23 | 24 | @Override 25 | public ProductResponse getProductById(Integer productId) { 26 | log.info("Fetching Product by Id: {}", productId); 27 | Product product =productRepository.findById(productId) 28 | .orElseThrow(()->new ProductNotFoundException("Product with given id doesn't exist")); 29 | //now convert the product to product response 30 | ProductResponse productResponse = convertToProductResponse(product); 31 | log.info("Fetched Product by Id: {}", productId); 32 | return productResponse; 33 | } 34 | 35 | @Override 36 | public Page getProducts(Pageable pageable) { 37 | log.info("Fetching products"); 38 | //Retrieve products from DB 39 | Page productPage = productRepository.findAll(pageable); 40 | //Map 41 | Page productResponses = productPage 42 | .map(this::convertToProductResponse); 43 | log.info("Fetched all products"); 44 | return productResponses; 45 | } 46 | 47 | @Override 48 | public List searchProductsByName(String keyword) { 49 | log.info("Searching product(s) by name: {}", keyword); 50 | //Call the custom query Method 51 | List products = productRepository.searchByName(keyword); 52 | //Map 53 | List productResponses = products.stream() 54 | .map(this::convertToProductResponse) 55 | .collect(Collectors.toList()); 56 | log.info("Fetched all products"); 57 | return productResponses; 58 | } 59 | 60 | @Override 61 | public List searchProductsByBrand(Integer brandId) { 62 | log.info("Searching product(s) by brandId: {}", brandId); 63 | //Call the custom query Method 64 | List products = productRepository.searchByBrand(brandId); 65 | //Map 66 | List productResponses = products.stream() 67 | .map(this::convertToProductResponse) 68 | .collect(Collectors.toList()); 69 | log.info("Fetched all products"); 70 | return productResponses; 71 | } 72 | 73 | @Override 74 | public List searchProductsByType(Integer typeId) { 75 | log.info("Searching product(s) by typeId: {}", typeId); 76 | //Call the custom query Method 77 | List products = productRepository.searchByType(typeId); 78 | //Map 79 | List productResponses = products.stream() 80 | .map(this::convertToProductResponse) 81 | .collect(Collectors.toList()); 82 | log.info("Fetched all products"); 83 | return productResponses; 84 | } 85 | 86 | @Override 87 | public List searchProductsByBrandandType(Integer brandId, Integer typeId) { 88 | log.info("Searching product(s) by brandId: {}, and typeId: {}", brandId, typeId); 89 | //Call the custom query Method 90 | List products = productRepository.searchByBrandAndType(brandId, typeId); 91 | //Map 92 | List productResponses = products.stream() 93 | .map(this::convertToProductResponse) 94 | .collect(Collectors.toList()); 95 | log.info("Fetched all products"); 96 | return productResponses; 97 | } 98 | 99 | @Override 100 | public List searchProductsByBrandTypeAndName(Integer brandId, Integer typeId, String keyword) { 101 | log.info("Searching product(s) by brandId: {}, typeId: {} and keyword: {}", brandId, typeId, keyword); 102 | //Call the custom query Method 103 | List products = productRepository.searchByBrandTypeAndName(brandId, typeId, keyword); 104 | //Map 105 | List productResponses = products.stream() 106 | .map(this::convertToProductResponse) 107 | .collect(Collectors.toList()); 108 | log.info("Fetched all products"); 109 | return productResponses; 110 | } 111 | 112 | private ProductResponse convertToProductResponse(Product product) { 113 | return ProductResponse.builder() 114 | .id(product.getId()) 115 | .name(product.getName()) 116 | .description(product.getDescription()) 117 | .price(product.getPrice()) 118 | .pictureUrl(product.getPictureUrl()) 119 | .productType(product.getType().getName()) 120 | .productBrand(product.getBrand().getName()) 121 | .build(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/TypeService.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.model.TypeResponse; 4 | 5 | import java.util.List; 6 | 7 | public interface TypeService { 8 | List getAllTypes(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ecoomerce/sportscenter/service/TypeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter.service; 2 | 3 | import com.ecoomerce.sportscenter.entity.Type; 4 | import com.ecoomerce.sportscenter.model.TypeResponse; 5 | import com.ecoomerce.sportscenter.repository.TypeRepository; 6 | import lombok.extern.log4j.Log4j2; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @Service 13 | @Log4j2 14 | public class TypeServiceImpl implements TypeService { 15 | private final TypeRepository typeRepository; 16 | 17 | public TypeServiceImpl(TypeRepository typeRepository) { 18 | this.typeRepository = typeRepository; 19 | } 20 | 21 | @Override 22 | public List getAllTypes() { 23 | log.info("Fetching all Types"); 24 | //Fetch Types from DB 25 | List typeList = typeRepository.findAll(); 26 | //now use stream operator to map with response 27 | List typeResponses = typeList.stream() 28 | .map(this::convertToTypeResponse) 29 | .collect(Collectors.toList()); 30 | log.info("Fetched all Types"); 31 | return typeResponses; 32 | } 33 | 34 | private TypeResponse convertToTypeResponse(Type type) { 35 | return TypeResponse.builder() 36 | .id(type.getId()) 37 | .name(type.getName()) 38 | .build(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | # Spring Boot Logging Configuration 2 | logging: 3 | level: 4 | root: INFO 5 | org.springframework.web: INFO 6 | com.ecoomerce.sportscenter: DEBUG 7 | 8 | server: 9 | port: 8080 10 | 11 | spring: 12 | datasource: 13 | url: jdbc:mysql://localhost:3306/sportscenter 14 | username: root 15 | password: password 16 | application: 17 | name: SportsCenter 18 | jpa: 19 | hibernate: 20 | ddl-auto: update 21 | naming: 22 | physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 23 | properties: 24 | hibernate: 25 | dialect: org.hibernate.dialect.MySQL8Dialect 26 | format_sql: true 27 | data: 28 | redis: 29 | host: localhost 30 | port: 6379 31 | -------------------------------------------------------------------------------- /src/test/java/com/ecoomerce/sportscenter/SportscenterApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ecoomerce.sportscenter; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SportscenterApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------