Skip to content

Commit

Permalink
Merge pull request #1 from vadymhrnk/develop
Browse files Browse the repository at this point in the history
implemented task
  • Loading branch information
vadymhrnk authored Mar 1, 2024
2 parents 3b0bbeb + 4b6813e commit 045f41e
Show file tree
Hide file tree
Showing 56 changed files with 8,777 additions and 15 deletions.
30 changes: 25 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@
<version>${org.mapstruct.version}</version>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down Expand Up @@ -86,8 +81,33 @@
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.github.cloudyrock.mongock</groupId>
<artifactId>mongock-spring-v5</artifactId>
</dependency>
<dependency>
<groupId>com.github.cloudyrock.mongock</groupId>
<artifactId>mongodb-springdata-v3-driver</artifactId>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.github.cloudyrock.mongock</groupId>
<artifactId>mongock-bom</artifactId>
<version>4.3.8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
Expand Down
55 changes: 55 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# <div align="center">Statistics API</div>

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.
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Role> 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);
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/example/statisticsapi/config/MapperConfig.java
Original file line number Diff line number Diff line change
@@ -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 = "<PACKAGE_NAME>.impl"
)
public class MapperConfig {
}
65 changes: 65 additions & 0 deletions src/main/java/com/example/statisticsapi/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<ConcurrentMapCacheManager> {
public static final String REPORT_CACHE = "reportCache";

@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(List.of(REPORT_CACHE));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<SalesAndTrafficByDateDto> 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<SalesAndTrafficByAsinDto> 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<SalesAndTrafficByDateDto> 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<SalesAndTrafficByAsinDto> getAllSalesAndTrafficBySelectedAsins(
Pageable pageable,
@RequestParam(required = false) List<String> asinList) {
return reportService.findAllSalesAndTrafficBySelectedAsins(pageable, asinList);
}
}

20 changes: 20 additions & 0 deletions src/main/java/com/example/statisticsapi/document/Report.java
Original file line number Diff line number Diff line change
@@ -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> salesAndTrafficByDate;
@DBRef
private List<SalesAndTrafficByAsin> salesAndTrafficByAsin;
}
Loading

0 comments on commit 045f41e

Please sign in to comment.