diff --git a/.env b/.env
new file mode 100644
index 0000000..0397607
--- /dev/null
+++ b/.env
@@ -0,0 +1,8 @@
+POSTGRES_DATABASE=carservice
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=postgres1234
+POSTGRES_DOCKER_PORT=5432
+POSTGRES_LOCAL_PORT=5433
+SPRING_DOCKER_PORT=8080
+SPRING_LOCAL_PORT=8081
+DEBUG_PORT=5005
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..e9776c0
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,64 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ main ]
+ schedule:
+ - cron: '16 17 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ permissions:
+ # required for all workflows
+ security-events: write
+
+ # only required for workflows in private repositories
+ actions: read
+ contents: read
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ # Override language selection by uncommenting this and choosing your languages
+ with:
+ languages: javascript
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below).
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # âī¸ If the Autobuild fails above, remove it and uncomment the following
+ # three lines and modify them (or add more) to build your code if your
+ # project uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..305dd44
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+# Builder stage
+FROM openjdk:17-jdk-alpine as builder
+WORKDIR application
+ARG JAR_FILE=target/*.jar
+COPY ${JAR_FILE} application.jar
+RUN java -Djarmode=layertools -jar application.jar extract
+
+
+# Final stage
+FROM openjdk:17-jdk-alpine
+WORKDIR application
+COPY --from=builder application/dependencies/ ./
+COPY --from=builder application/spring-boot-loader/ ./
+COPY --from=builder application/snapshot-dependencies/ ./
+COPY --from=builder application/application/ ./
+ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
+EXPOSE 8080
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..4548f16
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/compose.yaml b/compose.yaml
index 4d2047e..b7054b6 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -1,10 +1,31 @@
+version: "3.8"
services:
- mysql:
- image: 'mysql:latest'
+ postgresdb:
+ image: postgres:15.2
+ restart: unless-stopped
+ env_file: .env
environment:
- - 'MYSQL_DATABASE=mydatabase'
- - 'MYSQL_PASSWORD=secret'
- - 'MYSQL_ROOT_PASSWORD=verysecret'
- - 'MYSQL_USER=myuser'
+ - POSTGRES_USER=$POSTGRES_USER
+ - POSTGRES_PASSWORD=$POSTGRES_PASSWORD
+ - POSTGRES_DB=$POSTGRES_DATABASE
ports:
- - '3306'
+ - ${POSTGRES_LOCAL_PORT}:${POSTGRES_DOCKER_PORT}
+
+ app:
+ depends_on:
+ - postgresdb
+ image: book-store
+ restart: unless-stopped
+ build: .
+ env_file: .env
+ ports:
+ - ${SPRING_LOCAL_PORT}:${SPRING_DOCKER_PORT}
+ - ${DEBUG_PORT}:${DEBUG_PORT}
+ environment:
+ SPRING_APPLICATION_JSON: '{
+ "spring.datasource.url" : "jdbc:postgresql://postgresdb:$POSTGRES_DOCKER_PORT/$POSTGRES_DATABASE",
+ "spring.datasource.username" : "$POSTGRES_USER",
+ "spring.datasource.password" : "$POSTGRES_PASSWORD",
+ "spring.jpa.hibernate.ddl-auto" : "validate"
+ }'
+ JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:${DEBUG_PORT}"
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 95b77f1..aa5f0ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,12 +1,13 @@
-
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.3.3
-
+
com.marek
carsharing
@@ -14,43 +15,41 @@
carsharing
carsharing
-
-
-
-
-
-
-
-
-
-
-
-
17
+ checkstyle.xml
+ 1.5.5.Final
+ 0.12.6
org.springframework.boot
- spring-boot-starter-security
+ spring-boot-starter-data-jpa
-
org.springframework.boot
- spring-boot-docker-compose
- runtime
- true
+ spring-boot-starter-web
+
+ org.liquibase
+ liquibase-core
+
+
+
com.h2database
h2
runtime
+
+
- com.mysql
- mysql-connector-j
+ org.postgresql
+ postgresql
runtime
+
+
org.projectlombok
lombok
@@ -61,9 +60,94 @@
spring-boot-starter-test
test
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
+
+ org.testcontainers
+ postgresql
+ test
+
+
+
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ 3.3.2
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.6.0
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jjwt.version}
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jjwt.version}
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ com.stripe
+ stripe-java
+ 22.0.0
+
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+
+
+
+ org.telegram
+ telegrambots
+ 6.9.0
+
+
+
org.springframework.security
spring-security-test
+ 6.3.0
test
@@ -82,6 +166,56 @@
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.3.0
+
+
+ compile
+
+ check
+
+
+
+
+ ${maven.checkstyle.plugin.configLocation}
+ true
+ true
+ false
+ src
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ ${java.version}
+ ${java.version}
+
+
+ org.projectlombok
+ lombok
+ 1.18.32
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+ -Xlint:deprecation
+
+
+
+
diff --git a/src/main/java/com/marek/carsharing/annotation/FieldMatch.java b/src/main/java/com/marek/carsharing/annotation/FieldMatch.java
new file mode 100644
index 0000000..b4099ab
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/annotation/FieldMatch.java
@@ -0,0 +1,31 @@
+package com.marek.carsharing.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+@Constraint(validatedBy = FieldMatchValidator.class)
+@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FieldMatch {
+
+ String message() default "Fields do not match";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+ String first();
+
+ String second();
+
+ @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface List {
+ FieldMatch[] value();
+ }
+}
+
diff --git a/src/main/java/com/marek/carsharing/annotation/FieldMatchValidator.java b/src/main/java/com/marek/carsharing/annotation/FieldMatchValidator.java
new file mode 100644
index 0000000..9c44886
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/annotation/FieldMatchValidator.java
@@ -0,0 +1,41 @@
+package com.marek.carsharing.annotation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import org.springframework.beans.BeanWrapperImpl;
+
+public class FieldMatchValidator implements ConstraintValidator {
+
+ private String firstFieldName;
+ private String secondFieldName;
+ private String message;
+
+ @Override
+ public void initialize(FieldMatch constraintAnnotation) {
+ this.firstFieldName = constraintAnnotation.first();
+ this.secondFieldName = constraintAnnotation.second();
+ this.message = constraintAnnotation.message();
+ }
+
+ @Override
+ public boolean isValid(Object value, ConstraintValidatorContext context) {
+ if (value == null) {
+ return true;
+ }
+
+ BeanWrapperImpl wrapper = new BeanWrapperImpl(value);
+ Object firstField = wrapper.getPropertyValue(firstFieldName);
+ Object secondField = wrapper.getPropertyValue(secondFieldName);
+
+ boolean valid = (firstField == null && secondField == null)
+ || (firstField != null && firstField.equals(secondField));
+
+ if (!valid) {
+ context.buildConstraintViolationWithTemplate(message)
+ .addPropertyNode(firstFieldName)
+ .addConstraintViolation()
+ .disableDefaultConstraintViolation();
+ }
+ return valid;
+ }
+}
diff --git a/src/main/java/com/marek/carsharing/config/MapperConfig.java b/src/main/java/com/marek/carsharing/config/MapperConfig.java
new file mode 100644
index 0000000..82a16a0
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/config/MapperConfig.java
@@ -0,0 +1,14 @@
+package com.marek.carsharing.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/marek/carsharing/config/SecurityConfig.java b/src/main/java/com/marek/carsharing/config/SecurityConfig.java
new file mode 100644
index 0000000..f710735
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/config/SecurityConfig.java
@@ -0,0 +1,58 @@
+package com.marek.carsharing.config;
+
+import com.marek.carsharing.security.jwt.JwtAuthFilter;
+import lombok.RequiredArgsConstructor;
+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.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;
+
+@EnableMethodSecurity
+@Configuration
+@RequiredArgsConstructor
+public class SecurityConfig {
+ private final UserDetailsService userDetailsService;
+ private final JwtAuthFilter authFilter;
+
+ @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(
+ "/auth/**",
+ "/error",
+ "/swagger-ui/**")
+ .permitAll()
+ .anyRequest()
+ .authenticated()
+ )
+ .httpBasic(Customizer.withDefaults())
+ .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
+ .userDetailsService(userDetailsService)
+ .build();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(
+ AuthenticationConfiguration authenticationConfiguration
+ ) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+}
diff --git a/src/main/java/com/marek/carsharing/config/StripeConfig.java b/src/main/java/com/marek/carsharing/config/StripeConfig.java
new file mode 100644
index 0000000..409d72d
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/config/StripeConfig.java
@@ -0,0 +1,18 @@
+package com.marek.carsharing.config;
+
+import com.stripe.Stripe;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class StripeConfig {
+ @Value("${stripe.secret}")
+ private String stripeSecret;
+
+ @PostConstruct
+ public void init() {
+ Stripe.apiKey = stripeSecret;
+ }
+}
+
diff --git a/src/main/java/com/marek/carsharing/controller/AuthenticationController.java b/src/main/java/com/marek/carsharing/controller/AuthenticationController.java
new file mode 100644
index 0000000..a346c8a
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/controller/AuthenticationController.java
@@ -0,0 +1,43 @@
+package com.marek.carsharing.controller;
+
+import com.marek.carsharing.dto.user.UserDto;
+import com.marek.carsharing.dto.user.login.LoginRequestDto;
+import com.marek.carsharing.dto.user.login.LoginResponseDto;
+import com.marek.carsharing.dto.user.registration.RegisterRequestDto;
+import com.marek.carsharing.security.jwt.AuthService;
+import com.marek.carsharing.service.user.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+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;
+
+@RestController
+@RequestMapping("/auth")
+@RequiredArgsConstructor
+public class AuthenticationController {
+ private final UserService userService;
+ private final AuthService authService;
+
+ @Operation(summary = "Register a new user")
+ @PostMapping("/register")
+ @ResponseStatus(HttpStatus.CREATED)
+ public UserDto register(
+ @RequestBody @Valid RegisterRequestDto requestDto) {
+ return userService.register(requestDto);
+ }
+
+ @Operation(summary = "User login to obtain JWT tokens")
+ @PostMapping("/login")
+ @ResponseStatus(HttpStatus.OK)
+ public LoginResponseDto login(
+ @RequestBody @Valid LoginRequestDto requestDto) {
+ return authService.authenticate(requestDto);
+ }
+}
+
+
diff --git a/src/main/java/com/marek/carsharing/controller/CarsController.java b/src/main/java/com/marek/carsharing/controller/CarsController.java
new file mode 100644
index 0000000..103e82d
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/controller/CarsController.java
@@ -0,0 +1,72 @@
+package com.marek.carsharing.controller;
+
+import com.marek.carsharing.dto.car.CarDetailsDto;
+import com.marek.carsharing.dto.car.CarDto;
+import com.marek.carsharing.dto.car.CarRequestDto;
+import com.marek.carsharing.service.car.CarService;
+import io.swagger.v3.oas.annotations.Operation;
+import jakarta.validation.Valid;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+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;
+
+@RestController
+@RequestMapping("/cars")
+@RequiredArgsConstructor
+public class CarsController {
+ private final CarService carService;
+
+ @Operation(summary = "Add a new car - MANAGER only ",
+ description = "types = SEDAN, SUV, HATCHBACK, UNIVERSAL")
+ @PostMapping
+ @PreAuthorize("hasAnyAuthority('MANAGER')")
+ @ResponseStatus(HttpStatus.CREATED)
+ public CarDto addCar(@RequestBody @Valid CarRequestDto createCarRequestDto) {
+ return carService.addCar(createCarRequestDto);
+ }
+
+ @Operation(summary = "Get a list of cars")
+ @GetMapping
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.OK)
+ public List getCars(Pageable pageable) {
+ return carService.getCars(pageable);
+ }
+
+ @Operation(summary = "Get car's detailed information")
+ @GetMapping("/{id}")
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.OK)
+ public CarDetailsDto getCarDetails(@PathVariable Long id) {
+ return carService.getCarDetails(id);
+ }
+
+ @Operation(summary = "Update car information - MANAGER only ",
+ description = "types = SEDAN, SUV, HATCHBACK, UNIVERSAL")
+ @PutMapping("/{id}")
+ @PreAuthorize("hasAnyAuthority('MANAGER')")
+ @ResponseStatus(HttpStatus.OK)
+ public CarDto updateCar(@PathVariable Long id,
+ @RequestBody @Valid CarRequestDto updateCarRequestDto) {
+ return carService.updateCar(id, updateCarRequestDto);
+ }
+
+ @Operation(summary = "Delete car - MANAGER only ")
+ @DeleteMapping("/{id}")
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void deleteCar(@PathVariable Long id) {
+ carService.delete(id);
+ }
+}
diff --git a/src/main/java/com/marek/carsharing/controller/PaymentsController.java b/src/main/java/com/marek/carsharing/controller/PaymentsController.java
new file mode 100644
index 0000000..c5fb338
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/controller/PaymentsController.java
@@ -0,0 +1,63 @@
+package com.marek.carsharing.controller;
+
+import com.marek.carsharing.dto.payment.CreatePaymentRequestDto;
+import com.marek.carsharing.dto.payment.PaymentDto;
+import com.marek.carsharing.dto.payment.PaymentSearchParameters;
+import com.marek.carsharing.model.classes.User;
+import com.marek.carsharing.service.payment.PaymentService;
+import io.swagger.v3.oas.annotations.Operation;
+import jakarta.validation.Valid;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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;
+
+@RestController
+@RequestMapping("/payments")
+@RequiredArgsConstructor
+public class PaymentsController {
+ private final PaymentService paymentService;
+
+ @Operation(summary = "Get payments by user ID - MANAGER only ")
+ @GetMapping
+ @PreAuthorize("hasAnyAuthority('MANAGER')")
+ @ResponseStatus(HttpStatus.OK)
+ public List getPayments(PaymentSearchParameters searchParameters) {
+ return paymentService.getPayments(searchParameters);
+ }
+
+ @Operation(summary = "Create payment session")
+ @PostMapping
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.CREATED)
+ public PaymentDto createPaymentSession(@RequestBody @Valid CreatePaymentRequestDto requestDto,
+ Authentication authentication) {
+ User user = (User) authentication.getPrincipal();
+ return paymentService.createPayment(user, requestDto);
+ }
+
+ @Operation(summary = "Check successful Stripe payments")
+ @GetMapping("/success/{sessionId}")
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.OK)
+ public String checkPaymentSuccess(@PathVariable String sessionId) {
+ return paymentService.checkPaymentSuccess(sessionId);
+ }
+
+ @Operation(summary = "Return payment paused message")
+ @GetMapping("/cancel/{sessionId}")
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.OK)
+ public String paymentPaused(@PathVariable String sessionId) {
+ return paymentService.pausePayment(sessionId);
+ }
+}
+
diff --git a/src/main/java/com/marek/carsharing/controller/RentalsController.java b/src/main/java/com/marek/carsharing/controller/RentalsController.java
new file mode 100644
index 0000000..b225a8f
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/controller/RentalsController.java
@@ -0,0 +1,67 @@
+package com.marek.carsharing.controller;
+
+import com.marek.carsharing.dto.rental.CreateRentalRequestDto;
+import com.marek.carsharing.dto.rental.RentalDto;
+import com.marek.carsharing.dto.rental.RentalSearchParameters;
+import com.marek.carsharing.model.classes.User;
+import com.marek.carsharing.service.rental.RentalService;
+import io.swagger.v3.oas.annotations.Operation;
+import jakarta.validation.Valid;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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;
+
+@RestController
+@RequestMapping("/rentals")
+@RequiredArgsConstructor
+public class RentalsController {
+ private final RentalService rentalService;
+
+ @Operation(summary = "Add a new rental")
+ @PostMapping
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.CREATED)
+ public RentalDto addRental(
+ @RequestBody @Valid CreateRentalRequestDto createRentalRequestDto,
+ Authentication authentication) {
+ User user = (User) authentication.getPrincipal();
+ return rentalService.addRental(user, createRentalRequestDto);
+ }
+
+ @Operation(summary = "Get rentals by user ID and whether the rental is active - MANAGER only ")
+ @GetMapping
+ @PreAuthorize("hasAnyAuthority('MANAGER')")
+ @ResponseStatus(HttpStatus.OK)
+ public List getRentals(RentalSearchParameters parameters) {
+ return rentalService.getRentals(parameters);
+ }
+
+ @Operation(summary = "Get specific rental - MANAGER only ")
+ @GetMapping("/{id}")
+ @PreAuthorize("hasAnyAuthority('MANAGER')")
+ @ResponseStatus(HttpStatus.OK)
+ public RentalDto getRental(@PathVariable Long id) {
+ return rentalService.getRental(id);
+ }
+
+ @Operation(summary = "Return a rental")
+ @PostMapping("/{id}/return")
+ @PreAuthorize("hasAnyAuthority('MANAGER', 'CUSTOMER')")
+ @ResponseStatus(HttpStatus.OK)
+ public void returnRental(@PathVariable Long id,
+ Authentication authentication) {
+ User user = (User) authentication.getPrincipal();
+ rentalService.returnRental(user, id);
+ }
+}
+
+
diff --git a/src/main/java/com/marek/carsharing/controller/UsersController.java b/src/main/java/com/marek/carsharing/controller/UsersController.java
new file mode 100644
index 0000000..5d6fac4
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/controller/UsersController.java
@@ -0,0 +1,55 @@
+package com.marek.carsharing.controller;
+
+import com.marek.carsharing.dto.user.UpdateUserRequestDto;
+import com.marek.carsharing.dto.user.UserDto;
+import com.marek.carsharing.model.classes.User;
+import com.marek.carsharing.service.user.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+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;
+
+@RestController
+@RequestMapping("/users")
+@RequiredArgsConstructor
+public class UsersController {
+ private final UserService userService;
+
+ @Operation(summary = "Update user role - MANAGER only ",
+ description = """
+ If user have Role = Manager then Manager => Customer,\n
+ If user have Role = Customer then Customer => Manager,
+ """)
+ @PutMapping("/{id}/role")
+ @PreAuthorize("hasAnyAuthority('MANAGER')")
+ @ResponseStatus(HttpStatus.OK)
+ public UserDto updateUserRole(@PathVariable Long id) {
+ return userService.updateRole(id);
+ }
+
+ @Operation(summary = "Get my profile info")
+ @GetMapping("/me")
+ @ResponseStatus(HttpStatus.OK)
+ public UserDto getMyProfile(Authentication authentication) {
+ User user = (User) authentication.getPrincipal();
+ return userService.getMyProfile(user);
+ }
+
+ @Operation(summary = "Update my profile info")
+ @PutMapping("/me")
+ @ResponseStatus(HttpStatus.OK)
+ public UserDto updateMyProfile(Authentication authentication,
+ @RequestBody @Valid UpdateUserRequestDto request) {
+ User user = (User) authentication.getPrincipal();
+ return userService.updateMyProfile(user, request);
+ }
+}
diff --git a/src/main/java/com/marek/carsharing/dto/car/CarDetailsDto.java b/src/main/java/com/marek/carsharing/dto/car/CarDetailsDto.java
new file mode 100644
index 0000000..8ea8c76
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/car/CarDetailsDto.java
@@ -0,0 +1,14 @@
+package com.marek.carsharing.dto.car;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class CarDetailsDto {
+ private Long id;
+ private String model;
+ private String brand;
+ private String type;
+ private int inventory;
+ private BigDecimal dailyFee;
+}
diff --git a/src/main/java/com/marek/carsharing/dto/car/CarDto.java b/src/main/java/com/marek/carsharing/dto/car/CarDto.java
new file mode 100644
index 0000000..18f4400
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/car/CarDto.java
@@ -0,0 +1,14 @@
+package com.marek.carsharing.dto.car;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class CarDto {
+ private Long id;
+ private String model;
+ private String brand;
+ private int inventory;
+ private BigDecimal dailyFee;
+}
+
diff --git a/src/main/java/com/marek/carsharing/dto/car/CarRequestDto.java b/src/main/java/com/marek/carsharing/dto/car/CarRequestDto.java
new file mode 100644
index 0000000..5d5172c
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/car/CarRequestDto.java
@@ -0,0 +1,19 @@
+package com.marek.carsharing.dto.car;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.PositiveOrZero;
+import java.math.BigDecimal;
+
+public record CarRequestDto(
+ @NotBlank
+ String model,
+ @NotBlank
+ String brand,
+ @NotBlank
+ String type,
+ @PositiveOrZero
+ int inventory,
+ @PositiveOrZero
+ BigDecimal dailyFee
+) {
+}
diff --git a/src/main/java/com/marek/carsharing/dto/payment/CreatePaymentRequestDto.java b/src/main/java/com/marek/carsharing/dto/payment/CreatePaymentRequestDto.java
new file mode 100644
index 0000000..fafc458
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/payment/CreatePaymentRequestDto.java
@@ -0,0 +1,9 @@
+package com.marek.carsharing.dto.payment;
+
+import jakarta.validation.constraints.PositiveOrZero;
+
+public record CreatePaymentRequestDto(
+ @PositiveOrZero
+ Long rentalId
+) {
+}
diff --git a/src/main/java/com/marek/carsharing/dto/payment/PaymentDto.java b/src/main/java/com/marek/carsharing/dto/payment/PaymentDto.java
new file mode 100644
index 0000000..74b4404
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/payment/PaymentDto.java
@@ -0,0 +1,13 @@
+package com.marek.carsharing.dto.payment;
+
+import java.math.BigDecimal;
+import lombok.Data;
+
+@Data
+public class PaymentDto {
+ private Long id;
+ private Long rentalId;
+ private String sessionUrl;
+ private String sessionId;
+ private BigDecimal amountToPay;
+}
diff --git a/src/main/java/com/marek/carsharing/dto/payment/PaymentSearchParameters.java b/src/main/java/com/marek/carsharing/dto/payment/PaymentSearchParameters.java
new file mode 100644
index 0000000..8d45aba
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/payment/PaymentSearchParameters.java
@@ -0,0 +1,6 @@
+package com.marek.carsharing.dto.payment;
+
+public record PaymentSearchParameters(
+ String[] usersId
+) {
+}
diff --git a/src/main/java/com/marek/carsharing/dto/rental/CreateRentalRequestDto.java b/src/main/java/com/marek/carsharing/dto/rental/CreateRentalRequestDto.java
new file mode 100644
index 0000000..dc5de0f
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/rental/CreateRentalRequestDto.java
@@ -0,0 +1,15 @@
+package com.marek.carsharing.dto.rental;
+
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import java.time.LocalDate;
+
+public record CreateRentalRequestDto(
+ @NotNull
+ LocalDate rentalDate,
+ @NotNull
+ LocalDate returnDate,
+ @PositiveOrZero
+ Long carId
+) {
+}
diff --git a/src/main/java/com/marek/carsharing/dto/rental/RentalDto.java b/src/main/java/com/marek/carsharing/dto/rental/RentalDto.java
new file mode 100644
index 0000000..f843a38
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/rental/RentalDto.java
@@ -0,0 +1,14 @@
+package com.marek.carsharing.dto.rental;
+
+import java.time.LocalDate;
+import lombok.Data;
+
+@Data
+public class RentalDto {
+ private Long id;
+ private LocalDate rentalDate;
+ private LocalDate actualReturnDate;
+ private LocalDate returnDate;
+ private Long carId;
+}
+
diff --git a/src/main/java/com/marek/carsharing/dto/rental/RentalSearchParameters.java b/src/main/java/com/marek/carsharing/dto/rental/RentalSearchParameters.java
new file mode 100644
index 0000000..9c96786
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/rental/RentalSearchParameters.java
@@ -0,0 +1,8 @@
+package com.marek.carsharing.dto.rental;
+
+public record RentalSearchParameters(
+ String[] userId,
+ Boolean isActive
+) {
+}
+
diff --git a/src/main/java/com/marek/carsharing/dto/user/UpdateUserRequestDto.java b/src/main/java/com/marek/carsharing/dto/user/UpdateUserRequestDto.java
new file mode 100644
index 0000000..8818dd6
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/user/UpdateUserRequestDto.java
@@ -0,0 +1,11 @@
+package com.marek.carsharing.dto.user;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record UpdateUserRequestDto(
+ @NotBlank
+ String firstName,
+ @NotBlank
+ String lastName
+) {
+}
diff --git a/src/main/java/com/marek/carsharing/dto/user/UserDto.java b/src/main/java/com/marek/carsharing/dto/user/UserDto.java
new file mode 100644
index 0000000..8c07bbd
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/user/UserDto.java
@@ -0,0 +1,13 @@
+package com.marek.carsharing.dto.user;
+
+import lombok.Data;
+
+@Data
+public class UserDto {
+ private Long id;
+ private String email;
+ private String firstName;
+ private String lastName;
+ private String role;
+}
+
diff --git a/src/main/java/com/marek/carsharing/dto/user/login/LoginRequestDto.java b/src/main/java/com/marek/carsharing/dto/user/login/LoginRequestDto.java
new file mode 100644
index 0000000..201e7b9
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/user/login/LoginRequestDto.java
@@ -0,0 +1,12 @@
+package com.marek.carsharing.dto.user.login;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+
+public record LoginRequestDto(
+ @NotBlank
+ @Email
+ String email,
+ @NotBlank
+ String password) {
+}
diff --git a/src/main/java/com/marek/carsharing/dto/user/login/LoginResponseDto.java b/src/main/java/com/marek/carsharing/dto/user/login/LoginResponseDto.java
new file mode 100644
index 0000000..a9c73e7
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/user/login/LoginResponseDto.java
@@ -0,0 +1,5 @@
+package com.marek.carsharing.dto.user.login;
+
+public record LoginResponseDto(String token) {
+}
+
diff --git a/src/main/java/com/marek/carsharing/dto/user/registration/RegisterRequestDto.java b/src/main/java/com/marek/carsharing/dto/user/registration/RegisterRequestDto.java
new file mode 100644
index 0000000..e20a7b6
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/dto/user/registration/RegisterRequestDto.java
@@ -0,0 +1,28 @@
+package com.marek.carsharing.dto.user.registration;
+
+import com.marek.carsharing.annotation.FieldMatch;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+@Data
+@FieldMatch(first = "password", second = "repeatPassword",
+ message = "The password fields must match")
+public class RegisterRequestDto {
+ @NotBlank
+ @Email
+ private String email;
+ @NotBlank
+ @Length(min = 6, max = 20)
+ private String password;
+ @NotBlank
+ @Length(min = 6, max = 20)
+ private String repeatPassword;
+ @NotBlank
+ private String firstName;
+ @NotBlank
+ private String lastName;
+}
+
+
diff --git a/src/main/java/com/marek/carsharing/exception/BotNotificationException.java b/src/main/java/com/marek/carsharing/exception/BotNotificationException.java
new file mode 100644
index 0000000..302e4de
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/exception/BotNotificationException.java
@@ -0,0 +1,16 @@
+package com.marek.carsharing.exception;
+
+public class BotNotificationException extends RuntimeException {
+ public BotNotificationException(String message) {
+ super(message);
+ }
+
+ public BotNotificationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public BotNotificationException(Throwable cause) {
+ super(cause);
+ }
+}
+
diff --git a/src/main/java/com/marek/carsharing/exception/CustomGlobalExceptionHandler.java b/src/main/java/com/marek/carsharing/exception/CustomGlobalExceptionHandler.java
new file mode 100644
index 0000000..4627ab0
--- /dev/null
+++ b/src/main/java/com/marek/carsharing/exception/CustomGlobalExceptionHandler.java
@@ -0,0 +1,53 @@
+package com.marek.carsharing.exception;
+
+import jakarta.persistence.EntityNotFoundException;
+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