├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── maven-checks.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── README.md ├── compose.yaml ├── eKart_shopping_admin-panel.png ├── eKart_shopping_all-products.png ├── eKart_shopping_cart.png ├── eKart_shopping_home.png ├── eKart_shopping_orders.png ├── eKart_shopping_payment-gateway.png ├── ekartdb-ER.drawio ├── ekartdb-backend-ER-diagram.png ├── logo.jpg ├── logo_nobackground.png ├── mvnw ├── mvnw.cmd ├── pom.xml ├── schema.sql └── src ├── main ├── java │ └── com │ │ └── vedasole │ │ └── ekartecommercebackend │ │ ├── EkartEcommerceBackendApplication.java │ │ ├── config │ │ ├── ApplicationConfig.java │ │ └── SwaggerConfig.java │ │ ├── controller │ │ ├── AuthController.java │ │ ├── CategoryController.java │ │ ├── CustomerController.java │ │ ├── OrderController.java │ │ ├── OrderItemController.java │ │ ├── PaymentController.java │ │ ├── ProductController.java │ │ ├── ShoppingCartController.java │ │ └── ShoppingCartItemController.java │ │ ├── entity │ │ ├── Address.java │ │ ├── Category.java │ │ ├── Customer.java │ │ ├── Order.java │ │ ├── OrderItem.java │ │ ├── PasswordResetToken.java │ │ ├── Product.java │ │ ├── ShoppingCart.java │ │ ├── ShoppingCartItem.java │ │ └── User.java │ │ ├── exception │ │ ├── APIException.java │ │ ├── GlobalExceptionHandler.java │ │ └── ResourceNotFoundException.java │ │ ├── payload │ │ ├── AddressDto.java │ │ ├── ApiResponse.java │ │ ├── AuthenticationRequest.java │ │ ├── AuthenticationResponse.java │ │ ├── CategoryDto.java │ │ ├── CustomerDto.java │ │ ├── NewCustomerDto.java │ │ ├── OrderDto.java │ │ ├── OrderItemDto.java │ │ ├── PasswordResetRequestDto.java │ │ ├── ProductDto.java │ │ ├── ResetTokenRequestDto.java │ │ ├── ShoppingCartDto.java │ │ ├── ShoppingCartItemDto.java │ │ └── ValidateTokenRequestDto.java │ │ ├── repository │ │ ├── AddressRepo.java │ │ ├── CategoryRepo.java │ │ ├── CustomerRepo.java │ │ ├── OrderItemRepo.java │ │ ├── OrderRepo.java │ │ ├── PasswordResetTokenRepo.java │ │ ├── ProductRepo.java │ │ ├── ShoppingCartItemRepo.java │ │ ├── ShoppingCartRepo.java │ │ └── UserRepo.java │ │ ├── security │ │ ├── JWTAuthenticationEntryPoint.java │ │ ├── JWTAuthenticationFilter.java │ │ ├── JwtService.java │ │ └── WebSecurityConfig.java │ │ ├── service │ │ ├── service_impl │ │ │ ├── AuthenticationServiceImpl.java │ │ │ ├── CategoryServiceImpl.java │ │ │ ├── CustomerServiceImpl.java │ │ │ ├── EmailServiceImpl.java │ │ │ ├── OrderItemServiceImpl.java │ │ │ ├── OrderServiceImpl.java │ │ │ ├── PaymentServiceImpl.java │ │ │ ├── ProductServiceImpl.java │ │ │ ├── ShoppingCartItemServiceImpl.java │ │ │ ├── ShoppingCartServiceImpl.java │ │ │ ├── StripeService.java │ │ │ └── UserServiceImpl.java │ │ └── service_interface │ │ │ ├── AuthenticationService.java │ │ │ ├── CategoryService.java │ │ │ ├── CustomerService.java │ │ │ ├── EmailService.java │ │ │ ├── OrderItemService.java │ │ │ ├── OrderService.java │ │ │ ├── PaymentService.java │ │ │ ├── ProductService.java │ │ │ ├── ShoppingCartItemService.java │ │ │ ├── ShoppingCartService.java │ │ │ └── UserService.java │ │ └── utility │ │ ├── AppConstant.java │ │ └── ApplicationInitializer.java └── resources │ ├── application-dev.properties │ ├── application-docker.properties │ ├── application-prod.properties │ ├── application-uat.properties │ ├── application.properties │ ├── logback.xml │ └── templates │ ├── orderConfirmation.html │ ├── resetPassword.html │ └── welcome.html └── test ├── java └── com │ └── vedasole │ └── ekartecommercebackend │ ├── EkartEcommerceBackendApplicationTests.java │ ├── IT │ └── CategoryControllerITTest.java │ ├── config │ ├── ApplicationConfigTest.java │ └── TestMailConfig.java │ ├── controller │ └── CategoryControllerTest.java │ ├── entity │ ├── AddressTest.java │ └── CategoryTest.java │ ├── payload │ └── CustomerDtoTest.java │ ├── repository │ ├── CategoryRepoTest.java │ ├── CustomerRepoTest.java │ ├── PasswordResetTokenRepoTest.java │ ├── ProductRepoTest.java │ ├── ShoppingCartRepoTest.java │ └── UserRepoTest.java │ ├── security │ └── WebSecurityConfigTest.java │ ├── service │ └── serviceImpl │ │ └── UserServiceImplTest.java │ └── utility │ └── TestApplicationInitializer.java └── resources └── application.properties /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/engine/reference/builder/#dockerignore-file 6 | 7 | **/.DS_Store 8 | **/__pycache__ 9 | **/.venv 10 | **/.classpath 11 | **/.dockerignore 12 | **/.env 13 | **/.git 14 | **/.gitignore 15 | **/.project 16 | **/.settings 17 | **/.toolstarget 18 | **/.vs 19 | **/.vscode 20 | **/*.*proj.user 21 | **/*.dbmdl 22 | **/*.jfm 23 | **/bin 24 | **/charts 25 | **/docker-compose* 26 | **/compose* 27 | **/Dockerfile* 28 | **/node_modules 29 | **/npm-debug.log 30 | **/obj 31 | **/secrets.dev.yaml 32 | **/values.dev.yaml 33 | LICENSE 34 | README.md 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/maven-checks.yml: -------------------------------------------------------------------------------- 1 | name: Maven Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | - EK* 9 | pull_request: 10 | branches: 11 | - master 12 | - develop 13 | 14 | permissions: 15 | contents: read 16 | 17 | env: 18 | jsonSecretKey: 'fakeSecretKey' 19 | frontendDomainUrl: 'http://localhost:5173' 20 | ekartBackendPassword: 'fakeEkartBackendPassword' 21 | stripeApiKey: 'fake-stripe-api-key' 22 | stripeEndpointSecret: 'fake-stripe-endpoint-secret' 23 | adminPassword: 'fakeAdminPassword' 24 | 25 | jobs: 26 | build: 27 | runs-on: ubuntu-latest 28 | 29 | # Setup redis 30 | services: 31 | redis: 32 | image: redis:alpine 33 | ports: 34 | - 6379:6379 35 | options: >- 36 | --health-cmd "redis-cli ping" 37 | --health-interval 10s 38 | --health-timeout 5s 39 | --health-retries 3 40 | 41 | steps: 42 | - name: Checkout code 43 | uses: actions/checkout@v4 44 | 45 | - name: Set up JDK 46 | uses: actions/setup-java@v4 47 | with: 48 | java-version: '17' 49 | distribution: 'temurin' 50 | cache: maven 51 | cache-dependency-path: '**/pom.xml' 52 | 53 | - name: Install Redis CLI 54 | run: sudo apt-get install -y redis-tools 55 | 56 | - name: Build with Maven 57 | run: mvn clean install -DskipTests 58 | 59 | - name: Verify the build 60 | run: mvn verify 61 | 62 | - name: Wait for Redis to be ready 63 | run: | 64 | for i in {1..10}; do 65 | if redis-cli -h localhost ping; then 66 | echo "Redis is ready!" 67 | break 68 | fi 69 | echo "Waiting for Redis..." 70 | sleep 5 71 | done 72 | 73 | - name: Run tests 74 | run: mvn test -------------------------------------------------------------------------------- /.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 | .run 22 | .jpb 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | .github/workflows/qodana.yml -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/.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 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [Create Issue](https://github.com/ved-asole/eKart-ecommerce-backend/issues/new/choose). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.9.9-amazoncorretto-17-alpine AS build 2 | ENV REDIS_HOST=host.docker.internal 3 | WORKDIR /app 4 | COPY pom.xml . 5 | COPY src ./src 6 | RUN mvn clean package -DskipTests 7 | 8 | FROM amazoncorretto:17-alpine 9 | WORKDIR /app 10 | COPY --from=build /app/target/*.jar /app/app.jar 11 | EXPOSE 8000 12 | ENV SPRING_PROFILES_ACTIVE=docker 13 | ENTRYPOINT ["java","-jar","-Dspring.profiles.active=docker","app.jar"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ekart Shopping - An E-commerce Backend API 2 | 3 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ved-asole_eKart-ecommerce-backend&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ved-asole_eKart-ecommerce-backend) 4 | [![Maven Checks](https://github.com/ved-asole/eKart-ecommerce-backend/actions/workflows/maven-checks.yml/badge.svg)](https://github.com/ved-asole/eKart-ecommerce-backend/actions/workflows/maven-checks.yml) 5 | [![Better Stack Badge](https://uptime.betterstack.com/status-badges/v1/monitor/1h7k6.svg)]((https://coders-arena.betteruptime.com)) 6 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=ved-asole_eKart-ecommerce-backend&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=ved-asole_eKart-ecommerce-backend) 7 | 8 | # Live Link / Demo Link: 🔗 9 | Access my site at **[ekart.vedasole.me](https://ekart.vedasole.me)** 10 | 11 | # Table of Content: 📑 12 | 13 | - [About The App](#about-the-app) 14 | - [Screenshots](#screenshots) 15 | - [ER Diagram](#er-diagram) 16 | - [Technologies](#technologies) 17 | - [Setup](#setup) 18 | - [Approach](#approach) 19 | - [Status](#status) 20 | - [License](#license) 21 | 22 | # About the App: 📚 23 | Ekart Ecommerce is an online e-commerce app which provides the facility for online shopping from any location. 24 | A backend API project for the eKart E-commerce website developed using Java and Spring Framework. 25 | 26 | # Screenshots: 📷 27 | 28 | ### 1) Home Page 🏠 29 | ![Home Page](eKart_shopping_home.png) 30 | 31 | ### 2) All Products 🛍️ 32 | ![All Products](eKart_shopping_all-products.png) 33 | 34 | ### 3) Cart 🛒 35 | ![Shopping Cart](eKart_shopping_cart.png) 36 | 37 | ### 4) Orders 📦 38 | ![Orders](eKart_shopping_orders.png) 39 | 40 | ### 5) Admin Dashboard 📊 41 | ![Admin Panel](eKart_shopping_admin-panel.png) 42 | 43 | ### 6) Payment Gateway 💳 44 | ![Payment Gateway](eKart_shopping_payment-gateway.png) 45 | 46 | # ER Diagram: 📊 47 | ![Ekart Shopping ER Diagram](ekartdb-backend-ER-diagram.png) 48 | 49 | # Technologies: ☕️ ⚛️ 50 | 51 | - Java 52 | - Spring Boot 53 | - Spring Web 54 | - Spring Data Rest 55 | - Spring Data JPA 56 | - H2 and Postgres DB 57 | - Swagger 58 | 59 | # Setup: 💻 60 | 61 | ## Java + Spring Boot 62 | 63 | ## Available Scripts 64 | 65 | In the project directory, you can run: 66 | 67 | ### `mvn install` 68 | 69 | To install all the dependencies required for the project. 70 | 71 | ### `mvn spring-boot:run` 72 | 73 | Runs the app in the development mode.\ 74 | Open [http://localhost:8000](http://localhost:8000) to view it in your browser. 75 | 76 | 77 | ### `npm clean install` 78 | 79 | Builds the app for production to the `target` folder.\ 80 | It correctly bundles Spring Boot app in production mode and optimizes the build for the best performance. 81 | 82 | Your app is ready to be deployed! 83 | 84 | # Approach: 🚶 85 | We are using React for frontend UI and Java Spring Boot for backend API with MVC Architecture. 86 | 87 | # Status: 📶 88 | Work in Progress...🛠️ 89 | 90 | # License: ©️ 91 | Apache-2.0 License (**[Check Here](https://github.com/ved-asole/eKart-ecommerce-app/blob/master/LICENSE)**) -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.0.0' 2 | services: 3 | app: 4 | container_name: ekart-backend 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | - "8000:8000" 10 | environment: 11 | - dbUrl=jdbc:postgresql://db:5432/ekartdb 12 | - dbUsername=postgres 13 | - dbPassword=postgres 14 | - jsonSecretKey=${jsonSecretKey} 15 | - adminPassword=${adminPassword} 16 | - frontendDomainUrl=${frontendDomainUrl} 17 | - stripeApiKey=${stripeApiKey} 18 | - REDIS_HOST=redis 19 | - endpointSecret=${endpointSecret} 20 | - emailUsername=${emailUsername} 21 | - emailPassword=${emailPassword} 22 | 23 | # The commented out section below is an example of how to define a PostgreSQL 24 | # database that your application can use. `depends_on` tells Docker Compose to 25 | # start the database before your application. The `db-data` volume persists the 26 | # database data between container restarts. 27 | depends_on: 28 | db: 29 | condition: service_healthy 30 | redis: 31 | condition: service_healthy 32 | db: 33 | container_name: ekart-db 34 | image: postgres:17.0-alpine 35 | restart: always 36 | user: postgres 37 | volumes: 38 | - db-data:/var/lib/postgresql/data 39 | environment: 40 | - POSTGRES_DB=ekartdb 41 | - POSTGRES_USER=postgres 42 | - POSTGRES_PASSWORD=postgres 43 | ports: 44 | - "5432:5432" 45 | healthcheck: 46 | test: [ "CMD", "pg_isready" ] 47 | interval: 10s 48 | timeout: 5s 49 | retries: 5 50 | 51 | redis: 52 | container_name: redis 53 | image: redis:alpine 54 | restart: always 55 | ports: 56 | - "6379:6379" 57 | healthcheck: 58 | test: [ "CMD", "redis-cli", "ping" ] 59 | interval: 10s 60 | timeout: 5s 61 | retries: 5 62 | volumes: 63 | db-data: -------------------------------------------------------------------------------- /eKart_shopping_admin-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/eKart_shopping_admin-panel.png -------------------------------------------------------------------------------- /eKart_shopping_all-products.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/eKart_shopping_all-products.png -------------------------------------------------------------------------------- /eKart_shopping_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/eKart_shopping_cart.png -------------------------------------------------------------------------------- /eKart_shopping_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/eKart_shopping_home.png -------------------------------------------------------------------------------- /eKart_shopping_orders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/eKart_shopping_orders.png -------------------------------------------------------------------------------- /eKart_shopping_payment-gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/eKart_shopping_payment-gateway.png -------------------------------------------------------------------------------- /ekartdb-backend-ER-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/ekartdb-backend-ER-diagram.png -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/logo.jpg -------------------------------------------------------------------------------- /logo_nobackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ved-asole/eKart-ecommerce-backend/14e4e6a21b3e918f2da947269f1325a8e6349398/logo_nobackground.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.18 9 | 10 | 11 | com.vedasole 12 | ekart-ecommerce-backend 13 | 0.0.1-SNAPSHOT 14 | ekart-ecommerce-backend 15 | ekart-ecommerce-backend 16 | 17 | 17 18 | ved-asole 19 | https://sonarcloud.io 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-jpa 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-validation 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-data-redis 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-cache 41 | 42 | 43 | org.modelmapper 44 | modelmapper 45 | 3.1.0 46 | 47 | 48 | 49 | com.google.code.gson 50 | gson 51 | 2.10.1 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-devtools 56 | runtime 57 | true 58 | 59 | 60 | 61 | com.h2database 62 | h2 63 | 2.2.220 64 | runtime 65 | 66 | 67 | 68 | org.postgresql 69 | postgresql 70 | 42.7.2 71 | runtime 72 | 73 | 74 | org.projectlombok 75 | lombok 76 | 1.18.30 77 | true 78 | 79 | 80 | io.springfox 81 | springfox-boot-starter 82 | 3.0.0 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-hateoas 87 | 88 | 89 | io.jsonwebtoken 90 | jjwt-api 91 | 0.11.5 92 | 93 | 94 | io.jsonwebtoken 95 | jjwt-impl 96 | 0.11.5 97 | runtime 98 | 99 | 100 | io.jsonwebtoken 101 | jjwt-jackson 102 | 0.11.5 103 | runtime 104 | 105 | 106 | com.stripe 107 | stripe-java 108 | 25.13.0 109 | 110 | 111 | 112 | com.logtail 113 | logback-logtail 114 | 0.3.3 115 | 116 | 117 | ch.qos.logback 118 | logback-classic 119 | 1.2.13 120 | 121 | 122 | ch.qos.logback 123 | logback-core 124 | 1.2.13 125 | 126 | 127 | org.springframework.boot 128 | spring-boot-starter-mail 129 | 130 | 131 | org.springframework.boot 132 | spring-boot-starter-thymeleaf 133 | 134 | 135 | org.springframework.boot 136 | spring-boot-starter-security 137 | 138 | 139 | org.springframework.boot 140 | spring-boot-starter-test 141 | test 142 | 143 | 144 | org.springframework.security 145 | spring-security-test 146 | test 147 | 148 | 149 | 150 | 151 | 152 | 153 | org.springframework.boot 154 | spring-boot-maven-plugin 155 | 156 | 157 | 158 | org.projectlombok 159 | lombok 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | -- User table 2 | create table _user 3 | ( 4 | user_id bigint not null 5 | primary key, 6 | create_dt timestamp not null, 7 | email varchar(255) not null 8 | constraint uk_k11y3pdtsrjgy8w9b6q4bjwrx 9 | unique, 10 | password varchar(255) not null, 11 | role varchar(10) not null, 12 | update_dt timestamp not null 13 | ); 14 | 15 | alter table address 16 | owner to "ved-asole"; 17 | 18 | -- Customer table 19 | create table customer 20 | ( 21 | customer_id bigint not null 22 | primary key, 23 | create_dt timestamp not null, 24 | email varchar(255) not null 25 | constraint uk_dwk6cx0afu8bs9o4t536v1j5v 26 | unique, 27 | first_name varchar(20) not null, 28 | last_name varchar(20) not null, 29 | phone_number varchar(255) not null, 30 | update_dt timestamp not null, 31 | address_id bigint 32 | constraint "FKp0ork25utpkdwuhi874nf19cc" 33 | references address, 34 | user_id bigint not null 35 | constraint uk_j7ja2xvrxudhvssosd4nu1o92 36 | unique 37 | constraint "FKk2b4lf83ck1rq7vmfuver6e1c" 38 | references _user 39 | ); 40 | 41 | alter table customer 42 | owner to "ved-asole"; 43 | 44 | -- Category Table 45 | create table category 46 | ( 47 | category_id bigint not null 48 | primary key, 49 | active boolean not null, 50 | create_dt timestamp not null, 51 | "desc" varchar(1000), 52 | image varchar(255) not null, 53 | name varchar(20) not null, 54 | update_dt timestamp not null, 55 | parent_category_id bigint 56 | constraint "FK4wqwi3wgsrq5kka9k94vc5u2i" 57 | references category 58 | ); 59 | 60 | alter table category 61 | owner to "ved-asole"; 62 | 63 | -- Product table 64 | create table product 65 | ( 66 | product_id bigint not null 67 | primary key, 68 | create_dt timestamp not null, 69 | "desc" varchar(1000), 70 | discount double precision 71 | constraint product_discount_check 72 | check (discount >= (0)::double precision), 73 | image varchar(255) not null, 74 | name varchar(255) not null, 75 | price double precision not null 76 | constraint product_price_check 77 | check (price >= (0)::double precision), 78 | qty_in_stock integer 79 | constraint product_qty_in_stock_check 80 | check (qty_in_stock >= 0), 81 | sku varchar(255) not null 82 | constraint uk_q1mafxn973ldq80m1irp3mpvq 83 | unique, 84 | update_dt timestamp, 85 | category_id bigint not null 86 | constraint "FK7l29ekt1x29jup80y2iigimyy" 87 | references category 88 | ); 89 | 90 | alter table product 91 | owner to "ved-asole"; 92 | 93 | create index product_name_idx 94 | on product (name); 95 | 96 | create index product_desc_idx 97 | on product ("desc"); 98 | 99 | create index product_name_desc_idx 100 | on product (name, "desc"); 101 | 102 | create index product_category_idx 103 | on product (category_id); 104 | 105 | 106 | -- Address table 107 | alter table _user 108 | owner to "ved-asole"; 109 | 110 | create table address 111 | ( 112 | address_id bigint not null 113 | primary key, 114 | add_line1 varchar(100) not null, 115 | add_line2 varchar(100), 116 | city varchar(50) not null, 117 | country varchar(50) not null, 118 | create_dt timestamp not null, 119 | postal_code integer not null, 120 | state varchar(50) not null, 121 | update_dt timestamp not null 122 | ); 123 | 124 | --Cart Item table 125 | create table cart_item 126 | ( 127 | cart_item_id bigint not null 128 | primary key, 129 | create_dt timestamp not null, 130 | quantity bigint not null 131 | constraint cart_item_quantity_check 132 | check (quantity >= 0), 133 | update_dt timestamp, 134 | product_id bigint not null 135 | constraint "FKbqjyyaj7ikkmpvm4vw2l64y2s" 136 | references product, 137 | cart_id bigint not null 138 | constraint "FKlmddnw6pd7gder2x4r07f1ves" 139 | references cart 140 | ); 141 | 142 | alter table cart_item 143 | owner to "ved-asole"; 144 | 145 | -- Cart table 146 | create table cart 147 | ( 148 | cart_id bigint not null 149 | primary key, 150 | create_dt timestamp not null, 151 | discount double precision 152 | constraint cart_discount_check 153 | check (discount >= (0)::double precision), 154 | total double precision not null 155 | constraint cart_total_check 156 | check (total >= (0)::double precision), 157 | update_dt timestamp, 158 | customer_id bigint not null 159 | constraint uk_867x3yysb1f3jk41cv3vsoejj 160 | unique 161 | constraint "FKjkl19yyf10l5tb7j5npdhgy3b" 162 | references customer 163 | ); 164 | 165 | alter table cart 166 | owner to "ved-asole"; 167 | 168 | -- Order Item table 169 | create table order_item 170 | ( 171 | order_item_id bigint not null 172 | primary key, 173 | create_dt timestamp not null, 174 | quantity bigint not null 175 | constraint order_item_quantity_check 176 | check (quantity >= 0), 177 | update_dt timestamp, 178 | order_id bigint not null 179 | constraint "FKl1bqqbilx1hdy29vykrqkgu3p" 180 | references "order", 181 | product_id bigint not null 182 | constraint "FKsxgfmcie6oo67uxtk9hqk02mq" 183 | references product 184 | ); 185 | 186 | -- Order table 187 | create table "order" 188 | ( 189 | order_id bigint not null 190 | primary key, 191 | create_dt timestamp not null, 192 | order_status varchar(30) not null, 193 | total double precision not null 194 | constraint order_total_check 195 | check (total >= (0)::double precision), 196 | update_dt timestamp, 197 | address_id bigint not null 198 | constraint "FKjm6o0lh0tgj5m2tshjbaw5moj" 199 | references address, 200 | customer_id bigint not null 201 | constraint "FKk1m6gjs4m7rtgb5lw01g35yca" 202 | references customer 203 | ); 204 | 205 | alter table "order" 206 | owner to "ved-asole"; 207 | 208 | create index order_customer_id_idx 209 | on "order" (customer_id); 210 | 211 | create index order_customer_id_order_id_idx 212 | on "order" (customer_id, order_id); -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/EkartEcommerceBackendApplication.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.context.event.ApplicationReadyEvent; 9 | import org.springframework.cache.annotation.EnableCaching; 10 | import org.springframework.context.event.EventListener; 11 | import org.springframework.core.env.Environment; 12 | 13 | /** 14 | * Main class of the E-Kart E-Commerce Backend application. 15 | * @author : Ved Asole 16 | */ 17 | @SpringBootApplication 18 | @EnableCaching 19 | @Slf4j 20 | @RequiredArgsConstructor 21 | public class EkartEcommerceBackendApplication { 22 | 23 | @Value("${spring.application.name:defaultAppName}") 24 | private String appName; 25 | 26 | private final Environment environment; 27 | 28 | public static void main(String[] args) { 29 | SpringApplication.run(EkartEcommerceBackendApplication.class, args); 30 | } 31 | 32 | @EventListener(ApplicationReadyEvent.class) 33 | public void onApplicationReady() { 34 | log.info("EkartEcommerceBackendApplication started \uD83D\uDE80"); 35 | log.info("Application name: {}, Port:{}", appName, environment.getProperty("local.server.port")); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/config/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.SerializationFeature; 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 6 | import com.vedasole.ekartecommercebackend.repository.UserRepo; 7 | import lombok.AllArgsConstructor; 8 | import org.modelmapper.ModelMapper; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.AuthenticationProvider; 13 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 14 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 15 | import org.springframework.security.core.userdetails.UserDetailsService; 16 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 17 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 18 | import org.springframework.security.crypto.password.PasswordEncoder; 19 | 20 | @Configuration 21 | @AllArgsConstructor 22 | public class ApplicationConfig 23 | { 24 | private final UserRepo userRepo; 25 | 26 | 27 | /** 28 | * Creates a new instance of the MapperConfig class. 29 | * 30 | * @return a new MapperConfig instance 31 | */ 32 | @Bean 33 | public ModelMapper modelMapper() { 34 | ModelMapper modelMapper = new ModelMapper(); 35 | modelMapper.getConfiguration().setAmbiguityIgnored(true); 36 | return modelMapper; 37 | } 38 | 39 | /** 40 | * Creates a new instance of the ObjectMapper class. 41 | * 42 | * @return a new ObjectMapper instance 43 | */ 44 | @Bean 45 | public ObjectMapper objectMapper() { 46 | ObjectMapper objectMapper = new ObjectMapper(); 47 | objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 48 | objectMapper.registerModule(new JavaTimeModule()); 49 | return objectMapper; 50 | } 51 | 52 | /** 53 | * Creates a new instance of the UserDetailsService class. 54 | * 55 | * @return a new UserDetailsService instance 56 | */ 57 | @Bean 58 | public UserDetailsService userDetailsService(){ 59 | return username -> this.userRepo.findByEmailIgnoreCase(username) 60 | .orElseThrow(() -> new UsernameNotFoundException("User not found")); 61 | } 62 | 63 | /** 64 | * Creates a new instance of the AuthenticationProvider class. 65 | * 66 | * @return a new AuthenticationProvider instance 67 | */ 68 | @Bean 69 | public AuthenticationProvider authenticationProvider(){ 70 | DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); 71 | authProvider.setUserDetailsService(userDetailsService()); 72 | authProvider.setPasswordEncoder(passwordEncoder()); 73 | return authProvider; 74 | } 75 | 76 | /** 77 | * Creates a new instance of the AuthenticationManager class. 78 | * 79 | * @return a new AuthenticationManager instance 80 | */ 81 | @Bean 82 | public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { 83 | return config.getAuthenticationManager(); 84 | } 85 | 86 | /** 87 | * Creates a new instance of the PasswordEncoder class. 88 | * 89 | * @return a new BCryptPasswordEncoder instance 90 | */ 91 | @Bean 92 | public PasswordEncoder passwordEncoder() { 93 | return new BCryptPasswordEncoder(); 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.PathSelectors; 6 | import springfox.documentation.builders.RequestHandlerSelectors; 7 | import springfox.documentation.service.*; 8 | import springfox.documentation.spi.DocumentationType; 9 | import springfox.documentation.spi.service.contexts.SecurityContext; 10 | import springfox.documentation.spring.web.plugins.Docket; 11 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | @Configuration 17 | @EnableSwagger2 18 | public class SwaggerConfig { 19 | 20 | private static final String AUTHORIZATION_HEADER = "Authorization"; 21 | 22 | private ApiKey apiKeys(){ 23 | return new ApiKey("JWT", AUTHORIZATION_HEADER, "Header"); 24 | } 25 | 26 | private List securityContexts(){ 27 | SecurityContext context = SecurityContext.builder() 28 | .securityReferences(securityReferences()) 29 | .build(); 30 | return List.of(context); 31 | } 32 | 33 | private List securityReferences() { 34 | AuthorizationScope authorizationScope = new AuthorizationScope( 35 | "global", 36 | "access everything" 37 | ); 38 | return List.of(new SecurityReference( 39 | "JWT", 40 | new AuthorizationScope[]{ authorizationScope })); 41 | } 42 | 43 | @Bean 44 | public Docket api(){ 45 | return new Docket(DocumentationType.SWAGGER_2) 46 | .apiInfo(getApiInfo()) 47 | .securityContexts(securityContexts()) 48 | .securitySchemes(List.of(apiKeys())) 49 | .select() 50 | .apis(RequestHandlerSelectors.basePackage("com.vedasole.ekartecommercebackend")) 51 | .paths(PathSelectors.any()) 52 | .build(); 53 | } 54 | 55 | private ApiInfo getApiInfo() { 56 | return new ApiInfo( 57 | "Ekart E-commerce API", 58 | """ 59 | Ekart Ecommerce is an online e-commerce app which provides the facility for online shopping from any location. 60 | A backend API project for the eKart E-commerce website developed using Java and Spring Framework. 61 | 62 | Author: Ved Asole(ved-asole) 63 | 64 | Project Link: https://github.com/ved-asole/eKart-ecommerce-backend 65 | """, 66 | "1.0", 67 | "Terms of Service", 68 | new Contact( 69 | "Ved Asole", 70 | "https://www.vedasole.cloud", 71 | "ekart-support@vedasole.cloud" 72 | ), 73 | "License of APIs", 74 | "https://github.com/ved-asole/eKart-ecommerce-backend/blob/master/LICENSE", 75 | Collections.emptyList() 76 | ); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.controller; 2 | 3 | import com.vedasole.ekartecommercebackend.payload.*; 4 | import com.vedasole.ekartecommercebackend.service.service_interface.AuthenticationService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.validation.annotation.Validated; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.mail.MessagingException; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.validation.Valid; 15 | 16 | /** 17 | * Controller for handling authentication related requests. 18 | * 19 | * @author Ved Asole 20 | * @since 1.0.0 21 | */ 22 | @Validated 23 | @RestController 24 | @RequestMapping("/api/v1/auth") 25 | @CrossOrigin( 26 | value = { 27 | "http://localhost:5173", 28 | "https://ekart.vedasole.me", 29 | "https://ekart-shopping.netlify.app", 30 | "https://develop--ekart-shopping.netlify.app" 31 | }, 32 | allowCredentials = "true", 33 | exposedHeaders = {"Authorization"} 34 | ) 35 | @RequiredArgsConstructor 36 | public class AuthController { 37 | 38 | private final AuthenticationService authenticationService; 39 | 40 | /** 41 | * Authenticates a user and returns an authentication token. 42 | * 43 | * @param authRequest The authentication request containing the user's credentials. 44 | * @param request The HTTP request. 45 | * @return A ResponseEntity containing the authentication response and an HTTP status code. 46 | */ 47 | @PostMapping("/authenticate") 48 | public ResponseEntity authenticate( 49 | @Valid @RequestBody AuthenticationRequest authRequest, 50 | HttpServletRequest request 51 | ) { 52 | AuthenticationResponse authenticationResponse = this.authenticationService.authenticate(authRequest, request); 53 | HttpHeaders headers = new HttpHeaders(); 54 | headers.add("Authorization", "Bearer " + authenticationResponse.getToken()); 55 | return new ResponseEntity<>(authenticationResponse, headers, HttpStatus.OK); 56 | } 57 | 58 | /** 59 | * Checks if a user is authenticated based on the provided request. 60 | * 61 | * @param request The HTTP request. 62 | * @return A ResponseEntity containing a boolean indicating whether the user is authenticated or not, and an HTTP status code. 63 | */ 64 | @GetMapping("/check-token") 65 | public ResponseEntity isAuthenticated(HttpServletRequest request) { 66 | boolean isValid = this.authenticationService.authenticate(request); 67 | if(isValid) return new ResponseEntity<>(true, HttpStatus.OK); 68 | else return new ResponseEntity<>(false, HttpStatus.UNAUTHORIZED); 69 | } 70 | 71 | @PostMapping("/generate-reset-token") 72 | public ResponseEntity generatePasswordResetToken(@RequestBody ResetTokenRequestDto resetTokenRequestDto) { 73 | try { 74 | authenticationService.generatePasswordResetToken(resetTokenRequestDto.email()); 75 | return new ResponseEntity<>(new ApiResponse("Password reset token sent successfully", true), HttpStatus.OK); 76 | } catch (MessagingException e) { 77 | return new ResponseEntity<>(new ApiResponse("Cannot send token to the emailId: " + resetTokenRequestDto.email(), false), HttpStatus.INTERNAL_SERVER_ERROR); 78 | } 79 | } 80 | 81 | @PostMapping("/reset-password") 82 | public ResponseEntity resetPassword(@Valid @RequestBody PasswordResetRequestDto passwordResetRequestDto) { 83 | boolean tokenValid = authenticationService.isResetTokenValid(passwordResetRequestDto.getToken()); 84 | if (!tokenValid) { 85 | return new ResponseEntity<>(new ApiResponse("Invalid or expired token", false), HttpStatus.BAD_REQUEST); 86 | } 87 | boolean isReset = authenticationService.resetPassword(passwordResetRequestDto.getToken(), passwordResetRequestDto.getNewPassword()); 88 | if (isReset) { 89 | return new ResponseEntity<>(new ApiResponse("Password reset successfully", true), HttpStatus.OK); 90 | } else { 91 | return new ResponseEntity<>(new ApiResponse("Invalid or expired token", false), HttpStatus.BAD_REQUEST); 92 | } 93 | } 94 | 95 | @PostMapping("/validate-reset-token") 96 | public ResponseEntity isResetTokenValid(@RequestBody ValidateTokenRequestDto validateTokenRequestDto) { 97 | boolean isTokenValid = authenticationService.isResetTokenValid(validateTokenRequestDto.token()); 98 | if(isTokenValid) return new ResponseEntity<>(true, HttpStatus.OK); 99 | else return new ResponseEntity<>(false, HttpStatus.BAD_GATEWAY); 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/controller/OrderItemController.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.controller; 2 | 3 | import com.vedasole.ekartecommercebackend.payload.OrderItemDto; 4 | import com.vedasole.ekartecommercebackend.service.service_interface.OrderItemService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.hateoas.CollectionModel; 7 | import org.springframework.hateoas.EntityModel; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.validation.Valid; 13 | import java.util.List; 14 | 15 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 16 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; 17 | 18 | @RestController 19 | @RequestMapping("/api/v1/order/{orderId}/items") 20 | @CrossOrigin(value = { 21 | "http://localhost:5173", 22 | "https://ekart.vedasole.me", 23 | "https://ekart-shopping.netlify.app", 24 | "https://develop--ekart-shopping.netlify.app" 25 | }, allowCredentials = "true") 26 | @RequiredArgsConstructor 27 | public class OrderItemController { 28 | 29 | private final OrderItemService orderItemService; 30 | 31 | @PostMapping 32 | public ResponseEntity> createOrderItem( 33 | @PathVariable Long orderId, 34 | @RequestBody @Valid OrderItemDto orderItemDto 35 | ){ 36 | orderItemDto.setOrderId(orderId); 37 | OrderItemDto orderItem = orderItemService.createOrderItem(orderItemDto); 38 | EntityModel orderItemDtoEntityModel = EntityModel.of( 39 | orderItem 40 | // , 41 | // linkTo(methodOn(ShoppingCartItemController.class).getShoppingCartItem(shoppingCartItem.getCartId(), shoppingCartItem.getCartItemId())).withSelfRel(), 42 | // linkTo(methodOn(ShoppingCartController.class).getShoppingCart(shoppingCartItem.getCartId())).withRel("cart") 43 | ); 44 | return new ResponseEntity<>(orderItemDtoEntityModel, HttpStatus.CREATED); 45 | } 46 | 47 | @PutMapping 48 | public ResponseEntity> updateOrderItem( 49 | @PathVariable Long orderId, 50 | @RequestBody @Valid OrderItemDto orderItemDto 51 | ){ 52 | orderItemDto.setOrderId(orderId); 53 | OrderItemDto orderItem = orderItemService.updateOrderItem(orderItemDto); 54 | EntityModel orderItemDtoEntityModel = EntityModel.of( 55 | orderItem 56 | // , 57 | // linkTo(methodOn(ShoppingCartItemController.class).getShoppingCartItem(shoppingCartItem.getCartId(), shoppingCartItem.getCartItemId())).withSelfRel(), 58 | // linkTo(methodOn(ShoppingCartController.class).getShoppingCart(shoppingCartItem.getCartId())).withRel("cart") 59 | ); 60 | return new ResponseEntity<>(orderItemDtoEntityModel, HttpStatus.OK); 61 | } 62 | 63 | @DeleteMapping("/{orderItemId}") 64 | public ResponseEntity deleteOrderItem( 65 | @PathVariable Long orderId, 66 | @PathVariable Long orderItemId 67 | ) { 68 | orderItemService.deleteOrderItem(orderItemId); 69 | return ResponseEntity.noContent().build(); 70 | } 71 | 72 | @DeleteMapping 73 | public ResponseEntity deleteAllOrderItems( 74 | @PathVariable Long orderId 75 | ) { 76 | orderItemService.deleteAllOrderItems(orderId); 77 | return ResponseEntity.noContent().build(); 78 | } 79 | 80 | @GetMapping("/{orderItemId}") 81 | public ResponseEntity> getOrderItem( 82 | @PathVariable long orderId, 83 | @PathVariable long orderItemId 84 | ){ 85 | OrderItemDto orderItem = orderItemService.getOrderItem(orderItemId); 86 | EntityModel orderItemDtoEntityModel = EntityModel.of( 87 | orderItem, 88 | linkTo(methodOn(OrderItemController.class).getOrderItem(orderId, orderItemId)).withSelfRel() 89 | // , 90 | // linkTo(methodOn(ShoppingCartController.class).getShoppingCart(orderId)).withRel("order") 91 | ); 92 | return ResponseEntity.ok(orderItemDtoEntityModel); 93 | } 94 | 95 | @GetMapping 96 | public ResponseEntity> getAllOrderItems( 97 | @PathVariable long orderId 98 | ){ 99 | List allOrderItems = orderItemService.getAllOrderItems(orderId); 100 | CollectionModel orderItemDtoCollectionModel = CollectionModel.of( 101 | allOrderItems, 102 | linkTo(methodOn(OrderItemController.class).getAllOrderItems(orderId)).withSelfRel() 103 | // , 104 | // linkTo(methodOn(ShoppingCartController.class).getShoppingCart(orderId)).withRel("order") 105 | ); 106 | return ResponseEntity.ok(orderItemDtoCollectionModel); 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/controller/PaymentController.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.controller; 2 | 3 | import com.stripe.exception.SignatureVerificationException; 4 | import com.stripe.model.Event; 5 | import com.stripe.model.StripeObject; 6 | import com.stripe.model.checkout.Session; 7 | import com.stripe.net.LiveStripeResponseGetter; 8 | import com.stripe.net.Webhook; 9 | import com.vedasole.ekartecommercebackend.exception.APIException; 10 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartDto; 11 | import com.vedasole.ekartecommercebackend.service.service_interface.PaymentService; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import javax.validation.Valid; 20 | 21 | @RestController 22 | @RequestMapping("/api/v1/payment") 23 | @CrossOrigin(value = { 24 | "http://localhost:5173", 25 | "https://ekart.vedasole.me", 26 | "https://ekart-shopping.netlify.app", 27 | "https://develop--ekart-shopping.netlify.app" 28 | }, allowCredentials = "true") 29 | @RequiredArgsConstructor 30 | @Slf4j 31 | public class PaymentController { 32 | 33 | private final PaymentService paymentService; 34 | @Value("${stripe.endpoint.secret}") 35 | private String endpointSecret; 36 | 37 | @PostMapping("/create-checkout-session") 38 | public ResponseEntity createCheckoutSession( 39 | @Valid @RequestBody ShoppingCartDto shoppingCartDto 40 | ) { 41 | Session session = paymentService.createCheckoutSession(shoppingCartDto); 42 | return new ResponseEntity<>( 43 | session.getUrl(), 44 | HttpStatus.CREATED 45 | ); 46 | } 47 | 48 | @PostMapping("/webhook/stripe") 49 | public void handleStripeEvents( 50 | @RequestBody String payload, 51 | @RequestHeader("Stripe-Signature") String sigHeader 52 | ) { 53 | log.debug("Webhook received with sigHeader: {}", sigHeader); 54 | try { 55 | // Verify the signature 56 | Webhook.Signature.verifyHeader(payload, sigHeader, endpointSecret, 300L); 57 | log.debug("Webhook received and verified header: {}", sigHeader); 58 | 59 | Event event = Webhook.constructEvent(payload, sigHeader, endpointSecret); 60 | log.info("event_id : {}, event_type: {}", event.getId(), event.getType()); 61 | 62 | StripeObject stripeObject = StripeObject.deserializeStripeObject( 63 | event.getData().getObject().toJson(), 64 | event.getData().getObject().getClass(), 65 | new LiveStripeResponseGetter() 66 | ); 67 | 68 | paymentService.handleStripeEvent(stripeObject); 69 | log.info("Event processed successfully: {}", event.getId()); 70 | 71 | } catch (SignatureVerificationException e) { 72 | // Invalid signature 73 | log.error("Event signature verification failed: sigHeader[{}]", sigHeader, e); 74 | throw new APIException("Invalid Event- signature verification failed", HttpStatus.BAD_REQUEST); 75 | } catch (Exception e) { 76 | // Other exceptions 77 | log.error("Error processing event: {}", e.getMessage(), e); 78 | throw new APIException("Error processing event", HttpStatus.INTERNAL_SERVER_ERROR); 79 | } 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/controller/ShoppingCartController.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.controller; 2 | 3 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartDto; 4 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartItemDto; 5 | import com.vedasole.ekartecommercebackend.service.service_interface.ShoppingCartService; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.hateoas.EntityModel; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.validation.annotation.Validated; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.validation.Valid; 13 | 14 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 15 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; 16 | 17 | @Validated 18 | @RestController 19 | @RequestMapping("/api/v1/shopping-cart") 20 | @CrossOrigin(value = { 21 | "http://localhost:5173", 22 | "https://ekart.vedasole.me", 23 | "https://ekart-shopping.netlify.app", 24 | "https://develop--ekart-shopping.netlify.app" 25 | }, allowCredentials = "true") 26 | @RequiredArgsConstructor 27 | public class ShoppingCartController { 28 | 29 | private static final String CART_ITEMS = "cart-items"; 30 | private final ShoppingCartService shoppingCartService; 31 | 32 | @PostMapping({"/customer/{customerId}"}) 33 | //@PostAuthorize("hasRole('USER')") 34 | public ResponseEntity> createShoppingCart( 35 | @PathVariable long customerId, 36 | @RequestBody @Valid ShoppingCartDto shoppingCartDto 37 | ) { 38 | shoppingCartDto.setCustomerId(customerId); 39 | ShoppingCartDto shoppingCart = shoppingCartService.createCartWithItems(shoppingCartDto); 40 | EntityModel shoppingCartDtoEntityModel = EntityModel.of( 41 | shoppingCart, 42 | linkTo(methodOn(this.getClass()).getShoppingCart(shoppingCart.getCustomerId())).withSelfRel(), 43 | linkTo(methodOn(ShoppingCartItemController.class).getAllShoppingCartItems(shoppingCart.getCartId())).withRel(CART_ITEMS) 44 | ); 45 | return ResponseEntity.ok(shoppingCartDtoEntityModel); 46 | } 47 | 48 | @PutMapping("/{cartId}") 49 | //@PostAuthorize("hasRole('USER')") 50 | public ResponseEntity> updateCart( 51 | @PathVariable long cartId, 52 | @RequestBody @Valid ShoppingCartItemDto shoppingCartItemDto 53 | ) { 54 | shoppingCartItemDto.setCartId(cartId); 55 | ShoppingCartDto shoppingCart = shoppingCartService.addOrUpdateItemInCart(shoppingCartItemDto); 56 | EntityModel shoppingCartDtoEntityModel = EntityModel.of( 57 | shoppingCart, 58 | linkTo(methodOn(this.getClass()).getShoppingCart(cartId)).withSelfRel(), 59 | linkTo(methodOn(ShoppingCartItemController.class).getAllShoppingCartItems(shoppingCart.getCartId())).withRel(CART_ITEMS) 60 | ); 61 | return ResponseEntity.ok(shoppingCartDtoEntityModel); 62 | } 63 | 64 | @DeleteMapping("/customer/{customerId}") 65 | // //@PostAuthorize("hasRole('ADMIN')") 66 | public ResponseEntity deleteShoppingCart( 67 | @PathVariable long customerId 68 | ) { 69 | shoppingCartService.deleteCart(customerId); 70 | return ResponseEntity.noContent().build(); 71 | } 72 | 73 | @GetMapping("/customer/{customerId}") 74 | public ResponseEntity> getShoppingCart( 75 | @PathVariable long customerId 76 | ) { 77 | ShoppingCartDto shoppingCart = shoppingCartService.getCart(customerId); 78 | EntityModel shoppingCartDtoEntityModel = EntityModel.of( 79 | shoppingCart, 80 | linkTo(methodOn(this.getClass()).getShoppingCart(customerId)).withSelfRel(), 81 | linkTo(methodOn(ShoppingCartItemController.class).getAllShoppingCartItems(shoppingCart.getCartId())).withRel(CART_ITEMS) 82 | ); 83 | return ResponseEntity.ok(shoppingCartDtoEntityModel); 84 | } 85 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/controller/ShoppingCartItemController.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.controller; 2 | 3 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartItemDto; 4 | import com.vedasole.ekartecommercebackend.service.service_interface.ShoppingCartItemService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.hateoas.CollectionModel; 7 | import org.springframework.hateoas.EntityModel; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.validation.annotation.Validated; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import javax.validation.Valid; 14 | import java.util.List; 15 | 16 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; 17 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; 18 | 19 | @Validated 20 | @RestController 21 | @RequestMapping("/api/v1/shopping-cart/{cartId}/items") 22 | @CrossOrigin(value = { 23 | "http://localhost:5173", 24 | "https://ekart.vedasole.me", 25 | "https://ekart-shopping.netlify.app", 26 | "https://develop--ekart-shopping.netlify.app" 27 | }, allowCredentials = "true") 28 | @RequiredArgsConstructor 29 | public class ShoppingCartItemController { 30 | 31 | private final ShoppingCartItemService shoppingCartItemService; 32 | 33 | @PostMapping 34 | public ResponseEntity> createShoppingCartItem( 35 | @PathVariable Long cartId, 36 | @RequestBody @Valid ShoppingCartItemDto shoppingCartItemDto 37 | ){ 38 | shoppingCartItemDto.setCartId(cartId); 39 | ShoppingCartItemDto shoppingCartItem = shoppingCartItemService.createShoppingCartItem(shoppingCartItemDto); 40 | EntityModel shoppingCartItemDtoEntityModel = EntityModel.of( 41 | shoppingCartItem, 42 | linkTo(methodOn(ShoppingCartItemController.class).getShoppingCartItem(shoppingCartItem.getCartId(), shoppingCartItem.getCartItemId())).withSelfRel(), 43 | linkTo(methodOn(ShoppingCartController.class).getShoppingCart(shoppingCartItem.getCartId())).withRel("cart") 44 | ); 45 | return new ResponseEntity<>(shoppingCartItemDtoEntityModel, HttpStatus.CREATED); 46 | } 47 | 48 | @PutMapping 49 | public ResponseEntity> updateShoppingCartItem( 50 | @PathVariable Long cartId, 51 | @RequestBody @Valid ShoppingCartItemDto shoppingCartItemDto 52 | ){ 53 | shoppingCartItemDto.setCartId(cartId); 54 | ShoppingCartItemDto shoppingCartItem = shoppingCartItemService.updateShoppingCartItem(shoppingCartItemDto); 55 | EntityModel shoppingCartItemDtoEntityModel = EntityModel.of( 56 | shoppingCartItem, 57 | linkTo(methodOn(ShoppingCartItemController.class).getShoppingCartItem(shoppingCartItem.getCartId(), shoppingCartItem.getCartItemId())).withSelfRel(), 58 | linkTo(methodOn(ShoppingCartController.class).getShoppingCart(shoppingCartItem.getCartId())).withRel("cart") 59 | ); 60 | return new ResponseEntity<>(shoppingCartItemDtoEntityModel, HttpStatus.OK); 61 | } 62 | 63 | @DeleteMapping("/{cartItemId}") 64 | public ResponseEntity deleteShoppingCartItem( 65 | @PathVariable Long cartId, 66 | @PathVariable Long cartItemId 67 | ) { 68 | shoppingCartItemService.deleteShoppingCartItem(cartItemId); 69 | return ResponseEntity.noContent().build(); 70 | } 71 | 72 | @DeleteMapping 73 | public ResponseEntity deleteAllShoppingCartItems( 74 | @PathVariable Long cartId 75 | ) { 76 | shoppingCartItemService.deleteAllShoppingCartItems(cartId); 77 | return ResponseEntity.noContent().build(); 78 | } 79 | 80 | 81 | @GetMapping("/{cartItemId}") 82 | public ResponseEntity> getShoppingCartItem( 83 | @PathVariable long cartId, 84 | @PathVariable long cartItemId 85 | ){ 86 | ShoppingCartItemDto item = shoppingCartItemService.getShoppingCartItem(cartItemId); 87 | EntityModel shoppingCartItemDtoEntityModel = EntityModel.of( 88 | item, 89 | linkTo(methodOn(ShoppingCartItemController.class).getShoppingCartItem(item.getCartId(), item.getCartItemId())).withSelfRel(), 90 | linkTo(methodOn(ShoppingCartController.class).getShoppingCart(item.getCartId())).withRel("cart") 91 | ); 92 | return ResponseEntity.ok(shoppingCartItemDtoEntityModel); 93 | } 94 | 95 | @GetMapping 96 | public ResponseEntity> getAllShoppingCartItems( 97 | @PathVariable Long cartId 98 | ){ 99 | List allShoppingCartItems = shoppingCartItemService.getAllShoppingCartItems(cartId); 100 | CollectionModel shoppingCartItemDtoCollectionModel = CollectionModel.of( 101 | allShoppingCartItems, 102 | linkTo(methodOn(ShoppingCartItemController.class).getAllShoppingCartItems(cartId)).withSelfRel() 103 | ); 104 | return ResponseEntity.ok(shoppingCartItemDtoCollectionModel); 105 | } 106 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/Address.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | import org.hibernate.annotations.UpdateTimestamp; 9 | import org.springframework.validation.annotation.Validated; 10 | 11 | import javax.persistence.*; 12 | import java.time.LocalDateTime; 13 | 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Data 17 | @Builder 18 | @Validated 19 | @Entity 20 | @Table(name = "address") 21 | public class Address { 22 | 23 | @Id 24 | @Column(updatable = false) 25 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "address_seq") 26 | @SequenceGenerator(name = "address_seq", allocationSize = 0) 27 | private long addressId; 28 | 29 | @Column(name = "add_line1", length = 100, nullable = false) 30 | private String addLine1; 31 | 32 | @Column(name = "add_line2", length = 100) 33 | private String addLine2; 34 | 35 | 36 | @Column(name = "city", length = 50, nullable = false) 37 | private String city; 38 | 39 | 40 | @Column(name = "state", length = 50, nullable = false) 41 | private String state; 42 | 43 | @Column(name = "country", length = 50, nullable = false) 44 | private String country; 45 | 46 | @Column(name = "postal_code", length = 10, nullable = false) 47 | private int postalCode; 48 | 49 | @Column(name = "create_dt", nullable = false, updatable = false) 50 | @CreationTimestamp 51 | private LocalDateTime createdAt; 52 | 53 | @Column(name = "update_dt", nullable = false) 54 | @UpdateTimestamp 55 | private LocalDateTime updatedAt; 56 | 57 | public Address(long addressId, String addLine1, String addLine2, String city, String state, String country, int postalCode) { 58 | this.addressId = addressId; 59 | this.addLine1 = addLine1; 60 | this.addLine2 = addLine2; 61 | this.city = city; 62 | this.state = state; 63 | this.country = country; 64 | this.postalCode = postalCode; 65 | } 66 | 67 | public Address(String addLine1, String addLine2, String city, String state, String country, int postalCode) { 68 | this.addLine1 = addLine1; 69 | this.addLine2 = addLine2; 70 | this.city = city; 71 | this.state = state; 72 | this.country = country; 73 | this.postalCode = postalCode; 74 | } 75 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/Category.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.annotations.CreationTimestamp; 9 | import org.hibernate.annotations.UpdateTimestamp; 10 | import org.springframework.validation.annotation.Validated; 11 | 12 | import javax.persistence.*; 13 | import java.io.Serial; 14 | import java.io.Serializable; 15 | import java.time.LocalDateTime; 16 | 17 | /** 18 | * Entity class for Category 19 | */ 20 | @Builder 21 | @Validated 22 | @Entity 23 | @Data 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @JsonIgnoreProperties(ignoreUnknown=true) 27 | @Table(name = "category") 28 | public class Category implements Serializable { 29 | 30 | @Serial 31 | private static final long serialVersionUID = -5392075886775352349L; 32 | 33 | @Id 34 | @Column(name = "category_id", updatable = false) 35 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "category_seq") 36 | @SequenceGenerator(name = "category_seq", allocationSize = 0) 37 | private long categoryId; 38 | 39 | @Column(name = "name", nullable = false, length = 50) 40 | private String name; 41 | 42 | @Column(name = "image") 43 | private String image; 44 | 45 | @Column(name = "desc", length = 1000) 46 | private String desc; 47 | 48 | @ManyToOne 49 | @JoinColumn(name = "parent_category_id") 50 | private Category parentCategory; 51 | 52 | @Column(name = "active", nullable = false) 53 | private boolean active; 54 | 55 | @Column(name = "create_dt", nullable = false, updatable = false) 56 | @CreationTimestamp 57 | private LocalDateTime createdAt; 58 | 59 | @Column(name = "update_dt", nullable = false) 60 | @UpdateTimestamp 61 | private LocalDateTime updatedAt; 62 | 63 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.annotations.CreationTimestamp; 9 | import org.hibernate.annotations.UpdateTimestamp; 10 | import org.springframework.validation.annotation.Validated; 11 | 12 | import javax.persistence.*; 13 | import javax.validation.constraints.NotBlank; 14 | import javax.validation.constraints.NotNull; 15 | import javax.validation.constraints.Size; 16 | import java.time.LocalDateTime; 17 | 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @Data 21 | @Builder 22 | @Validated 23 | @Entity 24 | @Table(name = "customer") 25 | public class Customer { 26 | 27 | @Id 28 | @Column(updatable = false) 29 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customer_seq") 30 | @SequenceGenerator(name = "customer_seq", allocationSize = 1) 31 | private long customerId; 32 | 33 | @NotNull(message = "First name is required") 34 | @NotBlank(message = "First name cannot be blank")@Size( 35 | min = 3, 36 | max = 20, 37 | message = "First name must be between minimum of 3 characters " + 38 | "and maximum of 20 characters" 39 | ) 40 | @Column(name = "first_name", nullable = false, length = 20) 41 | private String firstName; 42 | 43 | @NotNull(message = "Last name is required") 44 | @NotBlank(message = "Last name cannot be blank") 45 | @Size( 46 | min = 3, 47 | max = 20, 48 | message = "Last name must be between minimum of 3 characters " + 49 | "and maximum of 20 characters" 50 | ) 51 | @Column(name = "last_name", nullable = false, length = 20) 52 | private String lastName; 53 | 54 | @NotNull(message = "Phone number is required") 55 | @NotBlank(message = "Phone number cannot be blank") 56 | @Column(name = "phone_number") 57 | private String phoneNumber; 58 | 59 | @NotNull(message = "Email is required") 60 | @NotBlank(message = "Email cannot be blank") 61 | @Column(name = "email", nullable = false, unique = true) 62 | private String email; 63 | 64 | @NotNull(message = "User is required") 65 | @OneToOne(cascade = CascadeType.ALL) 66 | @JoinColumn(name = "user_id", unique = true, nullable = false, updatable = false) 67 | private User user; 68 | 69 | @OneToOne(mappedBy = "customer", cascade = CascadeType.ALL) 70 | private ShoppingCart shoppingCart; 71 | 72 | @Column(name = "create_dt", nullable = false, updatable = false) 73 | @CreationTimestamp 74 | private LocalDateTime createdAt; 75 | 76 | @Column(name = "update_dt", nullable = false) 77 | @UpdateTimestamp 78 | private LocalDateTime updatedAt; 79 | 80 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/Order.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import com.vedasole.ekartecommercebackend.utility.AppConstant.OrderStatus; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.annotations.CreationTimestamp; 9 | import org.hibernate.annotations.UpdateTimestamp; 10 | import org.springframework.validation.annotation.Validated; 11 | 12 | import javax.persistence.*; 13 | import javax.validation.constraints.Min; 14 | import javax.validation.constraints.NotNull; 15 | import java.time.LocalDateTime; 16 | import java.util.List; 17 | 18 | 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Data 22 | @Builder 23 | @Validated 24 | @Entity 25 | @Table( 26 | name = "order", 27 | indexes = { 28 | @Index(name="order_customer_idx", columnList = "customer_id"), 29 | @Index(name="order_customer_order_id_idx", columnList = "customer_id, orderId"), 30 | } 31 | ) 32 | public class Order { 33 | 34 | @Id 35 | @Column(updatable = false) 36 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_seq") 37 | @SequenceGenerator(name = "order_seq", allocationSize = 0) 38 | private long orderId; 39 | 40 | @ManyToOne(optional = false) 41 | @JoinColumn(name = "customer_id", nullable = false) 42 | private Customer customer; 43 | 44 | @OneToMany(cascade = CascadeType.ALL, mappedBy = "order", fetch = FetchType.LAZY) 45 | private List orderItems; 46 | 47 | @ManyToOne(optional = false) 48 | @JoinColumn(name = "address_id") 49 | private Address address; 50 | 51 | @Min(value = 0, message = "Total must be greater than or equal to 0") 52 | private double total; 53 | 54 | @NotNull(message = "Order status is required") 55 | @Enumerated(EnumType.STRING) 56 | @Column(name = "order_status", length = 30, nullable = false) 57 | private OrderStatus orderStatus; 58 | 59 | @Column(name = "create_dt", nullable = false, updatable = false) 60 | @CreationTimestamp 61 | private LocalDateTime createdAt; 62 | 63 | @Column(name = "update_dt") 64 | @UpdateTimestamp 65 | private LocalDateTime updatedAt; 66 | 67 | public Order(long orderId, Customer customer, List orderItems, Address address, double total, OrderStatus orderStatus) { 68 | this.orderId = orderId; 69 | this.customer = customer; 70 | this.orderItems = orderItems; 71 | this.address = address; 72 | this.total = total; 73 | this.orderStatus = orderStatus; 74 | } 75 | 76 | public Order(Customer customer, List orderItems, Address address, double total, OrderStatus orderStatus) { 77 | this.customer = customer; 78 | this.orderItems = orderItems; 79 | this.address = address; 80 | this.total = total; 81 | this.orderStatus = orderStatus; 82 | } 83 | 84 | @PrePersist 85 | @PreUpdate 86 | private void onPersistOrUpdate() { 87 | calculateTotal(); 88 | } 89 | 90 | public void setOrderItems(List orderItems) { 91 | if (this.orderItems != null) { 92 | this.orderItems.forEach(orderItem -> orderItem.setOrder(null)); 93 | } 94 | this.orderItems = orderItems; 95 | if (orderItems != null) { 96 | this.orderItems.forEach(orderItem -> orderItem.setOrder(this)); 97 | } 98 | calculateTotal(); 99 | } 100 | 101 | public void calculateTotal() { 102 | this.total=0; 103 | if(!(orderItems == null || orderItems.isEmpty())) { 104 | this.total = orderItems.stream() 105 | .mapToDouble(orderItem -> { 106 | double finalPrice = orderItem.getProduct().getPrice() / 100 * (100 - orderItem.getProduct().getDiscount()); 107 | return finalPrice * orderItem.getQuantity(); 108 | }).reduce(0, Double::sum); 109 | } 110 | } 111 | 112 | @Override 113 | public String toString() { 114 | return "Order{" + 115 | "orderId=" + orderId + 116 | ", customer=" + customer + 117 | ", address=" + address + 118 | ", total=" + total + 119 | ", orderStatus=" + orderStatus + 120 | ", createdAt=" + createdAt + 121 | ", updatedAt=" + updatedAt + 122 | '}'; 123 | } 124 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | import org.hibernate.annotations.UpdateTimestamp; 9 | import org.springframework.validation.annotation.Validated; 10 | 11 | import javax.persistence.*; 12 | import javax.validation.constraints.Min; 13 | import java.time.LocalDateTime; 14 | 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Data 18 | @Builder 19 | @Validated 20 | @Entity 21 | @Table(name = "order_item") 22 | public class OrderItem { 23 | 24 | @Id 25 | @Column(name = "order_item_id", updatable = false) 26 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_item_seq") 27 | @SequenceGenerator(name = "order_item_seq", allocationSize = 0) 28 | private long orderItemId; 29 | 30 | @ManyToOne(fetch = FetchType.LAZY, optional = false) 31 | @JoinColumn(name = "order_id", nullable = false) 32 | private Order order; 33 | 34 | @ManyToOne(fetch = FetchType.LAZY, optional = false) 35 | @JoinColumn(name = "product_id", nullable = false) 36 | private Product product; 37 | 38 | @Column(name = "quantity", nullable = false) 39 | @Min(value = 0, message = "Product quantity cannot be negative") 40 | private long quantity; 41 | 42 | @Column(name = "create_dt", nullable = false, updatable = false) 43 | @CreationTimestamp 44 | private LocalDateTime createdAt; 45 | 46 | @Column(name = "update_dt") 47 | @UpdateTimestamp 48 | private LocalDateTime updatedAt; 49 | 50 | public OrderItem(Order order, Product product, long quantity) { 51 | this.order = order; 52 | this.product = product; 53 | this.quantity = quantity; 54 | } 55 | 56 | @PrePersist 57 | @PreUpdate 58 | @PreRemove 59 | private void onPersistOrUpdate() { this.getOrder().calculateTotal(); } 60 | 61 | @Override 62 | public String toString() { 63 | return "OrderItem{" + 64 | "orderItemId=" + orderItemId + 65 | ", product=" + product + 66 | ", quantity=" + quantity + 67 | ", createdAt=" + createdAt + 68 | ", updatedAt=" + updatedAt + 69 | '}'; 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/PasswordResetToken.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.time.LocalDateTime; 10 | 11 | @Entity 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Table(name = "reset_token") 17 | public class PasswordResetToken { 18 | 19 | @Id 20 | @Column(updatable = false) 21 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "reset_token_seq") 22 | @SequenceGenerator(name = "reset_token_seq", allocationSize = 1) 23 | private Long id; 24 | 25 | @Column(nullable = false, unique = true) 26 | private String token; 27 | 28 | @Column(nullable = false) 29 | private String email; 30 | 31 | @Column(nullable = false) 32 | private LocalDateTime expiryDate; 33 | 34 | public PasswordResetToken(String token, String email) { 35 | this.token = token; 36 | this.email = email; 37 | this.expiryDate = LocalDateTime.now().plusMinutes(30); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | import org.hibernate.annotations.UpdateTimestamp; 9 | import org.springframework.validation.annotation.Validated; 10 | 11 | import javax.persistence.*; 12 | import javax.validation.constraints.Min; 13 | import javax.validation.constraints.NotBlank; 14 | import javax.validation.constraints.NotNull; 15 | import java.time.LocalDateTime; 16 | 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Data 20 | @Builder 21 | @Validated 22 | @Entity 23 | @Table( 24 | name = "product", 25 | indexes = { 26 | @Index(name = "product_name_idx", columnList = "name"), 27 | @Index(name = "product_desc_idx", columnList = "desc"), 28 | @Index(name = "product_name_desc_idx", columnList = "name, desc"), 29 | @Index(name = "product_category_idx", columnList = "category_id") 30 | } 31 | ) 32 | public class Product { 33 | 34 | @Id 35 | @Column(name = "product_id" , updatable = false) 36 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq") 37 | @SequenceGenerator(name = "product_seq", allocationSize = 0) 38 | private long productId; 39 | 40 | @NotNull(message = "Product name cannot be null") 41 | @NotBlank(message = "Product name cannot be blank") 42 | @Column(name = "name", nullable = false) 43 | private String name; 44 | 45 | @NotNull(message = "Product image cannot be null") 46 | @NotBlank(message = "Product image cannot be blank") 47 | @Column(name = "image", nullable = false) 48 | private String image; 49 | 50 | @NotNull(message = "Product SKU cannot be null") 51 | @NotBlank(message = "Product SKU cannot be blank") 52 | @Column(name = "SKU", nullable = false, unique = true) 53 | private String sku; 54 | 55 | @Column(name = "desc", length = 1000) 56 | private String desc; 57 | 58 | @Column(name = "price", nullable = false) 59 | @NotNull(message = "Product price cannot be null") 60 | @Min(value = 0, message = "Product price must be greater than 0") 61 | private double price; 62 | 63 | @Column(name = "discount") 64 | @Min(value = 0, message = "Discount must not be negative") 65 | private double discount; 66 | 67 | @Column(name = "qtyInStock") 68 | @Min(value = 0, message = "Quantity in stock must be greater than 0") 69 | private int qtyInStock; 70 | 71 | @ManyToOne(optional = false,cascade = CascadeType.ALL) 72 | @JoinColumn(name = "category_id", nullable = false) 73 | private Category category; 74 | 75 | @Column(name = "create_dt", nullable = false, updatable = false) 76 | @CreationTimestamp 77 | private LocalDateTime createdAt; 78 | 79 | @Column(name = "update_dt") 80 | @UpdateTimestamp 81 | private LocalDateTime updatedAt; 82 | 83 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/ShoppingCart.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import net.minidev.json.annotate.JsonIgnore; 8 | import org.hibernate.annotations.CreationTimestamp; 9 | import org.hibernate.annotations.UpdateTimestamp; 10 | import org.springframework.validation.annotation.Validated; 11 | 12 | import javax.persistence.*; 13 | import javax.validation.constraints.Min; 14 | import java.time.LocalDateTime; 15 | import java.util.List; 16 | 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Data 20 | @Builder 21 | @Validated 22 | @Entity 23 | @Table(name = "cart") 24 | public class ShoppingCart { 25 | 26 | @Id 27 | @Column(updatable = false) 28 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cart_seq") 29 | @SequenceGenerator(name = "cart_seq", allocationSize = 0) 30 | private long cartId; 31 | 32 | @OneToOne(optional = false) 33 | @JoinColumn(name = "customer_id", nullable = false, unique = true) 34 | @JsonIgnore 35 | private Customer customer; 36 | 37 | @OneToMany(cascade = CascadeType.ALL, mappedBy = "shoppingCart", fetch = FetchType.LAZY) 38 | private List shoppingCartItems; 39 | 40 | @Column(name = "total", nullable = false) 41 | @Min(value = 0, message = "Total must be greater than or equal to 0") 42 | private double total; 43 | 44 | @Column(name = "discount") 45 | @Min(value = 0, message = "Discount must not be negative") 46 | private double discount; 47 | 48 | @Column(name = "create_dt", nullable = false, updatable = false) 49 | @CreationTimestamp 50 | private LocalDateTime createdAt; 51 | 52 | @Column(name = "update_dt") 53 | @UpdateTimestamp 54 | private LocalDateTime updatedAt; 55 | 56 | @PrePersist 57 | @PreUpdate 58 | private void onPersistOrUpdate() { 59 | calculateTotalAndDiscount(); 60 | } 61 | 62 | public void setShoppingCartItems(List shoppingCartItems) { 63 | if (this.shoppingCartItems != null) { 64 | this.shoppingCartItems.forEach(item -> item.setShoppingCart(null)); 65 | } 66 | this.shoppingCartItems = shoppingCartItems; 67 | if (shoppingCartItems != null) { 68 | this.shoppingCartItems.forEach(item -> item.setShoppingCart(this)); 69 | } 70 | calculateTotalAndDiscount(); 71 | } 72 | 73 | public void calculateTotalAndDiscount() { 74 | setTotal(0); 75 | setDiscount(0); 76 | if(!(shoppingCartItems == null || shoppingCartItems.isEmpty())) { 77 | shoppingCartItems.forEach(shoppingCartItem -> { 78 | double totalProductValue = shoppingCartItem.getProduct().getPrice() * shoppingCartItem.getQuantity(); 79 | setTotal(getTotal() + totalProductValue); 80 | setDiscount(getDiscount() + totalProductValue * shoppingCartItem.getProduct().getDiscount() / 100); 81 | }); 82 | } 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return "ShoppingCart{" + 88 | "cartId=" + cartId + 89 | ", customer=" + customer.getCustomerId() + 90 | ", shoppingCartItems=" + shoppingCartItems + 91 | ", total=" + total + 92 | ", discount=" + discount + 93 | ", createdAt=" + createdAt + 94 | ", updatedAt=" + updatedAt + 95 | '}'; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/ShoppingCartItem.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | import org.hibernate.annotations.UpdateTimestamp; 9 | import org.springframework.validation.annotation.Validated; 10 | 11 | import javax.persistence.*; 12 | import javax.validation.constraints.Min; 13 | import java.time.LocalDateTime; 14 | 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Data 18 | @Builder 19 | @Validated 20 | @Entity 21 | @Table(name = "cart_item") 22 | public class ShoppingCartItem { 23 | 24 | @Id 25 | @Column(name = "cart_item_id", updatable = false) 26 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cart_item_seq") 27 | @SequenceGenerator(name = "cart_item_seq", allocationSize = 0) 28 | private long cartItemId; 29 | 30 | @ManyToOne(fetch = FetchType.LAZY, optional = false) 31 | @JoinColumn(name = "product_id", nullable = false) 32 | private Product product; 33 | 34 | @ManyToOne(fetch = FetchType.LAZY, optional = false) 35 | @JoinColumn(name = "cart_id", nullable = false) 36 | private ShoppingCart shoppingCart; 37 | 38 | @Column(name = "quantity", nullable = false) 39 | @Min(value = 0, message = "Product quantity cannot be negative") 40 | private long quantity; 41 | 42 | @Column(name = "create_dt", nullable = false, updatable = false) 43 | @CreationTimestamp 44 | private LocalDateTime createdAt; 45 | 46 | @Column(name = "update_dt") 47 | @UpdateTimestamp 48 | private LocalDateTime updatedAt; 49 | 50 | @PrePersist 51 | @PreUpdate 52 | public void updateShoppingCartTotal() { 53 | this.getShoppingCart().calculateTotalAndDiscount(); 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "ShoppingCartItem{" + 59 | "cartItemId=" + cartItemId + 60 | ", product=" + product + 61 | ", quantity=" + quantity + 62 | '}'; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import com.vedasole.ekartecommercebackend.utility.AppConstant.Role; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.annotations.CreationTimestamp; 9 | import org.hibernate.annotations.UpdateTimestamp; 10 | import org.springframework.security.core.GrantedAuthority; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.validation.annotation.Validated; 14 | 15 | import javax.persistence.*; 16 | import javax.validation.constraints.Email; 17 | import javax.validation.constraints.NotBlank; 18 | import javax.validation.constraints.NotNull; 19 | import java.time.LocalDateTime; 20 | import java.util.Collection; 21 | import java.util.List; 22 | 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | @Builder 27 | @Validated 28 | @Entity 29 | @Table(name = "_user") 30 | public class User implements UserDetails { 31 | 32 | @Id 33 | @Column(updatable = false) 34 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq") 35 | @SequenceGenerator(name = "user_seq", allocationSize = 0) 36 | private long userId; 37 | 38 | @Email 39 | @NotBlank 40 | @Column(name = "email", nullable = false, unique = true) 41 | private String email; 42 | 43 | @NotBlank 44 | @Column(name = "password", nullable = false) 45 | private String password; 46 | 47 | @NotNull(message = "Role is required") 48 | @Enumerated(EnumType.STRING) 49 | @Column(name = "role", length = 10, nullable = false) 50 | private Role role; 51 | 52 | @Column(name = "create_dt", nullable = false, updatable = false) 53 | @CreationTimestamp 54 | private LocalDateTime createdAt; 55 | 56 | @Column(name = "update_dt", nullable = false) 57 | @UpdateTimestamp 58 | private LocalDateTime updatedAt; 59 | 60 | public User(long userId, String email, String password, Role role) { 61 | this.userId = userId; 62 | this.email = email; 63 | this.password = password; 64 | this.role = role; 65 | } 66 | 67 | public User(String email, String password, Role role) { 68 | this.email = email; 69 | this.password = password; 70 | this.role = role; 71 | } 72 | 73 | public User(String email, String password) { 74 | this.email = email; 75 | this.password = password; 76 | } 77 | 78 | @Override 79 | public Collection getAuthorities() { 80 | return List.of(new SimpleGrantedAuthority(role.getValue())); 81 | } 82 | 83 | @Override 84 | public String getUsername() { 85 | return email; 86 | } 87 | 88 | @Override 89 | public boolean isAccountNonExpired() { 90 | return true; 91 | } 92 | 93 | @Override 94 | public boolean isAccountNonLocked() { 95 | return true; 96 | } 97 | 98 | @Override 99 | public boolean isCredentialsNonExpired() { 100 | return true; 101 | } 102 | 103 | @Override 104 | public boolean isEnabled() { 105 | return true; 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/exception/APIException.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.HttpStatus; 7 | 8 | @Setter 9 | @Getter 10 | @Slf4j 11 | public class APIException extends RuntimeException { 12 | 13 | private final String message; 14 | private final HttpStatus httpStatus; 15 | 16 | public APIException(String message) { 17 | super(message); 18 | this.message = message; 19 | this.httpStatus= HttpStatus.INTERNAL_SERVER_ERROR; 20 | } 21 | 22 | public APIException(String message, HttpStatus httpStatus) { 23 | super(message); 24 | this.message = message; 25 | this.httpStatus = httpStatus; 26 | } 27 | 28 | public APIException(String message, Throwable cause) { 29 | super(message, cause); 30 | this.message = message; 31 | this.httpStatus= HttpStatus.INTERNAL_SERVER_ERROR; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.exception; 2 | 3 | import com.vedasole.ekartecommercebackend.payload.ApiResponse; 4 | import io.jsonwebtoken.*; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.authentication.BadCredentialsException; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.validation.FieldError; 11 | import org.springframework.web.bind.MethodArgumentNotValidException; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.RestControllerAdvice; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | @RestControllerAdvice 19 | @Slf4j 20 | public class GlobalExceptionHandler { 21 | 22 | @ExceptionHandler(ResourceNotFoundException.class) 23 | public ResponseEntity resourceNotFoundExceptionHandler( 24 | ResourceNotFoundException ex 25 | ){ 26 | String message = ex.getMessage(); 27 | log.error("ResourceNotFoundException: {}", message, ex); 28 | 29 | ApiResponse apiResponse = new ApiResponse(message, false); 30 | 31 | return new ResponseEntity<>( 32 | apiResponse, 33 | HttpStatus.NOT_FOUND 34 | ); 35 | } 36 | 37 | @ExceptionHandler(MethodArgumentNotValidException.class) 38 | public ResponseEntity> methodArgumentNotValidExceptionHandler( 39 | MethodArgumentNotValidException ex 40 | ){ 41 | HashMap errorList = new HashMap<>(); 42 | 43 | ex.getAllErrors().forEach(e -> { 44 | String objectName = ((FieldError)e).getField(); 45 | String message = e.getDefaultMessage(); 46 | errorList.put(objectName, message); 47 | }); 48 | 49 | log.error("MethodArgumentNotValidException: {}", errorList, ex); 50 | 51 | return new ResponseEntity<>(errorList, HttpStatus.BAD_REQUEST); 52 | } 53 | 54 | @ExceptionHandler(APIException.class) 55 | public ResponseEntity apiExceptionHandler(APIException ex) { 56 | 57 | String message = ex.getMessage(); 58 | HttpStatus status = ex.getHttpStatus(); 59 | ApiResponse apiResponse = new ApiResponse(message, false); 60 | 61 | log.error("APIException: {}", message, ex); 62 | 63 | return new ResponseEntity<>( 64 | apiResponse, 65 | status 66 | ); 67 | } 68 | 69 | @ExceptionHandler({ 70 | BadCredentialsException.class, 71 | IllegalArgumentException.class 72 | }) 73 | public ResponseEntity badCredentialsOrIllegalArgumentExceptionHandler(RuntimeException ex) { 74 | 75 | String message = ex.getMessage(); 76 | ApiResponse apiResponse = new ApiResponse(message, false); 77 | 78 | log.error("{} : {}", ex.getClass().getSimpleName(), message, ex); 79 | 80 | return new ResponseEntity<>( 81 | apiResponse, 82 | HttpStatus.BAD_REQUEST 83 | ); 84 | 85 | } 86 | 87 | @ExceptionHandler({ 88 | JwtException.class, 89 | MalformedJwtException.class, 90 | ExpiredJwtException.class, 91 | ClaimJwtException.class, 92 | UnsupportedJwtException.class, 93 | PrematureJwtException.class, 94 | UsernameNotFoundException.class 95 | }) 96 | public ResponseEntity authenticationExceptionHandler(Exception ex) { 97 | String message = ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage(); 98 | 99 | log.error("{}: {}", ex.getClass().getSimpleName(), message, ex); 100 | 101 | ApiResponse apiResponse = new ApiResponse(message, false); 102 | return new ResponseEntity<>( 103 | apiResponse, 104 | HttpStatus.UNAUTHORIZED 105 | ); 106 | } 107 | 108 | @ExceptionHandler(Exception.class) 109 | public ResponseEntity globalExceptionHandler(Exception ex) { 110 | String message = ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage(); 111 | 112 | log.error("{}: {}", ex.getClass().getSimpleName(), message, ex); 113 | 114 | ApiResponse apiResponse = new ApiResponse(message, false); 115 | return new ResponseEntity<>( 116 | apiResponse, 117 | HttpStatus.INTERNAL_SERVER_ERROR 118 | ); 119 | } 120 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class ResourceNotFoundException extends RuntimeException { 7 | 8 | private final String resourceName; 9 | private final String fieldName; 10 | private final String fieldValue; 11 | 12 | public ResourceNotFoundException(String resourceName, 13 | String fieldName, 14 | String fieldValue) { 15 | 16 | super(String.format("%s not found with %s : %s", 17 | resourceName, fieldName, fieldValue)); 18 | 19 | this.resourceName = resourceName; 20 | this.fieldName = fieldName; 21 | this.fieldValue = fieldValue; 22 | } 23 | 24 | public ResourceNotFoundException(String resourceName, 25 | String fieldName, 26 | Long fieldValue) { 27 | 28 | super(String.format("%s not found with %s : %s", 29 | resourceName, fieldName, fieldValue)); 30 | 31 | this.resourceName = resourceName; 32 | this.fieldName = fieldName; 33 | this.fieldValue = fieldValue.toString(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/AddressDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Address; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Value; 6 | import org.springframework.hateoas.server.core.Relation; 7 | 8 | import javax.validation.constraints.Max; 9 | import javax.validation.constraints.Min; 10 | import javax.validation.constraints.NotBlank; 11 | import javax.validation.constraints.NotNull; 12 | import java.io.Serial; 13 | import java.io.Serializable; 14 | 15 | /** 16 | * DTO for {@link Address} 17 | */ 18 | @Value //To make the class immutable 19 | @AllArgsConstructor 20 | @Relation(itemRelation = "address", collectionRelation = "addresses") 21 | public class AddressDto implements Serializable { 22 | @Serial 23 | private static final long serialVersionUID = 4607115799900867635L; 24 | 25 | long addressId; 26 | 27 | @NotNull(message = "Address Line 1 is required") 28 | @NotBlank(message = "Address Line 1 cannot be blank") 29 | String addLine1; 30 | 31 | @NotNull(message = "Address Line 2 is required") 32 | @NotBlank(message = "Address Line 2 cannot be blank") 33 | String addLine2; 34 | 35 | @NotNull(message = "City is required") 36 | @NotBlank(message = "City cannot be blank") 37 | String city; 38 | 39 | @NotNull(message = "State is required") 40 | @NotBlank(message = "State cannot be blank") 41 | String state; 42 | 43 | @NotNull(message = "Country is required") 44 | @NotBlank(message = "Country cannot be blank") 45 | String country; 46 | 47 | @NotNull(message = "Postal code is required") 48 | @NotBlank(message = "Postal code cannot be blank") 49 | @Min(5) 50 | @Max(value = 6, message = "Postal code should be not greater than 5 characters") 51 | int postalCode; 52 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/ApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | public record ApiResponse(String message, boolean success) { } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/AuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import lombok.*; 4 | import org.springframework.validation.annotation.Validated; 5 | 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | 10 | 11 | @Validated 12 | @Data 13 | public class AuthenticationRequest { 14 | 15 | @NotNull(message = "Email is required") 16 | @NotBlank(message = "Email cannot be blank") 17 | @Email 18 | private String email; 19 | 20 | @NotNull(message = "Password is required") 21 | @NotBlank(message = "Password cannot be blank") 22 | private String password; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/AuthenticationResponse.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.vedasole.ekartecommercebackend.utility.AppConstant.Role; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | 8 | 9 | @Builder 10 | @AllArgsConstructor 11 | @Data 12 | public class AuthenticationResponse { 13 | 14 | private String token; 15 | private String firstName; 16 | private String lastName; 17 | private long customerId; 18 | private String email; 19 | private Role role; 20 | private long cartId; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/CategoryDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.vedasole.ekartecommercebackend.entity.Category; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.hateoas.server.core.Relation; 10 | 11 | import javax.validation.constraints.NotBlank; 12 | import javax.validation.constraints.NotNull; 13 | import javax.validation.constraints.Size; 14 | import java.io.Serial; 15 | import java.io.Serializable; 16 | import java.time.LocalDateTime; 17 | 18 | /** 19 | * DTO for {@link Category} 20 | */ 21 | @Builder 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Data 25 | @Relation(itemRelation = "category", collectionRelation = "categories") 26 | public class CategoryDto implements Serializable { 27 | 28 | @Serial 29 | private static final long serialVersionUID = -6361844320830928689L; 30 | 31 | private long categoryId; 32 | 33 | @NotNull(message = "Category name is required") 34 | @NotBlank(message = "Category name cannot be blank") 35 | @Size(min = 3, max = 50, message = "Category name must be between 3 and 50 characters") 36 | private String name; 37 | 38 | private String image; 39 | 40 | @NotNull(message = "Category is required") 41 | @NotBlank(message = "Category cannot be blank") 42 | private String desc; 43 | 44 | private Category parentCategory; 45 | 46 | @NotNull(message = "Category should be either active or non-active") 47 | private boolean active; 48 | 49 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss:mm") 50 | private LocalDateTime createdAt; 51 | 52 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss:mm") 53 | private LocalDateTime updatedAt; 54 | 55 | public CategoryDto(String name, String image, String desc, Category parentCategory, boolean active) { 56 | this.name = name; 57 | this.image = image; 58 | this.desc = desc; 59 | this.parentCategory = parentCategory; 60 | this.active = active; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/CustomerDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.annotation.JsonIncludeProperties; 5 | import com.vedasole.ekartecommercebackend.entity.Customer; 6 | import com.vedasole.ekartecommercebackend.utility.AppConstant.Role; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import org.springframework.hateoas.server.core.Relation; 12 | 13 | import javax.persistence.EnumType; 14 | import javax.persistence.Enumerated; 15 | import javax.validation.constraints.*; 16 | import java.io.Serial; 17 | import java.io.Serializable; 18 | import java.time.LocalDateTime; 19 | 20 | /** 21 | * DTO for {@link Customer} 22 | */ 23 | @Builder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @Data 27 | @Relation(itemRelation = "customer", collectionRelation = "customers") 28 | public class CustomerDto implements Serializable { 29 | 30 | @Serial 31 | private static final long serialVersionUID = -4970632778733952870L; 32 | 33 | private long customerId; 34 | 35 | @NotBlank(message = "First name is required") 36 | @Size( 37 | min = 3, 38 | max = 20, 39 | message = "First name must be between minimum of 3 characters and maximum of 20 characters" 40 | ) 41 | private String firstName; 42 | 43 | @NotBlank(message = "Last name is required") 44 | @Size( 45 | min = 3, 46 | max = 20, 47 | message = "Last name must be between minimum of 3 characters " + 48 | "and maximum of 20 characters" 49 | ) 50 | private String lastName; 51 | 52 | @NotBlank(message = "Email address is required") 53 | @Email(message = "Email address is not valid", 54 | regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$" 55 | ) 56 | private String email; 57 | 58 | private String password; 59 | 60 | @NotNull(message = "Phone number is required") 61 | @Pattern( 62 | regexp = "^(\\+?\\d{0,3}?[- ]?)\\d{10}$", 63 | message = "Phone number must be a valid 10-digit number" 64 | ) 65 | private String phoneNumber; 66 | 67 | @Enumerated(EnumType.STRING) 68 | private Role role; 69 | 70 | @JsonIncludeProperties({"cartId"}) 71 | private ShoppingCartDto shoppingCart; 72 | 73 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 74 | private LocalDateTime createdAt; 75 | 76 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/NewCustomerDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.annotation.JsonIncludeProperties; 5 | import com.vedasole.ekartecommercebackend.entity.Customer; 6 | import com.vedasole.ekartecommercebackend.utility.AppConstant.Role; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | import org.springframework.hateoas.server.core.Relation; 12 | 13 | import javax.persistence.EnumType; 14 | import javax.persistence.Enumerated; 15 | import javax.validation.constraints.*; 16 | import java.io.Serial; 17 | import java.io.Serializable; 18 | import java.time.LocalDateTime; 19 | 20 | /** 21 | * DTO for new {@link Customer} 22 | */ 23 | @Builder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @Data 27 | @Relation(itemRelation = "customer", collectionRelation = "customers") 28 | public class NewCustomerDto implements Serializable { 29 | 30 | @Serial 31 | private static final long serialVersionUID = -4970632778733952870L; 32 | 33 | private long customerId; 34 | 35 | @NotBlank(message = "First name is required") 36 | @Size( 37 | min = 3, 38 | max = 20, 39 | message = "First name must be between minimum of 3 characters and maximum of 20 characters" 40 | ) 41 | private String firstName; 42 | 43 | @NotBlank(message = "Last name is required") 44 | @Size( 45 | min = 3, 46 | max = 20, 47 | message = "Last name must be between minimum of 3 characters " + 48 | "and maximum of 20 characters" 49 | ) 50 | private String lastName; 51 | 52 | @NotBlank(message = "Email address is required") 53 | @Email(message = "Email address is not valid", 54 | regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$" 55 | ) 56 | private String email; 57 | 58 | @NotBlank(message = "Password cannot be blank") 59 | @Size( 60 | min = 3, 61 | max = 20, 62 | message = "Password must be between minimum of 3 characters " + 63 | "and maximum of 20 characters" 64 | ) 65 | private String password; 66 | 67 | @NotNull(message = "Phone number is required") 68 | @Pattern( 69 | regexp = "^(\\+\\d{1,3}[- ]?)?\\d{10}$", 70 | message = "Phone number must be a valid 10-digit number" 71 | ) 72 | private String phoneNumber; 73 | 74 | @NotNull(message = "Role is required") 75 | @Enumerated(EnumType.STRING) 76 | private Role role; 77 | 78 | @JsonIncludeProperties({"cartId"}) 79 | private ShoppingCartDto shoppingCart; 80 | 81 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") 82 | private LocalDateTime createdAt; 83 | 84 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/OrderDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.vedasole.ekartecommercebackend.entity.Order; 5 | import com.vedasole.ekartecommercebackend.utility.AppConstant.OrderStatus; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import org.springframework.hateoas.server.core.Relation; 11 | 12 | import javax.validation.constraints.NotEmpty; 13 | import javax.validation.constraints.NotNull; 14 | import javax.validation.constraints.PositiveOrZero; 15 | import java.io.Serial; 16 | import java.io.Serializable; 17 | import java.time.LocalDateTime; 18 | import java.util.List; 19 | 20 | /** 21 | * DTO for {@link Order} 22 | */ 23 | @Builder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @Data 27 | @Relation(itemRelation = "order", collectionRelation = "orders") 28 | public class OrderDto implements Serializable { 29 | 30 | @Serial 31 | private static final long serialVersionUID = 4761708818033261120L; 32 | 33 | private long orderId; 34 | 35 | @NotNull(message = "Customer is required") 36 | private CustomerDto customer; 37 | 38 | @NotNull(message = "Order details are required") 39 | @NotEmpty(message = "Order details should not be empty") 40 | private List orderItems; 41 | 42 | // @NotNull(message = "Address is required") TODO: Until Address is implemented in Order 43 | private AddressDto address; 44 | 45 | @NotNull(message = "Total is required") 46 | @PositiveOrZero(message = "Total cannot be negative") 47 | private double total; 48 | 49 | private OrderStatus orderStatus; 50 | 51 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 52 | private LocalDateTime createdAt; 53 | 54 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 55 | private LocalDateTime updatedAt; 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/OrderItemDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.OrderItem; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.hateoas.server.core.Relation; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import javax.validation.constraints.Positive; 11 | import java.io.Serial; 12 | import java.io.Serializable; 13 | 14 | /** 15 | * DTO for {@link OrderItem} 16 | */ 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Data 20 | @Relation(itemRelation = "orderItem", collectionRelation = "orderItems") 21 | public class OrderItemDto implements Serializable { 22 | 23 | @Serial 24 | private static final long serialVersionUID = -5054394287337400890L; 25 | 26 | private long orderItemId; 27 | 28 | private long orderId; 29 | 30 | @NotNull(message = "Product is required") 31 | private ProductDto product; 32 | 33 | @NotNull(message = "Product quantity is required") 34 | @Positive(message = "Product quantity must be greater than 0") 35 | private long quantity; 36 | 37 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/PasswordResetRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class PasswordResetRequestDto { 13 | @NotBlank(message = "Token is required") 14 | private String token; 15 | @NotBlank(message = "New Password is required") 16 | private String newPassword; 17 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/ProductDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.vedasole.ekartecommercebackend.entity.Product; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.hateoas.server.core.Relation; 10 | 11 | import javax.validation.constraints.Min; 12 | import javax.validation.constraints.NotBlank; 13 | import javax.validation.constraints.NotNull; 14 | import javax.validation.constraints.Size; 15 | import java.io.Serial; 16 | import java.io.Serializable; 17 | import java.time.LocalDateTime; 18 | 19 | /** 20 | * DTO for {@link Product} 21 | */ 22 | @Builder 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @Data 26 | @Relation(itemRelation = "product", collectionRelation = "products") 27 | public final class ProductDto implements Serializable { 28 | 29 | @Serial 30 | private static final long serialVersionUID = 2079394134035966353L; 31 | 32 | private long productId; 33 | 34 | @NotNull(message = "Product name is required") 35 | @NotBlank(message = "Product name cannot be blank") 36 | @Size( 37 | min = 3, 38 | max = 100, 39 | message = "Product name must be between minimum of 3 characters and maximum of 100 characters" 40 | ) 41 | private String name; 42 | 43 | @NotNull(message = "Product image is required") 44 | @NotBlank(message = "Product image cannot be blank") 45 | private String image; 46 | 47 | @NotNull(message = "Product description is required") 48 | @NotBlank(message = "Product description cannot be blank") 49 | @Size(max = 1000, message = "Product description cannot be longer than 1000 characters") 50 | private String desc; 51 | 52 | @NotNull(message = "Product price is required") 53 | @Min(value = 0, message = "Product price cannot be negative") 54 | private double price; 55 | 56 | @NotNull(message = "Product discount is required") 57 | @Min(value = 0, message = "Product discount cannot be negative") 58 | private double discount; 59 | 60 | @NotNull(message = "Product quantity is required") 61 | @Min(value = 0, message = "Product quantity cannot be negative") 62 | private int qtyInStock; 63 | 64 | @NotNull(message = "Category ID is required") 65 | @Min(value = 1, message = "Category ID cannot be negative") 66 | private long categoryId; 67 | 68 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss:mm") 69 | private LocalDateTime createdAt; 70 | 71 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss:mm") 72 | private LocalDateTime updatedAt; 73 | 74 | public ProductDto(String name, String image, String desc, double price, double discount, int qtyInStock, long categoryId) { 75 | this.name = name; 76 | this.image = image; 77 | this.desc = desc; 78 | this.price = price; 79 | this.discount = discount; 80 | this.qtyInStock = qtyInStock; 81 | this.categoryId = categoryId; 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/ResetTokenRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | public record ResetTokenRequestDto(String email){ } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/ShoppingCartDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.ShoppingCart; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.hateoas.server.core.Relation; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.Positive; 12 | import javax.validation.constraints.PositiveOrZero; 13 | import java.io.Serial; 14 | import java.io.Serializable; 15 | import java.util.List; 16 | 17 | /** 18 | * DTO for {@link ShoppingCart} 19 | */ 20 | @Builder 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @Data 24 | @Relation(itemRelation = "shoppingCart") 25 | public class ShoppingCartDto implements Serializable { 26 | 27 | @Serial 28 | private static final long serialVersionUID = 2151030173521724266L; 29 | 30 | private long cartId; 31 | @NotNull(message = "Customer id is required") 32 | @Positive(message = "Customer id must be greater than or equal to 1") 33 | private long customerId; 34 | @NotNull(message = "Total is required") 35 | @PositiveOrZero(message = "Total cannot be negative") 36 | private double total; 37 | @NotNull(message = "Discount is required") 38 | @PositiveOrZero(message = "Discount cannot be negative") 39 | private double discount; 40 | private List shoppingCartItems; 41 | 42 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/ShoppingCartItemDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.ShoppingCartItem; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.hateoas.server.core.Relation; 9 | 10 | import javax.validation.constraints.NotNull; 11 | import javax.validation.constraints.Positive; 12 | import java.io.Serial; 13 | import java.io.Serializable; 14 | 15 | /** 16 | * DTO for {@link ShoppingCartItem} 17 | */ 18 | @Builder 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Data 22 | @Relation(itemRelation = "shoppingCartItem", collectionRelation = "shoppingCartItems") 23 | public class ShoppingCartItemDto implements Serializable { 24 | 25 | @Serial 26 | private static final long serialVersionUID = -2647249272100904224L; 27 | 28 | private long cartItemId; 29 | 30 | @NotNull(message = "Product is required") 31 | private ProductDto product; 32 | 33 | @NotNull(message = "Cart id is required") 34 | @Positive(message = "Cart id must be greater than 0") 35 | private long cartId; 36 | 37 | @NotNull(message = "Product quantity is required") 38 | @Positive(message = "Product quantity must be greater than 0") 39 | private long quantity; 40 | 41 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/payload/ValidateTokenRequestDto.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.payload; 2 | 3 | public record ValidateTokenRequestDto(String token){ } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/AddressRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Address; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface AddressRepo extends JpaRepository { } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/CategoryRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Category; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | @Repository 12 | public interface CategoryRepo extends JpaRepository { 13 | 14 | List findAllByParentCategoryIsNull(); 15 | Page findAllByParentCategoryIsNull(Pageable pageable); 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/CustomerRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Customer; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface CustomerRepo extends JpaRepository { 11 | 12 | Optional findByEmail(String email); 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/OrderItemRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.OrderItem; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | 9 | public interface OrderItemRepo extends JpaRepository { 10 | 11 | void deleteAllByOrderOrderId(long orderId); 12 | List findAllByOrderOrderId(long orderId); 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/OrderRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Order; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | 13 | public interface OrderRepo extends JpaRepository { 14 | List findAllByCustomer_CustomerId(Long customerId); 15 | Page findAllByCustomer_CustomerId(Pageable pageable, Long customerId); 16 | 17 | @Query(value = "SELECT SUM(o.total) FROM \"order\" o", nativeQuery = true) 18 | Double getTotalIncome(); 19 | 20 | @Query( 21 | value = "SELECT TO_CHAR(o.create_dt, 'YYYY-MM') AS date, SUM(o.total) AS income " + 22 | "FROM \"order\" o GROUP BY TO_CHAR(o.create_dt, 'YYYY-MM') " + 23 | "ORDER BY date" 24 | , nativeQuery = true 25 | ) 26 | List> getTotalIncomeByMonth(); 27 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/PasswordResetTokenRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.PasswordResetToken; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface PasswordResetTokenRepo extends JpaRepository { 11 | 12 | Optional findByToken(String token); 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/ProductRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Product; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.List; 9 | 10 | public interface ProductRepo extends JpaRepository { 11 | 12 | List findByNameIsContainingIgnoreCaseOrDescContainingIgnoreCase(String name, String desc, Pageable pageable); 13 | 14 | List findByCategoryCategoryId(long categoryId); 15 | 16 | Page findByCategoryCategoryId(long categoryId, Pageable pageable); 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/ShoppingCartItemRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.ShoppingCartItem; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | 9 | public interface ShoppingCartItemRepo extends JpaRepository { 10 | List findAllByShoppingCartCartId(long cartId); 11 | void deleteAllByShoppingCartCartId(long cartId); 12 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/ShoppingCartRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.ShoppingCart; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | 9 | public interface ShoppingCartRepo extends JpaRepository { 10 | 11 | Optional findByCustomer_CustomerId(long customerId); 12 | 13 | void deleteByCustomer_CustomerId(long customerId); 14 | 15 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/repository/UserRepo.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface UserRepo extends JpaRepository { 11 | Optional findByEmailIgnoreCase(String email); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/security/JWTAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.security; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.security.core.AuthenticationException; 5 | import org.springframework.security.web.AuthenticationEntryPoint; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.io.IOException; 11 | 12 | @Component 13 | @Slf4j 14 | public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint { 15 | 16 | @Override 17 | public void commence(HttpServletRequest request, 18 | HttpServletResponse response, 19 | AuthenticationException authException) 20 | throws IOException { 21 | log.error("JWTAuthenticationEntryPoint - Unauthorized request: {}", authException.getMessage()); 22 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied"); 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/security/JWTAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.security; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.lang.NonNull; 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.filter.OncePerRequestFilter; 12 | import javax.servlet.FilterChain; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.io.IOException; 16 | 17 | @Component 18 | @RequiredArgsConstructor 19 | public class JWTAuthenticationFilter extends OncePerRequestFilter { 20 | 21 | private final JwtService jwtService; 22 | private final UserDetailsService userDetailsService; 23 | 24 | @Override 25 | protected void doFilterInternal( 26 | @NonNull HttpServletRequest request, 27 | @NonNull HttpServletResponse response, 28 | @NonNull FilterChain filterChain 29 | ) throws IOException { 30 | 31 | try { 32 | //Checking if JWT token is present in Authorization header 33 | final String authHeader = request.getHeader("Authorization"); 34 | 35 | if (authHeader != null && authHeader.startsWith("Bearer") && authHeader.length() > 7) { 36 | final String jwt = authHeader.substring(7); 37 | final String userEmail = jwtService.extractUsername(jwt); 38 | 39 | if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { 40 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); 41 | if (jwtService.isTokenValid(jwt, userDetails)) { 42 | //Creating UsernamePasswordAuthenticationToken with user details and setting it in Spring SecurityContextHolder 43 | UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( 44 | userDetails, 45 | null, 46 | userDetails.getAuthorities() 47 | ); 48 | authToken.setDetails( 49 | //Setting WebAuthenticationDetailsSource in Spring SecurityContextHolder 50 | new WebAuthenticationDetailsSource().buildDetails(request) 51 | ); 52 | SecurityContextHolder.getContext().setAuthentication(authToken); 53 | } 54 | } else { 55 | logger.error("Invalid JWT token :" + jwt); 56 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token"); 57 | } 58 | } 59 | filterChain.doFilter(request, response); 60 | } catch (Exception e) { 61 | logger.error("Error occurred while processing JWT token: " + e.getMessage()); 62 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/security/JwtService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.security; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.ExpiredJwtException; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.SignatureAlgorithm; 7 | import io.jsonwebtoken.io.Decoders; 8 | import io.jsonwebtoken.security.Keys; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.security.Key; 15 | import java.util.Date; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.function.Function; 19 | 20 | @Service 21 | @Slf4j 22 | public class JwtService { 23 | 24 | private final String jwtSecretKey; 25 | 26 | public JwtService(@Value("${jwt.secret.key}") String jwtSecretKey) { 27 | this.jwtSecretKey = jwtSecretKey; 28 | } 29 | 30 | 31 | /** 32 | * Extracts the username from the given token. 33 | * 34 | * @param token the token containing user information 35 | * @return the extracted username 36 | */ 37 | public String extractUsername(String token) { 38 | return extractClaim(token, Claims::getSubject); 39 | } 40 | 41 | /** 42 | * Generates a token for the given user details. 43 | * 44 | * @param userDetails the user details for which the token is generated 45 | * @return the generated token 46 | */ 47 | public String generateToken(UserDetails userDetails) { 48 | return generateToken(new HashMap<>(), userDetails); 49 | } 50 | 51 | /** 52 | * Generates a JWT token with the given extra claims and user details. 53 | * 54 | * @param extraClaims a map of extra claims to include in the token 55 | * @param userDetails the user details for whom the token is being generated 56 | * @return the generated JWT token 57 | */ 58 | public String generateToken( 59 | Map extraClaims, 60 | UserDetails userDetails 61 | ){ 62 | return Jwts.builder() 63 | .setClaims(extraClaims) 64 | .setSubject(userDetails.getUsername()) 65 | .setIssuedAt(new Date(System.currentTimeMillis())) 66 | .setExpiration(new java.util.Date((System.currentTimeMillis() + 1000 * 60 * 60 * 24))) 67 | .signWith(getSigningKey(), SignatureAlgorithm.HS256) 68 | .compact(); 69 | } 70 | 71 | /** 72 | * Extracts a claim from a JWT token. 73 | * 74 | * @param the type of the claim to extract 75 | * @param token the JWT token containing the claim 76 | * @param claimsResolver a function that takes a {@link Claims} object and returns the desired claim 77 | * @return the extracted claim 78 | */ 79 | public T extractClaim(String token, Function claimsResolver) { 80 | final Claims claims = extractAllClaims(token); 81 | return claimsResolver.apply(claims); 82 | } 83 | 84 | /** 85 | * Extracts all claims from the given token. 86 | * 87 | * @param token the token from which to extract claims 88 | * @return the extracted claims 89 | */ 90 | private Claims extractAllClaims(String token){ 91 | return Jwts.parserBuilder() 92 | .setSigningKey(getSigningKey()) 93 | .build() 94 | .parseClaimsJws(token) 95 | .getBody(); 96 | } 97 | 98 | public boolean isTokenValid(String token, UserDetails userDetails) { 99 | try { 100 | final String username = extractUsername(token); 101 | return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); 102 | } catch (ExpiredJwtException e) { 103 | log.error("JWT token has expired for user: {}", e.getClaims().getSubject()); 104 | return false; 105 | } 106 | catch (Exception e) { 107 | log.error("Unable to check token validity: {}", e.getMessage()); 108 | return false; 109 | } 110 | } 111 | 112 | private boolean isTokenExpired(String token) { 113 | return extractExpiration(token).before(new Date()); 114 | } 115 | 116 | private Date extractExpiration(String token) { 117 | return extractClaim(token, Claims::getExpiration); 118 | } 119 | 120 | /** 121 | * This method is used to getCart the Signing Key for the JWT. 122 | * 123 | * @return the Signing Key 124 | */ 125 | private Key getSigningKey() { 126 | byte[] keyBytes = Decoders.BASE64.decode(jwtSecretKey); 127 | return Keys.hmacShaKeyFor(keyBytes); 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/security/WebSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.security; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.AuthenticationProvider; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.http.SessionCreationPolicy; 12 | import org.springframework.security.web.SecurityFilterChain; 13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 | import org.springframework.web.cors.CorsConfiguration; 15 | import org.springframework.web.cors.CorsConfigurationSource; 16 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | 21 | @Configuration 22 | @EnableWebSecurity 23 | @EnableGlobalMethodSecurity(prePostEnabled = true) 24 | @RequiredArgsConstructor 25 | public class WebSecurityConfig { 26 | 27 | private final JWTAuthenticationFilter jwtAuthFilter; 28 | private final JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint; 29 | private final AuthenticationProvider authenticationProvider; 30 | private static final String WEBHOOK_URL = "/api/v1/payment/webhook/*"; 31 | 32 | private static final String[] PUBLIC_URLS = { 33 | "/api/v1/", 34 | "/api/v1/auth/*", 35 | "/swagger-ui/**", 36 | "/swagger-resources/**", 37 | "/api/v2/**", 38 | "/v2/api-docs/**", 39 | "/h2-console/**" 40 | }; 41 | 42 | @Bean 43 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 44 | http 45 | .csrf() 46 | .disable() 47 | .authorizeHttpRequests(auth -> auth 48 | .antMatchers(HttpMethod.POST, "/api/v1/customers").permitAll() 49 | .antMatchers(HttpMethod.POST, WEBHOOK_URL).permitAll() 50 | .antMatchers(PUBLIC_URLS).permitAll() 51 | .antMatchers(HttpMethod.GET).permitAll() 52 | .anyRequest() 53 | .authenticated() 54 | ) 55 | .exceptionHandling(exceptionHandling -> exceptionHandling 56 | .authenticationEntryPoint(this.jwtAuthenticationEntryPoint) 57 | ) 58 | .sessionManagement(sessionManagement -> sessionManagement 59 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 60 | ) 61 | .authenticationProvider(authenticationProvider) 62 | .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) 63 | .formLogin().disable() 64 | .cors() 65 | .and() 66 | .headers().frameOptions().disable(); 67 | 68 | return http.build(); 69 | } 70 | 71 | @Bean 72 | CorsConfigurationSource corsConfigurationSource() { 73 | CorsConfiguration configuration = new CorsConfiguration(); 74 | configuration.setAllowedOrigins(Arrays.asList( 75 | "http://localhost:5173", 76 | "https://ekart.vedasole.me", 77 | "https://ekart-shopping.netlify.app", 78 | "https://develop--ekart-shopping.netlify.app" 79 | )); 80 | configuration.setAllowedMethods(Arrays.asList( 81 | HttpMethod.GET.name(), 82 | HttpMethod.POST.name(), 83 | HttpMethod.PUT.name(), 84 | HttpMethod.DELETE.name() 85 | )); 86 | configuration.setAllowedHeaders(List.of("*")); 87 | configuration.addExposedHeader("Authorization"); 88 | configuration.setAllowCredentials(true); 89 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 90 | source.registerCorsConfiguration("/**", configuration); 91 | return source; 92 | } 93 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_impl/EmailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_impl; 2 | 3 | import com.vedasole.ekartecommercebackend.service.service_interface.EmailService; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.mail.javamail.JavaMailSender; 7 | import org.springframework.mail.javamail.MimeMessageHelper; 8 | import org.springframework.stereotype.Service; 9 | import org.thymeleaf.TemplateEngine; 10 | import org.thymeleaf.context.Context; 11 | 12 | import javax.mail.MessagingException; 13 | import javax.mail.internet.MimeMessage; 14 | 15 | @Service 16 | @AllArgsConstructor 17 | @Slf4j 18 | public class EmailServiceImpl implements EmailService { 19 | 20 | private final JavaMailSender emailSender; 21 | private final TemplateEngine templateEngine; 22 | /** 23 | * Sends a MIME message with HTML content using Thymeleaf template engine. 24 | * 25 | * @param to the recipient email address 26 | * @param subject the subject of the email 27 | * @param context the context for the Thymeleaf template 28 | * @param template the name of the Thymeleaf template file (without extension) 29 | * @throws MessagingException if an error occurs while sending the email 30 | */ 31 | @Override 32 | public void sendMimeMessage(String to, String subject, Context context, String template) throws MessagingException { 33 | MimeMessage message = emailSender.createMimeMessage(); 34 | MimeMessageHelper helper = new MimeMessageHelper(message, "UTF-8"); 35 | helper.setFrom("no-reply.gmail.com"); 36 | helper.setTo(to); 37 | helper.setSubject(subject); 38 | String html = templateEngine.process(template, context); 39 | helper.setText(html, true); 40 | emailSender.send(message); 41 | log.info("Email sent to email: '{}' with subject: '{}'", to, subject); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_impl/StripeService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_impl; 2 | 3 | import com.stripe.Stripe; 4 | import com.stripe.exception.StripeException; 5 | import com.stripe.model.checkout.Session; 6 | import com.stripe.net.RequestOptions; 7 | import com.stripe.param.checkout.SessionCreateParams; 8 | import com.vedasole.ekartecommercebackend.entity.*; 9 | import com.vedasole.ekartecommercebackend.repository.AddressRepo; 10 | import com.vedasole.ekartecommercebackend.repository.OrderRepo; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import static com.vedasole.ekartecommercebackend.utility.AppConstant.OrderStatus.ORDER_CREATED; 20 | 21 | @Service 22 | @Transactional 23 | @Slf4j 24 | public class StripeService { 25 | 26 | @Value("${frontendDomainUrl:http://localhost:5173}") 27 | private String frontendDomainUrl; 28 | private final OrderRepo orderRepo; 29 | private final AddressRepo addressRepo; 30 | 31 | public StripeService( 32 | OrderRepo orderRepo, 33 | AddressRepo addressRepo, 34 | @Value("${stripeApiKey}") String stripeApiKey 35 | ) { 36 | this.orderRepo = orderRepo; 37 | this.addressRepo = addressRepo; 38 | Stripe.apiKey = stripeApiKey; 39 | } 40 | 41 | @Transactional 42 | public Session createCheckoutSession( 43 | ShoppingCart shoppingCart 44 | ) throws StripeException { 45 | 46 | List lineItemList = generateLineItems(shoppingCart); 47 | 48 | Order savedOrder = createOrder(shoppingCart); 49 | String orderId = Long.toString(savedOrder.getOrderId()); 50 | String clientReferenceNumber = "CUST" + shoppingCart.getCustomer().getCustomerId() + "_" + orderId; 51 | 52 | SessionCreateParams params = SessionCreateParams.builder() 53 | .setCustomerCreation(SessionCreateParams.CustomerCreation.IF_REQUIRED) 54 | .setShippingAddressCollection(SessionCreateParams.ShippingAddressCollection.builder() 55 | .addAllowedCountry(SessionCreateParams.ShippingAddressCollection.AllowedCountry.IN).build()) 56 | .setClientReferenceId(clientReferenceNumber) 57 | .setMode(SessionCreateParams.Mode.PAYMENT) 58 | .setCustomerEmail(shoppingCart.getCustomer().getEmail()) 59 | .putMetadata("order_id", orderId) 60 | .putMetadata("clientReferenceNumber", clientReferenceNumber) 61 | .putMetadata("customer_id", Long.toString(shoppingCart.getCustomer().getCustomerId())) 62 | .setSuccessUrl(frontendDomainUrl + "/paymentConfirmation?success=true&session_id={CHECKOUT_SESSION_ID}&order_id=" + orderId + "&client_reference_id=" + clientReferenceNumber) 63 | .setCancelUrl(frontendDomainUrl + "/paymentConfirmation?canceled=true&session_id={CHECKOUT_SESSION_ID}&order_id=" + orderId + "&client_reference_id=" + clientReferenceNumber) 64 | .addAllLineItem(lineItemList) 65 | .addPaymentMethodType(SessionCreateParams.PaymentMethodType.CARD) 66 | .build(); 67 | 68 | RequestOptions requestOptions = RequestOptions.builder() 69 | .setIdempotencyKey(clientReferenceNumber) 70 | .setMaxNetworkRetries(3) 71 | .build(); 72 | 73 | return Session.create(params, requestOptions); 74 | } 75 | 76 | private Order createOrder(ShoppingCart shoppingCart) { 77 | 78 | Address dummyAddress = addressRepo.save(new Address( 79 | "Dummy Address Line 1", 80 | "Dummy Address Line 2", 81 | "Dummy City", 82 | "Dummy State", 83 | "Dummy Country", 84 | 100001 85 | )); 86 | 87 | double totalAmount = shoppingCart.getTotal() - shoppingCart.getDiscount(); 88 | log.debug("Shopping Cart Total: {}, Discount: {}, Final Order Amount: {}", shoppingCart.getTotal(), shoppingCart.getDiscount(), totalAmount); 89 | log.debug("Creating order for Shopping Cart: {}", shoppingCart); 90 | 91 | Order order = new Order( 92 | 101L, 93 | shoppingCart.getCustomer(), 94 | new ArrayList<>(), 95 | dummyAddress, 96 | totalAmount, 97 | ORDER_CREATED 98 | ); 99 | Order savedOrder = orderRepo.save(order); 100 | List orderItems = shoppingCart.getShoppingCartItems().stream() 101 | .map(item -> new OrderItem( 102 | savedOrder, 103 | item.getProduct(), 104 | item.getQuantity() 105 | )).toList(); 106 | savedOrder.setOrderItems(new ArrayList<>(orderItems)); 107 | orderRepo.save(savedOrder); 108 | log.debug("Order created with ID: {}, Total Amount: {}", savedOrder.getOrderId(), savedOrder.getTotal()); 109 | return savedOrder; 110 | } 111 | 112 | private List generateLineItems(ShoppingCart shoppingCart) { 113 | return shoppingCart.getShoppingCartItems().stream() 114 | .map(item -> { 115 | if(item.getQuantity() > 0 && item.getQuantity() <= item.getProduct().getQtyInStock()) 116 | return SessionCreateParams.LineItem.builder() 117 | .setQuantity(item.getQuantity()) 118 | .setPriceData(generatePriceData(item)) 119 | .build(); 120 | else return null; 121 | }) 122 | .toList(); 123 | } 124 | 125 | private SessionCreateParams.LineItem.PriceData generatePriceData(ShoppingCartItem item) { 126 | return SessionCreateParams.LineItem.PriceData.builder() 127 | .setCurrency("INR") 128 | .setUnitAmount((long) (item.getProduct().getPrice() * (1 - (item.getProduct().getDiscount() / 100))) * 100) 129 | .setProductData( 130 | SessionCreateParams.LineItem.PriceData.ProductData.builder() 131 | .setName(item.getProduct().getName()) 132 | // .setDescription(item.getProduct().getDesc()) 133 | .addImage(item.getProduct().getImage()) 134 | .putMetadata("productId", Long.toString(item.getProduct().getProductId())) 135 | .putMetadata("SKU", item.getProduct().getSku()) 136 | .build() 137 | ).build(); 138 | } 139 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_impl; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.User; 4 | import com.vedasole.ekartecommercebackend.exception.APIException; 5 | import com.vedasole.ekartecommercebackend.exception.ResourceNotFoundException; 6 | import com.vedasole.ekartecommercebackend.repository.UserRepo; 7 | import com.vedasole.ekartecommercebackend.service.service_interface.UserService; 8 | import lombok.RequiredArgsConstructor; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.cache.annotation.CacheEvict; 11 | import org.springframework.cache.annotation.Cacheable; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.List; 18 | 19 | @Service 20 | @RequiredArgsConstructor 21 | @Transactional 22 | @Slf4j 23 | public class UserServiceImpl implements UserService { 24 | 25 | private final UserRepo userRepo; 26 | private final PasswordEncoder passwordEncoder; 27 | 28 | @Override 29 | public User createUser(User user) { 30 | user.setPassword(passwordEncoder.encode(user.getPassword())); 31 | if(this.userRepo.findByEmailIgnoreCase(user.getEmail()).isPresent()) { 32 | throw new APIException("User with email " + user.getEmail() + " already exists", HttpStatus.BAD_REQUEST); 33 | } 34 | return this.userRepo.save(user); 35 | } 36 | 37 | @Override 38 | @CacheEvict(value = "users") 39 | public User updateUser(User user, Long userId) { 40 | 41 | User savedUser = this.userRepo.findById(userId) 42 | .orElseThrow(() -> new ResourceNotFoundException( 43 | "User", "userId", userId 44 | )); 45 | 46 | savedUser.setEmail(user.getEmail()); 47 | savedUser.setRole(user.getRole()); 48 | if (!savedUser.getPassword().equals(user.getPassword())) { 49 | savedUser.setPassword(passwordEncoder.encode(user.getPassword())); 50 | } 51 | return this.userRepo.save(savedUser); 52 | } 53 | 54 | @Override 55 | @CacheEvict(value = "users") 56 | public boolean deleteUser(Long userId) { 57 | if (userId == null) { 58 | log.error("Invalid userId received to delete user : null"); 59 | throw new IllegalArgumentException("Invalid userId received to delete user : null"); 60 | } 61 | try{ 62 | this.userRepo.deleteById(userId); 63 | return true; 64 | } catch (Exception e) { 65 | return false; 66 | } 67 | } 68 | 69 | @Override 70 | @Cacheable(value = "users") 71 | @Transactional(readOnly = true) 72 | public User getUserById(Long userId) { 73 | return this.userRepo.findById(userId) 74 | .orElseThrow(() -> new ResourceNotFoundException( 75 | "User", "userId", userId 76 | )); 77 | } 78 | 79 | @Override 80 | @Transactional(readOnly = true) 81 | public List getAllUsers() { 82 | return this.userRepo.findAll(); 83 | } 84 | 85 | @Override 86 | public User getUserByEmail(String email) { 87 | return this.userRepo.findByEmailIgnoreCase(email) 88 | .orElseThrow(() -> new ResourceNotFoundException( 89 | "User", "email", email 90 | )); 91 | } 92 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/AuthenticationService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.payload.AuthenticationRequest; 4 | import com.vedasole.ekartecommercebackend.payload.AuthenticationResponse; 5 | 6 | import javax.mail.MessagingException; 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | /** 10 | * This interface defines the methods for authentication-related operations. 11 | */ 12 | public interface AuthenticationService { 13 | 14 | /** 15 | * Authenticates the user using the provided authentication request and HTTP request. 16 | * 17 | * @param authRequest The authentication request containing the user's credentials. 18 | * @param request The HTTP request containing additional information about the user's session. 19 | * @return An authentication response containing the user's authentication status and access token. 20 | */ 21 | AuthenticationResponse authenticate(AuthenticationRequest authRequest, HttpServletRequest request); 22 | 23 | /** 24 | * Authenticates the user based on the HTTP request. 25 | * 26 | * @param request The HTTP request containing additional information about the user's session. 27 | * @return True if the user is authenticated, false otherwise. 28 | */ 29 | boolean authenticate(HttpServletRequest request); 30 | 31 | /** 32 | * Validates the provided access token. 33 | * 34 | * @param token The access token to be validated. 35 | * @return True if the token is valid, false otherwise. 36 | */ 37 | boolean isTokenValid(String token); 38 | 39 | void generatePasswordResetToken(String email) throws MessagingException; 40 | 41 | boolean resetPassword(String token, String newPassword); 42 | 43 | boolean isResetTokenValid(String token); 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Category; 4 | import com.vedasole.ekartecommercebackend.payload.CategoryDto; 5 | import org.springframework.data.domain.Page; 6 | 7 | import java.util.List; 8 | 9 | public interface CategoryService { 10 | 11 | CategoryDto createCategory(CategoryDto categoryDto); 12 | 13 | CategoryDto updateCategory(CategoryDto categoryDto , Long categoryId); 14 | 15 | List getAllParentCategories(); 16 | 17 | Page getAllParentCategoriesByPage(int page, int size, String sortBy, String sortOrder); 18 | 19 | List getAllCategories(); 20 | 21 | CategoryDto getCategoryById(Long categoryId); 22 | 23 | Page getAllCategoriesByPage(int page, int size, String sortBy, String sortOrder); 24 | 25 | void deleteCategory(Long categoryId); 26 | 27 | Long getTotalCategoriesCount(); 28 | 29 | CategoryDto convertToDto(Category category); 30 | 31 | Category convertToEntity(CategoryDto categoryDto); 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Customer; 4 | import com.vedasole.ekartecommercebackend.entity.User; 5 | import com.vedasole.ekartecommercebackend.payload.CustomerDto; 6 | import com.vedasole.ekartecommercebackend.payload.NewCustomerDto; 7 | import org.springframework.data.domain.Page; 8 | 9 | import java.util.List; 10 | 11 | public interface CustomerService { 12 | 13 | CustomerDto createCustomer(NewCustomerDto newCustomerDto); 14 | CustomerDto updateCustomer(CustomerDto customerDto , Long customerId); 15 | List getAllCustomers(); 16 | Page getAllCustomersByPage(int page, int size, String sortBy, String sortOrder); 17 | CustomerDto getCustomerById(Long customerId); 18 | CustomerDto getCustomerByEmail(String email); 19 | void deleteCustomer(Long customerId); 20 | User getUserForCustomer(Long customerId); 21 | Long getTotalCustomersCount(); 22 | Customer convertToCustomer(CustomerDto customerDto); 23 | CustomerDto convertToCustomerDto(Customer customer); 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/EmailService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.thymeleaf.context.Context; 5 | 6 | import javax.mail.MessagingException; 7 | 8 | @Service 9 | public interface EmailService { 10 | 11 | void sendMimeMessage(String to, String subject, Context context, String template) throws MessagingException; 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/OrderItemService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.OrderItem; 4 | import com.vedasole.ekartecommercebackend.payload.OrderItemDto; 5 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartItemDto; 6 | 7 | import java.util.List; 8 | 9 | public interface OrderItemService { 10 | 11 | OrderItemDto createOrderItem(OrderItemDto orderItemDto); 12 | List createAllOrderItemsFromCartItems(List shoppingCartItemDtos); 13 | List createAllOrderItems(List orderItemDtos); 14 | OrderItemDto updateOrderItem(OrderItemDto orderItemDto); 15 | void deleteOrderItem(long orderItemId); 16 | void deleteAllOrderItems(long orderId); 17 | OrderItemDto getOrderItem(long orderId); 18 | List getAllOrderItems(long orderId); 19 | OrderItem convertToOrderItem(OrderItemDto orderItemDto); 20 | OrderItemDto convertToOrderItemDto(OrderItem orderItem); 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Order; 4 | import com.vedasole.ekartecommercebackend.payload.OrderDto; 5 | import org.springframework.data.domain.Page; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public interface OrderService { 11 | 12 | OrderDto createOrder(OrderDto orderDto) throws IllegalArgumentException; 13 | OrderDto updateOrder(OrderDto orderDto); 14 | void deleteOrder(Long orderId); 15 | OrderDto getOrder(Long orderId); 16 | List getAllOrdersByCustomer(Long customerId); 17 | List getAllOrders(); 18 | Page getAllOrdersPerPage(int page, int size, String sortBy, String sortOrder); 19 | Page getAllOrdersbyCustomerPerPage(long customerId, int page, int size, String sortBy, String sortOrder); 20 | Long getTotalOrdersCount(); 21 | Long getTotalIncome(); 22 | List> getTotalIncomeByMonth(); 23 | Order convertToOrder(OrderDto orderDto); 24 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/PaymentService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.stripe.model.PaymentIntent; 4 | import com.stripe.model.StripeObject; 5 | import com.stripe.model.checkout.Session; 6 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartDto; 7 | 8 | public interface PaymentService { 9 | 10 | Session createCheckoutSession(ShoppingCartDto shoppingCartDto); 11 | 12 | void handleCheckoutSessionEvent(Session session); 13 | 14 | void handlePaymentIntentEvent(PaymentIntent paymentIntent); 15 | 16 | void handleStripeEvent(StripeObject stripeObject); 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Product; 4 | import com.vedasole.ekartecommercebackend.payload.ProductDto; 5 | import org.springframework.data.domain.Page; 6 | 7 | import java.util.List; 8 | 9 | public interface ProductService { 10 | 11 | ProductDto createProduct(ProductDto productDto); 12 | 13 | ProductDto updateProduct(ProductDto productDto , Long productId); 14 | 15 | List getAllProducts(); 16 | 17 | Page getAllProductsPerPage(int page, int size, String sortBy, String sortOrder); 18 | 19 | ProductDto getProductById(Long productId); 20 | 21 | void deleteProduct(Long productId); 22 | 23 | List getProductsByNameOrDesc(int page, int size, String searchKey); 24 | 25 | List getAllProductsByCategory(long categoryId); 26 | 27 | Page getAllProductsByCategoryPerPage(long categoryId, int page, int size, String sortBy, String sortOrder); 28 | 29 | Long getTotalProductsCount(); 30 | 31 | Product productDtoToEntity(ProductDto productDto); 32 | 33 | ProductDto productEntityToDto(Product product); 34 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/ShoppingCartItemService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.ShoppingCartItem; 4 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartItemDto; 5 | 6 | import java.util.List; 7 | 8 | public interface ShoppingCartItemService { 9 | 10 | ShoppingCartItemDto createShoppingCartItem(ShoppingCartItemDto shoppingCartItemDto); 11 | List createShoppingCartWithAllItems(List shoppingCartItemDtos); 12 | ShoppingCartItemDto updateShoppingCartItem(ShoppingCartItemDto shoppingCartItemDto); 13 | void deleteShoppingCartItem(long cartItemId); 14 | void deleteAllShoppingCartItems(long cartId); 15 | ShoppingCartItemDto getShoppingCartItem(long cartItemId); 16 | List getAllShoppingCartItems(long cartId); 17 | ShoppingCartItem convertToShoppingCartItem(ShoppingCartItemDto shoppingCartItemDto); 18 | ShoppingCartItemDto convertToShoppingCartItemDto(ShoppingCartItem shoppingCartItem); 19 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/ShoppingCartService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | 4 | import com.vedasole.ekartecommercebackend.entity.ShoppingCart; 5 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartDto; 6 | import com.vedasole.ekartecommercebackend.payload.ShoppingCartItemDto; 7 | 8 | public interface ShoppingCartService { 9 | 10 | ShoppingCart createCart(ShoppingCartDto shoppingCartDto); 11 | ShoppingCartDto createCartWithItems(ShoppingCartDto shoppingCartDto); 12 | ShoppingCartDto addOrUpdateItemInCart(ShoppingCartItemDto shoppingCartItemDto); 13 | ShoppingCartDto getCart(long customerId); 14 | void deleteCart(long customerId); 15 | ShoppingCart convertToShoppingCart(ShoppingCartDto shoppingCartDto); 16 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/service/service_interface/UserService.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.service.service_interface; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.User; 4 | 5 | import java.util.List; 6 | 7 | public interface UserService { 8 | 9 | User createUser(User user); 10 | 11 | User updateUser(User user, Long userId); 12 | 13 | boolean deleteUser(Long userId); 14 | 15 | User getUserByEmail(String email); 16 | 17 | User getUserById(Long userId); 18 | 19 | List getAllUsers(); 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/utility/AppConstant.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.utility; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | public class AppConstant { 7 | 8 | @AllArgsConstructor 9 | @Getter 10 | public enum RELATIONS { 11 | 12 | CATEGORY("Category"), 13 | CATEGORIES("Categories"), 14 | PRODUCT("Product"), 15 | PRODUCTS("Products"), 16 | CUSTOMER("Customer"), 17 | CUSTOMERS("Customers"), 18 | SHOPPING_CART("ShoppingCart"), 19 | SHOPPING_CARTS("ShoppingCarts"), 20 | SHOPPING_CART_ITEM("ShoppingCartItem"), 21 | SHOPPING_CARTS_ITEMS("ShoppingCartItems"), 22 | ORDER("Order"), 23 | ORDERS("Orders"), 24 | ORDER_DETAIL("Order Detail"), 25 | ORDER_DETAILS("Order Details"), 26 | USER("User"), 27 | USERS("Users"); 28 | 29 | private final String value; 30 | 31 | } 32 | 33 | @AllArgsConstructor 34 | @Getter 35 | public enum OrderStatus { 36 | ORDER_CREATED("ORDER_CREATED"), 37 | ORDER_PLACED("ORDER_PLACED"), 38 | ORDER_EXPIRED("ORDER_EXPIRED"), 39 | ORDER_CANCELLED("ORDER_CANCELLED"), 40 | ORDER_FAILED("ORDER_FAILED"), 41 | ORDER_DISPATCHED("ORDER_DISPATCHED"), 42 | ORDER_SHIPPED("ORDER_SHIPPED"), 43 | ORDER_DELIVERED("ORDER_DELIVERED"), 44 | ORDER_COMPLETED("ORDER_COMPLETED"); 45 | 46 | private final String name; 47 | 48 | } 49 | 50 | @AllArgsConstructor 51 | @Getter 52 | public enum Role { 53 | USER("ROLE_USER"), 54 | ADMIN("ROLE_ADMIN"); 55 | 56 | private final String value; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/vedasole/ekartecommercebackend/utility/ApplicationInitializer.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.utility; 2 | 3 | import com.vedasole.ekartecommercebackend.exception.ResourceNotFoundException; 4 | import com.vedasole.ekartecommercebackend.payload.NewCustomerDto; 5 | import com.vedasole.ekartecommercebackend.service.service_interface.CustomerService; 6 | import lombok.NonNull; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.context.event.ApplicationReadyEvent; 9 | import org.springframework.context.ApplicationListener; 10 | import org.springframework.context.annotation.Profile; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | @Profile({"prod","uat","dev"}) 15 | public class ApplicationInitializer implements ApplicationListener { 16 | 17 | private final CustomerService customerService; 18 | private final String adminPassword; 19 | 20 | public ApplicationInitializer( 21 | CustomerService customerService, 22 | @Value("${admin.password}") String adminPassword 23 | ) { 24 | this.customerService = customerService; 25 | this.adminPassword = adminPassword; 26 | } 27 | 28 | @Override 29 | public void onApplicationEvent(@NonNull ApplicationReadyEvent event) { 30 | insertAdminUser(); 31 | } 32 | 33 | private void insertAdminUser() { 34 | String adminEmail = "admin@ekart.com"; 35 | try { 36 | customerService.getCustomerByEmail(adminEmail); 37 | } catch (ResourceNotFoundException e) { 38 | NewCustomerDto adminUser = NewCustomerDto.builder() 39 | .customerId(1) 40 | .email(adminEmail) 41 | .firstName("Admin") 42 | .lastName("User") 43 | .phoneNumber("1234567890") 44 | .password(adminPassword) 45 | .role(AppConstant.Role.ADMIN) 46 | .build(); 47 | customerService.createCustomer(adminUser); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:h2:mem:ekartdb 2 | spring.datasource.driverClassName=org.h2.Driver 3 | spring.datasource.username=ekart 4 | spring.datasource.password=password 5 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 6 | spring.jpa.show-sql = true 7 | spring.jpa.generate-ddl=true 8 | spring.jpa.hibernate.ddl-auto = create-drop 9 | spring.jpa.properties.hibernate.format_sql=true 10 | #Logging Configuration 11 | logging.level.root= DEBUG -------------------------------------------------------------------------------- /src/main/resources/application-docker.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=${dbUrl} 2 | spring.datasource.driverClassName=org.postgresql.Driver 3 | spring.datasource.username=${dbUsername} 4 | spring.datasource.password=${dbPassword} 5 | spring.jpa.hibernate.ddl-auto = update 6 | spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL10Dialect 7 | 8 | spring.redis.host=${REDIS_HOST:host.docker.internal} 9 | spring.redis.port=6379 10 | spring.cache.type=redis 11 | spring.cache.redis.time-to-live=15m -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | #Database Configuration 2 | spring.datasource.url=${neonEkartDBUrl} 3 | spring.datasource.driverClassName=org.postgresql.Driver 4 | spring.datasource.username=${neonEkartDBUsername} 5 | spring.datasource.password=${neonEkartDBPassword} 6 | spring.jpa.hibernate.ddl-auto = update 7 | spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL10Dialect -------------------------------------------------------------------------------- /src/main/resources/application-uat.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=${uatDBUrl} 2 | spring.datasource.driverClassName=org.postgresql.Driver 3 | spring.datasource.username=${uatDBUsername} 4 | spring.datasource.password=${uatDBPassword} 5 | spring.jpa.show-sql=true 6 | spring.jpa.generate-ddl=true 7 | spring.jpa.hibernate.ddl-auto = update 8 | spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL10Dialect 9 | 10 | #Logging Configuration 11 | logging.level.root= DEBUG -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=ekart-ecommerce-backend 2 | 3 | #Server Properties 4 | server.port=8000 5 | 6 | #Active Configuration Profile 7 | spring.profiles.active=prod 8 | 9 | #To resolve the reserved keywords issues with hibernate auto generated queries 10 | spring.jpa.properties.hibernate.globally_quoted_identifiers=true 11 | 12 | #Swagger Configuration 13 | spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER 14 | 15 | #JWT Configuration 16 | jwt.secret.key=${jsonSecretKey:ekartSecretKey} 17 | 18 | #Admin Configuration 19 | admin.password=${adminPassword:Admin@123} 20 | 21 | stripe.endpoint.secret=${stripeEndpointSecret} 22 | 23 | # Server Compression Configuration 24 | server.compression.enabled=true 25 | server.compression.min-response-size=1024 26 | 27 | #Caching Configuration 28 | spring.redis.host=localhost 29 | spring.redis.port=6379 30 | spring.cache.type=redis 31 | spring.cache.redis.time-to-live=15m 32 | 33 | #Email Configuration 34 | spring.mail.host=smtp.gmail.com 35 | spring.mail.port=587 36 | spring.mail.username=${emailUsername} 37 | spring.mail.password=${emailPassword} 38 | spring.mail.properties.mail.smtp.auth=true 39 | spring.mail.properties.mail.smtp.starttls.enable=true -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ekart_logs 5 | ${logtailSourceToken} 6 | requestId,requestTime 7 | string,int 8 | com.fasterxml.jackson.datatype.jsr310.JavaTimeModule 9 | 10 | 11 | 12 | %d{yyyy-MM-dd HH:mm:ss.SSS} %-4relative %magenta([%thread]) %highlight(%-5level) %cyan(%logger{35}) - %msg %n 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/templates/orderConfirmation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Order Confirmation 6 | 58 | 59 | 60 |
61 |
62 | EKart Shopping 63 |

