├── .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 | [](https://sonarcloud.io/summary/new_code?id=ved-asole_eKart-ecommerce-backend) 4 | [](https://github.com/ved-asole/eKart-ecommerce-backend/actions/workflows/maven-checks.yml) 5 | []((https://coders-arena.betteruptime.com)) 6 | [](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 |  30 | 31 | ### 2) All Products 🛍️ 32 |  33 | 34 | ### 3) Cart 🛒 35 |  36 | 37 | ### 4) Orders 📦 38 |  39 | 40 | ### 5) Admin Dashboard 📊 41 |  42 | 43 | ### 6) Payment Gateway 💳 44 |  45 | 46 | # ER Diagram: 📊 47 |  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 extends GrantedAuthority> 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 | 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 | Item Name 91 | Quantity 92 | Price 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 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 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Reset Your Password 46 | 47 | 48 | 49 | 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 | 56 | 57 | 58 | 59 | If you have any questions or need assistance, feel free to reach out to us at support@ekart.com. 60 | 61 | 62 | 63 | 64 | Best regards, 65 | The Ekart Shopping Team 66 | 67 | 68 | 69 | 70 | 71 | 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 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Welcome, ! 46 | 47 | 48 | 49 | 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 | 54 | 55 | 56 | 57 | Best regards, 58 | The Ekart Shopping Team 59 | 60 | 61 | 62 | 63 | 64 | 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 --------------------------------------------------------------------------------
Thank you for your order! Here are the details:
If you have any questions or need assistance, feel free to reach out to us at support@ekart.com.
Best regards,
The Ekart Shopping Team
We received a request to reset your password. Please click the link below to set a new password:
If you did not request a password reset, please ignore this email.
Thank you for signing up to our platform! We're excited to have you on board.
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.
If you have any questions or need help, please don't hesitate to reach out to us at support@ekart.com.