This commit is contained in:
2026-05-09 23:04:27 +02:00
parent b1324e3048
commit 3d456f2f81
8 changed files with 159 additions and 152 deletions
+130 -119
View File
@@ -1,124 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version> <version>4.0.6</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>de.zendric.app</groupId> <groupId>de.zendric.app</groupId>
<artifactId>XpenselyServer</artifactId> <artifactId>XpenselyServer</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
<name>XpenselyServer</name> <name>XpenselyServer</name>
<description>XpenselyServer used to handle the Xpensely App</description> <description>XpenselyServer used to handle the Xpensely App</description>
<url/> <url/>
<licenses> <licenses>
<license/> <license/>
</licenses> </licenses>
<developers> <developers>
<developer/> <developer/>
</developers> </developers>
<scm> <scm>
<connection/> <connection/>
<developerConnection/> <developerConnection/>
<tag/> <tag/>
<url/> <url/>
</scm> </scm>
<properties> <properties>
<java.version>21</java.version> <java.version>21</java.version>
</properties> <lombok.version>1.18.46</lombok.version>
<dependencies> </properties>
<dependency> <dependencies>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-data-jpa</artifactId> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-starter-data-jpa</artifactId>
<dependency> </dependency>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-security</artifactId> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-starter-security</artifactId>
<dependency> </dependency>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-validation</artifactId> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-starter-validation</artifactId>
<dependency> </dependency>
<groupId>com.bucket4j</groupId> <dependency>
<artifactId>bucket4j-core</artifactId> <groupId>com.bucket4j</groupId>
<version>8.10.1</version> <artifactId>bucket4j-core</artifactId>
</dependency> <version>8.10.1</version>
<dependency> </dependency>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<dependency> </dependency>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-oauth2-client</artifactId> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-starter-oauth2-client</artifactId>
<dependency> </dependency>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-web</artifactId> <groupId>org.springframework.boot</groupId>
</dependency> <artifactId>spring-boot-starter-web</artifactId>
<dependency> </dependency>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-devtools</artifactId> <groupId>org.springframework.boot</groupId>
<scope>runtime</scope> <artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <scope>runtime</scope>
</dependency> <optional>true</optional>
<dependency> </dependency>
<groupId>org.postgresql</groupId> <dependency>
<artifactId>postgresql</artifactId> <groupId>org.postgresql</groupId>
<scope>runtime</scope> <artifactId>postgresql</artifactId>
</dependency> <scope>runtime</scope>
<dependency> </dependency>
<groupId>org.projectlombok</groupId> <dependency>
<artifactId>lombok</artifactId> <groupId>org.projectlombok</groupId>
<optional>true</optional> <artifactId>lombok</artifactId>
</dependency> <optional>true</optional>
<dependency> </dependency>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-test</artifactId> <groupId>org.springframework.boot</groupId>
<scope>test</scope> <artifactId>spring-boot-starter-test</artifactId>
</dependency> <scope>test</scope>
<dependency> </dependency>
<groupId>com.h2database</groupId> <dependency>
<artifactId>h2</artifactId> <groupId>org.springframework.boot</groupId>
<scope>test</scope> <artifactId>spring-boot-starter-webmvc-test</artifactId>
</dependency> <scope>test</scope>
<dependency> </dependency>
<groupId>org.springframework.security</groupId> <dependency>
<artifactId>spring-security-test</artifactId> <groupId>org.springframework.boot</groupId>
<scope>test</scope> <artifactId>spring-boot-starter-data-jpa-test</artifactId>
</dependency> <scope>test</scope>
</dependencies> </dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<annotationProcessorPaths> <annotationProcessorPaths>
<path> <path>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration> <configuration>
<excludes> <excludes>
<exclude> <exclude>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
</exclude> </exclude>
</excludes> </excludes>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
@@ -9,7 +9,6 @@ import org.springframework.web.server.ResponseStatusException;
import de.zendric.app.xpensely_server.model.AppUser; import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.AppUserCreateRequest; import de.zendric.app.xpensely_server.model.AppUserCreateRequest;
import de.zendric.app.xpensely_server.model.Exception.UsernameAlreadyExistsException;
import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver; import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver;
import de.zendric.app.xpensely_server.services.UserService; import de.zendric.app.xpensely_server.services.UserService;
@@ -47,15 +46,9 @@ public class AppUserController {
@PostMapping("/createUser") @PostMapping("/createUser")
public ResponseEntity<AppUser> createUser(@RequestBody @Valid AppUserCreateRequest userRequest) { public ResponseEntity<AppUser> createUser(@RequestBody @Valid AppUserCreateRequest userRequest) {
try { AppUser convertedUser = userRequest.convertToAppUser();
AppUser convertedUser = userRequest.convertToAppUser(); AppUser nUser = userService.createUser(convertedUser);
AppUser nUser = userService.createUser(convertedUser); return new ResponseEntity<>(nUser, HttpStatus.CREATED);
return new ResponseEntity<>(nUser, HttpStatus.CREATED);
} catch (UsernameAlreadyExistsException e) {
return new ResponseEntity<>(null, HttpStatus.CONFLICT);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
} }
@DeleteMapping @DeleteMapping
@@ -68,6 +68,7 @@ public class ExpenseListController {
expenseList.setXpenselyStandardCategories(standardCategories); expenseList.setXpenselyStandardCategories(standardCategories);
expenseList.setSharedWith(null); expenseList.setSharedWith(null);
ExpenseList savedItem = expenseListService.createList(expenseList); ExpenseList savedItem = expenseListService.createList(expenseList);
log.debug("Created expense list '{}' for user {}", savedItem.getName(), authenticatedUser.getId());
return new ResponseEntity<>(savedItem, HttpStatus.CREATED); return new ResponseEntity<>(savedItem, HttpStatus.CREATED);
} }
@@ -106,7 +107,7 @@ public class ExpenseListController {
AppUser user = authenticatedUserResolver.resolveCurrentUser(authentication); AppUser user = authenticatedUserResolver.resolveCurrentUser(authentication);
Optional<ExpenseList> expenseListOpt = expenseListService.findById(expenseListId); Optional<ExpenseList> expenseListOpt = expenseListService.findById(expenseListId);
if (expenseListOpt.isEmpty()) if (expenseListOpt.isEmpty())
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); return new ResponseEntity<>(HttpStatus.NOT_FOUND);
assertMember(user, expenseListOpt.get()); assertMember(user, expenseListOpt.get());
AppUser expenseOwner = userService.getUserByName(expenseChangeRequest.getOwnerName()); AppUser expenseOwner = userService.getUserByName(expenseChangeRequest.getOwnerName());
Expense expense = expenseChangeRequest.convertToExpense(expenseOwner.getId(), expenseListOpt.get()); Expense expense = expenseChangeRequest.convertToExpense(expenseOwner.getId(), expenseListOpt.get());
@@ -6,7 +6,7 @@ import org.springframework.context.annotation.Profile;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
@Configuration @Configuration
@@ -18,7 +18,7 @@ public class SecurityConfig {
http http
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()) .anyRequest().permitAll())
.csrf().disable(); .csrf(csrf -> csrf.disable());
return http.build(); return http.build();
} }
@@ -33,7 +33,7 @@ public class SecurityConfig {
.jwt(Customizer.withDefaults())) .jwt(Customizer.withDefaults()))
.oauth2Login(Customizer.withDefaults()) .oauth2Login(Customizer.withDefaults())
.addFilterAfter(new RateLimitFilter(), BearerTokenAuthenticationFilter.class) .addFilterAfter(new RateLimitFilter(), BearerTokenAuthenticationFilter.class)
.csrf().disable(); .csrf(csrf -> csrf.disable());
return http.build(); return http.build();
} }
@@ -31,18 +31,10 @@ public class ExpenseListService {
this.customCategoryRepository = customCategoryRepository; this.customCategoryRepository = customCategoryRepository;
} }
public List<ExpenseList> getAllLists() {
return repository.findAll();
}
public ExpenseList createList(ExpenseList list) { public ExpenseList createList(ExpenseList list) {
return repository.save(list); return repository.save(list);
} }
public void deleteList(Long id) {
repository.deleteById(id);
}
public void deleteById(Long id) { public void deleteById(Long id) {
repository.deleteById(id); repository.deleteById(id);
} }
@@ -51,10 +43,6 @@ public class ExpenseListService {
return repository.findById(id); return repository.findById(id);
} }
public Iterable<ExpenseList> findAll() {
return repository.findAll();
}
public ExpenseList save(ExpenseList expenseList) { public ExpenseList save(ExpenseList expenseList) {
return repository.save(expenseList); return repository.save(expenseList);
} }
@@ -4,7 +4,7 @@ import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.repo.ExpenseListRepository; import de.zendric.app.xpensely_server.repo.ExpenseListRepository;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import java.util.Optional; import java.util.Optional;
@@ -6,8 +6,11 @@ import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver;
import de.zendric.app.xpensely_server.services.UserService; import de.zendric.app.xpensely_server.services.UserService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.security.oauth2.client.autoconfigure.servlet.OAuth2ClientWebSecurityAutoConfiguration;
import org.springframework.boot.security.oauth2.server.resource.autoconfigure.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -18,7 +21,11 @@ import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(AppUserController.class) @WebMvcTest(value = AppUserController.class, excludeAutoConfiguration = {
OAuth2ClientAutoConfiguration.class,
OAuth2ClientWebSecurityAutoConfiguration.class,
OAuth2ResourceServerAutoConfiguration.class
})
@AutoConfigureMockMvc(addFilters = false) @AutoConfigureMockMvc(addFilters = false)
@ActiveProfiles("test") @ActiveProfiles("test")
class AppUserControllerTest { class AppUserControllerTest {
@@ -9,8 +9,11 @@ import de.zendric.app.xpensely_server.services.ExpenseListService;
import de.zendric.app.xpensely_server.services.UserService; import de.zendric.app.xpensely_server.services.UserService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.security.oauth2.client.autoconfigure.servlet.OAuth2ClientWebSecurityAutoConfiguration;
import org.springframework.boot.security.oauth2.server.resource.autoconfigure.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -24,7 +27,11 @@ import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ExpenseListController.class) @WebMvcTest(value = ExpenseListController.class, excludeAutoConfiguration = {
OAuth2ClientAutoConfiguration.class,
OAuth2ClientWebSecurityAutoConfiguration.class,
OAuth2ResourceServerAutoConfiguration.class
})
@AutoConfigureMockMvc(addFilters = false) @AutoConfigureMockMvc(addFilters = false)
@ActiveProfiles("test") @ActiveProfiles("test")
class ExpenseListControllerTest { class ExpenseListControllerTest {