#7
All checks were successful
Build and Deploy Spring Boot Server / build (push) Successful in 10m8s

This commit is contained in:
2025-05-10 19:07:50 +02:00
parent 011bb03d3f
commit 814b2221c8
20 changed files with 288 additions and 28 deletions

52
dev-docker-compose.yml Normal file
View File

@@ -0,0 +1,52 @@
version: "3.8"
services:
xpensely-server:
build:
context: .
dockerfile: Dockerfile
image: xpensely-server:local
labels:
net.unraid.docker.icon: https://tea.zendric.de/Cedric/XpenselyServer/raw/branch/main/src/main/resources/static/xpensely_icon_white.png
container_name: xpensely-server
ports:
- 3636:8080
environment:
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
DB_PORT: 5432
DB_P_NAME: xpensely
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
SPRING_PROFILES_ACTIVE: test
depends_on:
postgresdb:
condition: service_healthy
networks:
- xpensely-network
postgresdb:
labels:
net.unraid.docker.icon: https://raw.githubusercontent.com/docker-library/docs/01c12653951b2fe592c1f93a13b4e289ada0e3a1/postgres/logo.png
image: postgres:14
container_name: postgresdb
ports:
- 5435:5432
environment:
POSTGRES_DB: xpensely
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
networks:
- xpensely-network
volumes:
- db_data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test:
- CMD-SHELL
- pg_isready -U ${DB_USERNAME} -d xpensely
interval: 10s
timeout: 5s
retries: 5
volumes:
db_data: null
networks:
xpensely-network: null

View File

@@ -25,7 +25,7 @@ services:
image: postgres:14
container_name: postgresdb
ports:
- 5432:5432
- 5435:5432
environment:
POSTGRES_DB: xpensely
POSTGRES_USER: ${DB_USERNAME}

View File

@@ -4,4 +4,4 @@ COPY ./target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT ["java","-jar", "app.jar"]

View File

@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.AppUserCreateRequest;
import de.zendric.app.xpensely_server.model.UsernameAlreadyExistsException;
import de.zendric.app.xpensely_server.model.Exception.UsernameAlreadyExistsException;
import de.zendric.app.xpensely_server.services.UserService;
@RestController

View File

