Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Book-Store.pdf
Binary file not shown.
Binary file added Book-Store.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 🎯 Project Overview
This application provides functionalities for managing users, books, categories, orders, and shopping carts. The system ensures secure access with role-based authorization (USER and ADMIN). The app is designed for book sales, allowing users to:

Create accounts and log in.
Purchase books conveniently and quickly from home.
Track the status of orders and know when they arrive at the post office.
This is my first large-scale application, and I faced numerous challenges during its development. Each part was difficult but immensely educational, and I gained a lot of knowledge that will be invaluable for my future projects.

# 🚀 Technologies Used
- Spring Boot
- Spring Security
- Spring Data JPA
- JWT (JSON Web Token)
- Swagger
- Liquibase
- MapStruct
- Lombok
- Hibernate Validator
- MySQL

# 📖 API Endpoints
1. AuthenticationController
- ```POST /auth/registration``` – Register a new user.
- ```POST /auth/login``` – User authentication (login).
2. BookController
- ```GET /books``` – Retrieve all books with pagination (ADMIN access only).
- ```GET /books/{id}``` – Retrieve a book by its ID.
- ```POST /books``` – Create a new book (ADMIN access only).
- ```PUT /books/{id}``` – Update a book by its ID (ADMIN access only).
- ```DELETE /books/{id}``` – Delete a book by its ID (ADMIN access only).
- ```GET /books/search``` – Search books by parameters.
3. CategoryController
- ```POST /categories``` – Create a new category (ADMIN access only).
- ```GET /categories``` – Retrieve all categories.
- ```GET /categories/{id}``` – Retrieve a category by its ID.
- ```PUT /categories/{id}``` – Update a category by its ID (ADMIN access only).
- ```DELETE /categories/{id}``` – Delete a category by its ID (ADMIN access only).
- ```GET /categories/{id}/books``` – Retrieve all books in a category by its ID.
4. OrderController
- ```POST /orders``` – Create a new order (USER access only).
- ```GET /orders``` – Retrieve all orders of the logged-in user (USER access only).
- ```PATCH /orders/{id}``` – Update the status of an order (ADMIN access only).
- ```GET /orders/{orderId}/items``` – Retrieve all items from a specific order (USER access only).
- ```GET /orders/{orderId}/items/{itemId}``` – Retrieve a specific item from an order (USER access only).
5. ShoppingCartController
- ```GET /cart``` – Retrieve the current user's shopping cart.
- ```POST /cart``` – Add a book to the shopping cart.
- ```PUT /cart/items/{cartItemId}``` – Update the quantity of items in the cart.
- ```DELETE /cart/items/{cartItemId}``` – Remove an item from the cart.

# Models and relations

![Book-Store](./Book-Store.png)

# 📚 Getting Started
I have always loved books, especially those that help understand the meaning of human existence and provide fuel for thought. Inspired by this passion, I decided to create my own application, where users can purchase books from the comfort of their homes.

# 📹Video Overview of Program Functionality
You can also find a video of the program at this link: https://www.loom.com/share/0c519d24efc04b64acf9a3e9096b40cb?sid=e3750a8b-c044-4b13-b382-535497228e21

