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
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app

COPY pom.xml .
RUN mvn dependency:go-offline

COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app

COPY --from=builder /app/target/*.jar app.jar

# Komenda uruchamiająca
ENTRYPOINT ["java", "-jar", "app.jar"]
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,44 @@
# code-warehouse-backend
# Code Warehouse Backend

A simple REST API built with **Spring Boot 3** for managing code and users. It includes JWT authentication, MySQL database support, and Swagger API documentation. The application runs in Docker containers.

---

## 🚀 Tech Stack

- Java 17
- Spring Boot
- Spring Security + JWT
- MySQL / H2
- Swagger (OpenAPI)
- Maven
- Docker

---

## ▶️ Getting Started

```bash
git clone <repo-url>
cd code-warehouse-backend
docker-compose up --build -d
```
## ▶️ Access

- **Application:** http://localhost:8082
- **Swagger (endpoints):** http://localhost:8082/swagger-ui.html

## 🛑 Stop Services

```bash
docker-compose down
```

## 👥 Authors

**Backend Team:**
- Dawid Dykacz

---

Made with ❤️ by the Programming Club Team
21 changes: 20 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,23 @@ services:
MYSQL_USER: user
MYSQL_PASSWORD: user123
ports:
- "3306:3306"
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10

app:
build: .
container_name: spring-app
restart: always
ports:
- "8082:8082"
depends_on:
mysql:
condition: service_healthy
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql-db:3306/mydb?allowPublicKeyRetrieval=true&useSSL=false
SPRING_DATASOURCE_USERNAME: user
SPRING_DATASOURCE_PASSWORD: user123
SPRING_JPA_HIBERNATE_DDL_AUTO: update
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>3.0.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, HttpSession ht
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(PATH + "auth/**").permitAll()
.requestMatchers(
"/swagger-ui/**",
"/swagger-ui.html",
"/v3/api-docs",
"/v3/api-docs/**",
"/swagger-resources/**",
"/webjars/**"
).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package pl.milosnicyit.codewarehousebackend.controllers.v1.auth;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -21,6 +24,13 @@ public AuthController(UsersService usersService) {
}

@PostMapping("/register")
@Operation(
summary = "Register a new user",
description = "Creates a new user account based on the provided credentials. Returns a JWT token, which might be null under certain conditions (e.g., pending email verification)."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "User successfully registered if jwt is not null. Check the response body for the JWT token (can be null)."),
})
public ResponseEntity<TokenResponse> register(@RequestBody UserRequest userRequest) {
final String token = this.usersService.registerUser(userRequest.getUsername(), userRequest.getPassword());
return ResponseEntity.ok(new TokenResponse(token));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package pl.milosnicyit.codewarehousebackend.controllers.v1.auth;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
Expand All @@ -10,5 +11,10 @@
@NoArgsConstructor
@ToString
public class TokenResponse {
@Schema(
description = "JWT authentication token.",
nullable = true,
example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
)
private String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pl.milosnicyit.codewarehousebackend.exeptions;

import lombok.NonNull;

public class UserAlreadyExistsException extends RuntimeException {
public UserAlreadyExistsException(@NonNull final String username) {
super("Username " + username + " already exists");
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
package pl.milosnicyit.codewarehousebackend.handlers;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pl.milosnicyit.codewarehousebackend.exeptions.UserAlreadyExistsException;

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<HandlerDTO> handleIllegalArgumentException(IllegalArgumentException exception) {
return ResponseEntity.badRequest().body(new HandlerDTO(exception.getMessage()));
}

@ExceptionHandler(UsernameNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<HandlerDTO> handleUsernameNotFoundException(UsernameNotFoundException exception) {
return ResponseEntity.badRequest().body(new HandlerDTO(exception.getMessage()));
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new HandlerDTO(exception.getMessage()));
}

@ExceptionHandler(UserAlreadyExistsException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ResponseEntity<HandlerDTO> handleUserAlreadyExistsException(UserAlreadyExistsException exception) {
return ResponseEntity
.status(HttpStatus.CONFLICT)
.body(new HandlerDTO(exception.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pl.milosnicyit.codewarehousebackend.users;

import org.commons.login.Password;
import pl.milosnicyit.codewarehousebackend.exeptions.UserAlreadyExistsException;
import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
import pl.milosnicyit.codewarehousebackend.password.PasswordEncoderService;
import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserDTO;
Expand All @@ -20,7 +21,7 @@ public UserAppService(UserRepositoryWrapper userRepositoryWrapper, PasswordEncod

public String registerUser(String username, String rawPassword) {
if (userRepositoryWrapper.existsByUsername(username)) {
return null;
throw new UserAlreadyExistsException(username);
}

final Password password = new Password(rawPassword);
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ spring.datasource.password=user123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ void shouldReturnEmptyTokenWhenUserAlreadyExistsEndToEnd() throws Exception {
mockMvc.perform(post(REGISTER_ENDPOINT)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonRequest))
.andExpect(status().isOk())
.andExpect(jsonPath("$.token").isEmpty());
.andExpect(status().is4xxClientError())
.andExpect(jsonPath("$.error").isNotEmpty());
}

private String getUserRequestJson(String username, String password) throws JsonProcessingException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import pl.milosnicyit.codewarehousebackend.exeptions.UserAlreadyExistsException;
import pl.milosnicyit.codewarehousebackend.jwt.JWTService;
import pl.milosnicyit.codewarehousebackend.password.PasswordEncoderService;
import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserDTO;
import pl.milosnicyit.codewarehousebackend.users.database.wrapper.UserRepositoryWrapper;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

Expand All @@ -33,15 +33,15 @@ class UserAppServiceTest {
private UserAppService userAppService;

@Test
void shouldReturnNullWhenUsernameIsAlreadyTaken() {
void shouldThrowExceptionWhenUsernameIsAlreadyTaken() {
String username = "zajetyLogin";
String rawPassword = "secretPassworddddddddddddddddddddddddddddddddddddd";

when(userRepositoryWrapper.existsByUsername(username)).thenReturn(true);

String result = userAppService.registerUser(username, rawPassword);

assertNull(result, "Should return null if user already exists");
assertThrows(
UserAlreadyExistsException.class,
() -> userAppService.registerUser(username, rawPassword)
);

verify(userRepositoryWrapper, never()).save(any());
verify(jwtService, never()).generateToken(anyString());
Expand Down
Loading