Skip to content
Merged
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
62 changes: 35 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

- [Giới thiệu](#gioi-thieu)
- [Công nghệ sử dụng](#cong-nghe)
- [Yêu cầu môi trường](#-yêu-cầu-môi-trường)
- [Cấu trúc dự án](#-cấu-trúc-dự-án)
- [🚀 Release](#-release)
- [🔨 Build từ mã nguồn](#-build-từ-mã-nguồn)
Expand Down Expand Up @@ -83,17 +82,6 @@

---

## 💻 Yêu cầu môi trường

| Công cụ | Phiên bản tối thiểu |
|---|---|
| Java (JDK) | **25** |
| Maven | 3.9+ |
| Docker | 24+ |
| Docker Compose | 2.x |

---

## 📁 Cấu trúc dự án

```
Expand Down Expand Up @@ -143,20 +131,39 @@ bigbid/

## 🚀 Release

### Yêu cầu môi trường
| Công cụ | Phiên bản tối thiểu |
|---|---|
| Java (JDK) | **25** |
| Docker | 24+ |

### Bước 1 — Tải bản Release

[![Release](https://img.shields.io/badge/Download-Release-blue?style=for-the-badge&logo=github)](https://github.com/Pumpkin-Nguyen/bigbid/releases)

### Bước 2 — Khởi động MySQL bằng Docker

## MacOS / Linux
```bash
docker volume create db_data && docker run -d --name bigbid-db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=anhemf36 -e MYSQL_DATABASE=bigbid -v db_data:/var/lib/mysql --restart always mysql:8.4
```

## Window
```bash
docker volume create db_data; `
docker run -d --name bigbid-db `
-p 3306:3306 `
-e MYSQL_ROOT_PASSWORD=anhemf36 `
-e MYSQL_DATABASE=bigbid `
-v db_data:/var/lib/mysql `
--restart always `
mysql:8.4
```

### Bước 3: Khởi động server và client

```bash
java -jar bigbid-server-1.0.0.jar --spring.profiles.active=prod
java -jar bigbid-server-1.0.0.jar
```
---

Expand All @@ -168,6 +175,14 @@ java -jar bigbid-client-1.0.0.jar

## 🔨 Build từ mã nguồn

### Yêu cầu môi trường
| Công cụ | Phiên bản tối thiểu |
|---|---|
| Java (JDK) | **25** |
| Maven | 3.9+ |
| Docker | 24+ |
| Docker Compose | 2.x |

### Clone repository

```bash
Expand All @@ -191,19 +206,19 @@ cd bigbid-server

#### macOS / Linux
```bash
cp src/main/resources/application-prod.yaml.example \
src/main/resources/application-prod.yaml
cp src/main/resources/application.yaml.example \
src/main/resources/application.yaml
```

#### Windows
```bash
copy src/main/resources/application-prod.yaml.example `
src/main/resources/application-prod.yaml
copy src/main/resources/application.yaml.example `
src/main/resources/application.yaml
```

---

### Bước 3 — Điền các config tại file `application-prod.yaml`
### Bước 3 — Điền các config tại file `application.yaml`

---

Expand All @@ -219,7 +234,7 @@ src/main/resources/application-prod.yaml
```
#### Run
```bash
java -jar target/bigbid-server-1.0.0.jar --spring.profiles.active=prod
java -jar target/bigbid-server-1.0.0.jar
```
---

Expand Down Expand Up @@ -284,13 +299,6 @@ java -jar target/bigbid-client-1.0.0.jar
- [x] Thông báo kết quả phiên qua WebSocket

---

## 📎 Báo cáo và Demo

| Tài liệu | Đường dẫn |
|--------------------------|-----------|
| 📄 **Báo cáo PDF** | |
| 🎥 **Video Demo** | |
| 💻 **GitHub Repository** | |

- https://drive.google.com/drive/folders/1IBAXfG7eJSammKFNKEgccJiwrgA384K_
---
27 changes: 26 additions & 1 deletion bigbid-client/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
Expand Down Expand Up @@ -36,6 +36,31 @@
<version>25</version>
</dependency>

<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>25</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>25</version>
<classifier>linux</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>25</version>
<classifier>mac</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>25</version>
<classifier>mac-aarch64</classifier>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import tools.jackson.databind.ObjectMapper;

@Slf4j
@Component
Expand All @@ -68,7 +67,6 @@ public class AuctionDetailController {
private final AuctionService auctionService;
private final AuthGuard authGuard;
private final WebSocketClient webSocketClient;
private final ObjectMapper objectMapper;

private final XYChart.Series<Number, Number> bidHistorySeries = new XYChart.Series<>();

Expand Down Expand Up @@ -249,8 +247,7 @@ private void loadAuctionBidHistory() {
});
}

private void applyBidHistory(
List<com.bigbid.client.model.response.BidTransactionResponse> bidHistory) {
private void applyBidHistory(List<BidTransactionResponse> bidHistory) {
if (bidHistory == null || bidHistory.isEmpty()) {
return;
}
Expand All @@ -265,8 +262,7 @@ private void applyBidHistory(
.forEach(this::appendBidPoint);
}

private void appendBidPoint(
com.bigbid.client.model.response.BidTransactionResponse bidTransaction) {
private void appendBidPoint(BidTransactionResponse bidTransaction) {
LocalDateTime timestamp = bidTransaction.getTimestamp();
long xValue = toEpochMillis(timestamp != null ? timestamp : LocalDateTime.now());
long yValue = bidTransaction.getAmount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ private void handleRegister() {
return;
}

if (LocalDate.now().minusYears(16).isAfter(dob)) {
if (dob.minusYears(16).isAfter(LocalDate.now())) {
AlertHelper.showWarning(
"Age Requirement", "You must be at least 16 years old to register.");
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum ErrorCode {
DATA_INTEGRITY_VIOLATION(9997, "Data error", HttpStatus.BAD_REQUEST),
INVALID_KEY(9998, "Uncategorized error", HttpStatus.BAD_REQUEST),
UNCATEGORIZED_EXCEPTION(9999, "Uncategorized error", HttpStatus.INTERNAL_SERVER_ERROR),
FILE_SIZE_LIMIT(9998, "File size limit exceeded", HttpStatus.BAD_REQUEST),

// USER EXCEPTION (1000-1099)
USER_EXISTED(1002, "User existed", HttpStatus.BAD_REQUEST),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import com.bigbid.server.dto.ApiResponse;

Expand All @@ -25,77 +26,88 @@ public class GlobalExceptionHandler {
public static final String MIN_ATTRIBUTE = "min";

@ExceptionHandler(value = RuntimeException.class)
ResponseEntity<ApiResponse> handleRuntimeException(RuntimeException exception) {
ResponseEntity<ApiResponse<?>> handleRuntimeException(RuntimeException exception) {
log.error("Exception: ", exception);
ApiResponse apiResponse = new ApiResponse();
ApiResponse<Object> apiResponse = new ApiResponse<>();

apiResponse.setCode(ErrorCode.UNCATEGORIZED_EXCEPTION.getCode());
apiResponse.setMessage(ErrorCode.UNCATEGORIZED_EXCEPTION.getMessage());

return ResponseEntity.badRequest().body(apiResponse);
}

@ExceptionHandler(value = MaxUploadSizeExceededException.class)
ResponseEntity<ApiResponse<?>> handleMaxUploadSizeExceededException(
MaxUploadSizeExceededException exception) {
ApiResponse<Object> apiResponse = new ApiResponse<>();

apiResponse.setCode(ErrorCode.FILE_SIZE_LIMIT.getCode());
apiResponse.setMessage(ErrorCode.FILE_SIZE_LIMIT.getMessage());

return ResponseEntity.badRequest().body(apiResponse);
}

@ExceptionHandler(value = AppException.class)
ResponseEntity<ApiResponse> handleAppException(AppException exception) {
ResponseEntity<ApiResponse<?>> handleAppException(AppException exception) {
ErrorCode errorCode = exception.getErrorCode();
ApiResponse apiResponse = new ApiResponse();
ApiResponse<Object> apiResponse = new ApiResponse<>();
apiResponse.setCode(errorCode.getCode());
apiResponse.setMessage(errorCode.getMessage());

return ResponseEntity.status(errorCode.getStatusCode()).body(apiResponse);
}

@ExceptionHandler(value = AccessDeniedException.class)
ResponseEntity<ApiResponse> handlingAccessDeniedException(AccessDeniedException exception) {
ResponseEntity<ApiResponse<?>> handlingAccessDeniedException(AccessDeniedException exception) {
ErrorCode errorCode = ErrorCode.UNAUTHORIZED;

return ResponseEntity.status(errorCode.getStatusCode())
.body(
ApiResponse.builder()
ApiResponse.<Object>builder()
.code(errorCode.getCode())
.message(errorCode.getMessage())
.build());
}

@ExceptionHandler(value = HttpMessageNotReadableException.class)
ResponseEntity<ApiResponse> handleHttpMessageNotReadableException(
ResponseEntity<ApiResponse<?>> handleHttpMessageNotReadableException(
HttpMessageNotReadableException exception) {
ErrorCode errorCode = ErrorCode.INVALID_REQUEST_BODY;
log.error("Exception: ", exception);
return ResponseEntity.status(errorCode.getStatusCode())
.body(
ApiResponse.builder()
ApiResponse.<Object>builder()
.code(errorCode.getCode())
.message(errorCode.getMessage())
.build());
}

@ExceptionHandler(value = DataIntegrityViolationException.class)
ResponseEntity<ApiResponse> handleDataIntegrityViolationException(
ResponseEntity<ApiResponse<?>> handleDataIntegrityViolationException(
DataIntegrityViolationException exception) {
ErrorCode errorCode = ErrorCode.DATA_INTEGRITY_VIOLATION;
return ResponseEntity.status(errorCode.getStatusCode())
.body(
ApiResponse.builder()
ApiResponse.<Object>builder()
.code(errorCode.getCode())
.message(errorCode.getMessage())
.build());
}

@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
ResponseEntity<ApiResponse> handleHttpRequestMethodNotSupportedException(
ResponseEntity<ApiResponse<?>> handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException exception) {
ErrorCode errorCode = ErrorCode.NOT_FOUND;
return ResponseEntity.status(errorCode.getStatusCode())
.body(
ApiResponse.builder()
ApiResponse.<Object>builder()
.code(errorCode.getCode())
.message(errorCode.getMessage())
.build());
}

@ExceptionHandler(value = MethodArgumentNotValidException.class)
ResponseEntity<ApiResponse> handleValidation(MethodArgumentNotValidException exception) {
ResponseEntity<ApiResponse<?>> handleValidation(MethodArgumentNotValidException exception) {
String enumKey = exception.getFieldError().getDefaultMessage();

ErrorCode errorCode = ErrorCode.INVALID_KEY;
Expand All @@ -116,7 +128,7 @@ ResponseEntity<ApiResponse> handleValidation(MethodArgumentNotValidException exc
} catch (IllegalArgumentException e) {
}

ApiResponse apiResponse = new ApiResponse();
ApiResponse<Object> apiResponse = new ApiResponse<>();
apiResponse.setCode(errorCode.getCode());
apiResponse.setMessage(
Objects.nonNull(attributes)
Expand Down