# Contacts
- Email: greqit.work@gmail.com
- [LinkedIn](https://www.linkedin.com/in/ivan-prystaia-7099a22b1/)
45 changes: 40 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<version>3.3.1</version>
<relativePath/>
</parent>
<groupId>mare.academy</groupId>
<groupId>mate.academy</groupId>
<artifactId>Spring-Boot-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring-Boot-web</name>
Expand Down Expand Up @@ -39,6 +39,10 @@
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
<spring-boot-starter-security.version>3.3.2</spring-boot-starter-security.version>
<jjwt.version>0.11.5</jjwt.version>
<maven.checkstyle.plugin.version>3.1.1</maven.checkstyle.plugin.version>
<maven.checkstyle.plugin.configLocation>
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
</maven.checkstyle.plugin.configLocation>
</properties>
<dependencies>
<dependency>
Expand All @@ -60,6 +64,17 @@
<version>1.6.14</version>
</dependency>

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>

<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.25</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
Expand Down Expand Up @@ -121,11 +136,10 @@
<scope>test</scope>
</dependency>


<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -224,6 +238,27 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven.checkstyle.plugin.version}</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<includes>**/src/main/java/**</includes>
<excludes>**/target/generated-sources/**</excludes>
<configLocation>${maven.checkstyle.plugin.configLocation}</configLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package mate.academy.springbootwebgreqit.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.springbootwebgreqit.dto.user.UserLoginRequestDto;
Expand All @@ -13,22 +17,31 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Auth managemant", description = "Endpoint to auth")
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthenticationController {
private final UserService userService;
private final AuthenticationService authenticationService;

@Operation(summary = "Register a new user")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "User registered successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input data")
})
@PostMapping("/registration")
public UserResponseDto register(@RequestBody
@Valid UserRegistrationRequestDto requestBody) {
public UserResponseDto register(@RequestBody @Valid UserRegistrationRequestDto requestBody) {
return userService.register(requestBody);
}

@Operation(summary = "Authenticate a user")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "User authenticated successfully"),
@ApiResponse(responseCode = "401", description = "Invalid credentials")
})
@PostMapping("/login")
public UserLoginResponseDto login(@RequestBody
@Valid UserLoginRequestDto response) {
public UserLoginResponseDto login(@RequestBody @Valid UserLoginRequestDto response) {
return authenticationService.authenticate(response);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mate.academy.springbootwebgreqit.controller;

import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.springbootwebgreqit.dto.BookDto;
Expand All @@ -27,40 +29,66 @@ public class BookController {
private final BookService bookService;

@PreAuthorize("hasRole('ADMIN')")
@Operation(summary = "Get all books with pagination")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Books retrieved successfully"),
@ApiResponse(responseCode = "403", description = "Access denied")
})
@GetMapping
@ApiOperation(value = "get all books with pagination")
public Page<BookDto> getAll(Pageable pageable) {
return bookService.findAll(pageable);
}

@Operation(summary = "Get book by id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Book retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Book not found")
})
@GetMapping("/{id}")
@ApiOperation(value = "Get book by id")
public BookDto getBookById(@Valid @PathVariable Long id) {
return bookService.findById(id);
}

@PreAuthorize("hasRole('ADMIN')")
@Operation(summary = "Create a book")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Book created successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input data")
})
@PostMapping
@ApiOperation(value = "create a book")
public BookDto createBook(@Valid @RequestBody CreateBookRequestDto requestDto) {
return bookService.save(requestDto);
}

@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public BookDto updateBook(@PathVariable Long id, @RequestBody @Valid UpdateBookRequestDto updateBookRequestDto) {
@Operation(summary = "Update a book")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Book updated successfully"),
@ApiResponse(responseCode = "404", description = "Book not found")
})
@PutMapping("/{id}")
public BookDto updateBook(@PathVariable Long id,
@RequestBody @Valid UpdateBookRequestDto updateBookRequestDto) {
return bookService.update(updateBookRequestDto);
}

@PreAuthorize("hasRole('ADMIN')")
@Operation(summary = "Delete a book")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Book deleted successfully"),
@ApiResponse(responseCode = "404", description = "Book not found")
})
@DeleteMapping("/{id}")
@ApiOperation(value = "delete a book")
public void deleteBook(@PathVariable Long id) {
bookService.deleteById(id);
}

@Operation(summary = "Search a book")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Books retrieved successfully"),
@ApiResponse(responseCode = "400", description = "Invalid search parameters")
})
@GetMapping("/search")
@ApiOperation(value = "search a book")
public Page<BookDto> searchBooks(BookSearchParameters searchParameters, Pageable pageable) {
return bookService.search(searchParameters, pageable);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package mate.academy.springbootwebgreqit.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.springbootwebgreqit.dto.BookDtoWithoutCategotyIds;
Expand All @@ -26,34 +29,65 @@ public class CategoryController {
private final CategoryService categoryService;

@PreAuthorize("hasRole('ADMIN')")
@Operation(summary = "Create a new category")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Category created successfully"),
@ApiResponse(responseCode = "400", description = "Invalid input data")
})
@PostMapping
public CategoryDto createCategory(@Valid @RequestBody CreateCategoryRequestDto categoryDto) {
return categoryService.save(categoryDto);
}

@PreAuthorize("hasRole('ADMIN')")
@Operation(summary = "Get all categories")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Categories retrieved successfully"),
@ApiResponse(responseCode = "403", description = "Access denied")
})
@GetMapping
public List<CategoryDto> getAll() {
return categoryService.findAll();
}

@Operation(summary = "Get category by id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Category retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Category not found")
})
@GetMapping("/{id}")
public CategoryDto getCategoryById(@Valid @PathVariable Long id) {
return categoryService.getById(id);
}

@PreAuthorize("hasRole('ADMIN')")
@Operation(summary = "Update a category")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Category updated successfully"),
@ApiResponse(responseCode = "404", description = "Category not found")
})
@PutMapping("/{id}")
public CategoryDto updateCategory(@PathVariable Long id, @RequestBody @Valid UpdateCategoryRequestDto categoryDto) {
public CategoryDto updateCategory(@PathVariable Long id,
@RequestBody @Valid UpdateCategoryRequestDto categoryDto) {
return categoryService.update(id, categoryDto);
}

@PreAuthorize("hasRole('ADMIN')")
@Operation(summary = "Delete a category")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Category deleted successfully"),
@ApiResponse(responseCode = "404", description = "Category not found")
})
@DeleteMapping("/{id}")
public void deleteCategory(@PathVariable Long id) {
categoryService.deleteById(id);
}

@Operation(summary = "Get books by category id")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Books retrieved successfully"),
@ApiResponse(responseCode = "404", description = "Category not found")
})
@GetMapping("/{id}/books")
public List<BookDtoWithoutCategotyIds> getBooksByCategoryId(@PathVariable Long id) {
return categoryService.findBooksByCategoryId(id);
Expand Down
Loading