Order Confirmation

64 |
65 |
66 |

Thank you for your order! Here are the details:

67 |
68 |
69 | Order Number: 70 | 71 |
72 |
73 | Order Status: 74 | 75 |
76 |
77 | Created On: 78 | 79 |
80 |
81 | Total Amount: 82 | 83 |
84 |
85 | 86 |

Order Items:

87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |
Item NameQuantityPrice
103 |
104 |
105 |

If you have any questions or need assistance, feel free to reach out to us at support@ekart.com.

106 |
107 |
108 |

Best regards,

109 |

The Ekart Shopping Team

110 |
111 |
112 | 113 | -------------------------------------------------------------------------------- /src/main/resources/templates/resetPassword.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Reset Password 6 | 7 | 31 | 32 | 33 | 34 | 35 | 36 | 70 | 71 |
37 | 38 | 39 | 42 | 43 | 44 | 47 | 48 | 49 | 56 | 57 | 58 | 61 | 62 | 63 | 67 | 68 |
40 | EKart Shopping 41 |
45 |

Reset Your Password

46 |
50 |

We received a request to reset your password. Please click the link below to set a new password:

51 |
52 | Reset Password 53 |
54 |

If you did not request a password reset, please ignore this email.

55 |
59 |

If you have any questions or need assistance, feel free to reach out to us at support@ekart.com.