@@ -96,6 +96,7 @@ class ExpenseListController {
}
@PostMapping("/create")
// TODO add handling of categories by using DTO
public ResponseEntity<ExpenseList> create(@RequestBody ExpenseList expenseList) {
try {
if (expenseList.getOwner() != null) {

View File

@@ -0,0 +1,8 @@
package de.zendric.app.xpensely_server.model.DTO;
public class ExpenseListDTO {
// TODO should combine the two categories two one;
// private List<CategoryDTO> availableCategories;
}

View File

@@ -1,4 +1,4 @@
package de.zendric.app.xpensely_server.model;
package de.zendric.app.xpensely_server.model.Exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

View File

@@ -34,7 +34,7 @@ public class Expense {
private Double amount;
private Double personalUseAmount;
private Double otherPersonAmount;
private String category;
private LocalDate date;
@ManyToOne

View File

@@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@@ -40,6 +41,14 @@ public class ExpenseList {
@ManyToOne
private AppUser sharedWith;
@ManyToOne(fetch = FetchType.EAGER)
private XpenselyStandardCategories xpenselyStandardCategories;
@OneToMany(mappedBy = "expenseList", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference
@jakarta.persistence.OrderBy("name ASC")
private List<XpenselyCustomCategory> customCategories;
@OneToMany(mappedBy = "expenseList", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference
@jakarta.persistence.OrderBy("date ASC, id ASC")

View File

@@ -0,0 +1,37 @@
package de.zendric.app.xpensely_server.model;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class XpenselyCustomCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(name = "color_code", length = 7, nullable = false)
private String colorCode;
@ManyToOne
@JoinColumn(name = "expense_list_id", nullable = false)
@JsonBackReference
private ExpenseList expenseList;
}

View File

@@ -0,0 +1,29 @@
package de.zendric.app.xpensely_server.model;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class XpenselyStandardCategories {
@Id
private Long id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "global_categories_id")
private List<XpenselyStandardCategory> categories;
}

View File

@@ -0,0 +1,27 @@
package de.zendric.app.xpensely_server.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class XpenselyStandardCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(name = "color_code", length = 7, nullable = false)
private String colorCode;
}

View File

@@ -0,0 +1,35 @@
package de.zendric.app.xpensely_server.preparation;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import de.zendric.app.xpensely_server.model.XpenselyStandardCategories;
import de.zendric.app.xpensely_server.model.XpenselyStandardCategory;
import de.zendric.app.xpensely_server.repo.XpenselyStandardCategoriesRepository;
@Component
public class DataInitializer implements CommandLineRunner {
@Autowired
private XpenselyStandardCategoriesRepository globalRepo;
@Override
public void run(String... args) throws Exception {
if (!globalRepo.findById(1L).isPresent()) {
XpenselyStandardCategories global = new XpenselyStandardCategories();
global.setId(1L);
List<XpenselyStandardCategory> categories = List.of(
new XpenselyStandardCategory(null, "Food", "#FF5733"),
new XpenselyStandardCategory(null, "Transportation", "#33C3FF"),
new XpenselyStandardCategory(null, "Entertainment", "#33FF57"),
new XpenselyStandardCategory(null, "Shopping", "#FF33A8"),
new XpenselyStandardCategory(null, "Bills", "#33FFEA"));
global.setCategories(categories);
globalRepo.save(global);
}
}
}

View File

@@ -0,0 +1,10 @@
package de.zendric.app.xpensely_server.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import de.zendric.app.xpensely_server.model.XpenselyCustomCategory;
@Repository
public interface XpenselyCustomCategoryRepository extends JpaRepository<XpenselyCustomCategory, Long> {
}

View File

@@ -0,0 +1,11 @@
package de.zendric.app.xpensely_server.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import de.zendric.app.xpensely_server.model.XpenselyStandardCategories;
@Repository
public interface XpenselyStandardCategoriesRepository extends JpaRepository<XpenselyStandardCategories, Long> {
}

View File

@@ -2,6 +2,7 @@ package de.zendric.app.xpensely_server.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -10,17 +11,19 @@ import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// @Bean
// public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
// Exception {
// http.authorizeHttpRequests(auth -> auth
// .anyRequest().permitAll()).csrf().disable();
// ;
@Bean
@Profile("test") // Only enable this for testing
public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll())
.csrf().disable();
// return http.build();
// }
return http.build();
}
@Bean
@Profile("!test")
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth

View File

@@ -14,8 +14,10 @@ import org.springframework.transaction.annotation.Transactional;
import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.Expense;
import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.model.XpenselyCustomCategory;
import de.zendric.app.xpensely_server.repo.ExpenseListRepository;
import de.zendric.app.xpensely_server.repo.ExpenseRepository;
import de.zendric.app.xpensely_server.repo.XpenselyCustomCategoryRepository;
import jakarta.persistence.EntityManager;
@Service
@@ -24,14 +26,17 @@ public class ExpenseListService {
private ExpenseListRepository repository;
private final ExpenseRepository expenseRepository;
private XpenselyCustomCategoryRepository customCategoryRepository;
@Autowired
private EntityManager entityManager;
@Autowired
public ExpenseListService(ExpenseListRepository repository, ExpenseRepository expenseRepository) {
public ExpenseListService(ExpenseListRepository repository, ExpenseRepository expenseRepository,
XpenselyCustomCategoryRepository customCategoryRepository) {
this.repository = repository;
this.expenseRepository = expenseRepository;
this.customCategoryRepository = customCategoryRepository;
}
public List<ExpenseList> getAllLists() {
@@ -181,4 +186,24 @@ public class ExpenseListService {
return expenseRepository.save(existingExpense);
}
// TODO implement API for this
public XpenselyCustomCategory addCustomCategory(Long expenseListId, XpenselyCustomCategory customCategory) {
ExpenseList expenseList = repository.findById(expenseListId)
.orElseThrow(() -> new RuntimeException("Expense List not found"));
customCategory.setExpenseList(expenseList);
return customCategoryRepository.save(customCategory);
}
// TODO implement API for this
public void deleteCustomCategory(Long expenseListId, Long categoryId) {
XpenselyCustomCategory category = customCategoryRepository.findById(categoryId)
.orElseThrow(() -> new RuntimeException("Custom Category not found"));
if (!category.getExpenseList().getId().equals(expenseListId)) {
throw new RuntimeException("Category does not belong to the specified Expense List");
}
customCategoryRepository.delete(category);
}
}

View File

@@ -6,7 +6,7 @@ import java.util.Optional;
import org.springframework.stereotype.Service;
import de.zendric.app.xpensely_server.model.AppUser;
import de.zendric.app.xpensely_server.model.UsernameAlreadyExistsException;
import de.zendric.app.xpensely_server.model.Exception.UsernameAlreadyExistsException;
import de.zendric.app.xpensely_server.repo.UserRepository;
@Service

View File

@@ -0,0 +1,26 @@
package de.zendric.app.xpensely_Server;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import de.zendric.app.xpensely_server.model.ExpenseList;
import de.zendric.app.xpensely_server.repo.ExpenseListRepository;
@DataJpaTest
class ExpenseListRepositoryTest {
@Autowired
private ExpenseListRepository expenseListRepository;
@Test
void testFindExpenseListById() {
// Assuming an ExpenseList with id = 1 exists in your test DB.
Optional<ExpenseList> optionalExpenseList = expenseListRepository.findById(1L);
ExpenseList expenseList = optionalExpenseList.get();
System.out.println("ExpenseList name: " + expenseList.getName());
}
}

View File

@@ -1,13 +0,0 @@
package de.zendric.app.xpensely_Server;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class XpenselyServerApplicationTests {
@Test
void contextLoads() {
}
}