Skip to content

Commit d0c19f4

Browse files
authored
Merge pull request #19 from dedis34/develop3
Last PR for bookShop app
2 parents a639641 + 58db42d commit d0c19f4

33 files changed

Lines changed: 645 additions & 154 deletions

Dockerfile

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
# Builder stage
2-
FROM openjdk:21-jdk-slim as BUILDER
3-
WORKDIR application
2+
FROM openjdk:21-jdk-slim as builder
3+
WORKDIR /application
44
ARG JAR_FILE=target/*.jar
55
COPY ${JAR_FILE} application.jar
66
RUN java -Djarmode=layertools -jar application.jar extract
77

8-
#Final stage
8+
# Final stage
99
FROM openjdk:21-jdk-slim
10-
WORKDIR Application
11-
COPY --from=builder application/dependencies/ ./
12-
COPY --from=builder application/spring-boot-loader/ ./
13-
COPY --from=builder application/snapshot-dependencies/ ./
14-
COPY --from=builder application/application/ ./
15-
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
10+
WORKDIR /application
11+
COPY --from=builder /application/dependencies/ ./
12+
COPY --from=builder /application/spring-boot-loader/ ./
13+
COPY --from=builder /application/snapshot-dependencies/ ./
14+
COPY --from=builder /application/application/ ./
15+
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
1616
EXPOSE 8080
1717

README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
📚 Online Book Store API
2+
A comprehensive backend application for managing an online bookstore, built with Java and Spring Boot. It offers full CRUD functionality for books, categories, shopping carts, and orders, along with user authentication and role-based access control.
3+
4+
---
5+
6+
🎯 Project Overview
7+
This application serves as the backend for an online book store, supporting two roles:
8+
9+
**Shopper (User)**
10+
- Sign up / Sign in
11+
- Browse books by category or all at once
12+
- View book details
13+
- Add/remove books to/from cart
14+
- Place orders and view order history
15+
16+
**Manager (Admin)**
17+
- Add, update, delete books
18+
- Manage categories
19+
- Update order status
20+
21+
The system is structured around 8 core domain models:
22+
`User`, `Role`, `Book`, `Category`, `ShoppingCart`, `CartItem`, `Order`, `OrderItem`
23+
24+
---
25+
26+
🚀 Technologies & Tools
27+
28+
| Tool | Description |
29+
|--------------------|----------------------------------|
30+
| Java 21 | Core language |
31+
| Spring Boot 3.4.1 | Main framework |
32+
| Spring Security | Authentication & authorization |
33+
| Spring Data JPA | Data persistence |
34+
| JWT | Token-based authentication |
35+
| MapStruct | DTO mapping |
36+
| Liquibase | DB schema management |
37+
| Hibernate Validator| Input validation |
38+
| Testcontainers | Integration tests with Docker |
39+
| MySQL / H2 | Databases (prod/test) |
40+
| Swagger | API documentation |
41+
| Lombok | Less boilerplate |
42+
| Checkstyle | Code quality |
43+
44+
---
45+
46+
🔌 API Endpoints Overview
47+
48+
**Authentication**
49+
- `POST /api/auth/register` – Register a new user
50+
- `POST /api/auth/login` – Login and receive a JWT token
51+
52+
📚 **Book**
53+
- `GET /api/books` – List all books
54+
- `GET /api/books/{id}` – View book by ID
55+
- `POST /api/books` – Add a book (admin only)
56+
- `PUT /api/books/{id}` – Update book
57+
- `DELETE /api/books/{id}` – Delete book
58+
- `GET /api/books/search` – Search books
59+
60+
🏷️ **Category**
61+
- `POST /api/categories` – Add a category
62+
- `GET /api/categories` – List categories
63+
- `GET /api/categories/{id}` – View category
64+
- `PUT /api/categories/{id}` – Update category
65+
- `DELETE /api/categories/{id}` – Delete category
66+
- `GET /api/categories/{id}/books` – View books in category
67+
68+
🛒 **Shopping Cart**
69+
- `GET /api/cart` – View cart
70+
- `POST /api/cart` – Add item to cart
71+
- `PUT /api/cart/cart-items/{cartItemId}` – Update item
72+
- `DELETE /api/cart/cart-items/{cartItemId}` – Remove item
73+
74+
📦 **Order**
75+
- `POST /api/orders` – Place an order
76+
- `GET /api/orders` – View order history
77+
- `GET /api/orders/{orderId}/items` – Items in an order
78+
- `GET /api/orders/{orderId}/items/{itemId}` – View specific item
79+
- `PATCH /api/orders/{id}` – Update order status
80+
81+
---
82+
83+
🌐 Live Demo
84+
85+
🧪 Swagger UI after starting the application locally:
86+
http://localhost:8080/swagger-ui.html
87+
88+
📹 YouTube Demo:
89+
https://youtu.be/K64DTGeaMEM
90+
91+
---
92+
93+
🔐 Default Users
94+
95+
| Role | Email | Password |
96+
|-------|---------------------|---------------|
97+
| Admin | admin@example.com | adminExample |
98+
| User | user@example.com | userExample |
99+
100+
🛠️ These accounts are automatically created on application startup via `DataInitializer`.
101+
102+
---
103+
104+
⚙️ Setup Instructions
105+
106+
**Prerequisites**
107+
- Java 17+
108+
- Docker
109+
- Maven
110+
111+
🧪 **Clone & Run Locally**
112+
```bash
113+
# Clone repo
114+
git clone https://github.com/dedis34/SpringBoot-BookShop.git
115+
116+
# Package app
117+
./mvnw clean package
118+
119+
# Run app with Docker
120+
docker compose up

docker-compose.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ services:
1414
volumes:
1515
- mysql-data:/var/lib/mysql
1616
healthcheck:
17-
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-pWetuop34!"]
17+
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-p${MYSQLDB_ROOT_PASSWORD}"]
1818
interval: 30s
1919
timeout: 10s
2020
retries: 3
@@ -24,9 +24,9 @@ services:
2424
depends_on:
2525
- db
2626
restart: on-failure
27-
image: my-app-image
2827
build:
29-
context: ./app
28+
context: ./
29+
dockerfile: Dockerfile
3030
env_file:
3131
- .env
3232
ports:
@@ -36,8 +36,11 @@ services:
3636
SPRING_APPLICATION_JSON: >
3737
{"spring.datasource.url":"jdbc:mysql://db:3306/lombok?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true",
3838
"spring.datasource.username":"root",
39-
"spring.datasource.password":"${MYSQL_ROOT_PASSWORD}",
40-
"spring.jpa.hibernate.ddl-auto":"update",
39+
"spring.datasource.password":"${MYSQLDB_ROOT_PASSWORD}",
40+
"spring.jpa.hibernate.ddl-auto":"none",
4141
"spring.jpa.show-sql":"true",
4242
"spring.jpa.properties.hibernate.format_sql":"true"}
4343
JAVA_TOOL_OPTIONS: "-Djava.security.egd=file:/dev/./urandom"
44+
45+
volumes:
46+
mysql-data:

pom.xml

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<description>SpringBoot-BookShop</description>
1919

2020
<properties>
21-
<java.version>21</java.version> <!-- Zmieniono na 21 -->
21+
<java.version>21</java.version>
2222
<maven.checkstyle.plugin.configLocation>checkstyle.xml</maven.checkstyle.plugin.configLocation>
2323
<jjwt.version>0.11.5</jjwt.version>
2424
<testcontainers.version>1.19.4</testcontainers.version>
@@ -69,17 +69,9 @@
6969
<dependency>
7070
<groupId>com.mysql</groupId>
7171
<artifactId>mysql-connector-j</artifactId>
72-
<version>8.2.0</version>
7372
<scope>runtime</scope>
7473
</dependency>
7574

76-
<!-- Hibernate Core (JPA Implementation) -->
77-
<dependency>
78-
<groupId>org.hibernate.orm</groupId>
79-
<artifactId>hibernate-core</artifactId>
80-
<version>6.2.7.Final</version>
81-
</dependency>
82-
8375
<!-- MapStruct for automatic bean mapping -->
8476
<dependency>
8577
<groupId>org.mapstruct</groupId>
@@ -110,7 +102,7 @@
110102
<dependency>
111103
<groupId>org.springdoc</groupId>
112104
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
113-
<version>2.1.0</version>
105+
<version>2.8.8</version>
114106
</dependency>
115107

116108
<!-- Spring Boot Security Starter -->
@@ -203,16 +195,36 @@
203195
<artifactId>liquibase-maven-plugin</artifactId>
204196
<version>4.23.0</version>
205197
</plugin>
198+
199+
<!-- Maven Jar Plugin to add main class to manifest -->
200+
<plugin>
201+
<groupId>org.apache.maven.plugins</groupId>
202+
<artifactId>maven-jar-plugin</artifactId>
203+
<version>3.1.0</version>
204+
<configuration>
205+
<archive>
206+
<manifestEntries>
207+
<Main-Class>org.example.SpringBootBookShopApplication</Main-Class>
208+
</manifestEntries>
209+
</archive>
210+
</configuration>
211+
</plugin>
212+
<plugin>
213+
<groupId>org.springframework.boot</groupId>
214+
<artifactId>spring-boot-maven-plugin</artifactId>
215+
<configuration>
216+
<layers>
217+
<enabled>true</enabled>
218+
</layers>
219+
</configuration>
220+
</plugin>
206221
</plugins>
207222
</build>
208223

209224
<!-- Profiles -->
210225
<profiles>
211226
<profile>
212227
<id>with-liquibase</id>
213-
<activation>
214-
<activeByDefault>true</activeByDefault>
215-
</activation>
216228
<build>
217229
<plugins>
218230
<!-- Liquibase Maven Plugin for database migrations -->
@@ -237,4 +249,4 @@
237249
</build>
238250
</profile>
239251
</profiles>
240-
</project>
252+
</project>

src/main/java/org/example/config/SecurityConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.springframework.security.authentication.AuthenticationManager;
99
import org.springframework.security.config.Customizer;
1010
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
11+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
1112
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1213
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
1314
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@@ -16,6 +17,7 @@
1617
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
1718

1819
@Configuration
20+
@EnableMethodSecurity
1921
@RequiredArgsConstructor
2022
public class SecurityConfig {
2123
private final CustomUserDetailsServiceImpl customUserDetailServiceImpl;
@@ -33,7 +35,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws
3335
.csrf(AbstractHttpConfigurer::disable)
3436
.authorizeHttpRequests(
3537
auth -> auth
36-
.requestMatchers("/auth/**", "/error", "/swagger-ui/**", "/v3/api-docs/**")
38+
.requestMatchers("/api/auth/**", "/error", "/swagger-ui/**", "/v3/api-docs/**")
3739
.permitAll()
3840
.anyRequest()
3941
.authenticated()

src/main/java/org/example/controller/BookController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
@Tag(name = "Book management", description = "Endpoints for managing books")
2929
@RequiredArgsConstructor
3030
@RestController
31-
@RequestMapping(value = "/books")
31+
@RequestMapping(value = "/api/books")
3232
public class BookController {
3333
private final BookService bookService;
3434

src/main/java/org/example/controller/ShoppingCartController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.swagger.v3.oas.annotations.Operation;
44
import io.swagger.v3.oas.annotations.tags.Tag;
5+
import jakarta.validation.Valid;
56
import lombok.RequiredArgsConstructor;
67
import org.example.dto.cartItem.AddItemToCartRequestDto;
78
import org.example.dto.cartItem.UpdateCartItemRequestDto;
@@ -53,7 +54,7 @@ public void removeCartItem(@PathVariable Long cartItemId) {
5354
@PutMapping("/cart-items/{cartItemId}")
5455
@PreAuthorize("hasRole('ROLE_USER')")
5556
public ShoppingCartResponseDto updateCartItem(@PathVariable Long cartItemId,
56-
@RequestBody UpdateCartItemRequestDto request) {
57+
@Valid @RequestBody UpdateCartItemRequestDto request) {
5758
return shoppingCartServiceImpl.updateCartItem(cartItemId, request);
5859
}
5960

src/main/java/org/example/customAnnotations/validators/FieldMatchValidator.java

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
import jakarta.validation.ConstraintValidator;
44
import jakarta.validation.ConstraintValidatorContext;
5-
import org.example.dto.user.UserRegistrationRequestDto;
65
import org.example.customAnnotations.FieldMatch;
6+
import org.springframework.beans.BeanWrapperImpl;
7+
8+
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
79

8-
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, UserRegistrationRequestDto> {
910
private String field;
1011
private String fieldMatch;
1112

@@ -16,22 +17,14 @@ public void initialize(FieldMatch constraintAnnotation) {
1617
}
1718

1819
@Override
19-
public boolean isValid(UserRegistrationRequestDto dto, ConstraintValidatorContext context) {
20-
if (dto == null) {
21-
return true;
22-
}
23-
24-
String fieldValue = getFieldValue(dto, field);
25-
String fieldMatchValue = getFieldValue(dto, fieldMatch);
20+
public boolean isValid(Object value, ConstraintValidatorContext context) {
21+
Object fieldValue = new BeanWrapperImpl(value).getPropertyValue(field);
22+
Object fieldMatchValue = new BeanWrapperImpl(value).getPropertyValue(fieldMatch);
2623

27-
return fieldValue != null && fieldValue.equals(fieldMatchValue);
28-
}
29-
30-
private String getFieldValue(UserRegistrationRequestDto dto, String fieldName) {
31-
try {
32-
return (String) dto.getClass().getDeclaredField(fieldName).get(dto);
33-
} catch (Exception e) {
34-
return null;
24+
if (fieldValue == null || fieldMatchValue == null) {
25+
return false;
3526
}
27+
28+
return fieldValue.equals(fieldMatchValue);
3629
}
3730
}

src/main/java/org/example/dto/shoppingCart/ShoppingCartResponseDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.example.dto.shoppingCart;
22

3+
import lombok.Builder;
34
import org.example.dto.cartItem.CartItemResponseDto;
45
import java.util.Set;
56

7+
@Builder
68
public record ShoppingCartResponseDto(
79
Long id,
810
Long userId,

src/main/java/org/example/dto/user/UserLoginRequestDto.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
public record UserLoginRequestDto(
88
@NotEmpty
9-
@Size(min = 8, max = 20)
9+
@Size(min = 8, max = 35)
1010
@Email
1111
String email,
1212
@NotEmpty

0 commit comments

Comments
 (0)