60 |
64 |

Best regards,

65 |

The Ekart Shopping Team

66 |
69 |
72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/templates/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome to Our Platform! 6 | 7 | 31 | 32 | 33 | 34 | 35 | 36 | 63 | 64 |
37 | 38 | 39 | 42 | 43 | 44 | 47 | 48 | 49 | 54 | 55 | 56 | 60 | 61 |
40 | EKart Shopping 41 |
45 |

Welcome, !

46 |
50 |

Thank you for signing up to our platform! We're excited to have you on board.

51 |

This is a confirmation email to let you know that your account has been created successfully. You can now log in and start exploring our features.

52 |

If you have any questions or need help, please don't hesitate to reach out to us at support@ekart.com.

53 |
57 |

Best regards,

58 |

The Ekart Shopping Team

59 |
62 |
65 | 66 | -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/EkartEcommerceBackendApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.modelmapper.ModelMapper; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | 11 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 12 | class EkartEcommerceBackendApplicationTests { 13 | 14 | @Autowired 15 | EkartEcommerceBackendApplication application; 16 | 17 | 18 | @Test 19 | void contextLoads() { 20 | assertNotNull(application); 21 | } 22 | 23 | @Test 24 | void whenCreatingModelMapper_thenNoExceptions() { 25 | // Act and Assert 26 | Assertions.assertDoesNotThrow(ModelMapper::new); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/config/ApplicationConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.config; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.User; 4 | import com.vedasole.ekartecommercebackend.repository.UserRepo; 5 | import com.vedasole.ekartecommercebackend.utility.AppConstant; 6 | import org.junit.jupiter.api.Disabled; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.mockito.InjectMocks; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | import org.springframework.security.authentication.BadCredentialsException; 13 | import org.springframework.security.core.userdetails.UserDetails; 14 | import org.springframework.security.core.userdetails.UserDetailsService; 15 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 16 | import org.springframework.security.crypto.password.PasswordEncoder; 17 | 18 | import java.util.Optional; 19 | 20 | import static org.assertj.core.api.Assertions.*; 21 | import static org.junit.jupiter.api.Assertions.assertThrows; 22 | import static org.mockito.ArgumentMatchers.any; 23 | import static org.mockito.Mockito.when; 24 | 25 | @ExtendWith(MockitoExtension.class) 26 | class ApplicationConfigTest { 27 | 28 | private final String USER_NOT_FOUND_ERROR_MESSAGE = "User not found"; 29 | @Mock 30 | private UserRepo userRepo; 31 | 32 | @InjectMocks 33 | private ApplicationConfig applicationConfig; 34 | 35 | @Test 36 | void shouldThrowExceptionWhenUserEmailNotFound() { 37 | String nonExistentEmail = "nonExistentEmail@example.com"; 38 | when(userRepo.findByEmailIgnoreCase(nonExistentEmail)).thenReturn(Optional.empty()); 39 | 40 | UsernameNotFoundException thrown = assertThrows(UsernameNotFoundException.class, () -> { 41 | applicationConfig.userDetailsService().loadUserByUsername(nonExistentEmail); 42 | }); 43 | assertThat(thrown.getMessage()).isEqualTo(USER_NOT_FOUND_ERROR_MESSAGE); 44 | } 45 | 46 | @Test 47 | void shouldHandleCaseSensitivityWhenSearchingForUserByEmail() { 48 | String email = "testUser@example.com"; 49 | String emailWithDifferentCase = "TESTUSER@EXAMPLE.COM"; 50 | User user = new User(email, "password", AppConstant.Role.USER); 51 | when(userRepo.findByEmailIgnoreCase(email)).thenReturn(Optional.of(user)); 52 | when(userRepo.findByEmailIgnoreCase(emailWithDifferentCase)).thenReturn(Optional.of(user)); 53 | 54 | UserDetails loadedUser1 = applicationConfig.userDetailsService().loadUserByUsername(email); 55 | UserDetails loadedUser2 = applicationConfig.userDetailsService().loadUserByUsername(emailWithDifferentCase); 56 | 57 | assertThat(loadedUser1).isEqualTo(loadedUser2); 58 | } 59 | 60 | @Test 61 | void shouldThrowExceptionWhenUserEmailIsNull() { 62 | String nullEmail = null; 63 | when(userRepo.findByEmailIgnoreCase(nullEmail)).thenReturn(Optional.empty()); 64 | 65 | UsernameNotFoundException thrown = assertThrows(UsernameNotFoundException.class, () -> { 66 | applicationConfig.userDetailsService().loadUserByUsername(nullEmail); 67 | }); 68 | assertThat(thrown.getMessage()).isEqualTo(USER_NOT_FOUND_ERROR_MESSAGE); 69 | } 70 | 71 | @Test 72 | void shouldThrowExceptionWhenUserEmailIsEmpty() { 73 | String emptyEmail = ""; 74 | when(userRepo.findByEmailIgnoreCase(emptyEmail)).thenReturn(Optional.empty()); 75 | 76 | UsernameNotFoundException thrown = assertThrows(UsernameNotFoundException.class, () -> { 77 | applicationConfig.userDetailsService().loadUserByUsername(emptyEmail); 78 | }); 79 | assertThat(thrown.getMessage()).isEqualTo(USER_NOT_FOUND_ERROR_MESSAGE); 80 | } 81 | 82 | @Test 83 | void shouldThrowExceptionWhenUserEmailIsOnlyWhitespace() { 84 | String whitespaceEmail = " "; 85 | when(userRepo.findByEmailIgnoreCase(whitespaceEmail)).thenReturn(Optional.empty()); 86 | 87 | UsernameNotFoundException thrown = assertThrows( 88 | UsernameNotFoundException.class, 89 | () -> applicationConfig.userDetailsService().loadUserByUsername(whitespaceEmail) 90 | ); 91 | assertThat(thrown.getMessage()).isEqualTo(USER_NOT_FOUND_ERROR_MESSAGE); 92 | } 93 | 94 | @Test 95 | @Disabled("The password strength validation is not implemented yet") 96 | void shouldValidatePasswordStrengthWhenCreatingANewUser() { 97 | String weakPassword = "weak"; 98 | String mediumPassword = "medium123"; 99 | String strongPassword = "StrongP@ssw0rd"; 100 | 101 | when(userRepo.save(any(User.class))).thenAnswer(invocation -> invocation.getArguments()[0]); 102 | 103 | UserDetailsService userDetailsService = applicationConfig.userDetailsService(); 104 | 105 | assertThatThrownBy(() -> userDetailsService.loadUserByUsername(weakPassword)) 106 | .isInstanceOf(BadCredentialsException.class) 107 | .hasMessage("Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one digit, and one special character"); 108 | 109 | assertThatCode(() -> userDetailsService.loadUserByUsername(mediumPassword)) 110 | .doesNotThrowAnyException(); 111 | 112 | assertThatCode(() -> userDetailsService.loadUserByUsername(strongPassword)) 113 | .doesNotThrowAnyException(); 114 | } 115 | 116 | @Test 117 | void shouldVerifyPasswordEncoderIsCorrectlyConfigured() { 118 | PasswordEncoder passwordEncoder = applicationConfig.passwordEncoder(); 119 | 120 | String plainTextPassword = "plainTextPassword"; 121 | String encodedPassword = passwordEncoder.encode(plainTextPassword); 122 | 123 | assertThat(passwordEncoder.matches(plainTextPassword, encodedPassword)).isTrue(); 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/config/TestMailConfig.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.mail.javamail.JavaMailSender; 6 | import org.springframework.mail.javamail.JavaMailSenderImpl; 7 | 8 | import javax.mail.internet.MimeMessage; 9 | 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.when; 12 | 13 | @Configuration 14 | public class TestMailConfig { 15 | 16 | @Bean 17 | public JavaMailSender javaMailSender() { 18 | JavaMailSenderImpl mailSender = mock(JavaMailSenderImpl.class); 19 | MimeMessage mimeMessage = new MimeMessage((javax.mail.Session) null); 20 | when(mailSender.createMimeMessage()).thenReturn(mimeMessage); 21 | return mailSender; 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/entity/CategoryTest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.entity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class CategoryTest { 8 | 9 | @Test 10 | void shouldCreateCategoryWithValidName() { 11 | // Arrange 12 | String expectedName = "Electronics"; 13 | 14 | // Act 15 | Category category = Category.builder() 16 | .categoryId(0) 17 | .name(expectedName) 18 | .active(true) 19 | .build(); 20 | 21 | // Assert 22 | assertEquals(expectedName, category.getName()); 23 | } 24 | 25 | @Test 26 | void shouldCreateCategoryWithActiveValueFalse() { 27 | // Act 28 | Category category = Category.builder() 29 | .categoryId(0) 30 | .name("Home Appliances") 31 | .active(false) 32 | .build(); 33 | 34 | // Assert 35 | assertFalse(category.isActive()); 36 | } 37 | 38 | @Test 39 | void shouldCreateCategoryWithParentCategory() { 40 | // Arrange 41 | Category parentCategory = Category.builder() 42 | .categoryId(0) 43 | .name("Parent Category") 44 | .active(true) 45 | .build(); 46 | 47 | // Act 48 | Category category = Category.builder() 49 | .categoryId(0) 50 | .name("Child Category") 51 | .active(true) 52 | .parentCategory(parentCategory) 53 | .build(); 54 | 55 | // Assert 56 | assertNotNull(category.getParentCategory()); 57 | assertEquals("Parent Category", category.getParentCategory().getName()); 58 | } 59 | 60 | @Test 61 | void shouldCreateCategoryWithImageAndVerifyImageUrlIsSetCorrectly() { 62 | // Arrange 63 | String expectedImageUrl = "http://example.com/image.jpg"; 64 | 65 | // Act 66 | Category category = Category.builder() 67 | .categoryId(0) 68 | .name("Fashion") 69 | .active(true) 70 | .image(expectedImageUrl) 71 | .build(); 72 | 73 | // Assert 74 | assertEquals(expectedImageUrl, category.getImage()); 75 | } 76 | 77 | @Test 78 | void shouldCreateCategoryWithDescriptionAndVerifyDescriptionIsSetCorrectly() { 79 | // Arrange 80 | String expectedDescription = "This category includes all electronic gadgets."; 81 | 82 | // Act 83 | Category category = Category.builder() 84 | .categoryId(0) 85 | .name("Electronics") 86 | .active(true) 87 | .desc(expectedDescription) 88 | .build(); 89 | 90 | // Assert 91 | assertEquals(expectedDescription, category.getDesc()); 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/repository/CustomerRepoTest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Customer; 4 | import com.vedasole.ekartecommercebackend.entity.ShoppingCart; 5 | import com.vedasole.ekartecommercebackend.entity.User; 6 | import com.vedasole.ekartecommercebackend.utility.AppConstant; 7 | import org.junit.jupiter.api.AfterEach; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 13 | 14 | import java.time.LocalDateTime; 15 | import java.util.NoSuchElementException; 16 | import java.util.Optional; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 20 | 21 | @DataJpaTest 22 | class CustomerRepoTest { 23 | 24 | @Autowired 25 | private CustomerRepo underTest; 26 | 27 | @BeforeEach 28 | void setUp() { 29 | //given 30 | String email = "johndoe@email.com"; 31 | new ShoppingCart(); 32 | Customer customer = new Customer( 33 | 1L, 34 | "John", 35 | "Doe", 36 | "1234567890", 37 | email, 38 | new User(email, "password", AppConstant.Role.USER), 39 | null, 40 | LocalDateTime.now(), 41 | LocalDateTime.now() 42 | ); 43 | underTest.save(customer); 44 | } 45 | 46 | @AfterEach 47 | void tearDown() { 48 | underTest.deleteAll(); 49 | } 50 | 51 | @Test 52 | void shouldFindCustomerWithEmailWhenExists() { 53 | 54 | //given 55 | String email = "johndoe@email.com"; 56 | 57 | //when 58 | Optional customerOptional = underTest.findByEmail(email); 59 | 60 | //then 61 | Assertions.assertTrue(customerOptional.isPresent()); 62 | 63 | Customer expectedCustomer = customerOptional.get(); 64 | assertThat(expectedCustomer).isNotNull(); 65 | assertThat(expectedCustomer.getEmail()).isEqualTo(email); 66 | assertThat(expectedCustomer.getFirstName()).isEqualTo("John"); 67 | assertThat(expectedCustomer.getLastName()).isEqualTo("Doe"); 68 | 69 | } 70 | 71 | @Test 72 | void shouldNotFindCustomerWithEmailAndThrowNoSuchElementException() { 73 | 74 | //given 75 | String email = "random@email.com"; 76 | 77 | //when 78 | Optional expected = underTest.findByEmail(email); 79 | 80 | //then 81 | Assertions.assertTrue(expected.isEmpty()); 82 | 83 | assertThatThrownBy(expected::get).isInstanceOf(NoSuchElementException.class); 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/repository/ProductRepoTest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Category; 4 | import com.vedasole.ekartecommercebackend.entity.Product; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 10 | import org.springframework.data.domain.PageRequest; 11 | 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.junit.jupiter.api.Assertions.assertTrue; 18 | 19 | @DataJpaTest 20 | class ProductRepoTest { 21 | 22 | @Autowired 23 | private ProductRepo underTest; 24 | 25 | @Autowired 26 | private CategoryRepo categoryRepo; 27 | 28 | @BeforeEach 29 | void setUp() { 30 | 31 | Category category1 = new Category(1L, "Category 1", "cat_img1.jpg", "Description 1", null, true, LocalDateTime.now(), LocalDateTime.now()); 32 | Category category2 = new Category(2L, "Category 2", "cat_img2.jpg", "Description 2", null, true, LocalDateTime.now(), LocalDateTime.now()); 33 | 34 | category1 = categoryRepo.save(category1); 35 | category2 = categoryRepo.save(category2); 36 | 37 | // Initialize the products in the database 38 | Product product1 = new Product( 39 | 1L, 40 | "Sample Product Name 1 containing word car", 41 | "img1.jpg", 42 | "PRD-001", 43 | "Sample Description to test same description methods with word : apple", 44 | 100.0, 45 | 10, 46 | 50, 47 | category1, 48 | LocalDateTime.now(), 49 | LocalDateTime.now() 50 | ); 51 | Product product2 = new Product( 52 | 2L, 53 | "Product 2", 54 | "img2.jpg", 55 | "PRD-002", 56 | "Description 2", 57 | 200.0, 58 | 20, 59 | 40, 60 | category1, 61 | LocalDateTime.now(), 62 | LocalDateTime.now() 63 | ); 64 | Product product3 = new Product( 65 | 3L, 66 | "Sample Product Name 3 containing word car", 67 | "img3.jpg", 68 | "PRD-003", 69 | "Sample Description to test same description methods with word : apple", 70 | 300.0, 71 | 30, 72 | 30, 73 | category2, 74 | LocalDateTime.now(), 75 | LocalDateTime.now() 76 | ); 77 | underTest.saveAll(List.of(product1, product2, product3)); 78 | } 79 | 80 | @AfterEach 81 | void tearDown() { 82 | underTest.deleteAll(); 83 | } 84 | 85 | @Test 86 | void shouldFindProductContainingNameWithIgnoreCaseWhenExists() { 87 | 88 | //When 89 | List products = underTest.findByNameIsContainingIgnoreCaseOrDescContainingIgnoreCase("Car", null, PageRequest.of(0, 10)); 90 | 91 | //Then 92 | assertThat(products).hasSize(2); 93 | products.forEach(product -> assertThat(product.getName()).containsIgnoringCase("Car")); 94 | 95 | } 96 | 97 | @Test 98 | void shouldNotFindProductContainingNameWithIgnoreCase() { 99 | 100 | //When 101 | List products = underTest.findByNameIsContainingIgnoreCaseOrDescContainingIgnoreCase("Banana", null, PageRequest.of(0, 10)); 102 | 103 | //Then 104 | assertTrue(products.isEmpty()); 105 | 106 | } 107 | 108 | @Test 109 | void shouldFindProductContainingDescWithIgnoreCaseWhenExists() { 110 | 111 | //When 112 | List products = underTest.findByNameIsContainingIgnoreCaseOrDescContainingIgnoreCase(null, "Apple", PageRequest.of(0, 10)); 113 | 114 | //Then 115 | assertThat(products).hasSize(2); 116 | products.forEach(product -> assertThat(product.getDesc()).containsIgnoringCase("Apple")); 117 | 118 | } 119 | 120 | @Test 121 | void shouldNotFindProductContainingDescWithIgnoreCase() { 122 | 123 | //When 124 | List products = underTest.findByNameIsContainingIgnoreCaseOrDescContainingIgnoreCase(null, "Banana", PageRequest.of(0, 10)); 125 | 126 | //Then 127 | assertTrue(products.isEmpty()); 128 | 129 | } 130 | 131 | @Test 132 | void shouldFindProductByCategoryWhenExists() { 133 | 134 | //Given 135 | List categories = categoryRepo.findAll(); 136 | Optional categoryOptional = categories.stream() 137 | .filter(category -> category.getName().equalsIgnoreCase("Category 1")) 138 | .findFirst(); 139 | assertTrue(categoryOptional.isPresent()); 140 | long categoryId = categoryOptional.get().getCategoryId(); 141 | 142 | //When 143 | List products = underTest.findByCategoryCategoryId(categoryId); 144 | 145 | //Then 146 | assertThat(products).hasSize(2); 147 | products.forEach(product -> { 148 | assertThat(product.getCategory().getCategoryId()).isEqualTo(categoryId); 149 | assertThat(product.getCategory()).isEqualTo(categoryOptional.get()); 150 | }); 151 | 152 | } 153 | 154 | @Test 155 | void shouldNotFindAnyProductsByCategoryAndThrowsIllegalArgumentException() { 156 | List savedProducts = underTest.findByCategoryCategoryId(0L); 157 | assertTrue(savedProducts.isEmpty()); 158 | } 159 | 160 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/repository/ShoppingCartRepoTest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.Customer; 4 | import com.vedasole.ekartecommercebackend.entity.ShoppingCart; 5 | import com.vedasole.ekartecommercebackend.entity.User; 6 | import com.vedasole.ekartecommercebackend.utility.AppConstant; 7 | import org.junit.jupiter.api.AfterEach; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 12 | 13 | import java.time.LocalDateTime; 14 | import java.util.NoSuchElementException; 15 | import java.util.Optional; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.junit.jupiter.api.Assertions.assertThrows; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | @DataJpaTest 22 | class ShoppingCartRepoTest { 23 | 24 | @Autowired 25 | private ShoppingCartRepo underTest; 26 | 27 | @Autowired 28 | private CustomerRepo customerRepo; 29 | 30 | @BeforeEach 31 | void setUp() { 32 | // Initialize the database for testing 33 | 34 | //Add Customer 35 | Customer customer = new Customer( 36 | 1L, 37 | "John", 38 | "Doe", 39 | "1234567890", 40 | "john@email.com", 41 | new User("john@email.com", "password", AppConstant.Role.USER), 42 | null, 43 | LocalDateTime.now(), 44 | LocalDateTime.now() 45 | ); 46 | Customer savedCustomer = customerRepo.save(customer); 47 | ShoppingCart shoppingCart = ShoppingCart.builder() 48 | .cartId(5) 49 | .customer(savedCustomer) 50 | .total(0) 51 | .discount(0).build(); 52 | ShoppingCart savedShoppingCart = underTest.save(shoppingCart); 53 | savedCustomer.setShoppingCart(savedShoppingCart); 54 | customerRepo.save(savedCustomer); 55 | } 56 | 57 | @AfterEach 58 | void tearDown() { 59 | underTest.deleteAll(); 60 | } 61 | 62 | @Test 63 | void shouldFindShoppingCartByCustomerIdWhenExists() { 64 | // Given 65 | String customerEmail = "john@email.com"; 66 | Optional customerOptional = customerRepo.findByEmail(customerEmail); 67 | assertTrue(customerOptional.isPresent()); 68 | long customerId = customerOptional.get().getCustomerId(); 69 | 70 | // When 71 | Optional shoppingCartOptional = underTest.findByCustomer_CustomerId(customerId); 72 | 73 | // Then 74 | assertTrue(shoppingCartOptional.isPresent()); 75 | 76 | ShoppingCart expected = shoppingCartOptional.get(); 77 | assertThat(expected).isNotNull(); 78 | assertThat(expected.getCustomer().getCustomerId()).isEqualTo(customerId); 79 | assertThat(expected.getCustomer().getEmail()).isEqualTo(customerEmail); 80 | assertThat(expected.getCustomer()).isNotNull().isEqualTo(customerOptional.get()); 81 | } 82 | 83 | @Test 84 | void shouldDeleteShoppingCartByCustomerId() { 85 | 86 | // Given 87 | String customerEmail = "john@email.com"; 88 | Optional customerOptional = customerRepo.findByEmail(customerEmail); 89 | assertTrue(customerOptional.isPresent()); 90 | Customer customer = customerOptional.get(); 91 | long cartId = customer.getShoppingCart().getCartId(); 92 | 93 | // When 94 | Optional expectedOptional = underTest.findById(cartId); 95 | assertTrue(expectedOptional.isPresent()); 96 | underTest.deleteByCustomer_CustomerId(customer.getCustomerId()); 97 | 98 | // Then 99 | Optional outputOptional = underTest.findById(cartId); 100 | assertTrue(outputOptional.isEmpty()); 101 | 102 | Optional shoppingCartOptional = underTest.findById(cartId); 103 | assertTrue(shoppingCartOptional.isEmpty()); 104 | assertThrows(NoSuchElementException.class, shoppingCartOptional::get); 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/repository/UserRepoTest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.repository; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.User; 4 | import com.vedasole.ekartecommercebackend.utility.AppConstant; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 11 | 12 | import java.util.NoSuchElementException; 13 | import java.util.Optional; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 17 | 18 | @DataJpaTest 19 | class UserRepoTest { 20 | 21 | @Autowired 22 | private UserRepo underTest; 23 | 24 | @BeforeEach 25 | void setUp() { 26 | //given 27 | User user = new User( 28 | "john@email.com", 29 | "pass@123", 30 | AppConstant.Role.USER 31 | ); 32 | underTest.save(user); 33 | } 34 | 35 | @AfterEach 36 | void tearDown() { 37 | underTest.deleteAll(); 38 | } 39 | 40 | @Test 41 | void shouldFindUserByEmailWhenExists() { 42 | 43 | // Given 44 | String email = "john@email.com"; 45 | 46 | // When 47 | Optional userOptional = underTest.findByEmailIgnoreCase(email); 48 | boolean foundUser = userOptional.isPresent(); 49 | 50 | // Then 51 | Assertions.assertTrue(foundUser); 52 | assertThat(userOptional.get()).isNotNull().hasFieldOrPropertyWithValue("email", email); 53 | } 54 | 55 | @Test 56 | void shouldNotFindUserByEmailAndThrowNoSuchElementException() { 57 | // Given 58 | String email = "random@email.com"; 59 | 60 | // When 61 | Optional userOptional = underTest.findByEmailIgnoreCase(email); 62 | 63 | // Then 64 | assertThatThrownBy(userOptional::get).isInstanceOf(NoSuchElementException.class); 65 | } 66 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/security/WebSecurityConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.security; 2 | 3 | import com.vedasole.ekartecommercebackend.utility.TestApplicationInitializer; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | @SpringBootTest 17 | @AutoConfigureMockMvc 18 | @Slf4j 19 | class WebSecurityConfigTest { 20 | 21 | @Autowired 22 | private MockMvc mockMvc; 23 | 24 | @Autowired 25 | private TestApplicationInitializer testApplicationInitializer; 26 | 27 | @Test 28 | void whenAccessPublicUrl_thenOk() throws Exception { 29 | mockMvc.perform(get("/api/v1/categories")) 30 | .andDo(result -> log.debug("whenAccessPublicUrl_thenOk result: {}", result.getResponse().getContentAsString())) 31 | .andExpect(status().isOk()); 32 | } 33 | 34 | @Test 35 | void whenAccessProtectedUrlWithoutAuth_thenUnauthorized() throws Exception { 36 | mockMvc.perform(get("/api/v1/auth/check-token")) 37 | .andExpect(status().isUnauthorized()); 38 | } 39 | 40 | @Test 41 | void whenAccessGeneralProtectedUrlWithAuth_thenOk() throws Exception { 42 | mockMvc.perform(get("/api/v1/auth/check-token") 43 | .header("Authorization", "Bearer " + testApplicationInitializer.getUserToken())) 44 | .andExpect(status().isOk()); 45 | } 46 | 47 | @Test 48 | void whenAccessAdminProtectedUrlWithAuth_thenOk() throws Exception { 49 | mockMvc.perform(delete("/api/v1/customers/3") 50 | .header("Authorization", "Bearer " + testApplicationInitializer.getAdminToken())) 51 | .andExpectAll( 52 | jsonPath("$.message").value("No class com.vedasole.ekartecommercebackend.entity.Customer entity with id 3 exists!"), 53 | jsonPath("$.success").value(false), 54 | status().isInternalServerError() 55 | ); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/test/java/com/vedasole/ekartecommercebackend/utility/TestApplicationInitializer.java: -------------------------------------------------------------------------------- 1 | package com.vedasole.ekartecommercebackend.utility; 2 | 3 | import com.vedasole.ekartecommercebackend.entity.User; 4 | import com.vedasole.ekartecommercebackend.exception.ResourceNotFoundException; 5 | import com.vedasole.ekartecommercebackend.payload.NewCustomerDto; 6 | import com.vedasole.ekartecommercebackend.security.JwtService; 7 | import com.vedasole.ekartecommercebackend.service.service_interface.CustomerService; 8 | import lombok.Getter; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.context.event.ApplicationReadyEvent; 13 | import org.springframework.context.event.EventListener; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.test.context.event.ApplicationEventsTestExecutionListener; 16 | 17 | @Component 18 | @Getter 19 | @Slf4j 20 | public class TestApplicationInitializer extends ApplicationEventsTestExecutionListener { 21 | 22 | @Autowired 23 | private JwtService jwtService; 24 | 25 | @Autowired 26 | private CustomerService customerService; 27 | 28 | @Value("${admin.password}") 29 | private String adminPassword; 30 | 31 | @Value("${admin.email}") 32 | private String adminEmail; 33 | 34 | @Value("${normalUser.email}") 35 | private String normalUserEmail; 36 | 37 | @Value("${normalUser.password}") 38 | private String normalUserPassword; 39 | 40 | private String adminToken=null; 41 | private String userToken=null; 42 | 43 | @EventListener(ApplicationReadyEvent.class) 44 | public void generateTokens() { 45 | log.info("ApplicationReadyEvent triggered and generating tokens"); 46 | insertAdminUser(); 47 | generateAdminToken(); 48 | insertNormalUser(); 49 | generateUserToken(); 50 | } 51 | 52 | private void insertAdminUser() { 53 | try { 54 | customerService.getCustomerByEmail(adminEmail); 55 | } catch (ResourceNotFoundException e) { 56 | NewCustomerDto adminUser = NewCustomerDto.builder() 57 | .customerId(1) 58 | .email(adminEmail) 59 | .firstName("Admin") 60 | .lastName("User") 61 | .phoneNumber("1234567890") 62 | .password(adminPassword) 63 | .role(AppConstant.Role.ADMIN) 64 | .build(); 65 | customerService.createCustomer(adminUser); 66 | } 67 | } 68 | 69 | private void generateAdminToken() { 70 | User user = new User(); 71 | user.setEmail(adminEmail); 72 | user.setPassword(adminPassword); 73 | this.adminToken = jwtService.generateToken(user); 74 | log.info("admin token generated"); 75 | } 76 | 77 | private void generateUserToken() { 78 | User user = new User(); 79 | user.setEmail(normalUserEmail); 80 | user.setPassword(normalUserPassword); 81 | this.userToken = jwtService.generateToken(user); 82 | log.info("user token generated"); 83 | } 84 | 85 | private void insertNormalUser() { 86 | NewCustomerDto normalUser = NewCustomerDto.builder() 87 | .customerId(2) 88 | .email(normalUserEmail) 89 | .firstName("Normal") 90 | .lastName("User") 91 | .phoneNumber("1234567890") 92 | .password(normalUserPassword) 93 | .role(AppConstant.Role.USER) 94 | .build(); 95 | customerService.createCustomer(normalUser); 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=ekart-ecommerce-backend 2 | 3 | spring.profiles.active=test 4 | 5 | #Database Configuration 6 | spring.datasource.url=jdbc:h2:mem:testdb 7 | spring.datasource.driverClassName=org.h2.Driver 8 | spring.datasource.username=test_user 9 | spring.datasource.password=test_password 10 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 11 | spring.jpa.show-sql=true 12 | spring.jpa.hibernate.ddl-auto=create-drop 13 | spring.jpa.properties.hibernate.format_sql=true 14 | spring.jpa.defer-datasource-initialization=true 15 | 16 | #To resolve the reserved keywords issues with hibernate auto generated queries 17 | spring.jpa.properties.hibernate.globally_quoted_identifiers=true 18 | 19 | #Swagger Configuration 20 | spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER 21 | 22 | #JWT Configuration 23 | jwt.secret.key=9411c60d0718932341849ff135e0785c2d3da49143d0fe03e3ca8e0e69215ee4 24 | 25 | #Admin Configuration 26 | admin.email=admin@ekart.com 27 | admin.password=testAdminPassword 28 | 29 | #Normal User Configuration 30 | normalUser.email=normal-user@ekart.com 31 | normalUser.password=normalUserTestPassword 32 | 33 | stripe.endpoint.secret=test-endpoint-secret 34 | 35 | #Logging Configuration 36 | logging.level.root=DEBUG --------------------------------------------------------------------------------