diff --git a/pom.xml b/pom.xml
index 41576bb..2fd8434 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,11 +51,6 @@
${org.mapstruct.version}
-
- com.h2database
- h2
- test
-
org.projectlombok
lombok
@@ -86,8 +81,33 @@
jjwt-jackson
${jjwt.version}
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
+
+ com.github.cloudyrock.mongock
+ mongock-spring-v5
+
+
+ com.github.cloudyrock.mongock
+ mongodb-springdata-v3-driver
+
+
+
+
+ com.github.cloudyrock.mongock
+ mongock-bom
+ 4.3.8
+ pom
+ import
+
+
+
+
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..4658dc9
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,55 @@
+# Statistics API
+
+Welcome to the Statistics API project! This project aims to create a Spring Boot RESTful API that updates statistics in the database and caches responses that we retrieve.
+This README.md file provides an overview of the project.
+
+## Get Started
+
+To run the project, follow this instruction:
+
+1. Clone the repository:
+ ```bash
+ git clone https://github.com/vadymhrnk/statistics-api.git
+ ```
+2. Download [JDK](https://www.oracle.com/java/technologies/downloads/), [Apache Maven](https://maven.apache.org/download.cgi) and [Docker](https://docs.docker.com/get-docker/)
+3. Use Docker to build and run MongoDB:
+ ```bash
+ docker-compose -f docker-compose.yaml -p statistics-api up -d
+ ```
+4. Build and run the project using:
+ ```bash
+ mvn clean spring-boot:run
+ ```
+
+### Backend Technologies
+- **Java 17**: the primary programming language for backend development.
+- **Spring Boot**: the framework for building and deploying Java-based applications with ease.
+- **Spring Security**: ensures secure authentication and authorization within the application.
+- **Spring Data MongoDB**: provides easy integration with MongoDB for data access.
+- **Spring Boot Starter Web**: starter for building web applications, including RESTful APIs.
+- **Spring Boot Starter Cache**: starter for using Spring Frameworkâs caching support.
+- **Spring Boot Starter Validation**: starter for using JSR-380 Bean Validation with Hibernate Validator.
+- **MapStruct**: simplifies the implementation of bean mappings, reducing manual coding effort.
+- **Lombok**: a tool to reduce boilerplate code, enhancing code readability and conciseness.
+- **MongoDB Driver**: driver for MongoDB integration.
+
+### API Documentation
+- **Springdoc OpenAPI**: an OpenAPI for generating documentation.
+
+### Security
+- **JWT (JSON Web Token)**: used for secure communication and authorization between client and server.
+
+### Database Migration
+- **Mongock**: enables MongoDB database migration management.
+
+### Application Endpoints
+
+- **Authentication controller:**
+ - `POST: /auth/registration` -> Sign in to the app.
+ - `POST: /auth/login` -> Log in to get token for further interactions.
+
+- **Report controller:**
+ - `GET: /reports/dates` -> Get list of all reports by dates.
+ - `GET: /reports/dates?firstDate=2024-02-15&secondDate=2024-02-16` -> Get list of all reports by selected dates
+ - `GET: /reports/ASIN` -> Get all reports by ASINs.
+ - `GET: /reports/ASIN?asinList=B07JWCZKSJ&asinList=B09ZDDDS1X` -> Get all reports by specific ASINs.
\ No newline at end of file
diff --git a/src/main/java/com/example/statisticsapi/StatisticsApiApplication.java b/src/main/java/com/example/statisticsapi/StatisticsApiApplication.java
index 7830ddd..f061781 100644
--- a/src/main/java/com/example/statisticsapi/StatisticsApiApplication.java
+++ b/src/main/java/com/example/statisticsapi/StatisticsApiApplication.java
@@ -1,9 +1,13 @@
package com.example.statisticsapi;
+import com.github.cloudyrock.spring.v5.EnableMongock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
+@EnableMongock
+@EnableScheduling
public class StatisticsApiApplication {
public static void main(String[] args) {
diff --git a/src/main/java/com/example/statisticsapi/config/DatabaseChangelog.java b/src/main/java/com/example/statisticsapi/config/DatabaseChangelog.java
new file mode 100644
index 0000000..3c89de0
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/config/DatabaseChangelog.java
@@ -0,0 +1,26 @@
+package com.example.statisticsapi.config;
+
+import com.example.statisticsapi.document.Role;
+import com.example.statisticsapi.repository.RoleRepository;
+import com.github.cloudyrock.mongock.ChangeLog;
+import com.github.cloudyrock.mongock.ChangeSet;
+import java.util.ArrayList;
+import java.util.List;
+
+@ChangeLog
+public class DatabaseChangelog {
+ @ChangeSet(order = "001", id = "seedDatabase", author = "Vadym Hurnik")
+ public void seedDatabase(RoleRepository roleRepository) {
+ List roleList = new ArrayList<>();
+
+ Role user = new Role();
+ user.setRoleName(Role.RoleName.USER);
+ roleList.add(user);
+
+ Role admin = new Role();
+ admin.setRoleName(Role.RoleName.ADMIN);
+ roleList.add(admin);
+
+ roleRepository.insert(roleList);
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/config/MapperConfig.java b/src/main/java/com/example/statisticsapi/config/MapperConfig.java
new file mode 100644
index 0000000..8aa490e
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/config/MapperConfig.java
@@ -0,0 +1,13 @@
+package com.example.statisticsapi.config;
+
+import org.mapstruct.InjectionStrategy;
+import org.mapstruct.NullValueCheckStrategy;
+
+@org.mapstruct.MapperConfig(
+ componentModel = "spring",
+ injectionStrategy = InjectionStrategy.CONSTRUCTOR,
+ nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
+ implementationPackage = ".impl"
+)
+public class MapperConfig {
+}
diff --git a/src/main/java/com/example/statisticsapi/config/SecurityConfig.java b/src/main/java/com/example/statisticsapi/config/SecurityConfig.java
new file mode 100644
index 0000000..694af37
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/config/SecurityConfig.java
@@ -0,0 +1,65 @@
+package com.example.statisticsapi.config;
+
+import com.example.statisticsapi.securtity.JwtAuthenticationFilter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@RequiredArgsConstructor
+@EnableMethodSecurity
+public class SecurityConfig {
+ @Value("${spring.security.public.endpoints}")
+ private String[] publicEndpoints;
+
+ private final UserDetailsService userDetailsService;
+ private final JwtAuthenticationFilter jwtAuthenticationFilter;
+
+ @Bean
+ public PasswordEncoder getPasswordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ return http
+ .cors(AbstractHttpConfigurer::disable)
+ .csrf(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(
+ auth -> auth
+ .requestMatchers(publicEndpoints)
+ .permitAll()
+ .anyRequest()
+ .authenticated()
+ )
+ .httpBasic(Customizer.withDefaults())
+ .sessionManagement(
+ session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ )
+ .addFilterBefore(
+ jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class
+ )
+ .userDetailsService(userDetailsService)
+ .build();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(
+ AuthenticationConfiguration authenticationConfiguration
+ ) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/config/cache/CachingConfig.java b/src/main/java/com/example/statisticsapi/config/cache/CachingConfig.java
new file mode 100644
index 0000000..f2e6a48
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/config/cache/CachingConfig.java
@@ -0,0 +1,16 @@
+package com.example.statisticsapi.config.cache;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableCaching
+public class CachingConfig {
+ @Bean
+ public CacheManager cacheManager() {
+ return new ConcurrentMapCacheManager();
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/config/cache/SimpleCacheCustomizer.java b/src/main/java/com/example/statisticsapi/config/cache/SimpleCacheCustomizer.java
new file mode 100644
index 0000000..5e52085
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/config/cache/SimpleCacheCustomizer.java
@@ -0,0 +1,17 @@
+package com.example.statisticsapi.config.cache;
+
+import java.util.List;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SimpleCacheCustomizer
+ implements CacheManagerCustomizer {
+ public static final String REPORT_CACHE = "reportCache";
+
+ @Override
+ public void customize(ConcurrentMapCacheManager cacheManager) {
+ cacheManager.setCacheNames(List.of(REPORT_CACHE));
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/controller/AuthenticationController.java b/src/main/java/com/example/statisticsapi/controller/AuthenticationController.java
new file mode 100644
index 0000000..e53ae78
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/controller/AuthenticationController.java
@@ -0,0 +1,47 @@
+package com.example.statisticsapi.controller;
+
+import com.example.statisticsapi.dto.internal.UserLoginRequestDto;
+import com.example.statisticsapi.dto.internal.UserLoginResponseDto;
+import com.example.statisticsapi.dto.internal.UserRegistrationRequestDto;
+import com.example.statisticsapi.dto.internal.UserResponseDto;
+import com.example.statisticsapi.exception.AuthenticationException;
+import com.example.statisticsapi.exception.RegistrationException;
+import com.example.statisticsapi.securtity.AuthenticationService;
+import com.example.statisticsapi.service.user.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "Authentication manager", description = "Endpoint to authenticate users")
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/auth")
+public class AuthenticationController {
+ private final UserService userService;
+ private final AuthenticationService authenticationService;
+
+ @PostMapping("/registration")
+ @ResponseStatus(HttpStatus.CREATED)
+ @Operation(summary = "Register a new user", description = "Register a new user")
+ public UserResponseDto register(@RequestBody @Valid UserRegistrationRequestDto requestDto)
+ throws RegistrationException {
+ return userService.register(requestDto);
+ }
+
+ @PostMapping("/login")
+ @Operation(
+ summary = "Login using existing credentials",
+ description = "Login using existing credentials"
+ )
+ public UserLoginResponseDto login(@RequestBody @Valid UserLoginRequestDto requestDto)
+ throws AuthenticationException {
+ return authenticationService.authenticate(requestDto);
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/controller/ReportController.java b/src/main/java/com/example/statisticsapi/controller/ReportController.java
new file mode 100644
index 0000000..13cb360
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/controller/ReportController.java
@@ -0,0 +1,71 @@
+package com.example.statisticsapi.controller;
+
+import com.example.statisticsapi.dto.external.SalesAndTrafficByAsinDto;
+import com.example.statisticsapi.dto.external.SalesAndTrafficByDateDto;
+import com.example.statisticsapi.service.report.ReportService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.time.LocalDate;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "Report selector", description = "Endpoint for viewing reports")
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/reports")
+public class ReportController {
+ private final ReportService reportService;
+
+ @PreAuthorize("hasRole('USER')")
+ @GetMapping("/dates")
+ @Operation(
+ summary = "Get all reports by date",
+ description = "Get a list of all reports by date"
+ )
+ public List getAllSalesAndTrafficByDate(Pageable pageable) {
+ return reportService.findAllSalesAndTrafficByDate(pageable);
+ }
+
+ @PreAuthorize("hasRole('USER')")
+ @GetMapping("/ASIN")
+ @Operation(
+ summary = "Get all reports by ASIN",
+ description = "Get a list of all by ASIN"
+ )
+ public List getAllSalesAndTrafficByAsin(Pageable pageable) {
+ return reportService.findAllSalesAndTrafficByAsin(pageable);
+ }
+
+ @PreAuthorize("hasRole('USER')")
+ @GetMapping(value = "/dates", params = {"firstDate", "secondDate"})
+ @Operation(
+ summary = "Get all reports by selected dates",
+ description = "Get a list of all by selected dates"
+ )
+ public List getAllSalesAndTrafficBySelectedDates(
+ Pageable pageable,
+ @RequestParam(required = false) LocalDate firstDate,
+ @RequestParam(required = false) LocalDate secondDate
+ ) {
+ return reportService.findAllSalesAndTrafficBySelectedDates(pageable, firstDate, secondDate);
+ }
+
+ @PreAuthorize("hasRole('USER')")
+ @GetMapping(value = "/ASIN", params = {"asinList"})
+ @Operation(
+ summary = "Get all reports by selected ASINs",
+ description = "Get a list of all by selected ASINs"
+ )
+ public List getAllSalesAndTrafficBySelectedAsins(
+ Pageable pageable,
+ @RequestParam(required = false) List asinList) {
+ return reportService.findAllSalesAndTrafficBySelectedAsins(pageable, asinList);
+ }
+}
+
diff --git a/src/main/java/com/example/statisticsapi/document/Report.java b/src/main/java/com/example/statisticsapi/document/Report.java
new file mode 100644
index 0000000..b23b3d0
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/document/Report.java
@@ -0,0 +1,20 @@
+package com.example.statisticsapi.document;
+
+import com.example.statisticsapi.dto.external.ReportSpecificationDto;
+import java.util.List;
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Document
+public class Report {
+ @Id
+ private String id;
+ private ReportSpecificationDto reportSpecification;
+ @DBRef
+ private List salesAndTrafficByDate;
+ @DBRef
+ private List salesAndTrafficByAsin;
+}
diff --git a/src/main/java/com/example/statisticsapi/document/Role.java b/src/main/java/com/example/statisticsapi/document/Role.java
new file mode 100644
index 0000000..0d01708
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/document/Role.java
@@ -0,0 +1,26 @@
+package com.example.statisticsapi.document;
+
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.index.Indexed;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.security.core.GrantedAuthority;
+
+@Data
+@Document
+public class Role implements GrantedAuthority {
+ @Id
+ private String id;
+ @Indexed(unique = true)
+ private RoleName roleName;
+
+ @Override
+ public String getAuthority() {
+ return "ROLE_" + roleName.name();
+ }
+
+ public enum RoleName {
+ USER,
+ ADMIN
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/document/SalesAndTrafficByAsin.java b/src/main/java/com/example/statisticsapi/document/SalesAndTrafficByAsin.java
new file mode 100644
index 0000000..c5beecc
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/document/SalesAndTrafficByAsin.java
@@ -0,0 +1,17 @@
+package com.example.statisticsapi.document;
+
+import com.example.statisticsapi.dto.external.SalesByAsinDto;
+import com.example.statisticsapi.dto.external.TrafficByAsinDto;
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Document
+public class SalesAndTrafficByAsin {
+ @Id
+ private String id;
+ private String parentAsin;
+ private SalesByAsinDto salesByAsin;
+ private TrafficByAsinDto trafficByAsin;
+}
diff --git a/src/main/java/com/example/statisticsapi/document/SalesAndTrafficByDate.java b/src/main/java/com/example/statisticsapi/document/SalesAndTrafficByDate.java
new file mode 100644
index 0000000..b40f6d7
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/document/SalesAndTrafficByDate.java
@@ -0,0 +1,18 @@
+package com.example.statisticsapi.document;
+
+import com.example.statisticsapi.dto.external.SalesByDateDto;
+import com.example.statisticsapi.dto.external.TrafficByDateDto;
+import java.time.LocalDate;
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Document
+public class SalesAndTrafficByDate {
+ @Id
+ private String id;
+ private LocalDate date;
+ private SalesByDateDto salesByDate;
+ private TrafficByDateDto trafficByDate;
+}
diff --git a/src/main/java/com/example/statisticsapi/document/User.java b/src/main/java/com/example/statisticsapi/document/User.java
new file mode 100644
index 0000000..f0bbe4b
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/document/User.java
@@ -0,0 +1,61 @@
+package com.example.statisticsapi.document;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.index.Indexed;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+@Data
+@Document
+public class User implements UserDetails {
+ @Id
+ private String id;
+ @Indexed(unique = true)
+ private String email;
+ private String password;
+ private String firstName;
+ private String lastName;
+ @DBRef
+ private Set roles = new HashSet<>();
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return roles;
+ }
+
+ @Override
+ public String getUsername() {
+ return email;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/MoneyAmountAndCurrencyCodeDto.java b/src/main/java/com/example/statisticsapi/dto/external/MoneyAmountAndCurrencyCodeDto.java
new file mode 100644
index 0000000..a4b662c
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/MoneyAmountAndCurrencyCodeDto.java
@@ -0,0 +1,14 @@
+package com.example.statisticsapi.dto.external;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class MoneyAmountAndCurrencyCodeDto {
+ private BigDecimal amount;
+ private CurrencyCode currencyCode;
+
+ public enum CurrencyCode {
+ USD
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/ReportOptionsDto.java b/src/main/java/com/example/statisticsapi/dto/external/ReportOptionsDto.java
new file mode 100644
index 0000000..de0f86c
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/ReportOptionsDto.java
@@ -0,0 +1,9 @@
+package com.example.statisticsapi.dto.external;
+
+import lombok.Data;
+
+@Data
+public class ReportOptionsDto {
+ private String dateGranularity;
+ private String asinGranularity;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/ReportResponseDto.java b/src/main/java/com/example/statisticsapi/dto/external/ReportResponseDto.java
new file mode 100644
index 0000000..e9c8651
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/ReportResponseDto.java
@@ -0,0 +1,10 @@
+package com.example.statisticsapi.dto.external;
+
+import lombok.Data;
+
+@Data
+public class ReportResponseDto {
+ private ReportSpecificationDto reportSpecification;
+ private SalesAndTrafficByDateDto salesAndTrafficByDate;
+ private SalesAndTrafficByAsinDto salesAndTrafficByAsin;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/ReportSpecificationDto.java b/src/main/java/com/example/statisticsapi/dto/external/ReportSpecificationDto.java
new file mode 100644
index 0000000..4bc255f
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/ReportSpecificationDto.java
@@ -0,0 +1,14 @@
+package com.example.statisticsapi.dto.external;
+
+import java.time.LocalDate;
+import java.util.List;
+import lombok.Data;
+
+@Data
+public class ReportSpecificationDto {
+ private String reportType;
+ private ReportOptionsDto reportOptions;
+ private LocalDate dataStartTime;
+ private LocalDate dataEndTime;
+ private List marketplaceIds;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/SalesAndTrafficByAsinDto.java b/src/main/java/com/example/statisticsapi/dto/external/SalesAndTrafficByAsinDto.java
new file mode 100644
index 0000000..751ba7b
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/SalesAndTrafficByAsinDto.java
@@ -0,0 +1,10 @@
+package com.example.statisticsapi.dto.external;
+
+import lombok.Data;
+
+@Data
+public class SalesAndTrafficByAsinDto {
+ private String parentAsin;
+ private SalesByAsinDto salesByAsin;
+ private TrafficByAsinDto trafficByAsin;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/SalesAndTrafficByDateDto.java b/src/main/java/com/example/statisticsapi/dto/external/SalesAndTrafficByDateDto.java
new file mode 100644
index 0000000..f02c2df
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/SalesAndTrafficByDateDto.java
@@ -0,0 +1,11 @@
+package com.example.statisticsapi.dto.external;
+
+import java.time.LocalDate;
+import lombok.Data;
+
+@Data
+public class SalesAndTrafficByDateDto {
+ private LocalDate date;
+ private SalesByDateDto salesByDate;
+ private TrafficByDateDto trafficByDate;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/SalesByAsinDto.java b/src/main/java/com/example/statisticsapi/dto/external/SalesByAsinDto.java
new file mode 100644
index 0000000..e789901
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/SalesByAsinDto.java
@@ -0,0 +1,13 @@
+package com.example.statisticsapi.dto.external;
+
+import lombok.Data;
+
+@Data
+public class SalesByAsinDto {
+ private int unitsOrdered;
+ private int unitsOrderedB2B;
+ private MoneyAmountAndCurrencyCodeDto orderedProductSales;
+ private MoneyAmountAndCurrencyCodeDto orderedProductSalesB2B;
+ private int totalOrderItems;
+ private int totalOrderItemsB2B;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/SalesByDateDto.java b/src/main/java/com/example/statisticsapi/dto/external/SalesByDateDto.java
new file mode 100644
index 0000000..0b70f10
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/SalesByDateDto.java
@@ -0,0 +1,27 @@
+package com.example.statisticsapi.dto.external;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class SalesByDateDto {
+ private MoneyAmountAndCurrencyCodeDto orderedProductSales;
+ private MoneyAmountAndCurrencyCodeDto orderedProductSalesB2B;
+ private int unitsOrdered;
+ private int unitsOrderedB2B;
+ private int totalOrderItems;
+ private int totalOrderItemsB2B;
+ private MoneyAmountAndCurrencyCodeDto averageSalesPerOrderItem;
+ private MoneyAmountAndCurrencyCodeDto averageSalesPerOrderItemB2B;
+ private BigDecimal averageUnitsPerOrderItem;
+ private BigDecimal averageUnitsPerOrderItemB2B;
+ private MoneyAmountAndCurrencyCodeDto averageSellingPrice;
+ private MoneyAmountAndCurrencyCodeDto averageSellingPriceB2B;
+ private int unitsRefunded;
+ private BigDecimal refundRate;
+ private int claimsGranted;
+ private MoneyAmountAndCurrencyCodeDto claimsAmount;
+ private MoneyAmountAndCurrencyCodeDto shippedProductSales;
+ private int unitsShipped;
+ private int ordersShipped;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/TrafficByAsinDto.java b/src/main/java/com/example/statisticsapi/dto/external/TrafficByAsinDto.java
new file mode 100644
index 0000000..fa18f12
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/TrafficByAsinDto.java
@@ -0,0 +1,36 @@
+package com.example.statisticsapi.dto.external;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class TrafficByAsinDto {
+ private int browserSessions;
+ private int browserSessionsB2B;
+ private int mobileAppSessions;
+ private int mobileAppSessionsB2B;
+ private int sessions;
+ private int sessionsB2B;
+ private BigDecimal browserSessionPercentage;
+ private BigDecimal browserSessionPercentageB2B;
+ private BigDecimal mobileAppSessionPercentage;
+ private BigDecimal mobileAppSessionPercentageB2B;
+ private BigDecimal sessionPercentage;
+ private BigDecimal sessionPercentageB2B;
+ private int browserPageViews;
+ private int browserPageViewsB2B;
+ private int mobileAppPageViews;
+ private int mobileAppPageViewsB2B;
+ private int pageViews;
+ private int pageViewsB2B;
+ private BigDecimal browserPageViewsPercentage;
+ private BigDecimal browserPageViewsPercentageB2B;
+ private BigDecimal mobileAppPageViewsPercentage;
+ private BigDecimal mobileAppPageViewsPercentageB2B;
+ private BigDecimal pageViewsPercentage;
+ private BigDecimal pageViewsPercentageB2B;
+ private BigDecimal buyBoxPercentage;
+ private BigDecimal buyBoxPercentageB2B;
+ private BigDecimal unitSessionPercentage;
+ private BigDecimal unitSessionPercentageB2B;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/external/TrafficByDateDto.java b/src/main/java/com/example/statisticsapi/dto/external/TrafficByDateDto.java
new file mode 100644
index 0000000..8d27c2c
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/external/TrafficByDateDto.java
@@ -0,0 +1,31 @@
+package com.example.statisticsapi.dto.external;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class TrafficByDateDto {
+ private int browserPageViews;
+ private int browserPageViewsB2B;
+ private int mobileAppPageViews;
+ private int mobileAppPageViewsB2B;
+ private int pageViews;
+ private int pageViewsB2B;
+ private int browserSessions;
+ private int browserSessionsB2B;
+ private int mobileAppSessions;
+ private int mobileAppSessionsB2B;
+ private int sessions;
+ private int sessionsB2B;
+ private BigDecimal buyBoxPercentage;
+ private BigDecimal buyBoxPercentageB2B;
+ private BigDecimal orderItemSessionPercentage;
+ private BigDecimal orderItemSessionPercentageB2B;
+ private BigDecimal unitSessionPercentage;
+ private BigDecimal unitSessionPercentageB2B;
+ private BigDecimal averageOfferCount;
+ private BigDecimal averageParentItems;
+ private int feedbackReceived;
+ private int negativeFeedbackReceived;
+ private BigDecimal receivedNegativeFeedbackRate;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/internal/UserLoginRequestDto.java b/src/main/java/com/example/statisticsapi/dto/internal/UserLoginRequestDto.java
new file mode 100644
index 0000000..b1b3462
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/internal/UserLoginRequestDto.java
@@ -0,0 +1,14 @@
+package com.example.statisticsapi.dto.internal;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class UserLoginRequestDto {
+ @NotBlank
+ @Email
+ private String email;
+ @NotBlank
+ private String password;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/internal/UserLoginResponseDto.java b/src/main/java/com/example/statisticsapi/dto/internal/UserLoginResponseDto.java
new file mode 100644
index 0000000..2c825d9
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/internal/UserLoginResponseDto.java
@@ -0,0 +1,4 @@
+package com.example.statisticsapi.dto.internal;
+
+public record UserLoginResponseDto(String token) {
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/internal/UserRegistrationRequestDto.java b/src/main/java/com/example/statisticsapi/dto/internal/UserRegistrationRequestDto.java
new file mode 100644
index 0000000..ceccd8a
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/internal/UserRegistrationRequestDto.java
@@ -0,0 +1,29 @@
+package com.example.statisticsapi.dto.internal;
+
+import com.example.statisticsapi.validation.FieldMatch;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+@Data
+@FieldMatch
+public class UserRegistrationRequestDto {
+ @Email
+ @NotBlank
+ private String email;
+
+ @NotBlank
+ @Length(min = 6, max = 40)
+ private String password;
+
+ @NotBlank
+ @Length(min = 6, max = 40)
+ private String repeatPassword;
+
+ @NotBlank
+ private String firstName;
+
+ @NotBlank
+ private String lastName;
+}
diff --git a/src/main/java/com/example/statisticsapi/dto/internal/UserResponseDto.java b/src/main/java/com/example/statisticsapi/dto/internal/UserResponseDto.java
new file mode 100644
index 0000000..9ac78ab
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/dto/internal/UserResponseDto.java
@@ -0,0 +1,11 @@
+package com.example.statisticsapi.dto.internal;
+
+import lombok.Data;
+
+@Data
+public class UserResponseDto {
+ private String id;
+ private String email;
+ private String firstName;
+ private String lastName;
+}
diff --git a/src/main/java/com/example/statisticsapi/exception/AuthenticationException.java b/src/main/java/com/example/statisticsapi/exception/AuthenticationException.java
new file mode 100644
index 0000000..834222e
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/exception/AuthenticationException.java
@@ -0,0 +1,9 @@
+package com.example.statisticsapi.exception;
+
+import org.springframework.security.authentication.BadCredentialsException;
+
+public class AuthenticationException extends RuntimeException {
+ public AuthenticationException(String message, BadCredentialsException ex) {
+ super(message + ex.getMessage());
+ }
+}
diff --git a/src/main/java/com/example/statisticsapi/exception/CustomGlobalExceptionHandler.java b/src/main/java/com/example/statisticsapi/exception/CustomGlobalExceptionHandler.java
new file mode 100644
index 0000000..d329642
--- /dev/null
+++ b/src/main/java/com/example/statisticsapi/exception/CustomGlobalExceptionHandler.java
@@ -0,0 +1,64 @@
+package com.example.statisticsapi.exception;
+
+import java.time.LocalDateTime;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@ControllerAdvice
+public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
+ @Override
+ protected ResponseEntity