diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3671e2e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.idea +.github \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..0a090b0 --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +DATABASE_URL= +DATABASE_USER= +DATABASE_PWD= +APP_LOG_LEVEL=DEBUG/INFO/ERROR +TOKEN_EXPIRATION_TIME=7200000 +TOKEN_SECRET_KEY=TOKEN_SIZE_IS_MIN_24_CHARS +DEMO_ONLY=TRUE/FALSE \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7b6fc38 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "docker" + directory: "/" + target-branch: "main" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "main" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ecefcb3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,191 @@ +name: build + +on: + push: + branches: [ main ] + paths: [ '**.java', '.github/workflows/build.yml' ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + test: + name: Test coverage + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Checkout badges branch to a badges directory nested inside first checkout + uses: actions/checkout@v4 + with: + ref: badges + path: badges + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '20' + distribution: 'temurin' + + - name: Prepare Language Files + run: | + mkdir -p ./mylfa/languages + curl -o ./mylfa/languages/osd.traineddata https://github.com/tesseract-ocr/tessdata/raw/main/osd.traineddata + + - name: Build with Maven + env: + DEMO_ONLY: ${{ secrets.DEMO_ONLY }} + TOKEN_SECRET_KEY: ${{ secrets.TOKEN_SECRET_KEY }} + TOKEN_EXPIRATION_TIME: ${{ secrets.TOKEN_EXPIRATION_TIME }} + APP_LOG_LEVEL: ${{ secrets.APP_LOG_LEVEL }} + DATABASE_PWD: ${{ secrets.DATABASE_PWD }} + DATABASE_USER: ${{ secrets.DATABASE_USER }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + run: | + mvn -B test + + - name: Generate JaCoCo badge + id: jacoco + uses: cicirello/jacoco-badge-generator@v2 + with: + badges-directory: badges + generate-branches-badge: true + generate-summary: true + + - name: Log coverage percentages to workflow output + run: | + echo "coverage = ${{ steps.jacoco.outputs.coverage }}" + echo "branches = ${{ steps.jacoco.outputs.branches }}" + + - name: Upload JaCoCo coverage report as a workflow artifact + uses: actions/upload-artifact@v3 + with: + name: jacoco-report + path: target/site/jacoco/ + + - name: Commit and push the coverage badges and summary file + if: ${{ github.event_name != 'pull_request' }} + run: | + cd badges + if [[ `git status --porcelain *.svg *.json` ]]; then + git config --global user.name 'github-actions' + git config --global user.email '1650200+github-actions[bot]@users.noreply.github.com' + git add *.svg *.json + git commit -m "Autogenerated JaCoCo coverage badges" *.svg *.json + git push + fi + + - name: Comment on PR with coverage percentages + if: ${{ github.event_name == 'pull_request' }} + run: | + REPORT=$( + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + + de.whiteo + mylfa + mylfa + 1.1 + mylfa core service + jar + + + UTF-8 + 17 + 17 + 3.1.5 + 42.6.0 + 3.6.0 + 9.22.3 + 2.2.224 + 4.4 + 3.13.0 + 2.15.3 + 1.18.30 + 0.2.0 + 1.5.5.Final + 1.11.5 + 0.12.3 + 5.8.0 + 0.8.11 + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.version} + + + spring-boot-starter-tomcat + org.springframework.boot + + + + + org.springframework.boot + spring-boot-starter-undertow + ${spring.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring.version} + + + org.springframework.boot + spring-boot-starter-actuator + ${spring.version} + + + org.springframework.boot + spring-boot-starter-validation + ${spring.version} + + + org.springframework.boot + spring-boot-starter-security + ${spring.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring.version} + test + + + + + io.hypersistence + hypersistence-utils-hibernate-62 + ${hypersistence-utils.version} + + + + + org.postgresql + postgresql + ${postgres.version} + runtime + + + + + org.flywaydb + flyway-core + ${flyway.version} + + + + + com.h2database + h2 + ${h2database.version} + + + + + org.apache.commons + commons-collections4 + ${apache-commons-collections4.version} + + + org.apache.commons + commons-lang3 + ${apache-commons-lang3.version} + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + io.micrometer + micrometer-registry-prometheus + ${micrometer-registry-prometheus.version} + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + + + + + net.sourceforge.tess4j + tess4j + ${tess4j.version} + + + + + mylfa + + + src/main/resources + true + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + repackage + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + + prepare-agent + + + + generate-code-coverage-report + test + + report + + + + + + + \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/App.java b/src/main/java/de/whiteo/mylfa/App.java new file mode 100644 index 0000000..5b5fcf6 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/App.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.util.TimeZone; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@EnableAsync +@SpringBootApplication +@EnableAspectJAutoProxy +public class App { + + public static void main(String[] args) { + TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin")); + SpringApplication.run(App.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/AbstractRestController.java b/src/main/java/de/whiteo/mylfa/api/AbstractRestController.java new file mode 100644 index 0000000..db9d5a4 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/AbstractRestController.java @@ -0,0 +1,66 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.domain.AbstractEntity; +import de.whiteo.mylfa.security.Auth; +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.AbstractService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequiredArgsConstructor +@Auth(AuthInterceptor.class) +public abstract class AbstractRestController { + + private final AbstractService service; + private final AuthInterceptor authInterceptor; + + @GetMapping("/{id}") + public ResponseEntity findById(@PathVariable("id") UUID id) { + log.debug("Start call 'findById' with parameters: {}", id); + D response = service.findById(authInterceptor.getUserName(), id); + log.debug("End call 'findById' with answer {}", response); + return ResponseEntity.ok(response); + } + + @PostMapping + public ResponseEntity create(@Valid @RequestBody R request) { + log.debug("Start call 'create' with parameters: {}", request); + service.create(authInterceptor.getUserName(), request); + log.debug("End call 'create'"); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @PutMapping("/{id}/edit") + public ResponseEntity update(@PathVariable("id") UUID id, @Valid @RequestBody R request) { + log.debug("Start call 'update' with parameters: {}, {}", id, request); + D response = service.update(authInterceptor.getUserName(), id, request); + log.debug("End call 'update' with answer {}", response); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable("id") UUID id) { + log.debug("Start call 'delete' with parameters: {}", id); + service.delete(authInterceptor.getUserName(), id); + log.debug("End call 'delete'"); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/AuthenticateRestController.java b/src/main/java/de/whiteo/mylfa/api/AuthenticateRestController.java new file mode 100644 index 0000000..e1c5d79 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/AuthenticateRestController.java @@ -0,0 +1,45 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.dto.user.UserLoginRequest; +import de.whiteo.mylfa.dto.user.UserLoginResponse; +import de.whiteo.mylfa.service.UserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Leo Tanas (github) + */ + + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/authenticate") +public class AuthenticateRestController { + + private final UserService service; + + @PostMapping + public ResponseEntity authenticate(@Valid @RequestBody UserLoginRequest request) { + log.debug("Start call 'authenticate' with parameters: {}", request); + UserLoginResponse response = service.getToken(null, request); + log.debug("End call 'authenticate' with answer {}", response); + return ResponseEntity.ok(response); + } + + @GetMapping() + public ResponseEntity validate(HttpServletRequest request) { + log.debug("Start call 'validate'"); + Boolean response = service.validateToken(request); + log.debug("End call 'validate' with answer {}", response); + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/CurrencyTypeRestController.java b/src/main/java/de/whiteo/mylfa/api/CurrencyTypeRestController.java new file mode 100644 index 0000000..09fd624 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/CurrencyTypeRestController.java @@ -0,0 +1,45 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.CurrencyTypeService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequestMapping("/api/v1/currency-type") +public class CurrencyTypeRestController extends + AbstractRestController { + + private final AuthInterceptor authInterceptor; + private final CurrencyTypeService service; + + public CurrencyTypeRestController(CurrencyTypeService service, AuthInterceptor authInterceptor) { + super(service, authInterceptor); + + this.authInterceptor = authInterceptor; + this.service = service; + } + + @GetMapping() + public ResponseEntity> findAll( + @RequestParam(value = "hide", required = false) Boolean hide, Pageable pageable) { + log.debug("Start call 'findAll' with hide {} and parameters: {}", hide, pageable); + Page page = service.findAll(authInterceptor.getUserName(), hide, pageable); + log.debug("End call 'findAll' with answer {}", page); + return ResponseEntity.ok(page); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/ExpenseCategoryRestController.java b/src/main/java/de/whiteo/mylfa/api/ExpenseCategoryRestController.java new file mode 100644 index 0000000..d9f5291 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/ExpenseCategoryRestController.java @@ -0,0 +1,45 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.ExpenseCategoryService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequestMapping("/api/v1/expense-category") +public class ExpenseCategoryRestController extends + AbstractRestController { + + private final AuthInterceptor authInterceptor; + private final ExpenseCategoryService service; + + public ExpenseCategoryRestController(ExpenseCategoryService service, AuthInterceptor authInterceptor) { + super(service, authInterceptor); + + this.authInterceptor = authInterceptor; + this.service = service; + } + + @GetMapping() + public ResponseEntity> findAll( + @RequestParam(value = "hide", required = false) Boolean hide, Pageable pageable) { + log.debug("Start call 'findAll' with hide {} and parameters: {}", hide, pageable); + Page page = service.findAll(authInterceptor.getUserName(), hide, pageable); + log.debug("End call 'findAll' with answer {}", page); + return ResponseEntity.ok(page); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/ExpenseRestController.java b/src/main/java/de/whiteo/mylfa/api/ExpenseRestController.java new file mode 100644 index 0000000..3ad20d3 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/ExpenseRestController.java @@ -0,0 +1,58 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.domain.Expense; +import de.whiteo.mylfa.dto.expense.ExpenseCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.expense.ExpenseResponse; +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.ExpenseService; +import de.whiteo.mylfa.util.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequestMapping("/api/v1/expense") +public class ExpenseRestController extends + AbstractRestController { + + private final AuthInterceptor authInterceptor; + private final ExpenseService service; + + public ExpenseRestController(ExpenseService service, AuthInterceptor authInterceptor) { + super(service, authInterceptor); + + this.authInterceptor = authInterceptor; + this.service = service; + } + + @GetMapping() + public ResponseEntity> findAll( + @RequestParam(value = "categoryId", required = false) UUID categoryId, + @RequestParam(value = "currencyTypeId", required = false) UUID currencyTypeId, + @RequestParam(value = "startDate", required = false) + @DateTimeFormat(pattern = DateUtil.PATTERN_ISO_8601) LocalDateTime startDate, + @RequestParam(value = "endDate", required = false) + @DateTimeFormat(pattern = DateUtil.PATTERN_ISO_8601) LocalDateTime endDate, + Pageable pageable) { + log.debug("Start call 'findAll' with categoryId {}, currencyTypeId {}, startDate {}, endDate {} and pageable {}", + categoryId, currencyTypeId, startDate, endDate, pageable); + Page page = service.findAll( + authInterceptor.getUserName(), categoryId, currencyTypeId, startDate, endDate, pageable); + log.debug("End call 'findAll' with answer {}", page); + return ResponseEntity.ok(page); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/ImageRestController.java b/src/main/java/de/whiteo/mylfa/api/ImageRestController.java new file mode 100644 index 0000000..7f8baa9 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/ImageRestController.java @@ -0,0 +1,32 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.ImageService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/image") +public class ImageRestController { + + private final AuthInterceptor authInterceptor; + private final ImageService service; + + @PostMapping("/parse") + public ResponseEntity parseImage(@RequestParam("image") MultipartFile image) { + String parsedText = service.getSumFromImage(authInterceptor.getUserName(), image); + return ResponseEntity.ok(parsedText); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/IncomeCategoryRestController.java b/src/main/java/de/whiteo/mylfa/api/IncomeCategoryRestController.java new file mode 100644 index 0000000..5d75b76 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/IncomeCategoryRestController.java @@ -0,0 +1,45 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.IncomeCategoryService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequestMapping("/api/v1/income-category") +public class IncomeCategoryRestController extends + AbstractRestController { + + private final AuthInterceptor authInterceptor; + private final IncomeCategoryService service; + + public IncomeCategoryRestController(IncomeCategoryService service, AuthInterceptor authInterceptor) { + super(service, authInterceptor); + + this.authInterceptor = authInterceptor; + this.service = service; + } + + @GetMapping() + public ResponseEntity> findAll( + @RequestParam(value = "hide", required = false) Boolean hide, Pageable pageable) { + log.debug("Start call 'findAll' with hide {} and parameters: {}", hide, pageable); + Page page = service.findAll(authInterceptor.getUserName(), hide, pageable); + log.debug("End call 'findAll' with answer {}", page); + return ResponseEntity.ok(page); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/IncomeRestController.java b/src/main/java/de/whiteo/mylfa/api/IncomeRestController.java new file mode 100644 index 0000000..f01a3dc --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/IncomeRestController.java @@ -0,0 +1,58 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.domain.Income; +import de.whiteo.mylfa.dto.income.IncomeCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.income.IncomeResponse; +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.IncomeService; +import de.whiteo.mylfa.util.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequestMapping("/api/v1/income") +public class IncomeRestController extends + AbstractRestController { + + private final AuthInterceptor authInterceptor; + private final IncomeService service; + + public IncomeRestController(IncomeService service, AuthInterceptor authInterceptor) { + super(service, authInterceptor); + + this.authInterceptor = authInterceptor; + this.service = service; + } + + @GetMapping() + public ResponseEntity> findAll( + @RequestParam(value = "categoryId", required = false) UUID categoryId, + @RequestParam(value = "currencyTypeId", required = false) UUID currencyTypeId, + @RequestParam(value = "startDate", required = false) + @DateTimeFormat(pattern = DateUtil.PATTERN_ISO_8601) LocalDateTime startDate, + @RequestParam(value = "endDate", required = false) + @DateTimeFormat(pattern = DateUtil.PATTERN_ISO_8601) LocalDateTime endDate, + Pageable pageable) { + log.debug("Start call 'findAll' with categoryId {}, currencyTypeId {}, startDate {}, endDate {} and pageable {}", + categoryId, currencyTypeId, startDate, endDate, pageable); + Page page = service.findAll( + authInterceptor.getUserName(), categoryId, currencyTypeId, startDate, endDate, pageable); + log.debug("End call 'findAll' with answer {}", page); + return ResponseEntity.ok(page); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/UserRestController.java b/src/main/java/de/whiteo/mylfa/api/UserRestController.java new file mode 100644 index 0000000..e3fad81 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/UserRestController.java @@ -0,0 +1,90 @@ +package de.whiteo.mylfa.api; + +import de.whiteo.mylfa.dto.user.UserCreateRequest; +import de.whiteo.mylfa.dto.user.UserResponse; +import de.whiteo.mylfa.dto.user.UserUpdatePasswordRequest; +import de.whiteo.mylfa.dto.user.UserUpdatePropertiesRequest; +import de.whiteo.mylfa.dto.user.UserUpdateRequest; +import de.whiteo.mylfa.security.Auth; +import de.whiteo.mylfa.security.AuthInterceptor; +import de.whiteo.mylfa.service.UserService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/user") +@Auth(AuthInterceptor.class) +public class UserRestController { + + private final UserService service; + + @GetMapping("/{id}") + public ResponseEntity find(@PathVariable("id") UUID id) { + log.debug("Start call 'find' with parameters: {}", id); + UserResponse response = service.find(id); + log.debug("End call 'find' with answer {}", response); + return ResponseEntity.ok(response); + } + + @PostMapping("/create") + public ResponseEntity create(@Valid @RequestBody UserCreateRequest request) { + log.debug("Start call 'create' with parameters: {}", request); + service.create(request); + log.debug("End call 'create'"); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @PutMapping("/{id}/edit") + public ResponseEntity update(@PathVariable("id") UUID id, + @Valid @RequestBody UserUpdateRequest updateRequest) { + log.debug("Start call 'update' with parameters: {} {}", id, updateRequest); + UserResponse response = service.update(id, updateRequest); + log.debug("End call 'update' with answer {}", response); + return ResponseEntity.ok(response); + } + + @PutMapping("/{id}/edit-properties") + public ResponseEntity updateProperties(@PathVariable("id") UUID id, + @Valid @RequestBody UserUpdatePropertiesRequest updateRequest) { + log.debug("Start call 'updateProperties' with parameters: {} {}", id, updateRequest); + UserResponse response = service.updateProperties(id, updateRequest); + log.debug("End call 'updateProperties' with answer {}", response); + return ResponseEntity.ok(response); + } + + @PutMapping("/{id}/edit-password") + public ResponseEntity updatePassword(@PathVariable("id") UUID id, + @Valid @RequestBody UserUpdatePasswordRequest updateRequest) { + log.debug("Start call 'updatePassword' with parameters: {} {}", id, updateRequest); + UserResponse response = service.updatePassword(id, updateRequest); + log.debug("End call 'updatePassword' with answer {}", response); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable("id") UUID id) { + log.debug("Start call 'delete' with parameters: {}", id); + service.delete(id); + log.debug("End call 'delete'"); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/api/VersionRestController.java b/src/main/java/de/whiteo/mylfa/api/VersionRestController.java new file mode 100644 index 0000000..616c16e --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/api/VersionRestController.java @@ -0,0 +1,27 @@ +package de.whiteo.mylfa.api; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/version") +public class VersionRestController { + + @Value("${app.version}") + private String appVersion; + + @GetMapping() + public String getAppVersion() { + return appVersion; + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/config/AppConfig.java b/src/main/java/de/whiteo/mylfa/config/AppConfig.java new file mode 100644 index 0000000..d30a85d --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/config/AppConfig.java @@ -0,0 +1,32 @@ +package de.whiteo.mylfa.config; + +import de.whiteo.mylfa.security.AuthInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @author Leo Tanas (github) + */ + +@Configuration +@RequiredArgsConstructor +public class AppConfig implements WebMvcConfigurer { + + private final AuthInterceptor authInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor).addPathPatterns("/**"); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedHeaders("Authorization", "Content-Type"); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/config/NoModifyDemoMode.java b/src/main/java/de/whiteo/mylfa/config/NoModifyDemoMode.java new file mode 100644 index 0000000..e85fcfa --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/config/NoModifyDemoMode.java @@ -0,0 +1,15 @@ +package de.whiteo.mylfa.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Leo Tanas (github) + */ + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoModifyDemoMode { +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/config/NoModifyDemoModeAspect.java b/src/main/java/de/whiteo/mylfa/config/NoModifyDemoModeAspect.java new file mode 100644 index 0000000..7662743 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/config/NoModifyDemoModeAspect.java @@ -0,0 +1,32 @@ +package de.whiteo.mylfa.config; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @author Leo Tanas (github) + */ + +@Aspect +@Component +public class NoModifyDemoModeAspect { + + @Value("${app.demo-only}") + private boolean demoOnly; + + @Pointcut("@annotation(NoModifyDemoMode)") + public void noModifyMethods() { + } + + @Around("noModifyMethods()") + public Object validateDemoMode(ProceedingJoinPoint joinPoint) throws Throwable { + if (demoOnly) { + throw new UnsupportedOperationException("The service is in demo mode"); + } + return joinPoint.proceed(); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/config/SecurityConfig.java b/src/main/java/de/whiteo/mylfa/config/SecurityConfig.java new file mode 100644 index 0000000..c8c275b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/config/SecurityConfig.java @@ -0,0 +1,53 @@ +package de.whiteo.mylfa.config; + +import de.whiteo.mylfa.security.JwtAuthenticationFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +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.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import static org.springframework.security.config.Customizer.withDefaults; + +/** + * @author Leo Tanas (github) + */ + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.httpBasic(withDefaults()) + .cors(withDefaults()) + .csrf(AbstractHttpConfigurer::disable) + .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .logout(logout -> logout + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessHandler(logoutSuccessHandler()) + ); + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public LogoutSuccessHandler logoutSuccessHandler() { + return (request, response, authentication) -> response.setStatus(HttpStatus.OK.value()); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/config/WebServerConfig.java b/src/main/java/de/whiteo/mylfa/config/WebServerConfig.java new file mode 100644 index 0000000..f6e338f --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/config/WebServerConfig.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa.config; + +import io.undertow.server.DefaultByteBufferPool; +import io.undertow.websockets.jsr.WebSocketDeploymentInfo; +import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Configuration; + +/** + * @author Leo Tanas (github) + */ + +@Configuration +public class WebServerConfig implements WebServerFactoryCustomizer { + + @Override + public void customize(UndertowServletWebServerFactory factory) { + factory.addDeploymentInfoCustomizers(deploymentInfo -> { + WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo(); + webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 1024)); + deploymentInfo.addServletContextAttribute( + "io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo); + }); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/domain/AbstractEntity.java b/src/main/java/de/whiteo/mylfa/domain/AbstractEntity.java new file mode 100644 index 0000000..611d9a6 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/domain/AbstractEntity.java @@ -0,0 +1,46 @@ +package de.whiteo.mylfa.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Getter +@Setter +@Entity +@NoArgsConstructor +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class AbstractEntity { + + @Id + @GeneratedValue(generator = "uuid2") + @GenericGenerator(name = "uuid2", strategy = "uuid2") + @Column(name = "id", columnDefinition = "UUID") + private UUID id; + + @Column(name = "user_id", nullable = false, columnDefinition = "UUID") + private UUID userId; + + @UpdateTimestamp + @Column(name = "modify_date", nullable = false) + private LocalDateTime modifyDate; + + @CreationTimestamp + @Column(name = "creation_date", nullable = false, updatable = false) + private LocalDateTime creationDate; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/domain/CurrencyType.java b/src/main/java/de/whiteo/mylfa/domain/CurrencyType.java new file mode 100644 index 0000000..e33b101 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/domain/CurrencyType.java @@ -0,0 +1,24 @@ +package de.whiteo.mylfa.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author Leo Tanas (github) + */ + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class CurrencyType extends AbstractEntity { + + @Column(name = "name", nullable = false, unique = true) + private String name; + + @Column(name = "hide", nullable = false) + private boolean hide; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/domain/Expense.java b/src/main/java/de/whiteo/mylfa/domain/Expense.java new file mode 100644 index 0000000..472db9e --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/domain/Expense.java @@ -0,0 +1,33 @@ +package de.whiteo.mylfa.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class Expense extends AbstractEntity { + + @Column(name = "category_id", nullable = false, columnDefinition = "UUID") + private UUID categoryId; + + @Column(name = "currency_type_id", nullable = false, columnDefinition = "UUID") + private UUID currencyTypeId; + + @Column(name = "amount", nullable = false) + private BigDecimal amount; + + @Column(name = "description") + private String description; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/domain/ExpenseCategory.java b/src/main/java/de/whiteo/mylfa/domain/ExpenseCategory.java new file mode 100644 index 0000000..06ac1b3 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/domain/ExpenseCategory.java @@ -0,0 +1,29 @@ +package de.whiteo.mylfa.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class ExpenseCategory extends AbstractEntity { + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "hide", nullable = false) + private boolean hide; + + @Column(name = "parent_id") + private UUID parentId; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/domain/Income.java b/src/main/java/de/whiteo/mylfa/domain/Income.java new file mode 100644 index 0000000..b2e7590 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/domain/Income.java @@ -0,0 +1,33 @@ +package de.whiteo.mylfa.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class Income extends AbstractEntity { + + @Column(name = "category_id", nullable = false, columnDefinition = "UUID") + private UUID categoryId; + + @Column(name = "currency_type_id", nullable = false, columnDefinition = "UUID") + private UUID currencyTypeId; + + @Column(name = "amount", nullable = false) + private BigDecimal amount; + + @Column(name = "description") + private String description; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/domain/IncomeCategory.java b/src/main/java/de/whiteo/mylfa/domain/IncomeCategory.java new file mode 100644 index 0000000..789ef46 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/domain/IncomeCategory.java @@ -0,0 +1,29 @@ +package de.whiteo.mylfa.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class IncomeCategory extends AbstractEntity { + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "hide", nullable = false) + private boolean hide; + + @Column(name = "parent_id") + private UUID parentId; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/domain/User.java b/src/main/java/de/whiteo/mylfa/domain/User.java new file mode 100644 index 0000000..393271f --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/domain/User.java @@ -0,0 +1,58 @@ +package de.whiteo.mylfa.domain; + +import io.hypersistence.utils.hibernate.type.json.JsonType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "user") +public class User { + + @Id + @GeneratedValue(generator = "uuid2") + @GenericGenerator(name = "uuid2", strategy = "uuid2") + @Column(name = "id", columnDefinition = "UUID") + private UUID id; + + @Column(name = "email", nullable = false, unique = true) + private String email; + + @Column(name = "verified", nullable = false) + private boolean verified; + + @Column(name = "password", nullable = false) + private char[] password; + + @Type(JsonType.class) + @Column(name = "properties", columnDefinition = "jsonb") + private Map properties; + + @UpdateTimestamp + @Column(name = "modify_date", nullable = false) + private LocalDateTime modifyDate; + + @CreationTimestamp + @Column(name = "creation_date", nullable = false, updatable = false) + private LocalDateTime creationDate; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/currencytype/CurrencyTypeCreateOrUpdateRequest.java b/src/main/java/de/whiteo/mylfa/dto/currencytype/CurrencyTypeCreateOrUpdateRequest.java new file mode 100644 index 0000000..07f597c --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/currencytype/CurrencyTypeCreateOrUpdateRequest.java @@ -0,0 +1,21 @@ +package de.whiteo.mylfa.dto.currencytype; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class CurrencyTypeCreateOrUpdateRequest { + + private boolean hide; + + @NotNull(message = "Name cannot be null") + @NotBlank(message = "Name cannot be blank") + @Size(min = 1, max = 30, message = "Name must be between 1 and 30 characters") + private String name; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/currencytype/CurrencyTypeResponse.java b/src/main/java/de/whiteo/mylfa/dto/currencytype/CurrencyTypeResponse.java new file mode 100644 index 0000000..f806e54 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/currencytype/CurrencyTypeResponse.java @@ -0,0 +1,19 @@ +package de.whiteo.mylfa.dto.currencytype; + +import lombok.Builder; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +@Builder +public class CurrencyTypeResponse { + + private UUID id; + private String name; + private boolean hide; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/expense/ExpenseCreateOrUpdateRequest.java b/src/main/java/de/whiteo/mylfa/dto/expense/ExpenseCreateOrUpdateRequest.java new file mode 100644 index 0000000..95119f8 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/expense/ExpenseCreateOrUpdateRequest.java @@ -0,0 +1,28 @@ +package de.whiteo.mylfa.dto.expense; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class ExpenseCreateOrUpdateRequest { + + @NotNull(message = "Currency type id cannot be null") + private UUID currencyTypeId; + + @NotNull(message = "Category id cannot be null") + private UUID categoryId; + + @NotNull(message = "Amount cannot be null") + @Positive + private BigDecimal amount; + + private String description; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/expense/ExpenseResponse.java b/src/main/java/de/whiteo/mylfa/dto/expense/ExpenseResponse.java new file mode 100644 index 0000000..4627015 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/expense/ExpenseResponse.java @@ -0,0 +1,24 @@ +package de.whiteo.mylfa.dto.expense; + +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +@Builder +public class ExpenseResponse { + + private UUID id; + private ExpenseCategoryResponse category; + private BigDecimal amount; + private String description; + private CurrencyTypeResponse currencyType; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/expensecategory/ExpenseCategoryCreateOrUpdateRequest.java b/src/main/java/de/whiteo/mylfa/dto/expensecategory/ExpenseCategoryCreateOrUpdateRequest.java new file mode 100644 index 0000000..eda2f8b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/expensecategory/ExpenseCategoryCreateOrUpdateRequest.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa.dto.expensecategory; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class ExpenseCategoryCreateOrUpdateRequest { + + @NotNull(message = "Name cannot be null") + @NotBlank(message = "Name cannot be blank") + @Size(min = 1, max = 30, message = "Name must be between 1 and 30 characters") + private String name; + + private boolean hide; + + private UUID parentId; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/expensecategory/ExpenseCategoryResponse.java b/src/main/java/de/whiteo/mylfa/dto/expensecategory/ExpenseCategoryResponse.java new file mode 100644 index 0000000..e2d117b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/expensecategory/ExpenseCategoryResponse.java @@ -0,0 +1,20 @@ +package de.whiteo.mylfa.dto.expensecategory; + +import lombok.Builder; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +@Builder +public class ExpenseCategoryResponse { + + private UUID id; + private String name; + private boolean hide; + private ExpenseCategoryResponse parent; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/income/IncomeCreateOrUpdateRequest.java b/src/main/java/de/whiteo/mylfa/dto/income/IncomeCreateOrUpdateRequest.java new file mode 100644 index 0000000..dd23d1a --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/income/IncomeCreateOrUpdateRequest.java @@ -0,0 +1,28 @@ +package de.whiteo.mylfa.dto.income; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class IncomeCreateOrUpdateRequest { + + @NotNull(message = "Currency type id cannot be null") + private UUID currencyTypeId; + + @NotNull(message = "Category id cannot be null") + private UUID categoryId; + + @NotNull(message = "Amount cannot be null") + @Positive + private BigDecimal amount; + + private String description; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/income/IncomeResponse.java b/src/main/java/de/whiteo/mylfa/dto/income/IncomeResponse.java new file mode 100644 index 0000000..ec5d758 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/income/IncomeResponse.java @@ -0,0 +1,24 @@ +package de.whiteo.mylfa.dto.income; + +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +@Builder +public class IncomeResponse { + + private UUID id; + private IncomeCategoryResponse category; + private BigDecimal amount; + private String description; + private CurrencyTypeResponse currencyType; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/incomecategory/IncomeCategoryCreateOrUpdateRequest.java b/src/main/java/de/whiteo/mylfa/dto/incomecategory/IncomeCategoryCreateOrUpdateRequest.java new file mode 100644 index 0000000..fd9b424 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/incomecategory/IncomeCategoryCreateOrUpdateRequest.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa.dto.incomecategory; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class IncomeCategoryCreateOrUpdateRequest { + + @NotNull(message = "Name cannot be null") + @NotBlank(message = "Name cannot be blank") + @Size(min = 1, max = 30, message = "Name must be between 1 and 30 characters") + private String name; + + private boolean hide; + + private UUID parentId; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/incomecategory/IncomeCategoryResponse.java b/src/main/java/de/whiteo/mylfa/dto/incomecategory/IncomeCategoryResponse.java new file mode 100644 index 0000000..d5b6771 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/incomecategory/IncomeCategoryResponse.java @@ -0,0 +1,20 @@ +package de.whiteo.mylfa.dto.incomecategory; + +import lombok.Builder; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +@Builder +public class IncomeCategoryResponse { + + private UUID id; + private String name; + private boolean hide; + private IncomeCategoryResponse parent; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserCreateRequest.java b/src/main/java/de/whiteo/mylfa/dto/user/UserCreateRequest.java new file mode 100644 index 0000000..071100c --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserCreateRequest.java @@ -0,0 +1,29 @@ +package de.whiteo.mylfa.dto.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.Map; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class UserCreateRequest { + + @Email(message = "Email should be valid", regexp = "^(?=.{1,64}@)[A-Za-z0-9_-]" + + "+(\\.[A-Za-z0-9_-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$") + private String email; + + @NotNull(message = "Password cannot be null") + @NotBlank(message = "Password cannot be blank") + @Size(min = 5, max = 30, message = "Password must be between 5 and 30 characters") + private String password; + + @NotNull(message = "Properties cannot be null") + private Map properties; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserLoginRequest.java b/src/main/java/de/whiteo/mylfa/dto/user/UserLoginRequest.java new file mode 100644 index 0000000..6753c4c --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserLoginRequest.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa.dto.user; + +import lombok.Data; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class UserLoginRequest { + + @Email(message = "Email should be valid", regexp = "^(?=.{1,64}@)[A-Za-z0-9_-]" + + "+(\\.[A-Za-z0-9_-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$") + private String email; + + @NotNull(message = "Password cannot be null") + @NotBlank(message = "Password cannot be blank") + @Size(min = 5, max = 30, message = "Password must be between 5 and 30 characters") + private String password; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserLoginResponse.java b/src/main/java/de/whiteo/mylfa/dto/user/UserLoginResponse.java new file mode 100644 index 0000000..9f72e74 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserLoginResponse.java @@ -0,0 +1,16 @@ +package de.whiteo.mylfa.dto.user; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * @author Leo Tanas (github) + */ + +@Data +@AllArgsConstructor +public class UserLoginResponse { + + private String token; + private UserResponse user; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserResponse.java b/src/main/java/de/whiteo/mylfa/dto/user/UserResponse.java new file mode 100644 index 0000000..9677e8b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserResponse.java @@ -0,0 +1,19 @@ +package de.whiteo.mylfa.dto.user; + +import lombok.Builder; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +@Builder +public class UserResponse { + + private UUID id; + private String email; + private boolean verified; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserUpdatePasswordRequest.java b/src/main/java/de/whiteo/mylfa/dto/user/UserUpdatePasswordRequest.java new file mode 100644 index 0000000..219d3f2 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserUpdatePasswordRequest.java @@ -0,0 +1,24 @@ +package de.whiteo.mylfa.dto.user; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class UserUpdatePasswordRequest { + + @NotNull(message = "Password cannot be null") + @NotBlank(message = "Password cannot be blank") + @Size(min = 5, max = 30, message = "Password must be between 5 and 30 characters") + private String password; + + @NotNull(message = "Old password cannot be null") + @NotBlank(message = "Old password cannot be blank") + @Size(min = 5, max = 30, message = "Old password must be between 5 and 30 characters") + private String oldPassword; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserUpdatePropertiesRequest.java b/src/main/java/de/whiteo/mylfa/dto/user/UserUpdatePropertiesRequest.java new file mode 100644 index 0000000..269f82c --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserUpdatePropertiesRequest.java @@ -0,0 +1,17 @@ +package de.whiteo.mylfa.dto.user; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Map; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class UserUpdatePropertiesRequest { + + @NotNull(message = "Properties cannot be null") + private Map properties; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserUpdateRequest.java b/src/main/java/de/whiteo/mylfa/dto/user/UserUpdateRequest.java new file mode 100644 index 0000000..bbfbf2b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserUpdateRequest.java @@ -0,0 +1,16 @@ +package de.whiteo.mylfa.dto.user; + +import jakarta.validation.constraints.Email; +import lombok.Data; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class UserUpdateRequest { + + @Email(message = "Email should be valid", regexp = "^(?=.{1,64}@)[A-Za-z0-9_-]" + + "+(\\.[A-Za-z0-9_-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$") + private String email; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/dto/user/UserValidateRequest.java b/src/main/java/de/whiteo/mylfa/dto/user/UserValidateRequest.java new file mode 100644 index 0000000..20e4642 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/dto/user/UserValidateRequest.java @@ -0,0 +1,17 @@ +package de.whiteo.mylfa.dto.user; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Data +public class UserValidateRequest { + + @NotNull(message = "userId cannot be null") + private UUID userId; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/exception/AppRuntimeException.java b/src/main/java/de/whiteo/mylfa/exception/AppRuntimeException.java new file mode 100644 index 0000000..7a3330d --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/exception/AppRuntimeException.java @@ -0,0 +1,12 @@ +package de.whiteo.mylfa.exception; + +/** + * @author Leo Tanas (github) + */ + +public class AppRuntimeException extends RuntimeException { + + public AppRuntimeException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/exception/ErrorType.java b/src/main/java/de/whiteo/mylfa/exception/ErrorType.java new file mode 100644 index 0000000..5e3d787 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/exception/ErrorType.java @@ -0,0 +1,20 @@ +package de.whiteo.mylfa.exception; + +/** + * @author Leo Tanas (github) + */ + +public enum ErrorType { + UNDEFINED_ERROR, + BAD_REQUEST, + NOT_FOUND, + AUTHORIZATION_ERROR, + REQUEST_MESSAGE_CONVERSION_ERROR, + REQUEST_INVALID_MESSAGE, + REQUEST_TYPE_MISMATCH, + REQUEST_MISSING_PARAMETER, + UNSUPPORTED_OPERATION, + FORBIDDEN, + CONFLICT, + REQUEST_ROUTE_ERROR +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/exception/ExceptionHelper.java b/src/main/java/de/whiteo/mylfa/exception/ExceptionHelper.java new file mode 100644 index 0000000..f7743a2 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/exception/ExceptionHelper.java @@ -0,0 +1,41 @@ +package de.whiteo.mylfa.exception; + +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.util.ClassUtil; +import org.springframework.http.converter.HttpMessageConversionException; + +import java.util.stream.Collectors; + +/** + * @author Leo Tanas (github) + */ + +public class ExceptionHelper { + + public static final String UNDEFINED_FORMAT_ERROR = "Undefined format error"; + + private ExceptionHelper() { + } + + public static String getRootCauseMessage(Throwable ex) { + Throwable rootCause = ClassUtil.getRootCause(ex); + return ClassUtil.exceptionMessage(rootCause); + } + + public static String getJsonExceptionMessage(HttpMessageConversionException ex) { + if (ex == null) { + return UNDEFINED_FORMAT_ERROR; + } + + String message = getRootCauseMessage(ex); + Throwable cause = ex.getCause(); + if (cause instanceof JsonMappingException error) { + String field = error.getPath() + .stream() + .map(JsonMappingException.Reference::getFieldName) + .collect(Collectors.joining(".")); + return "Format error: " + message + " link to '" + field + "'"; + } + return message; + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/exception/ExecutionConflictException.java b/src/main/java/de/whiteo/mylfa/exception/ExecutionConflictException.java new file mode 100644 index 0000000..3e2307b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/exception/ExecutionConflictException.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +/** + * @author Leo Tanas (github) + */ + +@Getter +public class ExecutionConflictException extends RuntimeException { + + private final ErrorType errorType; + private final HttpStatus status; + + public ExecutionConflictException(String message) { + this(ErrorType.CONFLICT, HttpStatus.CONFLICT, message, null); + } + + public ExecutionConflictException(ErrorType errorType, HttpStatus status, String message, Throwable error) { + super(message, error); + this.errorType = errorType; + this.status = status; + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/exception/NotFoundObjectException.java b/src/main/java/de/whiteo/mylfa/exception/NotFoundObjectException.java new file mode 100644 index 0000000..d63f0d1 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/exception/NotFoundObjectException.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +/** + * @author Leo Tanas (github) + */ + +@Getter +public class NotFoundObjectException extends RuntimeException { + + private final ErrorType errorType; + private final HttpStatus status; + + public NotFoundObjectException(String message) { + this(ErrorType.NOT_FOUND, HttpStatus.NOT_FOUND, message, null); + } + + public NotFoundObjectException(ErrorType errorType, HttpStatus status, String message, Throwable error) { + super(message, error); + this.errorType = errorType; + this.status = status; + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/exception/ResponseError.java b/src/main/java/de/whiteo/mylfa/exception/ResponseError.java new file mode 100644 index 0000000..9bd4c52 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/exception/ResponseError.java @@ -0,0 +1,24 @@ +package de.whiteo.mylfa.exception; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * @author Leo Tanas (github) + */ + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ResponseError { + + private LocalDateTime timestamp; + private Integer status; + private String message; + private String code; +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/exception/RestExceptionHandler.java b/src/main/java/de/whiteo/mylfa/exception/RestExceptionHandler.java new file mode 100644 index 0000000..e0d9d80 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/exception/RestExceptionHandler.java @@ -0,0 +1,149 @@ +package de.whiteo.mylfa.exception; + +import jakarta.validation.ConstraintViolationException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.TypeMismatchException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import java.time.LocalDateTime; +import java.util.List; + +import static de.whiteo.mylfa.exception.ExceptionHelper.getJsonExceptionMessage; +import static de.whiteo.mylfa.exception.ExceptionHelper.getRootCauseMessage; +import static java.util.Optional.ofNullable; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CONFLICT; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@RestControllerAdvice +@RequiredArgsConstructor +public class RestExceptionHandler { + + public static ResponseEntity buildResponse(HttpStatus status, ErrorType code, String msg, + Throwable ex) { + if (NOT_FOUND.equals(status) || CONFLICT.equals(status)) { + log.warn(msg); + } else { + log.error(msg, ex); + } + return ResponseEntity.status(status.value()) + .body(ResponseError.builder() + .code(code.name()) + .message(msg) + .timestamp(LocalDateTime.now()) + .status(status.value()) + .build()); + } + + @ExceptionHandler(Throwable.class) + @ResponseStatus(INTERNAL_SERVER_ERROR) + public ResponseEntity handleUndefinedException(Throwable ex) { + String message = "Undefined error: " + getRootCauseMessage(ex); + return buildResponse(INTERNAL_SERVER_ERROR, ErrorType.UNDEFINED_ERROR, message, ex); + } + + @ExceptionHandler(HttpClientErrorException.class) + @ResponseStatus(UNAUTHORIZED) + protected ResponseEntity handleHttpClientErrorException(HttpClientErrorException ex) { + return buildResponse(UNAUTHORIZED, ErrorType.AUTHORIZATION_ERROR, ex.getMessage(), ex); + } + + @ExceptionHandler(BadCredentialsException.class) + @ResponseStatus(UNAUTHORIZED) + protected ResponseEntity handleBadCredentialsException(BadCredentialsException ex) { + return buildResponse(UNAUTHORIZED, ErrorType.AUTHORIZATION_ERROR, ex.getMessage(), ex); + } + + @ExceptionHandler(ExecutionConflictException.class) + @ResponseStatus(CONFLICT) + protected ResponseEntity handleExecutionConflictException(ExecutionConflictException ex) { + return buildResponse(CONFLICT, ErrorType.CONFLICT, ex.getMessage(), ex); + } + + @ExceptionHandler(NotFoundObjectException.class) + @ResponseStatus(NOT_FOUND) + protected ResponseEntity handleNotFoundObjectException(NotFoundObjectException ex) { + return buildResponse(NOT_FOUND, ErrorType.NOT_FOUND, ex.getMessage(), ex); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(BAD_REQUEST) + protected ResponseEntity handleMethodArgumentTypeMismatchException( + MethodArgumentTypeMismatchException ex) { + return buildResponse(BAD_REQUEST, ErrorType.BAD_REQUEST, ex.getMessage(), ex); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + @ResponseStatus(BAD_REQUEST) + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) { + return buildResponse(BAD_REQUEST, ErrorType.BAD_REQUEST, ex.getMessage(), ex); + } + + @ExceptionHandler(HttpMessageConversionException.class) + @ResponseStatus(BAD_REQUEST) + public ResponseEntity handleMessageConversionException(HttpMessageConversionException ex) { + return buildResponse(BAD_REQUEST, ErrorType.REQUEST_MESSAGE_CONVERSION_ERROR, + getJsonExceptionMessage(ex), ex); + } + + @ExceptionHandler(ServletRequestBindingException.class) + @ResponseStatus(BAD_REQUEST) + public ResponseEntity handleServletRequestBinding(ServletRequestBindingException ex) { + return buildResponse(BAD_REQUEST, ErrorType.REQUEST_MISSING_PARAMETER, + getRootCauseMessage(ex), ex); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(BAD_REQUEST) + public ResponseEntity handleConstraintViolation(ConstraintViolationException ex) { + return buildResponse(BAD_REQUEST, ErrorType.REQUEST_MISSING_PARAMETER, + getRootCauseMessage(ex), ex); + } + + @ExceptionHandler(TypeMismatchException.class) + @ResponseStatus(BAD_REQUEST) + public ResponseEntity handleTypeMismatch(TypeMismatchException ex) { + String requiredType = ofNullable(ex.getRequiredType()).map(Class::getSimpleName).orElse(""); + String message = getRootCauseMessage(ex) + ", required type '" + requiredType + "'"; + return buildResponse(BAD_REQUEST, ErrorType.REQUEST_TYPE_MISMATCH, message, ex); + } + + @ExceptionHandler(UnsupportedOperationException.class) + @ResponseStatus(BAD_REQUEST) + public ResponseEntity handleUnsupportedOperation(UnsupportedOperationException ex) { + return buildResponse(BAD_REQUEST, ErrorType.UNSUPPORTED_OPERATION, getRootCauseMessage(ex), ex); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(BAD_REQUEST) + @ResponseBody + public ResponseEntity validationError(MethodArgumentNotValidException ex) { + BindingResult result = ex.getBindingResult(); + List fieldErrors = result.getFieldErrors(); + String errors = fieldErrors.stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()).findFirst().orElse("ops!"); + return buildResponse(BAD_REQUEST, ErrorType.BAD_REQUEST, errors, ex); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/mapper/AbstractMapper.java b/src/main/java/de/whiteo/mylfa/mapper/AbstractMapper.java new file mode 100644 index 0000000..73bbd8f --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/mapper/AbstractMapper.java @@ -0,0 +1,13 @@ +package de.whiteo.mylfa.mapper; + +import org.springframework.stereotype.Component; + +/** + * @author Leo Tanas (github) + */ + +@Component +public interface AbstractMapper { + + D toResponse(E entity); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/mapper/CurrencyTypeMapper.java b/src/main/java/de/whiteo/mylfa/mapper/CurrencyTypeMapper.java new file mode 100644 index 0000000..d0e2b6b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/mapper/CurrencyTypeMapper.java @@ -0,0 +1,16 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import org.mapstruct.Mapper; + +/** + * @author Leo Tanas (github) + */ + +@Mapper(componentModel = "spring") +public interface CurrencyTypeMapper extends AbstractMapper { + + @Override + CurrencyTypeResponse toResponse(CurrencyType entity); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/mapper/ExpenseCategoryMapper.java b/src/main/java/de/whiteo/mylfa/mapper/ExpenseCategoryMapper.java new file mode 100644 index 0000000..2002dd7 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/mapper/ExpenseCategoryMapper.java @@ -0,0 +1,23 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Leo Tanas (github) + */ + +@Mapper(componentModel = "spring") +public interface ExpenseCategoryMapper extends AbstractMapper { + + @Override + ExpenseCategoryResponse toResponse(ExpenseCategory entity); + + @Mapping(target = "id", source = "entity.id") + @Mapping(target = "name", source = "entity.name") + @Mapping(target = "hide", source = "entity.hide") + @Mapping(target = "parent", source = "parent") + ExpenseCategoryResponse toResponse(ExpenseCategory entity, ExpenseCategoryResponse parent); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/mapper/ExpenseMapper.java b/src/main/java/de/whiteo/mylfa/mapper/ExpenseMapper.java new file mode 100644 index 0000000..338ce96 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/mapper/ExpenseMapper.java @@ -0,0 +1,26 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.domain.Expense; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.expense.ExpenseResponse; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Leo Tanas (github) + */ + +@Mapper(componentModel = "spring") +public interface ExpenseMapper extends AbstractMapper { + + @Override + ExpenseResponse toResponse(Expense entity); + + @Mapping(target = "id", source = "entity.id") + @Mapping(target = "amount", source = "entity.amount") + @Mapping(target = "description", source = "entity.description") + @Mapping(target = "currencyType", source = "currencyType") + @Mapping(target = "category", source = "category") + ExpenseResponse toResponse(Expense entity, CurrencyTypeResponse currencyType, ExpenseCategoryResponse category); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/mapper/IncomeCategoryMapper.java b/src/main/java/de/whiteo/mylfa/mapper/IncomeCategoryMapper.java new file mode 100644 index 0000000..3714d55 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/mapper/IncomeCategoryMapper.java @@ -0,0 +1,23 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Leo Tanas (github) + */ + +@Mapper(componentModel = "spring") +public interface IncomeCategoryMapper extends AbstractMapper { + + @Override + IncomeCategoryResponse toResponse(IncomeCategory entity); + + @Mapping(target = "id", source = "entity.id") + @Mapping(target = "name", source = "entity.name") + @Mapping(target = "hide", source = "entity.hide") + @Mapping(target = "parent", source = "parent") + IncomeCategoryResponse toResponse(IncomeCategory entity, IncomeCategoryResponse parent); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/mapper/IncomeMapper.java b/src/main/java/de/whiteo/mylfa/mapper/IncomeMapper.java new file mode 100644 index 0000000..46ec755 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/mapper/IncomeMapper.java @@ -0,0 +1,26 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.domain.Income; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.income.IncomeResponse; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Leo Tanas (github) + */ + +@Mapper(componentModel = "spring") +public interface IncomeMapper extends AbstractMapper { + + @Override + IncomeResponse toResponse(Income entity); + + @Mapping(target = "id", source = "entity.id") + @Mapping(target = "amount", source = "entity.amount") + @Mapping(target = "description", source = "entity.description") + @Mapping(target = "currencyType", source = "currencyType") + @Mapping(target = "category", source = "category") + IncomeResponse toResponse(Income entity, CurrencyTypeResponse currencyType, IncomeCategoryResponse category); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/mapper/UserMapper.java b/src/main/java/de/whiteo/mylfa/mapper/UserMapper.java new file mode 100644 index 0000000..faaabd9 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/mapper/UserMapper.java @@ -0,0 +1,22 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.user.UserLoginResponse; +import de.whiteo.mylfa.dto.user.UserResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Leo Tanas (github) + */ + +@Mapper(componentModel = "spring") +public interface UserMapper extends AbstractMapper { + + @Override + UserResponse toResponse(User entity); + + @Mapping(target = "token", source = "token") + @Mapping(target = "user", source = "user") + UserLoginResponse toLoginResponse(UserResponse user, String token); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/repository/AbstractRepository.java b/src/main/java/de/whiteo/mylfa/repository/AbstractRepository.java new file mode 100644 index 0000000..ced5280 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/repository/AbstractRepository.java @@ -0,0 +1,48 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.domain.AbstractEntity; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Primary +public interface AbstractRepository extends JpaRepository { + + default E getByIdAndUserId(UUID id, UUID userId) { + if (null == id) { + throw new NotFoundObjectException("ID is blank"); + } + if (null == userId) { + throw new NotFoundObjectException("User ID is blank"); + } + return findByIdAndUserId(id, userId).orElseThrow(() -> new NotFoundObjectException(String.format( + "Object ID = %s not found", id))); + } + + default E getOrThrow(UUID id) { + if (null == id) { + throw new NotFoundObjectException("ID is blank"); + } + return findById(id).orElseThrow(() -> new NotFoundObjectException(String.format( + "Object ID = %s not found", id))); + } + + default E getOrNull(UUID id) { + if (null == id) { + return null; + } + return findById(id).orElseThrow(() -> new NotFoundObjectException(String.format( + "Object ID = %s not found", id))); + } + + Optional findByIdAndUserId(UUID id, UUID userId); + + void deleteAllByUserId(UUID userId); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/repository/CurrencyTypeRepository.java b/src/main/java/de/whiteo/mylfa/repository/CurrencyTypeRepository.java new file mode 100644 index 0000000..156730b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/repository/CurrencyTypeRepository.java @@ -0,0 +1,24 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.domain.CurrencyType; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +public interface CurrencyTypeRepository extends AbstractRepository { + + Optional findByNameIgnoreCase(String name); + + @Query(value = "SELECT c FROM CurrencyType c " + + "WHERE c.userId = :userId " + + "AND (:hide IS NULL OR c.hide = :hide )") + Page findAllByUserIdAndHide(@Param("userId") UUID userId, @Param("hide") Boolean hide, Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/repository/ExpenseCategoryRepository.java b/src/main/java/de/whiteo/mylfa/repository/ExpenseCategoryRepository.java new file mode 100644 index 0000000..b64e577 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/repository/ExpenseCategoryRepository.java @@ -0,0 +1,32 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.domain.ExpenseCategory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +public interface ExpenseCategoryRepository extends AbstractRepository { + + Optional findByNameIgnoreCase(String name); + + @Query("SELECT ec, p FROM ExpenseCategory ec " + + "LEFT JOIN ExpenseCategory p ON ec.parentId = p.id AND ec.userId = :userId " + + "WHERE ec.userId = :userId " + + "AND ec.id = :id") + List findByUserIdAndId(@Param("userId") UUID userId, @Param("id") UUID id); + + @Query("SELECT ec, p FROM ExpenseCategory ec " + + "LEFT JOIN ExpenseCategory p ON ec.parentId = p.id " + + "WHERE ec.userId = :userId " + + "AND (:hide IS NULL OR ec.hide = :hide)") + Page findAllByParamsWithJoin(@Param("userId") UUID userId, @Param("hide") Boolean hide, Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/repository/ExpenseRepository.java b/src/main/java/de/whiteo/mylfa/repository/ExpenseRepository.java new file mode 100644 index 0000000..3f5837a --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/repository/ExpenseRepository.java @@ -0,0 +1,40 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.domain.Expense; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +public interface ExpenseRepository extends AbstractRepository { + + @Query("SELECT e, ct, ec FROM Expense e " + + "LEFT JOIN CurrencyType ct ON e.currencyTypeId = ct.id AND ct.userId = :userId " + + "LEFT JOIN ExpenseCategory ec ON e.categoryId = ec.id AND ec.userId = :userId " + + "WHERE e.userId = :userId " + + "AND e.id = :id") + List findByUserIdAndId(@Param("userId") UUID userId, @Param("id") UUID id); + + @Query("SELECT e, ct, ec FROM Expense e " + + "LEFT JOIN CurrencyType ct ON e.currencyTypeId = ct.id AND ct.userId = :userId " + + "LEFT JOIN ExpenseCategory ec ON e.categoryId = ec.id AND ec.userId = :userId " + + "WHERE e.userId = :userId " + + "AND (coalesce(:categoryId, NULL) IS NULL OR e.categoryId = :categoryId) " + + "AND (coalesce(:currencyTypeId, NULL) IS NULL OR e.currencyTypeId = :currencyTypeId) " + + "AND (coalesce(:startDate, NULL) IS NULL OR e.creationDate >= :startDate) " + + "AND (coalesce(:endDate, NULL) IS NULL OR e.creationDate <= :endDate)") + Page findAllByParamsWithJoin(@Param("userId") UUID userId, + @Param("categoryId") UUID categoryId, + @Param("currencyTypeId") UUID currencyTypeId, + @Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate, + Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/repository/IncomeCategoryRepository.java b/src/main/java/de/whiteo/mylfa/repository/IncomeCategoryRepository.java new file mode 100644 index 0000000..b2210d2 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/repository/IncomeCategoryRepository.java @@ -0,0 +1,32 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.domain.IncomeCategory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +public interface IncomeCategoryRepository extends AbstractRepository { + + Optional findByNameIgnoreCase(String name); + + @Query("SELECT ic, p FROM IncomeCategory ic " + + "LEFT JOIN IncomeCategory p ON ic.parentId = p.id AND ic.userId = :userId " + + "WHERE ic.userId = :userId " + + "AND ic.id = :id") + List findByUserIdAndId(@Param("userId") UUID userId, @Param("id") UUID id); + + @Query("SELECT ic, p FROM IncomeCategory ic " + + "LEFT JOIN ExpenseCategory p ON ic.parentId = p.id " + + "WHERE ic.userId = :userId " + + "AND (:hide IS NULL OR ic.hide = :hide)") + Page findAllByParamsWithJoin(@Param("userId") UUID userId, @Param("hide") Boolean hide, Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/repository/IncomeRepository.java b/src/main/java/de/whiteo/mylfa/repository/IncomeRepository.java new file mode 100644 index 0000000..adc5436 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/repository/IncomeRepository.java @@ -0,0 +1,40 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.domain.Income; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +public interface IncomeRepository extends AbstractRepository { + + @Query("SELECT i, ct, ic FROM Income i " + + "LEFT JOIN CurrencyType ct ON i.currencyTypeId = ct.id AND ct.userId = :userId " + + "LEFT JOIN IncomeCategory ic ON i.categoryId = ic.id AND ic.userId = :userId " + + "WHERE i.userId = :userId " + + "AND i.id = :id") + List findByUserIdAndId(@Param("userId") UUID userId, @Param("id") UUID id); + + @Query("SELECT i, ct, ic FROM Income i " + + "LEFT JOIN CurrencyType ct ON i.currencyTypeId = ct.id AND ct.userId = :userId " + + "LEFT JOIN IncomeCategory ic ON i.categoryId = ic.id AND ic.userId = :userId " + + "WHERE i.userId = :userId " + + "AND (coalesce(:categoryId, NULL) IS NULL OR i.categoryId = :categoryId) " + + "AND (coalesce(:currencyTypeId, NULL) IS NULL OR i.currencyTypeId = :currencyTypeId) " + + "AND (coalesce(:startDate, NULL) IS NULL OR i.creationDate >= :startDate) " + + "AND (coalesce(:endDate, NULL) IS NULL OR i.creationDate <= :endDate)") + Page findAllByParamsWithJoin(@Param("userId") UUID userId, + @Param("categoryId") UUID categoryId, + @Param("currencyTypeId") UUID currencyTypeId, + @Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate, + Pageable pageable); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/repository/UserRepository.java b/src/main/java/de/whiteo/mylfa/repository/UserRepository.java new file mode 100644 index 0000000..d06b40e --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/repository/UserRepository.java @@ -0,0 +1,25 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +public interface UserRepository extends JpaRepository { + + default User getOrThrow(UUID id) { + if (null == id) { + throw new NotFoundObjectException("ID is blank"); + } + return findById(id).orElseThrow(() -> new NotFoundObjectException(String.format( + "Object ID = %s not found", id))); + } + + Optional findByEmailIgnoreCase(String email); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/security/Auth.java b/src/main/java/de/whiteo/mylfa/security/Auth.java new file mode 100644 index 0000000..b28c87a --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/security/Auth.java @@ -0,0 +1,19 @@ +package de.whiteo.mylfa.security; + +import org.springframework.web.servlet.HandlerInterceptor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Leo Tanas (github) + */ + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Auth { + + Class value(); +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/security/AuthInterceptor.java b/src/main/java/de/whiteo/mylfa/security/AuthInterceptor.java new file mode 100644 index 0000000..32c69c9 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/security/AuthInterceptor.java @@ -0,0 +1,59 @@ +package de.whiteo.mylfa.security; + +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author Leo Tanas (github) + */ + +@Component +@RequiredArgsConstructor +@Order(Ordered.HIGHEST_PRECEDENCE) +public class AuthInterceptor implements HandlerInterceptor { + + private final ThreadLocal users = new ThreadLocal<>(); + private final JwtTokenUtil jwtTokenUtil; + + @Override + public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, + @NonNull Object handler) throws Exception { + if (request.getServletPath().equals("/version") + || request.getServletPath().equals("/api/v1/authenticate") + || request.getServletPath().equals("/api/v1/user/create") + || request.getRequestURI().equals("/api/v1/authenticate") + || request.getRequestURI().equals("/api/v1/user/create")) { + return true; + } + + String token = jwtTokenUtil.getToken(request); + if (jwtTokenUtil.validateToken(token)) { + String user = jwtTokenUtil.getUser(token); + if (StringUtils.isNotBlank(user)) { + users.set(user); + return true; + } + } + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + return false; + } + + public String getUserName() { + return users.get(); + } + + @Override + public void postHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, + @NonNull Object handler, ModelAndView modelAndView) { + users.remove(); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/security/JwtAuthenticationFilter.java b/src/main/java/de/whiteo/mylfa/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..f5f71bd --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/security/JwtAuthenticationFilter.java @@ -0,0 +1,35 @@ +package de.whiteo.mylfa.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.dto.user.UserLoginRequest; +import de.whiteo.mylfa.exception.AppRuntimeException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import java.io.IOException; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException { + try { + UserLoginRequest loginRequest = new ObjectMapper().readValue(request.getInputStream(), UserLoginRequest.class); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + loginRequest.getEmail(), loginRequest.getPassword()); + return getAuthenticationManager().authenticate(authToken); + } catch (IOException e) { + throw new AppRuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/security/UserDetailsImpl.java b/src/main/java/de/whiteo/mylfa/security/UserDetailsImpl.java new file mode 100644 index 0000000..e2d8bbf --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/security/UserDetailsImpl.java @@ -0,0 +1,56 @@ +package de.whiteo.mylfa.security; + +import lombok.Builder; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Builder +public class UserDetailsImpl implements UserDetails { + + private UUID id; + private String name; + private char[] password; + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + @Override + public String getPassword() { + return String.valueOf(password); + } + + @Override + public String getUsername() { + return name; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/AbstractService.java b/src/main/java/de/whiteo/mylfa/service/AbstractService.java new file mode 100644 index 0000000..dde63d7 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/AbstractService.java @@ -0,0 +1,46 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.config.NoModifyDemoMode; +import de.whiteo.mylfa.domain.AbstractEntity; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.exception.ExecutionConflictException; +import de.whiteo.mylfa.mapper.AbstractMapper; +import de.whiteo.mylfa.repository.AbstractRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Service +@RequiredArgsConstructor +public abstract class AbstractService { + + private final AbstractRepository repository; + private final AbstractMapper mapper; + private final UserService userService; + + public D findById(String userName, UUID id) { + User user = userService.findByEmail(userName); + return mapper.toResponse(repository.getByIdAndUserId(id, user.getId())); + } + + @NoModifyDemoMode + public void delete(String userName, UUID id) { + User user = userService.findByEmail(userName); + repository.delete(repository.getByIdAndUserId(id, user.getId())); + } + + public abstract D create(String userName, R request); + + public abstract D update(String userName, UUID id, R request); + + protected void checkCurrencyTypeIds(UUID id1, UUID id2) { + if (!id1.equals(id2)) { + throw new ExecutionConflictException("Currency type IDs are different"); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/CurrencyTypeService.java b/src/main/java/de/whiteo/mylfa/service/CurrencyTypeService.java new file mode 100644 index 0000000..b5c1ab4 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/CurrencyTypeService.java @@ -0,0 +1,80 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.config.NoModifyDemoMode; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.exception.ExecutionConflictException; +import de.whiteo.mylfa.mapper.CurrencyTypeMapper; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@Service +public class CurrencyTypeService extends + AbstractService { + + private final CurrencyTypeRepository repository; + private final CurrencyTypeMapper mapper; + private final UserService userService; + + public CurrencyTypeService(CurrencyTypeRepository repository, CurrencyTypeMapper mapper, UserService userService) { + super(repository, mapper, userService); + + this.userService = userService; + this.repository = repository; + this.mapper = mapper; + } + + public Page findAll(String userName, Boolean hide, Pageable pageable) { + User user = userService.findByEmail(userName); + + return repository.findAllByUserIdAndHide(user.getId(), hide, pageable).map(mapper::toResponse); + } + + @NoModifyDemoMode + public CurrencyTypeResponse create(String userName, CurrencyTypeCreateOrUpdateRequest request) { + checkUniqueName(request.getName()); + + User user = userService.findByEmail(userName); + + CurrencyType currencyType = new CurrencyType(); + currencyType.setName(request.getName()); + currencyType.setHide(request.isHide()); + currencyType.setUserId(user.getId()); + return mapper.toResponse(repository.save(currencyType)); + } + + @NoModifyDemoMode + public CurrencyTypeResponse update(String userName, UUID id, CurrencyTypeCreateOrUpdateRequest request) { + checkUniqueName(request.getName()); + + User user = userService.findByEmail(userName); + + CurrencyType currencyType = repository.getByIdAndUserId(id, user.getId()); + currencyType.setName(request.getName()); + currencyType.setHide(request.isHide()); + return mapper.toResponse(repository.save(currencyType)); + } + + private void checkUniqueName(String name) { + if (StringUtils.isNotBlank(name)) { + repository.findByNameIgnoreCase(name).ifPresent( + existingEntity -> { + throw new ExecutionConflictException("Object named '" + + name + "' already exists in the system"); + }); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/ExpenseCategoryService.java b/src/main/java/de/whiteo/mylfa/service/ExpenseCategoryService.java new file mode 100644 index 0000000..4369c1f --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/ExpenseCategoryService.java @@ -0,0 +1,118 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.config.NoModifyDemoMode; +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import de.whiteo.mylfa.exception.ExecutionConflictException; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import de.whiteo.mylfa.mapper.ExpenseCategoryMapper; +import de.whiteo.mylfa.repository.ExpenseCategoryRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@Service +public class ExpenseCategoryService extends + AbstractService { + + private final ExpenseCategoryRepository repository; + private final ExpenseCategoryMapper mapper; + private final UserService userService; + + public ExpenseCategoryService(ExpenseCategoryRepository repository, ExpenseCategoryMapper mapper, + UserService userService) { + super(repository, mapper, userService); + + this.userService = userService; + this.repository = repository; + this.mapper = mapper; + } + + public Page findAll(String userName, Boolean hide, Pageable pageable) { + User user = userService.findByEmail(userName); + + Page page = repository.findAllByParamsWithJoin(user.getId(), hide, pageable); + + List responses = page.stream() + .map(this::mapObjectsToResponse).collect(Collectors.toList()); + + return new PageImpl<>(responses, pageable, page.getTotalElements()); + } + + @Override + public ExpenseCategoryResponse findById(String userName, UUID id) { + User user = userService.findByEmail(userName); + + List result = repository.findByUserIdAndId(user.getId(), id); + if (result.isEmpty()) { + throw new NotFoundObjectException("Expense category with specified ID not found"); + } + + return mapObjectsToResponse(result.get(0)); + } + + @NoModifyDemoMode + public ExpenseCategoryResponse create(String userName, ExpenseCategoryCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + checkUniqueName(request.getName()); + + ExpenseCategory parentCategory = repository.getOrNull(request.getParentId()); + + ExpenseCategory expenseCategory = new ExpenseCategory(); + expenseCategory.setName(request.getName()); + expenseCategory.setHide(request.isHide()); + expenseCategory.setParentId(request.getParentId()); + expenseCategory.setUserId(user.getId()); + return mapper.toResponse(repository.save(expenseCategory), mapper.toResponse(parentCategory)); + } + + @NoModifyDemoMode + public ExpenseCategoryResponse update(String userName, UUID id, ExpenseCategoryCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + checkUniqueName(request.getName()); + + ExpenseCategory parentCategory = repository.getOrNull(request.getParentId()); + + ExpenseCategory expenseCategory = repository.getByIdAndUserId(id, user.getId()); + expenseCategory.setName(request.getName()); + expenseCategory.setHide(request.isHide()); + expenseCategory.setParentId(request.getParentId()); + return mapper.toResponse(repository.save(expenseCategory), mapper.toResponse(parentCategory)); + } + + private void checkUniqueName(String name) { + if (StringUtils.isNotBlank(name)) { + repository.findByNameIgnoreCase(name).ifPresent( + existingEntity -> { + throw new ExecutionConflictException("Object named '" + + name + "' already exists in the system"); + }); + } + } + + private ExpenseCategoryResponse mapObjectsToResponse(Object[] objects) { + ExpenseCategory category = (ExpenseCategory) objects[0]; + ExpenseCategory parentCategory = (ExpenseCategory) objects[1]; + ExpenseCategoryResponse categoryResponse = mapper.toResponse(category); + if (null != parentCategory) { + categoryResponse.setParent(mapper.toResponse(parentCategory)); + } + return categoryResponse; + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/ExpenseService.java b/src/main/java/de/whiteo/mylfa/service/ExpenseService.java new file mode 100644 index 0000000..437aa63 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/ExpenseService.java @@ -0,0 +1,133 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.Expense; +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.expense.ExpenseCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.expense.ExpenseResponse; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import de.whiteo.mylfa.mapper.CurrencyTypeMapper; +import de.whiteo.mylfa.mapper.ExpenseCategoryMapper; +import de.whiteo.mylfa.mapper.ExpenseMapper; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.ExpenseCategoryRepository; +import de.whiteo.mylfa.repository.ExpenseRepository; +import de.whiteo.mylfa.util.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@Service +public class ExpenseService extends AbstractService { + + private final CurrencyTypeRepository currencyTypeRepository; + private final ExpenseCategoryRepository categoryRepository; + private final CurrencyTypeMapper currencyTypeMapper; + private final ExpenseCategoryMapper categoryMapper; + private final ExpenseRepository repository; + private final UserService userService; + private final ExpenseMapper mapper; + + public ExpenseService(ExpenseCategoryRepository categoryRepository, CurrencyTypeRepository currencyTypeRepository, + CurrencyTypeMapper currencyTypeMapper, ExpenseCategoryMapper categoryMapper, + ExpenseRepository repository, UserService userService, ExpenseMapper mapper) { + super(repository, mapper, userService); + + this.categoryRepository = categoryRepository; + this.currencyTypeRepository = currencyTypeRepository; + this.currencyTypeMapper = currencyTypeMapper; + this.categoryMapper = categoryMapper; + this.userService = userService; + this.repository = repository; + this.mapper = mapper; + } + + public Page findAll(String userName, UUID categoryId, UUID currencyTypeId, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { + User user = userService.findByEmail(userName); + + Page page = repository.findAllByParamsWithJoin(user.getId(), + categoryId, + currencyTypeId, + DateUtil.setCorrectTime(startDate, true), + DateUtil.setCorrectTime(endDate, false), + pageable); + + List responses = page.getContent() + .stream() + .map(this::mapObjectsToResponse).toList(); + + return new PageImpl<>(responses, pageable, page.getTotalElements()); + } + + @Override + public ExpenseResponse findById(String userName, UUID id) { + User user = userService.findByEmail(userName); + + List result = repository.findByUserIdAndId(user.getId(), id); + if (result.isEmpty()) { + throw new NotFoundObjectException("Expense with specified ID not found"); + } + + return mapObjectsToResponse(result.get(0)); + } + + public ExpenseResponse create(String userName, ExpenseCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + ExpenseCategory category = categoryRepository.getOrThrow(request.getCategoryId()); + CurrencyType currencyType = currencyTypeRepository.getOrThrow(request.getCurrencyTypeId()); + + Expense expense = new Expense(); + expense.setCategoryId(category.getId()); + expense.setDescription(request.getDescription()); + expense.setCurrencyTypeId(currencyType.getId()); + expense.setAmount(request.getAmount()); + expense.setUserId(user.getId()); + return mapper.toResponse( + repository.save(expense), + currencyTypeMapper.toResponse(currencyType), + categoryMapper.toResponse(category)); + } + + public ExpenseResponse update(String userName, UUID id, ExpenseCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + Expense expense = repository.getByIdAndUserId(id, user.getId()); + ExpenseCategory category = categoryRepository.getOrThrow(request.getCategoryId()); + CurrencyType currencyType = currencyTypeRepository.getOrThrow(request.getCurrencyTypeId()); + + checkCurrencyTypeIds(expense.getCurrencyTypeId(), currencyType.getId()); + + expense.setCategoryId(category.getId()); + expense.setDescription(request.getDescription()); + expense.setAmount(request.getAmount()); + return mapper.toResponse( + repository.save(expense), + currencyTypeMapper.toResponse(currencyType), + categoryMapper.toResponse(category)); + } + + private ExpenseResponse mapObjectsToResponse(Object[] objects) { + Expense expense = (Expense) objects[0]; + CurrencyType currencyType = (CurrencyType) objects[1]; + ExpenseCategory category = (ExpenseCategory) objects[2]; + CurrencyTypeResponse currencyTypeResponse = currencyTypeMapper.toResponse(currencyType); + ExpenseCategoryResponse categoryResponse = categoryMapper.toResponse(category); + return mapper.toResponse(expense, currencyTypeResponse, categoryResponse); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/ImageService.java b/src/main/java/de/whiteo/mylfa/service/ImageService.java new file mode 100644 index 0000000..e98fe81 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/ImageService.java @@ -0,0 +1,85 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.domain.User; +import lombok.RequiredArgsConstructor; +import net.sourceforge.tess4j.Tesseract; +import net.sourceforge.tess4j.TesseractException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.imageio.ImageIO; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Leo Tanas (github) + */ + +@Service +@RequiredArgsConstructor +public class ImageService { + + private final UserService userService; + + @Value("${app.lang-directory}") + private String directory; + + public String getSumFromImage(String userName, MultipartFile image) { + User user = userService.findByEmail(userName); + String lang = userService.getLangProperty(user); + + CompletableFuture ocrResultFuture = parseImage(lang, image); + + try { + String ocrResult = ocrResultFuture.join(); + return extractSumFromString(ocrResult); + } catch (Exception e) { + System.out.println("OCR processing error: " + e.getMessage()); + return "0"; + } + } + + @Async + protected CompletableFuture parseImage(String lang, MultipartFile image) { + Tesseract tesseract = new Tesseract(); + tesseract.setPageSegMode(1); + tesseract.setOcrEngineMode(1); + tesseract.setVariable("user_defined_dpi", "300"); + tesseract.setVariable("debug_file", getNullDevicePath()); + tesseract.setLanguage(lang); + tesseract.setDatapath(directory); + + try { + byte[] bytes; + bytes = image.getBytes(); + InputStream is = new ByteArrayInputStream(bytes); + return CompletableFuture.completedFuture(tesseract.doOCR(ImageIO.read(is))); + } catch (TesseractException | IOException e) { + throw new RuntimeException(e); + } + } + + private String extractSumFromString(String text) { + Pattern pattern = Pattern.compile("\\b\\d+[.,]\\d{2}\\b"); + Matcher matcher = pattern.matcher(text); + if (matcher.find()) { + return matcher.group(); + } + return "0"; + } + + private String getNullDevicePath() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) { + return "nul"; + } else { + return "/dev/null"; + } + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/IncomeCategoryService.java b/src/main/java/de/whiteo/mylfa/service/IncomeCategoryService.java new file mode 100644 index 0000000..bc09896 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/IncomeCategoryService.java @@ -0,0 +1,118 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.config.NoModifyDemoMode; +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import de.whiteo.mylfa.exception.ExecutionConflictException; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import de.whiteo.mylfa.mapper.IncomeCategoryMapper; +import de.whiteo.mylfa.repository.IncomeCategoryRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@Service +public class IncomeCategoryService extends + AbstractService { + + private final IncomeCategoryRepository repository; + private final IncomeCategoryMapper mapper; + private final UserService userService; + + public IncomeCategoryService(IncomeCategoryRepository repository, IncomeCategoryMapper mapper, + UserService userService) { + super(repository, mapper, userService); + + this.userService = userService; + this.repository = repository; + this.mapper = mapper; + } + + public Page findAll(String userName, Boolean hide, Pageable pageable) { + User user = userService.findByEmail(userName); + + Page page = repository.findAllByParamsWithJoin(user.getId(), hide, pageable); + + List responses = page.stream() + .map(this::mapObjectsToResponse).collect(Collectors.toList()); + + return new PageImpl<>(responses, pageable, page.getTotalElements()); + } + + @Override + public IncomeCategoryResponse findById(String userName, UUID id) { + User user = userService.findByEmail(userName); + + List result = repository.findByUserIdAndId(user.getId(), id); + if (result.isEmpty()) { + throw new NotFoundObjectException("Income category with specified ID not found"); + } + + return mapObjectsToResponse(result.get(0)); + } + + @NoModifyDemoMode + public IncomeCategoryResponse create(String userName, IncomeCategoryCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + checkUniqueName(request.getName()); + + IncomeCategory parentCategory = repository.getOrNull(request.getParentId()); + + IncomeCategory incomeCategory = new IncomeCategory(); + incomeCategory.setName(request.getName()); + incomeCategory.setHide(request.isHide()); + incomeCategory.setUserId(user.getId()); + incomeCategory.setParentId(request.getParentId()); + return mapper.toResponse(repository.save(incomeCategory), mapper.toResponse(parentCategory)); + } + + @NoModifyDemoMode + public IncomeCategoryResponse update(String userName, UUID id, IncomeCategoryCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + checkUniqueName(request.getName()); + + IncomeCategory parentCategory = repository.getOrNull(request.getParentId()); + + IncomeCategory incomeCategory = repository.getByIdAndUserId(id, user.getId()); + incomeCategory.setName(request.getName()); + incomeCategory.setHide(request.isHide()); + incomeCategory.setParentId(request.getParentId()); + return mapper.toResponse(repository.save(incomeCategory), mapper.toResponse(parentCategory)); + } + + private void checkUniqueName(String name) { + if (StringUtils.isNotBlank(name)) { + repository.findByNameIgnoreCase(name).ifPresent( + existingEntity -> { + throw new ExecutionConflictException("Object named '" + + name + "' already exists in the system"); + }); + } + } + + private IncomeCategoryResponse mapObjectsToResponse(Object[] objects) { + IncomeCategory category = (IncomeCategory) objects[0]; + IncomeCategory parentCategory = (IncomeCategory) objects[1]; + IncomeCategoryResponse categoryResponse = mapper.toResponse(category); + if (null != parentCategory) { + categoryResponse.setParent(mapper.toResponse(parentCategory)); + } + return categoryResponse; + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/IncomeService.java b/src/main/java/de/whiteo/mylfa/service/IncomeService.java new file mode 100644 index 0000000..aa2d268 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/IncomeService.java @@ -0,0 +1,133 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.Income; +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.income.IncomeCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.income.IncomeResponse; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import de.whiteo.mylfa.mapper.CurrencyTypeMapper; +import de.whiteo.mylfa.mapper.IncomeCategoryMapper; +import de.whiteo.mylfa.mapper.IncomeMapper; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.IncomeCategoryRepository; +import de.whiteo.mylfa.repository.IncomeRepository; +import de.whiteo.mylfa.util.DateUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@Service +public class IncomeService extends AbstractService { + + private final CurrencyTypeRepository currencyTypeRepository; + private final IncomeCategoryRepository categoryRepository; + private final CurrencyTypeMapper currencyTypeMapper; + private final IncomeCategoryMapper categoryMapper; + private final IncomeRepository repository; + private final UserService userService; + private final IncomeMapper mapper; + + public IncomeService(IncomeCategoryRepository categoryRepository, CurrencyTypeRepository currencyTypeRepository, + CurrencyTypeMapper currencyTypeMapper, IncomeCategoryMapper categoryMapper, + IncomeRepository repository, UserService userService, IncomeMapper mapper) { + super(repository, mapper, userService); + + this.categoryRepository = categoryRepository; + this.currencyTypeRepository = currencyTypeRepository; + this.currencyTypeMapper = currencyTypeMapper; + this.categoryMapper = categoryMapper; + this.userService = userService; + this.repository = repository; + this.mapper = mapper; + } + + public Page findAll(String userName, UUID categoryId, UUID currencyTypeId, + LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) { + User user = userService.findByEmail(userName); + + Page page = repository.findAllByParamsWithJoin(user.getId(), + categoryId, + currencyTypeId, + DateUtil.setCorrectTime(startDate, true), + DateUtil.setCorrectTime(endDate, false), + pageable); + + List responses = page.getContent() + .stream() + .map(this::mapObjectsToResponse).toList(); + + return new PageImpl<>(responses, pageable, page.getTotalElements()); + } + + @Override + public IncomeResponse findById(String userName, UUID id) { + User user = userService.findByEmail(userName); + + List result = repository.findByUserIdAndId(user.getId(), id); + if (result.isEmpty()) { + throw new NotFoundObjectException("Income with specified ID not found"); + } + + return mapObjectsToResponse(result.get(0)); + } + + public IncomeResponse create(String userName, IncomeCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + IncomeCategory category = categoryRepository.getOrThrow(request.getCategoryId()); + CurrencyType currencyType = currencyTypeRepository.getOrThrow(request.getCurrencyTypeId()); + + Income income = new Income(); + income.setCategoryId(category.getId()); + income.setDescription(request.getDescription()); + income.setCurrencyTypeId(currencyType.getId()); + income.setAmount(request.getAmount()); + income.setUserId(user.getId()); + return mapper.toResponse( + repository.save(income), + currencyTypeMapper.toResponse(currencyType), + categoryMapper.toResponse(category)); + } + + public IncomeResponse update(String userName, UUID id, IncomeCreateOrUpdateRequest request) { + User user = userService.findByEmail(userName); + + Income income = repository.getByIdAndUserId(id, user.getId()); + IncomeCategory category = categoryRepository.getOrThrow(request.getCategoryId()); + CurrencyType currencyType = currencyTypeRepository.getOrThrow(request.getCurrencyTypeId()); + + checkCurrencyTypeIds(income.getCurrencyTypeId(), currencyType.getId()); + + income.setCategoryId(category.getId()); + income.setDescription(request.getDescription()); + income.setAmount(request.getAmount()); + return mapper.toResponse( + repository.save(income), + currencyTypeMapper.toResponse(currencyType), + categoryMapper.toResponse(category)); + } + + private IncomeResponse mapObjectsToResponse(Object[] objects) { + Income income = (Income) objects[0]; + CurrencyType currencyType = (CurrencyType) objects[1]; + IncomeCategory category = (IncomeCategory) objects[2]; + CurrencyTypeResponse currencyTypeResponse = currencyTypeMapper.toResponse(currencyType); + IncomeCategoryResponse categoryResponse = categoryMapper.toResponse(category); + return mapper.toResponse(income, currencyTypeResponse, categoryResponse); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/LanguageService.java b/src/main/java/de/whiteo/mylfa/service/LanguageService.java new file mode 100644 index 0000000..d1e31f4 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/LanguageService.java @@ -0,0 +1,67 @@ +package de.whiteo.mylfa.service; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@Service +@RequiredArgsConstructor +public class LanguageService { + + @Value("${app.lang-directory}") + private String directory; + @Value("${app.lang-repository}") + private String repository; + @Value("${app.lang-file-extension}") + private String fileExtension; + + @Async + public void download(String lang) { + String fileName = lang + fileExtension; + + if (fileExist(directory + fileName)) { + log.info("File '{}' is already exist", fileName); + return; + } + + try { + log.info("Downloading file '{}' has been started", fileName); + URL url = new URL(repository + fileName); + try (BufferedInputStream in = new BufferedInputStream(url.openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(directory + fileName)) { + byte[] dataBuffer = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + } + log.info("Downloading file '{}' has benn completed", fileName); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @PostConstruct + private void init() { + download("osd"); + } + + private boolean fileExist(String fileName) { + File file = new File(fileName); + return file.exists(); + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/service/UserService.java b/src/main/java/de/whiteo/mylfa/service/UserService.java new file mode 100644 index 0000000..a221a81 --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/service/UserService.java @@ -0,0 +1,168 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.config.NoModifyDemoMode; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.user.UserCreateRequest; +import de.whiteo.mylfa.dto.user.UserLoginRequest; +import de.whiteo.mylfa.dto.user.UserLoginResponse; +import de.whiteo.mylfa.dto.user.UserResponse; +import de.whiteo.mylfa.dto.user.UserUpdatePasswordRequest; +import de.whiteo.mylfa.dto.user.UserUpdatePropertiesRequest; +import de.whiteo.mylfa.dto.user.UserUpdateRequest; +import de.whiteo.mylfa.exception.ExecutionConflictException; +import de.whiteo.mylfa.mapper.UserMapper; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.ExpenseCategoryRepository; +import de.whiteo.mylfa.repository.ExpenseRepository; +import de.whiteo.mylfa.repository.IncomeCategoryRepository; +import de.whiteo.mylfa.repository.IncomeRepository; +import de.whiteo.mylfa.repository.UserRepository; +import de.whiteo.mylfa.security.UserDetailsImpl; +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Service +@RequiredArgsConstructor +public class UserService implements UserDetailsService { + + private final IncomeCategoryRepository incomeCategoryRepository; + private final ExpenseCategoryRepository expenseCategoryRepository; + private final CurrencyTypeRepository currencyTypeRepository; + private final ExpenseRepository expenseRepository; + private final IncomeRepository incomeRepository; + private final LanguageService languageService; + private final JwtTokenUtil jwtTokenUtil; + private final UserRepository repository; + private final UserMapper mapper; + + public UserResponse find(UUID id) { + return mapper.toResponse(repository.getOrThrow(id)); + } + + @Transactional + @NoModifyDemoMode + public void delete(UUID id) { + User user = repository.getOrThrow(id); + + incomeCategoryRepository.deleteAllByUserId(user.getId()); + incomeRepository.deleteAllByUserId(user.getId()); + + expenseCategoryRepository.deleteAllByUserId(user.getId()); + expenseRepository.deleteAllByUserId(user.getId()); + + currencyTypeRepository.deleteAllByUserId(user.getId()); + + repository.delete(user); + } + + @NoModifyDemoMode + public UserResponse create(UserCreateRequest request) { + checkUniqueEmail(request.getEmail()); + + String lang = request.getProperties().get("lang"); + languageService.download(lang); + + User user = new User(); + user.setEmail(request.getEmail()); + user.setVerified(false); + user.setPassword(request.getPassword().toCharArray()); + user.setProperties(request.getProperties()); + return mapper.toResponse(repository.save(user)); + } + + @NoModifyDemoMode + public UserResponse update(UUID id, UserUpdateRequest request) { + User user = repository.getOrThrow(id); + + checkUniqueEmail(request.getEmail()); + + user.setVerified(false); + user.setEmail(request.getEmail()); + return mapper.toResponse(repository.save(user)); + } + + @NoModifyDemoMode + public UserResponse updateProperties(UUID id, UserUpdatePropertiesRequest updateRequest) { + if (!updateRequest.getProperties().containsKey("lang")) { + throw new ExecutionConflictException("Lang property not found"); + } + + User user = repository.getOrThrow(id); + user.setProperties(updateRequest.getProperties()); + return mapper.toResponse(repository.save(user)); + } + + @NoModifyDemoMode + public UserResponse updatePassword(UUID id, UserUpdatePasswordRequest request) { + User user = repository.getOrThrow(id); + + passwdVerification(user.getPassword(), request.getOldPassword().toCharArray()); + + user.setPassword(request.getPassword().toCharArray()); + return mapper.toResponse(repository.save(user)); + } + + public String getLangProperty(User user) { + return user.getProperties().get("lang"); + } + + public UserLoginResponse getToken(UUID id, UserLoginRequest request) { + User user = null == id ? findByEmail(request.getEmail()) : repository.getOrThrow(id); + + passwdVerification(user.getPassword(), request.getPassword().toCharArray()); + + UserResponse response = mapper.toResponse(user); + + return mapper.toLoginResponse(response, jwtTokenUtil.generateToken(loadUserByUsername(user.getEmail()))); + } + + public Boolean validateToken(HttpServletRequest request) { + String token = jwtTokenUtil.getToken(request); + return jwtTokenUtil.validateToken(token); + } + + @Override + public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException { + User user = findByEmail(name); + return UserDetailsImpl.builder() + .id(user.getId()) + .name(user.getEmail()) + .password(user.getPassword()).build(); + } + + public User findByEmail(String email) { + return repository.findByEmailIgnoreCase(email) + .orElseThrow(() -> new BadCredentialsException("User not found with email: " + email)); + } + + private void passwdVerification(char[] passwd1, char[] passwd2) { + if (!Arrays.equals(passwd1, passwd2)) { + throw new ExecutionConflictException("Password verification failed"); + } + } + + private void checkUniqueEmail(String email) { + if (StringUtils.isNotBlank(email)) { + repository.findByEmailIgnoreCase(email).ifPresent( + existingEntity -> { + throw new ExecutionConflictException("Object with email '" + + email + "' already exists in the system"); + }); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/util/DateUtil.java b/src/main/java/de/whiteo/mylfa/util/DateUtil.java new file mode 100644 index 0000000..90b5a9b --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/util/DateUtil.java @@ -0,0 +1,24 @@ +package de.whiteo.mylfa.util; + +import java.time.LocalDateTime; + +/** + * @author Leo Tanas (github) + */ + +public class DateUtil { + + public static final String PATTERN_ISO_8601 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + public static LocalDateTime setCorrectTime(LocalDateTime localDateTime, boolean isStartOfDay) { + if (null == localDateTime) { + return null; + } + + if (isStartOfDay) { + return localDateTime.toLocalDate().atStartOfDay(); + } else { + return localDateTime.toLocalDate().atStartOfDay().plusDays(1).minusNanos(1); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/whiteo/mylfa/util/JwtTokenUtil.java b/src/main/java/de/whiteo/mylfa/util/JwtTokenUtil.java new file mode 100644 index 0000000..c034c7e --- /dev/null +++ b/src/main/java/de/whiteo/mylfa/util/JwtTokenUtil.java @@ -0,0 +1,84 @@ +package de.whiteo.mylfa.util; + +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@Slf4j +@Component +public class JwtTokenUtil { + + private static final String TOKEN_PREFIX = "Bearer "; + + @Value("${jwt.secret.key}") + private String secretKey; + + @Value("${jwt.expiration.time}") + private long expirationTime; + + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + claims.put("sessionId", UUID.randomUUID().toString()); + + return Jwts.builder() + .claims().empty().add(claims).and() + .subject(userDetails.getUsername()) + .expiration(new Date(System.currentTimeMillis() + expirationTime)) + .signWith(Keys.hmacShaKeyFor(secretKey.getBytes())) + .compact(); + } + + public String getUser(String token) { + JwtParser jwtParser = getJwtParser(); + return jwtParser.parseSignedClaims(token).getPayload().getSubject(); + } + + public boolean validateToken(String token) { + try { + JwtParser jwtParser = getJwtParser(); + jwtParser.parseSignedClaims(token); + return true; + } catch (MalformedJwtException e) { + log.warn("Invalid JWT token"); + } catch (ExpiredJwtException e) { + log.warn("Expired JWT token"); + } catch (UnsupportedJwtException e) { + log.warn("Unsupported JWT token"); + } catch (IllegalArgumentException e) { + log.warn("JWT claims string is empty"); + } + return false; + } + + public String getToken(HttpServletRequest request) { + String header = request.getHeader(HttpHeaders.AUTHORIZATION); + if (header != null && header.startsWith(TOKEN_PREFIX)) { + return header.substring(TOKEN_PREFIX.length()); + } + throw new HttpClientErrorException(HttpStatus.UNAUTHORIZED, "Invalid authorization type"); + } + + private JwtParser getJwtParser() { + return Jwts.parser().verifyWith(Keys.hmacShaKeyFor(secretKey.getBytes())).build(); + } +} \ No newline at end of file diff --git a/src/main/resources/11application.yml b/src/main/resources/11application.yml new file mode 100644 index 0000000..f121e19 --- /dev/null +++ b/src/main/resources/11application.yml @@ -0,0 +1,54 @@ +spring: + application: + name: mylfa + datasource: + url: jdbc:postgresql://${DATABASE_URL} + username: ${DATABASE_USER} + password: ${DATABASE_PWD} + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + flyway: + schemas: mylfa + table: flyway_schema_history + jpa: + show-sql: false + hibernate: + ddl-auto: none + properties: + hibernate: + default_schema: mylfa + jdbc: + batch_size: 1000 + order_inserts: true + order_updates: true + lob: + non_contextual_creation: true + open-in-view: false + sql: + init: + platform: h2 + encoding: UTF-8 + main: + banner-mode: off +server: + port: 8080 + servlet: + context-path: /mylfa +logging: + level: + de: + whiteo: + mylfa: ${APP_LOG_LEVEL} +jwt: + expiration: + time: ${TOKEN_EXPIRATION_TIME} + secret: + key: ${TOKEN_SECRET_KEY} +app: + version: "@project.version@" + demo-only: ${DEMO_ONLY} + main-directory: "./mylfa/" + lang-directory: "./mylfa/languages/" + lang-repository: "https://github.com/tesseract-ocr/tessdata/raw/main/" + lang-file-extension: ".traineddata" \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..1399283 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,54 @@ +spring: + application: + name: mylfa + datasource: + url: jdbc:postgresql://ru.cloud.whiteo.de:5432/mylfa + username: dbuser + password: Password!23 + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + flyway: + schemas: mylfa + table: flyway_schema_history + jpa: + show-sql: false + hibernate: + ddl-auto: none + properties: + hibernate: + default_schema: mylfa + jdbc: + batch_size: 1000 + order_inserts: true + order_updates: true + lob: + non_contextual_creation: true + open-in-view: false + sql: + init: + platform: h2 + encoding: UTF-8 + main: + banner-mode: off +server: + port: 8080 + servlet: + context-path: /mylfa +logging: + level: + de: + whiteo: + mylfa: DEBUG +jwt: + expiration: + time: 86400000 + secret: + key: super_secret_key_yeah_this_key_is_amazing_1234567890!@# +app: + version: "@project.version@" + demo-only: false + main-directory: "./mylfa/" + lang-directory: "./mylfa/languages/" + lang-repository: "https://github.com/tesseract-ocr/tessdata/raw/main/" + lang-file-extension: ".traineddata" \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__app_initial.sql b/src/main/resources/db/migration/V1__app_initial.sql new file mode 100644 index 0000000..01bd5a1 --- /dev/null +++ b/src/main/resources/db/migration/V1__app_initial.sql @@ -0,0 +1,101 @@ +CREATE SCHEMA IF NOT EXISTS mylfa; + +CREATE TABLE IF NOT EXISTS mylfa.user +( + id UUID NOT NULL, + email VARCHAR NOT NULL UNIQUE, + verified BOOLEAN NOT NULL, + password VARCHAR NOT NULL, + modify_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + properties JSONB, + CONSTRAINT pk_user PRIMARY KEY (id), + CONSTRAINT unique_user__name_key UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS mylfa.currency_type +( + id UUID NOT NULL, + name VARCHAR NOT NULL UNIQUE, + hide BOOLEAN NOT NULL DEFAULT FALSE, + modify_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + user_id UUID, + CONSTRAINT pk_currency_type PRIMARY KEY (id), + CONSTRAINT unique_currency_type__name_key UNIQUE (name), + CONSTRAINT fk_currency_type__user_id FOREIGN KEY (user_id) REFERENCES mylfa.user (id) +); + +CREATE INDEX IF NOT EXISTS currency_type__user_fkey ON mylfa.currency_type (user_id); + +CREATE TABLE IF NOT EXISTS mylfa.income_category +( + id UUID NOT NULL, + name VARCHAR NOT NULL UNIQUE, + hide BOOLEAN NOT NULL, + parent_id UUID, + modify_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + user_id UUID, + CONSTRAINT pk_income_category PRIMARY KEY (id), + CONSTRAINT unique_income_category__name_key UNIQUE (name), + CONSTRAINT fk_income_category__user_id FOREIGN KEY (user_id) REFERENCES mylfa.user (id) +); + +CREATE INDEX IF NOT EXISTS income_category__user_fkey ON mylfa.income_category (user_id); + +CREATE TABLE IF NOT EXISTS mylfa.income +( + id UUID NOT NULL, + user_id UUID, + category_id UUID NOT NULL, + currency_type_id UUID NOT NULL, + amount DECIMAL NOT NULL, + modify_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + description VARCHAR, + CONSTRAINT pk_income PRIMARY KEY (id), + CONSTRAINT fk_income__user_id FOREIGN KEY (user_id) REFERENCES mylfa.user (id), + CONSTRAINT fk_income__category_id FOREIGN KEY (category_id) REFERENCES mylfa.income_category (id), + CONSTRAINT fk_income__currency_type_id FOREIGN KEY (currency_type_id) REFERENCES mylfa.currency_type (id) +); + +CREATE INDEX IF NOT EXISTS income__user_fkey ON mylfa.income (user_id); +CREATE INDEX IF NOT EXISTS income__category_fkey ON mylfa.income (category_id); +CREATE INDEX IF NOT EXISTS income__currency_type_fkey ON mylfa.income (currency_type_id); + +CREATE TABLE IF NOT EXISTS mylfa.expense_category +( + id UUID NOT NULL, + name VARCHAR NOT NULL UNIQUE, + hide BOOLEAN NOT NULL, + parent_id UUID, + modify_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + user_id UUID, + CONSTRAINT pk_expense_category PRIMARY KEY (id), + CONSTRAINT unique_expense_category__name_key UNIQUE (name), + CONSTRAINT fk_expense_category__user_id FOREIGN KEY (user_id) REFERENCES mylfa.user (id) +); + +CREATE INDEX IF NOT EXISTS expense_category__user_fkey ON mylfa.expense_category (user_id); + +CREATE TABLE IF NOT EXISTS mylfa.expense +( + id UUID NOT NULL, + user_id UUID, + category_id UUID NOT NULL, + currency_type_id UUID NOT NULL, + amount DECIMAL NOT NULL, + description VARCHAR, + modify_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, + CONSTRAINT pk_expense PRIMARY KEY (id), + CONSTRAINT fk_expense__user_id FOREIGN KEY (user_id) REFERENCES mylfa.user (id), + CONSTRAINT fk_expense__category_id FOREIGN KEY (category_id) REFERENCES mylfa.expense_category (id), + CONSTRAINT fk_expense__currency_type_id FOREIGN KEY (currency_type_id) REFERENCES mylfa.currency_type (id) +); + +CREATE INDEX IF NOT EXISTS expense__user_fkey ON mylfa.expense (user_id); +CREATE INDEX IF NOT EXISTS expense__category_fkey ON mylfa.expense (category_id); +CREATE INDEX IF NOT EXISTS expense__currency_type_fkey ON mylfa.expense (currency_type_id); \ No newline at end of file diff --git a/src/main/resources/db/migration/V2__demo_data.sql b/src/main/resources/db/migration/V2__demo_data.sql new file mode 100644 index 0000000..75a831e --- /dev/null +++ b/src/main/resources/db/migration/V2__demo_data.sql @@ -0,0 +1,73 @@ +INSERT INTO mylfa."user" (id, email, verified, password, modify_date, creation_date) +VALUES (gen_random_uuid(), 'demo@demo.demo', TRUE, 'demodemo', now(), now()) +ON CONFLICT DO NOTHING; + +INSERT INTO mylfa.currency_type (id, name, hide, creation_date, modify_date, user_id) +VALUES (gen_random_uuid(), 'dollar', FALSE, now(), now(), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')), + (gen_random_uuid(), 'euro', FALSE, now(), now(), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')), + (gen_random_uuid(), 'btc', TRUE, now(), now(), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')) +ON CONFLICT DO NOTHING; + +INSERT INTO mylfa.income_category (id, name, hide, creation_date, modify_date, user_id) +VALUES (gen_random_uuid(), 'salary', FALSE, now(), now(), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')) +ON CONFLICT DO NOTHING; + +INSERT INTO mylfa.income_category (id, name, hide, creation_date, modify_date, parent_id, user_id) +VALUES (gen_random_uuid(), 'bonus', FALSE, now(), now(), + (SELECT id FROM mylfa.income_category WHERE name = 'salary'), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')), + (gen_random_uuid(), 'hide income category', TRUE, now(), now(), + (SELECT id FROM mylfa.income_category WHERE name = 'salary'), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')) +ON CONFLICT DO NOTHING; + +INSERT INTO mylfa.income (id, user_id, category_id, currency_type_id, amount, modify_date, creation_date, description) +VALUES (gen_random_uuid(), (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo'), + (SELECT id FROM mylfa.income_category WHERE name = 'salary'), + (SELECT id FROM mylfa.currency_type WHERE name = 'dollar'), + 10, now(), now(), 'first salary'), + (gen_random_uuid(), (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo'), + (SELECT id FROM mylfa.income_category WHERE name = 'bonus'), + (SELECT id FROM mylfa.currency_type WHERE name = 'euro'), + 11, now(), now(), 'first bonus'), + (gen_random_uuid(), (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo'), + (SELECT id FROM mylfa.income_category WHERE name = 'hide income category'), + (SELECT id FROM mylfa.currency_type WHERE name = 'btc'), + 12, now(), now(), 'first hide income') +ON CONFLICT DO NOTHING; + +INSERT INTO mylfa.expense_category (id, name, hide, creation_date, modify_date, user_id) +VALUES (gen_random_uuid(), 'market', FALSE, now(), now(), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')) +ON CONFLICT DO NOTHING; + +INSERT INTO mylfa.expense_category (id, name, hide, creation_date, modify_date, parent_id, user_id) +VALUES (gen_random_uuid(), 'food', FALSE, now(), now(), + (SELECT id FROM mylfa.expense_category WHERE name = 'market'), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')), + (gen_random_uuid(), 'household chemistry', FALSE, now(), now(), + (SELECT id FROM mylfa.expense_category WHERE name = 'market'), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')), + (gen_random_uuid(), 'hide expense category', TRUE, now(), now(), + (SELECT id FROM mylfa.expense_category WHERE name = 'market'), + (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo')) +ON CONFLICT DO NOTHING; + +INSERT INTO mylfa.expense (id, user_id, category_id, currency_type_id, amount, description, modify_date, creation_date) +VALUES (gen_random_uuid(), (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo'), + (SELECT id FROM mylfa.expense_category WHERE name = 'food'), + (SELECT id FROM mylfa.currency_type WHERE name = 'dollar'), + 20, 'first food', now(), now()), + (gen_random_uuid(), (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo'), + (SELECT id FROM mylfa.expense_category WHERE name = 'household chemistry'), + (SELECT id FROM mylfa.currency_type WHERE name = 'euro'), + 21, 'first chemistry', now(), now()), + (gen_random_uuid(), (SELECT id FROM mylfa."user" WHERE email = 'demo@demo.demo'), + (SELECT id FROM mylfa.expense_category WHERE name = 'hide expense category'), + (SELECT id FROM mylfa.currency_type WHERE name = 'btc'), + 22, 'first hide expense', now(), now()) +ON CONFLICT DO NOTHING; \ No newline at end of file diff --git a/src/main/resources/hibernate.properties b/src/main/resources/hibernate.properties new file mode 100644 index 0000000..a22fe63 --- /dev/null +++ b/src/main/resources/hibernate.properties @@ -0,0 +1 @@ +hibernate.types.print.banner=false \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/AppTest.java b/src/test/java/de/whiteo/mylfa/AppTest.java new file mode 100644 index 0000000..fe00936 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/AppTest.java @@ -0,0 +1,16 @@ +package de.whiteo.mylfa; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +class AppTest { + + @Test + void contextLoads() { + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/api/AuthenticateRestControllerTest.java b/src/test/java/de/whiteo/mylfa/api/AuthenticateRestControllerTest.java new file mode 100644 index 0000000..d826f43 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/api/AuthenticateRestControllerTest.java @@ -0,0 +1,76 @@ +package de.whiteo.mylfa.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.user.UserLoginRequest; +import de.whiteo.mylfa.repository.UserRepository; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class AuthenticateRestControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + @Autowired + private UserRepository userRepository; + @Autowired + private ObjectMapper objectMapper; + private UserBuilder userBuilder; + private MockMvc mockMvc; + + @PostConstruct + void initialize() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void authenticate_successful() throws Exception { + User user = userBuilder.buildUser(); + + UserLoginRequest request = prepareTest(user.getEmail(), String.valueOf(user.getPassword())); + + mockMvc.perform(post("/api/v1/authenticate") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void authenticate_unsuccessful() throws Exception { + UserLoginRequest request = prepareTest(RandomStringUtils.randomAlphabetic(5) + "@test.com", + RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(post("/api/v1/authenticate") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnauthorized()); + } + + private UserLoginRequest prepareTest(String email, String passwd) { + return UserBuilder.buildUserLoginRequest(email, passwd); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/api/CurrencyTypeRestControllerTest.java b/src/test/java/de/whiteo/mylfa/api/CurrencyTypeRestControllerTest.java new file mode 100644 index 0000000..bb1ed4b --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/api/CurrencyTypeRestControllerTest.java @@ -0,0 +1,213 @@ +package de.whiteo.mylfa.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeCreateOrUpdateRequest; +import de.whiteo.mylfa.helper.TokenHelper; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.UserRepository; +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class CurrencyTypeRestControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + @Autowired + private CurrencyTypeRepository repository; + @Autowired + private UserRepository userRepository; + private CurrencyTypeBuilder builder; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private JwtTokenUtil jwtTokenUtil; + private TokenHelper tokenHelper; + private UserBuilder userBuilder; + private MockMvc mockMvc; + + @PostConstruct + void initialize() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + builder = new CurrencyTypeBuilder(repository); + tokenHelper = new TokenHelper(jwtTokenUtil); + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create_successful() throws Exception { + User user = userBuilder.buildUser(); + + prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(post("/api/v1/currency-type") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isCreated()); + } + + @Test + @Transactional + void create_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + prepareTest(randomString, user); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest(randomString); + + mockMvc.perform(post("/api/v1/currency-type") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isConflict()); + } + + @Test + @Transactional + void delete_successful() throws Exception { + User user = userBuilder.buildUser(); + + CurrencyType currencyType = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + mockMvc.perform(delete("/api/v1/currency-type/" + currencyType.getId()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNoContent()); + } + + @Test + @Transactional + void delete_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(delete(String.format("/api/v1/currency-type/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void update_successful() throws Exception { + User user = userBuilder.buildUser(); + + CurrencyType currencyType = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(put(String.format("/api/v1/currency-type/%s/edit", currencyType.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + CurrencyType currencyType = prepareTest(randomString, user); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest(randomString); + + mockMvc.perform(put(String.format("/api/v1/currency-type/%s/edit", currencyType.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isConflict()); + } + + @Test + @Transactional + void find_successful() throws Exception { + User user = userBuilder.buildUser(); + + CurrencyType currencyType = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + mockMvc.perform(get(String.format("/api/v1/currency-type/%s", currencyType.getId())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get(String.format("/api/v1/currency-type/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_all_successful() throws Exception { + User user = userBuilder.buildUser(); + + for (int i = 0; i < 10; i++) { + prepareTest(RandomStringUtils.randomAlphabetic(20), user); + } + + mockMvc.perform(get("/api/v1/currency-type") + .param("page", "0") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_empty_successful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get("/api/v1/currency-type") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + private CurrencyType prepareTest(String name, User user) { + return builder.buildEntity(name, user.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/api/ExpenseCategoryRestControllerTest.java b/src/test/java/de/whiteo/mylfa/api/ExpenseCategoryRestControllerTest.java new file mode 100644 index 0000000..4c9d71f --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/api/ExpenseCategoryRestControllerTest.java @@ -0,0 +1,213 @@ +package de.whiteo.mylfa.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.helper.TokenHelper; +import de.whiteo.mylfa.repository.ExpenseCategoryRepository; +import de.whiteo.mylfa.repository.UserRepository; +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ExpenseCategoryRestControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + @Autowired + private ExpenseCategoryRepository repository; + private ExpenseCategoryBuilder builder; + @Autowired + private UserRepository userRepository; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private JwtTokenUtil jwtTokenUtil; + private TokenHelper tokenHelper; + private UserBuilder userBuilder; + private MockMvc mockMvc; + + @PostConstruct + void initialize() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + builder = new ExpenseCategoryBuilder(repository); + tokenHelper = new TokenHelper(jwtTokenUtil); + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create_successful() throws Exception { + User user = userBuilder.buildUser(); + + prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(post("/api/v1/expense-category") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isCreated()); + } + + @Test + @Transactional + void create_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + prepareTest(randomString, user); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequest(randomString); + + mockMvc.perform(post("/api/v1/expense-category") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isConflict()); + } + + @Test + @Transactional + void delete_successful() throws Exception { + User user = userBuilder.buildUser(); + + ExpenseCategory expenseCategory = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + mockMvc.perform(delete("/api/v1/expense-category/" + expenseCategory.getId()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNoContent()); + } + + @Test + @Transactional + void delete_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(delete(String.format("/api/v1/expense-category/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void update_successful() throws Exception { + User user = userBuilder.buildUser(); + + ExpenseCategory expenseCategory = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(put(String.format("/api/v1/expense-category/%s/edit", expenseCategory.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + ExpenseCategory expenseCategory = prepareTest(randomString, user); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequest(randomString); + + mockMvc.perform(put(String.format("/api/v1/expense-category/%s/edit", expenseCategory.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isConflict()); + } + + @Test + @Transactional + void find_successful() throws Exception { + User user = userBuilder.buildUser(); + + ExpenseCategory expenseCategory = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + mockMvc.perform(get(String.format("/api/v1/expense-category/%s", expenseCategory.getId())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get(String.format("/api/v1/expense-category/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_all_successful() throws Exception { + User user = userBuilder.buildUser(); + + for (int i = 0; i < 10; i++) { + prepareTest(RandomStringUtils.randomAlphabetic(20), user); + } + + mockMvc.perform(get("/api/v1/expense-category") + .param("page", "0") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_empty_successful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get("/api/v1/expense-category") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + private ExpenseCategory prepareTest(String name, User user) { + return builder.buildEntity(name, user.getId(), null); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/api/ExpenseRestControllerTest.java b/src/test/java/de/whiteo/mylfa/api/ExpenseRestControllerTest.java new file mode 100644 index 0000000..eaae280 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/api/ExpenseRestControllerTest.java @@ -0,0 +1,234 @@ +package de.whiteo.mylfa.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.ExpenseBuilder; +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.Expense; +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.expense.ExpenseCreateOrUpdateRequest; +import de.whiteo.mylfa.helper.TokenHelper; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.ExpenseCategoryRepository; +import de.whiteo.mylfa.repository.ExpenseRepository; +import de.whiteo.mylfa.repository.UserRepository; +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ExpenseRestControllerTest { + + @Autowired + private ExpenseCategoryRepository expenseCategoryRepository; + @Autowired + private CurrencyTypeRepository currencyTypeRepository; + @Autowired + private WebApplicationContext webApplicationContext; + private ExpenseCategoryBuilder expenseCategoryBuilder; + private CurrencyTypeBuilder currencyTypeBuilder; + @Autowired + private ExpenseRepository repository; + @Autowired + private UserRepository userRepository; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private JwtTokenUtil jwtTokenUtil; + private TokenHelper tokenHelper; + private UserBuilder userBuilder; + private ExpenseBuilder builder; + private MockMvc mockMvc; + + @PostConstruct + void initialize() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + expenseCategoryBuilder = new ExpenseCategoryBuilder(expenseCategoryRepository); + currencyTypeBuilder = new CurrencyTypeBuilder(currencyTypeRepository); + userBuilder = new UserBuilder(userRepository); + tokenHelper = new TokenHelper(jwtTokenUtil); + builder = new ExpenseBuilder(repository); + } + + @Test + @Transactional + void create_successful() throws Exception { + User user = userBuilder.buildUser(); + + Expense expense = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + ExpenseCreateOrUpdateRequest request = ExpenseBuilder.buildRequest( + expense.getCategoryId(), expense.getCurrencyTypeId()); + + mockMvc.perform(post("/api/v1/expense") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isCreated()); + } + + @Test + @Transactional + void create_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + Expense expense = prepareTest(randomString, user.getId()); + + ExpenseCreateOrUpdateRequest request = ExpenseBuilder.buildRequest( + expense.getCategoryId(), UUID.randomUUID()); + + mockMvc.perform(post("/api/v1/expense") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void delete_successful() throws Exception { + User user = userBuilder.buildUser(); + + Expense expense = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + mockMvc.perform(delete("/api/v1/expense/" + expense.getId()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNoContent()); + } + + @Test + @Transactional + void delete_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(delete(String.format("/api/v1/expense/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void update_successful() throws Exception { + User user = userBuilder.buildUser(); + + Expense expense = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + ExpenseCreateOrUpdateRequest request = ExpenseBuilder.buildRequest( + expense.getCategoryId(), expense.getCurrencyTypeId()); + + mockMvc.perform(put(String.format("/api/v1/expense/%s/edit", expense.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + Expense expense = prepareTest(randomString, user.getId()); + + ExpenseCreateOrUpdateRequest request = ExpenseBuilder.buildRequest( + expense.getCategoryId(), expense.getCurrencyTypeId()); + + + mockMvc.perform(put(String.format("/api/v1/expense/%s/edit", UUID.randomUUID())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_successful() throws Exception { + User user = userBuilder.buildUser(); + + Expense expense = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + mockMvc.perform(get(String.format("/api/v1/expense/%s", expense.getId())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get(String.format("/api/v1/expense/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_all_successful() throws Exception { + User user = userBuilder.buildUser(); + + for (int i = 0; i < 10; i++) { + prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + } + + mockMvc.perform(get("/api/v1/expense") + .param("page", "0") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_empty_successful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get("/api/v1/expense") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + private Expense prepareTest(String name, UUID userId) { + CurrencyType currencyType = currencyTypeBuilder.buildEntity(name, userId); + + ExpenseCategory expenseCategory = expenseCategoryBuilder.buildEntity(name, userId, null); + + return builder.buildEntity(userId, expenseCategory.getId(), currencyType.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/api/IncomeCategoryRestControllerTest.java b/src/test/java/de/whiteo/mylfa/api/IncomeCategoryRestControllerTest.java new file mode 100644 index 0000000..f87aa8c --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/api/IncomeCategoryRestControllerTest.java @@ -0,0 +1,213 @@ +package de.whiteo.mylfa.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.helper.TokenHelper; +import de.whiteo.mylfa.repository.IncomeCategoryRepository; +import de.whiteo.mylfa.repository.UserRepository; +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class IncomeCategoryRestControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + @Autowired + private IncomeCategoryRepository repository; + private IncomeCategoryBuilder builder; + @Autowired + private UserRepository userRepository; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private JwtTokenUtil jwtTokenUtil; + private TokenHelper tokenHelper; + private UserBuilder userBuilder; + private MockMvc mockMvc; + + @PostConstruct + void initialize() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + builder = new IncomeCategoryBuilder(repository); + tokenHelper = new TokenHelper(jwtTokenUtil); + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create_successful() throws Exception { + User user = userBuilder.buildUser(); + + prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(post("/api/v1/income-category") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isCreated()); + } + + @Test + @Transactional + void create_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + prepareTest(randomString, user); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest(randomString); + + mockMvc.perform(post("/api/v1/income-category") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isConflict()); + } + + @Test + @Transactional + void delete_successful() throws Exception { + User user = userBuilder.buildUser(); + + IncomeCategory incomeCategory = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + mockMvc.perform(delete("/api/v1/income-category/" + incomeCategory.getId()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNoContent()); + } + + @Test + @Transactional + void delete_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(delete(String.format("/api/v1/income-category/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void update_successful() throws Exception { + User user = userBuilder.buildUser(); + + IncomeCategory incomeCategory = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(put(String.format("/api/v1/income-category/%s/edit", incomeCategory.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + IncomeCategory incomeCategory = prepareTest(randomString, user); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest(randomString); + + mockMvc.perform(put(String.format("/api/v1/income-category/%s/edit", incomeCategory.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isConflict()); + } + + @Test + @Transactional + void find_successful() throws Exception { + User user = userBuilder.buildUser(); + + IncomeCategory incomeCategory = prepareTest(RandomStringUtils.randomAlphabetic(20), user); + + mockMvc.perform(get(String.format("/api/v1/income-category/%s", incomeCategory.getId())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get(String.format("/api/v1/income-category/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_all_successful() throws Exception { + User user = userBuilder.buildUser(); + + for (int i = 0; i < 10; i++) { + prepareTest(RandomStringUtils.randomAlphabetic(20), user); + } + + mockMvc.perform(get("/api/v1/income-category") + .param("page", "0") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_empty_successful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get("/api/v1/income-category") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + private IncomeCategory prepareTest(String name, User user) { + return builder.buildEntity(name, user.getId(), null); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/api/IncomeRestControllerTest.java b/src/test/java/de/whiteo/mylfa/api/IncomeRestControllerTest.java new file mode 100644 index 0000000..f872aef --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/api/IncomeRestControllerTest.java @@ -0,0 +1,234 @@ +package de.whiteo.mylfa.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.IncomeBuilder; +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.Income; +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.income.IncomeCreateOrUpdateRequest; +import de.whiteo.mylfa.helper.TokenHelper; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.IncomeCategoryRepository; +import de.whiteo.mylfa.repository.IncomeRepository; +import de.whiteo.mylfa.repository.UserRepository; +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class IncomeRestControllerTest { + + @Autowired + private IncomeCategoryRepository incomeCategoryRepository; + @Autowired + private CurrencyTypeRepository currencyTypeRepository; + @Autowired + private WebApplicationContext webApplicationContext; + private IncomeCategoryBuilder incomeCategoryBuilder; + private CurrencyTypeBuilder currencyTypeBuilder; + @Autowired + private IncomeRepository repository; + @Autowired + private UserRepository userRepository; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private JwtTokenUtil jwtTokenUtil; + private TokenHelper tokenHelper; + private UserBuilder userBuilder; + private IncomeBuilder builder; + private MockMvc mockMvc; + + @PostConstruct + void initialize() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + incomeCategoryBuilder = new IncomeCategoryBuilder(incomeCategoryRepository); + currencyTypeBuilder = new CurrencyTypeBuilder(currencyTypeRepository); + userBuilder = new UserBuilder(userRepository); + tokenHelper = new TokenHelper(jwtTokenUtil); + builder = new IncomeBuilder(repository); + } + + @Test + @Transactional + void create_successful() throws Exception { + User user = userBuilder.buildUser(); + + Income income = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + IncomeCreateOrUpdateRequest request = IncomeBuilder.buildRequest( + income.getCategoryId(), income.getCurrencyTypeId()); + + mockMvc.perform(post("/api/v1/income") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isCreated()); + } + + @Test + @Transactional + void create_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + Income income = prepareTest(randomString, user.getId()); + + IncomeCreateOrUpdateRequest request = IncomeBuilder.buildRequest( + income.getCategoryId(), UUID.randomUUID()); + + mockMvc.perform(post("/api/v1/income") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void delete_successful() throws Exception { + User user = userBuilder.buildUser(); + + Income income = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + mockMvc.perform(delete("/api/v1/income/" + income.getId()) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNoContent()); + } + + @Test + @Transactional + void delete_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(delete(String.format("/api/v1/income/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void update_successful() throws Exception { + User user = userBuilder.buildUser(); + + Income income = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + IncomeCreateOrUpdateRequest request = IncomeBuilder.buildRequest( + income.getCategoryId(), income.getCurrencyTypeId()); + + mockMvc.perform(put(String.format("/api/v1/income/%s/edit", income.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + String randomString = RandomStringUtils.randomAlphabetic(20); + Income income = prepareTest(randomString, user.getId()); + + IncomeCreateOrUpdateRequest request = IncomeBuilder.buildRequest( + income.getCategoryId(), income.getCurrencyTypeId()); + + + mockMvc.perform(put(String.format("/api/v1/income/%s/edit", UUID.randomUUID())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_successful() throws Exception { + User user = userBuilder.buildUser(); + + Income income = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + mockMvc.perform(get(String.format("/api/v1/income/%s", income.getId())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_unsuccessful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get(String.format("/api/v1/income/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_all_successful() throws Exception { + User user = userBuilder.buildUser(); + + for (int i = 0; i < 10; i++) { + prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + } + + mockMvc.perform(get("/api/v1/income") + .param("page", "0") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_empty_successful() throws Exception { + User user = userBuilder.buildUser(); + + mockMvc.perform(get("/api/v1/income") + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + private Income prepareTest(String name, UUID userId) { + CurrencyType currencyType = currencyTypeBuilder.buildEntity(name, userId); + + IncomeCategory incomeCategory = incomeCategoryBuilder.buildEntity(name, userId, null); + + return builder.buildEntity(userId, incomeCategory.getId(), currencyType.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/api/UserRestControllerTest.java b/src/test/java/de/whiteo/mylfa/api/UserRestControllerTest.java new file mode 100644 index 0000000..337e91e --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/api/UserRestControllerTest.java @@ -0,0 +1,226 @@ +package de.whiteo.mylfa.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.user.UserCreateRequest; +import de.whiteo.mylfa.dto.user.UserUpdatePasswordRequest; +import de.whiteo.mylfa.dto.user.UserUpdatePropertiesRequest; +import de.whiteo.mylfa.dto.user.UserUpdateRequest; +import de.whiteo.mylfa.helper.TokenHelper; +import de.whiteo.mylfa.repository.UserRepository; +import de.whiteo.mylfa.util.JwtTokenUtil; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class UserRestControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + @Autowired + private UserRepository repository; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private JwtTokenUtil jwtTokenUtil; + private TokenHelper tokenHelper; + private UserBuilder builder; + private MockMvc mockMvc; + + @PostConstruct + void initialize() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + builder = new UserBuilder(repository); + tokenHelper = new TokenHelper(jwtTokenUtil); + } + + @Test + @Transactional + void create_successful() throws Exception { + UserCreateRequest request = UserBuilder.buildUserCreateRequest( + RandomStringUtils.randomAlphabetic(5) + "@test.com", RandomStringUtils.randomAlphabetic(20)); + + mockMvc.perform(post("/api/v1/user/create") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + } + + @Test + @Transactional + void create_unsuccessful() throws Exception { + builder.buildUser(); + + UserCreateRequest request = UserBuilder.buildUserCreateRequest(UserBuilder.EMAIL, UserBuilder.PASSWORD); + + mockMvc.perform(post("/api/v1/user/create") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()); + } + + @Test + @Transactional + void delete_successful() throws Exception { + User user = builder.buildUser(); + + mockMvc.perform(delete(String.format("/api/v1/user/%s", user.getId())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNoContent()); + } + + @Test + @Transactional + void delete_unsuccessful() throws Exception { + mockMvc.perform(delete(String.format("/api/v1/user/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(RandomStringUtils.randomAlphabetic(20)))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void update_successful() throws Exception { + User user = builder.buildUser(); + + UserUpdateRequest request = UserBuilder.buildUserUpdateRequest( + RandomStringUtils.randomAlphabetic(5) + "@test.com"); + + mockMvc.perform(put(String.format("/api/v1/user/%s/edit", user.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_unsuccessful() throws Exception { + User user = builder.buildUser(); + + UserUpdateRequest request = UserBuilder.buildUserUpdateRequest( + RandomStringUtils.randomAlphabetic(5) + "@test.com"); + + mockMvc.perform(put(String.format("/api/v1/user/%s/edit", UUID.randomUUID())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void update_properties_successful() throws Exception { + User user = builder.buildUser(); + + Map properties = new HashMap<>(); + properties.put("lang", "eng"); + properties.put("showHide", "true"); + + UserUpdatePropertiesRequest request = UserBuilder.buildUserUpdatePropertiesRequest(properties); + + mockMvc.perform(put(String.format("/api/v1/user/%s/edit-properties", user.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_properties_unsuccessful() throws Exception { + User user = builder.buildUser(); + + Map properties = new HashMap<>(); + properties.put(RandomStringUtils.randomAlphabetic(3), RandomStringUtils.randomAlphabetic(3)); + + UserUpdatePropertiesRequest request = UserBuilder.buildUserUpdatePropertiesRequest(properties); + + mockMvc.perform(put(String.format("/api/v1/user/%s/edit-properties", user.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isConflict()); + } + @Test + @Transactional + void update_password_successful() throws Exception { + User user = builder.buildUser(); + + UserUpdatePasswordRequest request = UserBuilder.buildUserPasswordUpdateRequest( + RandomStringUtils.randomAlphabetic(20), + String.valueOf(user.getPassword())); + + mockMvc.perform(put(String.format("/api/v1/user/%s/edit-password", user.getId())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void update_password_unsuccessful() throws Exception { + User user = builder.buildUser(); + + UserUpdatePasswordRequest request = UserBuilder.buildUserPasswordUpdateRequest( + RandomStringUtils.randomAlphabetic(20), + String.valueOf(user.getPassword())); + + mockMvc.perform(put(String.format("/api/v1/user/%s/edit-password", UUID.randomUUID())) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + void find_successful() throws Exception { + User user = builder.buildUser(); + + mockMvc.perform(get(String.format("/api/v1/user/%s", user.getId())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void find_unsuccessful() throws Exception { + User user = builder.buildUser(); + + mockMvc.perform(get(String.format("/api/v1/user/%s", UUID.randomUUID())) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", tokenHelper.getToken(user.getEmail()))) + .andExpect(status().isNotFound()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/builder/CurrencyTypeBuilder.java b/src/test/java/de/whiteo/mylfa/builder/CurrencyTypeBuilder.java new file mode 100644 index 0000000..7b5017e --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/builder/CurrencyTypeBuilder.java @@ -0,0 +1,44 @@ +package de.whiteo.mylfa.builder; + +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeCreateOrUpdateRequest; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.Random; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class CurrencyTypeBuilder { + + private final static Random random = new Random(); + private final CurrencyTypeRepository repository; + + public static CurrencyTypeCreateOrUpdateRequest buildRequest(String name) { + CurrencyTypeCreateOrUpdateRequest request = new CurrencyTypeCreateOrUpdateRequest(); + request.setName(name); + request.setHide(new Random().nextBoolean()); + return request; + } + + public static CurrencyType buildCurrencyType() { + CurrencyType currencyType = new CurrencyType(); + currencyType.setId(UUID.randomUUID()); + currencyType.setHide(random.nextBoolean()); + currencyType.setName(RandomStringUtils.randomAlphabetic(20)); + return currencyType; + } + + public CurrencyType buildEntity(String name, UUID userId) { + CurrencyType currencyType = repository.findByNameIgnoreCase(name).orElse(new CurrencyType()); + currencyType.setHide(new Random().nextBoolean()); + currencyType.setName(name); + currencyType.setUserId(userId); + return repository.save(currencyType); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/builder/ExpenseBuilder.java b/src/test/java/de/whiteo/mylfa/builder/ExpenseBuilder.java new file mode 100644 index 0000000..6d2db50 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/builder/ExpenseBuilder.java @@ -0,0 +1,52 @@ +package de.whiteo.mylfa.builder; + +import de.whiteo.mylfa.domain.Expense; +import de.whiteo.mylfa.dto.expense.ExpenseCreateOrUpdateRequest; +import de.whiteo.mylfa.repository.ExpenseRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; + +import java.math.BigDecimal; +import java.util.Random; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class ExpenseBuilder { + + private final static Random random = new Random(); + private final ExpenseRepository repository; + + public static ExpenseCreateOrUpdateRequest buildRequest(UUID incomeCategoryId, UUID currencyTypeId) { + ExpenseCreateOrUpdateRequest request = new ExpenseCreateOrUpdateRequest(); + request.setDescription(RandomStringUtils.randomAlphabetic(20)); + request.setAmount(BigDecimal.valueOf(new Random().nextInt(100))); + request.setCategoryId(incomeCategoryId); + request.setCurrencyTypeId(currencyTypeId); + return request; + } + + public static Expense buildExpense(UUID currencyTypeId, UUID categoryId) { + Expense expense = new Expense(); + expense.setId(UUID.randomUUID()); + expense.setUserId(UUID.randomUUID()); + expense.setAmount(BigDecimal.valueOf(random.nextInt())); + expense.setCategoryId(categoryId); + expense.setCurrencyTypeId(currencyTypeId); + expense.setDescription(RandomStringUtils.randomAlphabetic(20)); + return expense; + } + + public Expense buildEntity(UUID userId, UUID outlayCategoryId, UUID CurrencyTypeId) { + Expense expense = new Expense(); + expense.setAmount(BigDecimal.valueOf(new Random().nextInt())); + expense.setCategoryId(outlayCategoryId); + expense.setDescription(RandomStringUtils.randomAlphabetic(20)); + expense.setUserId(userId); + expense.setCurrencyTypeId(CurrencyTypeId); + return repository.save(expense); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/builder/ExpenseCategoryBuilder.java b/src/test/java/de/whiteo/mylfa/builder/ExpenseCategoryBuilder.java new file mode 100644 index 0000000..166b679 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/builder/ExpenseCategoryBuilder.java @@ -0,0 +1,54 @@ +package de.whiteo.mylfa.builder; + +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.repository.ExpenseCategoryRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.Random; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class ExpenseCategoryBuilder { + + private final static Random random = new Random(); + private final ExpenseCategoryRepository repository; + + public static ExpenseCategoryCreateOrUpdateRequest buildRequest(String name) { + ExpenseCategoryCreateOrUpdateRequest request = new ExpenseCategoryCreateOrUpdateRequest(); + request.setName(name); + request.setHide(new Random().nextBoolean()); + return request; + } + + public static ExpenseCategoryCreateOrUpdateRequest buildRequestWithParentId(String name, UUID parentId) { + ExpenseCategoryCreateOrUpdateRequest request = new ExpenseCategoryCreateOrUpdateRequest(); + request.setName(name); + request.setHide(new Random().nextBoolean()); + request.setParentId(parentId); + return request; + } + + public static ExpenseCategory buildExpenseCategory(UUID parentId) { + ExpenseCategory category = new ExpenseCategory(); + category.setId(UUID.randomUUID()); + category.setParentId(parentId); + category.setHide(random.nextBoolean()); + category.setName(RandomStringUtils.randomAlphabetic(20)); + return category; + } + + public ExpenseCategory buildEntity(String name, UUID userId, UUID parentId) { + ExpenseCategory expenseCategory = new ExpenseCategory(); + expenseCategory.setHide(new Random().nextBoolean()); + expenseCategory.setName(name); + expenseCategory.setParentId(parentId); + expenseCategory.setUserId(userId); + return repository.save(expenseCategory); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/builder/IncomeBuilder.java b/src/test/java/de/whiteo/mylfa/builder/IncomeBuilder.java new file mode 100644 index 0000000..8411115 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/builder/IncomeBuilder.java @@ -0,0 +1,51 @@ +package de.whiteo.mylfa.builder; + +import de.whiteo.mylfa.domain.Income; +import de.whiteo.mylfa.dto.income.IncomeCreateOrUpdateRequest; +import de.whiteo.mylfa.repository.IncomeRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; + +import java.math.BigDecimal; +import java.util.Random; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class IncomeBuilder { + private final static Random random = new Random(); + private final IncomeRepository repository; + + public static IncomeCreateOrUpdateRequest buildRequest(UUID incomeCategoryId, UUID currencyTypeId) { + IncomeCreateOrUpdateRequest request = new IncomeCreateOrUpdateRequest(); + request.setDescription(RandomStringUtils.randomAlphabetic(20)); + request.setAmount(BigDecimal.valueOf(new Random().nextInt(100))); + request.setCategoryId(incomeCategoryId); + request.setCurrencyTypeId(currencyTypeId); + return request; + } + + public static Income buildIncome(UUID currencyTypeId, UUID categoryId) { + Income income = new Income(); + income.setId(UUID.randomUUID()); + income.setUserId(UUID.randomUUID()); + income.setAmount(BigDecimal.valueOf(random.nextInt())); + income.setCategoryId(categoryId); + income.setCurrencyTypeId(currencyTypeId); + income.setDescription(RandomStringUtils.randomAlphabetic(20)); + return income; + } + + public Income buildEntity(UUID userId, UUID incomeCategoryId, UUID CurrencyTypeId) { + Income income = new Income(); + income.setAmount(BigDecimal.valueOf(new Random().nextInt())); + income.setCategoryId(incomeCategoryId); + income.setDescription(RandomStringUtils.randomAlphabetic(20)); + income.setUserId(userId); + income.setCurrencyTypeId(CurrencyTypeId); + return repository.save(income); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/builder/IncomeCategoryBuilder.java b/src/test/java/de/whiteo/mylfa/builder/IncomeCategoryBuilder.java new file mode 100644 index 0000000..efb1931 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/builder/IncomeCategoryBuilder.java @@ -0,0 +1,55 @@ +package de.whiteo.mylfa.builder; + +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.repository.IncomeCategoryRepository; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.Random; +import java.util.UUID; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class IncomeCategoryBuilder { + + private final static Random random = new Random(); + + private final IncomeCategoryRepository repository; + + public static IncomeCategoryCreateOrUpdateRequest buildRequest(String name) { + IncomeCategoryCreateOrUpdateRequest request = new IncomeCategoryCreateOrUpdateRequest(); + request.setName(name); + request.setHide(new Random().nextBoolean()); + return request; + } + + public static IncomeCategoryCreateOrUpdateRequest buildRequestWithParentId(String name, UUID parentId) { + IncomeCategoryCreateOrUpdateRequest request = new IncomeCategoryCreateOrUpdateRequest(); + request.setName(name); + request.setHide(new Random().nextBoolean()); + request.setParentId(parentId); + return request; + } + + public static IncomeCategory buildIncomeCategory(UUID parentId) { + IncomeCategory incomeCategory = new IncomeCategory(); + incomeCategory.setId(UUID.randomUUID()); + incomeCategory.setParentId(parentId); + incomeCategory.setHide(random.nextBoolean()); + incomeCategory.setName(RandomStringUtils.randomAlphabetic(20)); + return incomeCategory; + } + + public IncomeCategory buildEntity(String name, UUID userId, UUID parentId) { + IncomeCategory incomeCategory = new IncomeCategory(); + incomeCategory.setHide(new Random().nextBoolean()); + incomeCategory.setName(name); + incomeCategory.setParentId(parentId); + incomeCategory.setUserId(userId); + return repository.save(incomeCategory); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/builder/UserBuilder.java b/src/test/java/de/whiteo/mylfa/builder/UserBuilder.java new file mode 100644 index 0000000..ddface7 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/builder/UserBuilder.java @@ -0,0 +1,72 @@ +package de.whiteo.mylfa.builder; + +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.user.UserCreateRequest; +import de.whiteo.mylfa.dto.user.UserLoginRequest; +import de.whiteo.mylfa.dto.user.UserUpdatePasswordRequest; +import de.whiteo.mylfa.dto.user.UserUpdatePropertiesRequest; +import de.whiteo.mylfa.dto.user.UserUpdateRequest; +import de.whiteo.mylfa.repository.UserRepository; +import lombok.RequiredArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class UserBuilder { + + public final static String EMAIL = "test1@test.com"; + public final static String PASSWORD = "test1"; + + private final UserRepository repository; + + public static UserLoginRequest buildUserLoginRequest(String email, String passwd) { + UserLoginRequest request = new UserLoginRequest(); + request.setEmail(email); + request.setPassword(passwd); + return request; + } + + public static UserCreateRequest buildUserCreateRequest(String email, String passwd) { + UserCreateRequest request = new UserCreateRequest(); + Map properties = new HashMap<>(); + properties.put("lang", "eng"); + request.setProperties(properties); + request.setEmail(email); + request.setPassword(passwd); + return request; + } + + public static UserUpdatePasswordRequest buildUserPasswordUpdateRequest(String passwd, String oldPasswd) { + UserUpdatePasswordRequest request = new UserUpdatePasswordRequest(); + request.setPassword(passwd); + request.setOldPassword(oldPasswd); + return request; + } + + public static UserUpdateRequest buildUserUpdateRequest(String email) { + UserUpdateRequest request = new UserUpdateRequest(); + request.setEmail(email); + return request; + } + + public static UserUpdatePropertiesRequest buildUserUpdatePropertiesRequest(Map properties) { + UserUpdatePropertiesRequest request = new UserUpdatePropertiesRequest(); + request.setProperties(properties); + return request; + } + + public User buildUser() { + User user = repository.findByEmailIgnoreCase(EMAIL).orElse(new User()); + Map properties = new HashMap<>(); + properties.put("lang", "eng"); + user.setProperties(properties); + user.setEmail(EMAIL); + user.setPassword(PASSWORD.toCharArray()); + return repository.save(user); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/helper/TokenHelper.java b/src/test/java/de/whiteo/mylfa/helper/TokenHelper.java new file mode 100644 index 0000000..f75f212 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/helper/TokenHelper.java @@ -0,0 +1,23 @@ +package de.whiteo.mylfa.helper; + +import de.whiteo.mylfa.security.UserDetailsImpl; +import de.whiteo.mylfa.util.JwtTokenUtil; +import lombok.RequiredArgsConstructor; + +/** + * @author Leo Tanas (github) + */ + +@RequiredArgsConstructor +public class TokenHelper { + + private final JwtTokenUtil jwtTokenUtil; + + public String getToken(String namePassword) { + UserDetailsImpl userDetails = UserDetailsImpl.builder() + .name(namePassword) + .password(namePassword.toCharArray()) + .build(); + return "Bearer " + jwtTokenUtil.generateToken(userDetails); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/mapper/CurrencyTypeMapperTest.java b/src/test/java/de/whiteo/mylfa/mapper/CurrencyTypeMapperTest.java new file mode 100644 index 0000000..a754bf1 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/mapper/CurrencyTypeMapperTest.java @@ -0,0 +1,27 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +class CurrencyTypeMapperTest { + + private final CurrencyTypeMapper mapper = Mappers.getMapper(CurrencyTypeMapper.class); + + @Test + void testToResponse() { + CurrencyType currencyType = CurrencyTypeBuilder.buildCurrencyType(); + CurrencyTypeResponse response = mapper.toResponse(currencyType); + assertEquals(currencyType.getId(), response.getId()); + assertEquals(currencyType.getName(), response.getName()); + assertEquals(currencyType.isHide(), response.isHide()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/mapper/ExpenseCategoryMapperTest.java b/src/test/java/de/whiteo/mylfa/mapper/ExpenseCategoryMapperTest.java new file mode 100644 index 0000000..ade852e --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/mapper/ExpenseCategoryMapperTest.java @@ -0,0 +1,29 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +class ExpenseCategoryMapperTest { + + private final ExpenseCategoryMapper mapper = Mappers.getMapper(ExpenseCategoryMapper.class); + + @Test + void testToResponse() { + ExpenseCategory parentCategory = ExpenseCategoryBuilder.buildExpenseCategory(null); + ExpenseCategory category = ExpenseCategoryBuilder.buildExpenseCategory(parentCategory.getId()); + ExpenseCategoryResponse response = mapper.toResponse(category, mapper.toResponse(parentCategory)); + assertEquals(category.getId(), response.getId()); + assertEquals(category.getParentId(), response.getParent().getId()); + assertEquals(category.getName(), response.getName()); + assertEquals(category.isHide(), response.isHide()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/mapper/ExpenseMapperTest.java b/src/test/java/de/whiteo/mylfa/mapper/ExpenseMapperTest.java new file mode 100644 index 0000000..4587fe9 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/mapper/ExpenseMapperTest.java @@ -0,0 +1,42 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.ExpenseBuilder; +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.domain.Expense; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.expense.ExpenseResponse; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +class ExpenseMapperTest { + + private final ExpenseCategoryMapper categoryMapper = Mappers.getMapper(ExpenseCategoryMapper.class); + private final CurrencyTypeMapper currencyTypeMapper = Mappers.getMapper(CurrencyTypeMapper.class); + private final ExpenseMapper mapper = Mappers.getMapper(ExpenseMapper.class); + + @Test + void testToResponse() { + CurrencyTypeResponse currencyTypeResponse = currencyTypeMapper.toResponse( + CurrencyTypeBuilder.buildCurrencyType()); + + ExpenseCategoryResponse categoryResponse = categoryMapper.toResponse( + ExpenseCategoryBuilder.buildExpenseCategory(null)); + + Expense expense = ExpenseBuilder.buildExpense(currencyTypeResponse.getId(), categoryResponse.getId()); + + ExpenseResponse response = mapper.toResponse(expense, currencyTypeResponse, categoryResponse); + assertEquals(expense.getId(), response.getId()); + assertEquals(expense.getDescription(), response.getDescription()); + assertEquals(expense.getAmount(), response.getAmount()); + assertEquals(expense.getCategoryId(), response.getCategory().getId()); + assertEquals(expense.getCurrencyTypeId(), response.getCurrencyType().getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/mapper/IncomeCategoryMapperTest.java b/src/test/java/de/whiteo/mylfa/mapper/IncomeCategoryMapperTest.java new file mode 100644 index 0000000..1bf9801 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/mapper/IncomeCategoryMapperTest.java @@ -0,0 +1,29 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +class IncomeCategoryMapperTest { + + private final IncomeCategoryMapper mapper = Mappers.getMapper(IncomeCategoryMapper.class); + + @Test + void testToResponse() { + IncomeCategory parentCategory = IncomeCategoryBuilder.buildIncomeCategory(null); + IncomeCategory category = IncomeCategoryBuilder.buildIncomeCategory(parentCategory.getId()); + IncomeCategoryResponse response = mapper.toResponse(category, mapper.toResponse(parentCategory)); + assertEquals(category.getId(), response.getId()); + assertEquals(category.getParentId(), response.getParent().getId()); + assertEquals(category.getName(), response.getName()); + assertEquals(category.isHide(), response.isHide()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/mapper/IncomeMapperTest.java b/src/test/java/de/whiteo/mylfa/mapper/IncomeMapperTest.java new file mode 100644 index 0000000..71df022 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/mapper/IncomeMapperTest.java @@ -0,0 +1,42 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.IncomeBuilder; +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.domain.Income; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.dto.income.IncomeResponse; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +class IncomeMapperTest { + + private final CurrencyTypeMapper currencyTypeMapper = Mappers.getMapper(CurrencyTypeMapper.class); + private final IncomeCategoryMapper categoryMapper = Mappers.getMapper(IncomeCategoryMapper.class); + private final IncomeMapper mapper = Mappers.getMapper(IncomeMapper.class); + + @Test + void testToResponse() { + CurrencyTypeResponse currencyTypeResponse = currencyTypeMapper.toResponse( + CurrencyTypeBuilder.buildCurrencyType()); + + IncomeCategoryResponse categoryResponse = categoryMapper.toResponse( + IncomeCategoryBuilder.buildIncomeCategory(null)); + + Income income = IncomeBuilder.buildIncome(currencyTypeResponse.getId(), categoryResponse.getId()); + + IncomeResponse response = mapper.toResponse(income, currencyTypeResponse, categoryResponse); + assertEquals(income.getId(), response.getId()); + assertEquals(income.getDescription(), response.getDescription()); + assertEquals(income.getAmount(), response.getAmount()); + assertEquals(income.getCategoryId(), response.getCategory().getId()); + assertEquals(income.getCurrencyTypeId(), response.getCurrencyType().getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/mapper/UserMapperTest.java b/src/test/java/de/whiteo/mylfa/mapper/UserMapperTest.java new file mode 100644 index 0000000..d0ee588 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/mapper/UserMapperTest.java @@ -0,0 +1,39 @@ +package de.whiteo.mylfa.mapper; + +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.user.UserResponse; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import java.util.Random; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +class UserMapperTest { + + private final UserMapper mapper = Mappers.getMapper(UserMapper.class); + private final Random random = new Random(); + + @Test + void testToResponse() { + User user = buildEntity(); + UserResponse response = mapper.toResponse(user); + assertEquals(user.getId(), response.getId()); + assertEquals(user.getEmail(), response.getEmail()); + assertEquals(user.isVerified(), response.isVerified()); + } + + private User buildEntity() { + User user = new User(); + user.setId(UUID.randomUUID()); + user.setEmail(RandomStringUtils.randomAlphabetic(20)); + user.setVerified(random.nextBoolean()); + return user; + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/repository/CurrencyTypeRepositoryTest.java b/src/test/java/de/whiteo/mylfa/repository/CurrencyTypeRepositoryTest.java new file mode 100644 index 0000000..83edf5f --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/repository/CurrencyTypeRepositoryTest.java @@ -0,0 +1,64 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class CurrencyTypeRepositoryTest { + + @Autowired + private CurrencyTypeRepository repository; + private CurrencyTypeBuilder currencyTypeBuilder; + @Autowired + private UserRepository userRepository; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + userBuilder = new UserBuilder(userRepository); + currencyTypeBuilder = new CurrencyTypeBuilder(repository); + } + + @Test + void get() { + CurrencyType currencyType = prepareTest(); + CurrencyType dbCurrencyType = repository.getByIdAndUserId(currencyType.getId(), currencyType.getUserId()); + asserts(currencyType, dbCurrencyType); + } + + @Test + void findByNameIgnoreCase() { + CurrencyType currencyType = prepareTest(); + CurrencyType dbCurrencyType = repository.findByNameIgnoreCase(currencyType.getName()).orElseThrow( + () -> new NotFoundObjectException("Object not found")); + asserts(currencyType, dbCurrencyType); + } + + private void asserts(CurrencyType currencyType, CurrencyType dbCurrencyType) { + assertEquals(currencyType.getId(), dbCurrencyType.getId()); + assertEquals(currencyType.isHide(), dbCurrencyType.isHide()); + assertEquals(currencyType.getName(), dbCurrencyType.getName()); + assertEquals(currencyType.getUserId(), dbCurrencyType.getUserId()); + } + + private CurrencyType prepareTest() { + User user = userBuilder.buildUser(); + return currencyTypeBuilder.buildEntity(RandomStringUtils.randomAlphabetic(20), user.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/repository/ExpenseCategoryRepositoryTest.java b/src/test/java/de/whiteo/mylfa/repository/ExpenseCategoryRepositoryTest.java new file mode 100644 index 0000000..d56ca60 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/repository/ExpenseCategoryRepositoryTest.java @@ -0,0 +1,70 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ExpenseCategoryRepositoryTest { + + private ExpenseCategoryBuilder expenseCategoryBuilder; + @Autowired + private ExpenseCategoryRepository repository; + @Autowired + private UserRepository userRepository; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + expenseCategoryBuilder = new ExpenseCategoryBuilder(repository); + userBuilder = new UserBuilder(userRepository); + } + + @Test + void get() { + ExpenseCategory parentCategory = prepareTest(null); + ExpenseCategory category = prepareTest(parentCategory.getId()); + + ExpenseCategory dbExpenseCategory = repository.getByIdAndUserId(category.getId(), category.getUserId()); + asserts(category, dbExpenseCategory); + } + + @Test + void findByNameIgnoreCase() { + ExpenseCategory parentCategory = prepareTest(null); + ExpenseCategory category = prepareTest(parentCategory.getId()); + ExpenseCategory dbExpenseCategory = repository.findByNameIgnoreCase(category.getName()).orElseThrow( + () -> new NotFoundObjectException("Object not found")); + asserts(category, dbExpenseCategory); + } + + private void asserts(ExpenseCategory expenseCategory, ExpenseCategory dbExpenseCategory) { + assertEquals(expenseCategory.getId(), dbExpenseCategory.getId()); + assertEquals(expenseCategory.getParentId(), dbExpenseCategory.getParentId()); + assertEquals(expenseCategory.isHide(), dbExpenseCategory.isHide()); + assertEquals(expenseCategory.getName(), dbExpenseCategory.getName()); + assertEquals(expenseCategory.getUserId(), dbExpenseCategory.getUserId()); + } + + private ExpenseCategory prepareTest(UUID parentId) { + User user = userBuilder.buildUser(); + return expenseCategoryBuilder.buildEntity(RandomStringUtils.randomAlphabetic(20), user.getId(), parentId); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/repository/ExpenseRepositoryTest.java b/src/test/java/de/whiteo/mylfa/repository/ExpenseRepositoryTest.java new file mode 100644 index 0000000..d8fc4d4 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/repository/ExpenseRepositoryTest.java @@ -0,0 +1,76 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.ExpenseBuilder; +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.Expense; +import de.whiteo.mylfa.domain.ExpenseCategory; +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ExpenseRepositoryTest { + + @Autowired + private ExpenseCategoryRepository expenseCategoryRepository; + @Autowired + private CurrencyTypeRepository currencyTypeRepository; + private ExpenseCategoryBuilder expenseCategoryBuilder; + @Autowired + private ExpenseRepository repository; + private CurrencyTypeBuilder currencyTypeBuilder; + @Autowired + private UserRepository userRepository; + private ExpenseBuilder expenseBuilder; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + expenseCategoryBuilder = new ExpenseCategoryBuilder(expenseCategoryRepository); + currencyTypeBuilder = new CurrencyTypeBuilder(currencyTypeRepository); + expenseBuilder = new ExpenseBuilder(repository); + userBuilder = new UserBuilder(userRepository); + } + + @Test + void get() { + Expense expense = prepareTest(); + Expense dbExpense = repository.getByIdAndUserId(expense.getId(), expense.getUserId()); + asserts(expense, dbExpense); + } + + private void asserts(Expense expense, Expense dbExpense) { + assertEquals(expense.getId(), dbExpense.getId()); + assertEquals(expense.getDescription(), dbExpense.getDescription()); + assertEquals(expense.getCategoryId(), dbExpense.getCategoryId()); + assertEquals(expense.getAmount(), dbExpense.getAmount()); + assertEquals(expense.getUserId(), dbExpense.getUserId()); + } + + private Expense prepareTest() { + UUID userId = userBuilder.buildUser().getId(); + + CurrencyType currencyType = currencyTypeBuilder.buildEntity( + RandomStringUtils.randomAlphabetic(20), userId); + + ExpenseCategory expenseCategory = expenseCategoryBuilder.buildEntity( + RandomStringUtils.randomAlphabetic(20), userId, null); + + return expenseBuilder.buildEntity(userId, expenseCategory.getId(), currencyType.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/repository/IncomeCategoryRepositoryTest.java b/src/test/java/de/whiteo/mylfa/repository/IncomeCategoryRepositoryTest.java new file mode 100644 index 0000000..2a9d70e --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/repository/IncomeCategoryRepositoryTest.java @@ -0,0 +1,71 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class IncomeCategoryRepositoryTest { + + private IncomeCategoryBuilder incomeCategoryBuilder; + @Autowired + private IncomeCategoryRepository repository; + @Autowired + private UserRepository userRepository; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + incomeCategoryBuilder = new IncomeCategoryBuilder(repository); + userBuilder = new UserBuilder(userRepository); + } + + @Test + void getOrThrow() { + IncomeCategory parentCategory = prepareTest(null); + IncomeCategory category = prepareTest(parentCategory.getId()); + + IncomeCategory dbIncomeCategory = repository.getByIdAndUserId(category.getId(), category.getUserId()); + asserts(category, dbIncomeCategory); + } + + @Test + void findByNameIgnoreCase() { + IncomeCategory parentCategory = prepareTest(null); + IncomeCategory category = prepareTest(parentCategory.getId()); + + IncomeCategory dbIncomeCategory = repository.findByNameIgnoreCase(category.getName()).orElseThrow( + () -> new NotFoundObjectException("Object not found")); + asserts(category, dbIncomeCategory); + } + + private void asserts(IncomeCategory incomeCategory, IncomeCategory dbIncomeCategory) { + assertEquals(incomeCategory.getId(), dbIncomeCategory.getId()); + assertEquals(incomeCategory.getParentId(), dbIncomeCategory.getParentId()); + assertEquals(incomeCategory.isHide(), dbIncomeCategory.isHide()); + assertEquals(incomeCategory.getName(), dbIncomeCategory.getName()); + assertEquals(incomeCategory.getUserId(), dbIncomeCategory.getUserId()); + } + + private IncomeCategory prepareTest(UUID parentId) { + User user = userBuilder.buildUser(); + return incomeCategoryBuilder.buildEntity(RandomStringUtils.randomAlphabetic(20), user.getId(), parentId); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/repository/IncomeRepositoryTest.java b/src/test/java/de/whiteo/mylfa/repository/IncomeRepositoryTest.java new file mode 100644 index 0000000..25713af --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/repository/IncomeRepositoryTest.java @@ -0,0 +1,76 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.IncomeBuilder; +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.Income; +import de.whiteo.mylfa.domain.IncomeCategory; +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class IncomeRepositoryTest { + + @Autowired + private IncomeCategoryRepository incomeCategoryRepository; + @Autowired + private CurrencyTypeRepository currencyTypeRepository; + private IncomeCategoryBuilder incomeCategoryBuilder; + @Autowired + private IncomeRepository repository; + private CurrencyTypeBuilder currencyTypeBuilder; + @Autowired + private UserRepository userRepository; + private IncomeBuilder incomeBuilder; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + incomeCategoryBuilder = new IncomeCategoryBuilder(incomeCategoryRepository); + currencyTypeBuilder = new CurrencyTypeBuilder(currencyTypeRepository); + incomeBuilder = new IncomeBuilder(repository); + userBuilder = new UserBuilder(userRepository); + } + + @Test + void get() { + Income income = prepareTest(); + Income dbIncome = repository.getByIdAndUserId(income.getId(), income.getUserId()); + asserts(income, dbIncome); + } + + private void asserts(Income income, Income dbIncome) { + assertEquals(income.getId(), dbIncome.getId()); + assertEquals(income.getDescription(), dbIncome.getDescription()); + assertEquals(income.getCategoryId(), dbIncome.getCategoryId()); + assertEquals(income.getAmount(), dbIncome.getAmount()); + assertEquals(income.getUserId(), dbIncome.getUserId()); + } + + private Income prepareTest() { + UUID userId = userBuilder.buildUser().getId(); + + CurrencyType currencyType = currencyTypeBuilder.buildEntity( + RandomStringUtils.randomAlphabetic(20), userId); + + IncomeCategory incomeCategory = incomeCategoryBuilder. + buildEntity(RandomStringUtils.randomAlphabetic(20), userId, null); + + return incomeBuilder.buildEntity(userId, incomeCategory.getId(), currencyType.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/repository/UserRepositoryTest.java b/src/test/java/de/whiteo/mylfa/repository/UserRepositoryTest.java new file mode 100644 index 0000000..d715fd4 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/repository/UserRepositoryTest.java @@ -0,0 +1,52 @@ +package de.whiteo.mylfa.repository; + +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.exception.NotFoundObjectException; +import jakarta.annotation.PostConstruct; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class UserRepositoryTest { + + @Autowired + private UserRepository repository; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + userBuilder = new UserBuilder(repository); + } + + @Test + void getOrThrow() { + User user = userBuilder.buildUser(); + User dbUser = repository.getOrThrow(user.getId()); + asserts(user, dbUser); + } + + @Test + void findByNameIgnoreCase() { + User user = userBuilder.buildUser(); + User dbUser = repository.findByEmailIgnoreCase(user.getEmail()).orElseThrow( + () -> new NotFoundObjectException("Object not found")); + asserts(user, dbUser); + } + + private void asserts(User user, User dbUser) { + assertEquals(user.getId(), dbUser.getId()); + assertEquals(user.getPassword(), dbUser.getPassword()); + assertEquals(user.isVerified(), dbUser.isVerified()); + assertEquals(user.getEmail(), dbUser.getEmail()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/service/CurrencyTypeServiceTest.java b/src/test/java/de/whiteo/mylfa/service/CurrencyTypeServiceTest.java new file mode 100644 index 0000000..f468b5e --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/service/CurrencyTypeServiceTest.java @@ -0,0 +1,95 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.currencytype.CurrencyTypeResponse; +import de.whiteo.mylfa.repository.UserRepository; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class CurrencyTypeServiceTest { + + @Autowired + private UserRepository userRepository; + @Autowired + private CurrencyTypeService service; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create() { + User user = userBuilder.buildUser(); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + CurrencyTypeResponse response = service.create(user.getEmail(), request); + asserts(response, request); + } + + @Test + @Transactional + void update() { + User user = userBuilder.buildUser(); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + CurrencyTypeResponse createResponse = service.create(user.getEmail(), request); + CurrencyTypeCreateOrUpdateRequest updateRequest = CurrencyTypeBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + CurrencyTypeResponse response = service.update(user.getEmail(), createResponse.getId(), updateRequest); + asserts(response, updateRequest); + } + + @Test + @Transactional + void findById() { + User user = userBuilder.buildUser(); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + CurrencyTypeResponse createResponse = service.create(user.getEmail(), request); + CurrencyTypeResponse response = service.findById(user.getEmail(), createResponse.getId()); + asserts(response, request); + } + + @SuppressWarnings("java:S2699") + @Test + @Transactional + void delete() { + User user = userBuilder.buildUser(); + + CurrencyTypeCreateOrUpdateRequest request = CurrencyTypeBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + CurrencyTypeResponse createResponse = service.create(user.getEmail(), request); + service.delete(user.getEmail(), createResponse.getId()); + } + + private void asserts(CurrencyTypeResponse response, CurrencyTypeCreateOrUpdateRequest request) { + assertEquals(response.getName(), request.getName()); + assertEquals(response.isHide(), request.isHide()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/service/ExpenseCategoryServiceTest.java b/src/test/java/de/whiteo/mylfa/service/ExpenseCategoryServiceTest.java new file mode 100644 index 0000000..cdbbab1 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/service/ExpenseCategoryServiceTest.java @@ -0,0 +1,153 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.expensecategory.ExpenseCategoryResponse; +import de.whiteo.mylfa.repository.UserRepository; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ExpenseCategoryServiceTest { + + @Autowired + private UserRepository userRepository; + @Autowired + private ExpenseCategoryService service; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create() { + User user = userBuilder.buildUser(); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + ExpenseCategoryResponse response = service.create(user.getEmail(), request); + asserts(response, request, false); + } + + @Test + @Transactional + void update() { + User user = userBuilder.buildUser(); + + ExpenseCategoryCreateOrUpdateRequest createRequest = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + ExpenseCategoryResponse createResponse = service.create(user.getEmail(), createRequest); + + ExpenseCategoryCreateOrUpdateRequest updateRequest = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + ExpenseCategoryResponse response = service.update(user.getEmail(), createResponse.getId(), updateRequest); + asserts(response, updateRequest, false); + } + + @Test + @Transactional + void find_by_id() { + User user = userBuilder.buildUser(); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + ExpenseCategoryResponse createResponse = service.create(user.getEmail(), request); + ExpenseCategoryResponse response = service.findById(user.getEmail(), createResponse.getId()); + asserts(response, request, false); + } + + @Test + @Transactional + void create_with_parent() { + User user = userBuilder.buildUser(); + + ExpenseCategoryCreateOrUpdateRequest parentRequest = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + ExpenseCategoryResponse parentResponse = service.create(user.getEmail(), parentRequest); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), parentResponse.getId()); + ExpenseCategoryResponse response = service.create(user.getEmail(), request); + asserts(response, request, true); + } + + @Test + @Transactional + void update_with_parent() { + User user = userBuilder.buildUser(); + + ExpenseCategoryCreateOrUpdateRequest parentRequest = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + ExpenseCategoryResponse parentResponse = service.create(user.getEmail(), parentRequest); + + ExpenseCategoryCreateOrUpdateRequest createRequest = ExpenseCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), parentResponse.getId()); + ExpenseCategoryResponse createResponse = service.create(user.getEmail(), createRequest); + + ExpenseCategoryCreateOrUpdateRequest updateParentRequest = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + ExpenseCategoryResponse updateParentResponse = service.create(user.getEmail(), updateParentRequest); + + ExpenseCategoryCreateOrUpdateRequest updateRequest = ExpenseCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), updateParentResponse.getId()); + ExpenseCategoryResponse response = service.update(user.getEmail(), createResponse.getId(), updateRequest); + asserts(response, updateRequest, true); + } + + @Test + @Transactional + void find_by_id_with_parent() { + User user = userBuilder.buildUser(); + + ExpenseCategoryCreateOrUpdateRequest parentRequest = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + ExpenseCategoryResponse parentResponse = service.create(user.getEmail(), parentRequest); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), parentResponse.getId()); + ExpenseCategoryResponse createResponse = service.create(user.getEmail(), request); + + ExpenseCategoryResponse response = service.findById(user.getEmail(), createResponse.getId()); + asserts(response, request, true); + } + + @SuppressWarnings("java:S2699") + @Test + @Transactional + void delete() { + User user = userBuilder.buildUser(); + + ExpenseCategoryCreateOrUpdateRequest request = ExpenseCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + ExpenseCategoryResponse createResponse = service.create(user.getEmail(), request); + service.delete(user.getEmail(), createResponse.getId()); + } + + private void asserts(ExpenseCategoryResponse response, ExpenseCategoryCreateOrUpdateRequest request, + boolean withParent) { + assertEquals(response.getName(), request.getName()); + assertEquals(response.isHide(), request.isHide()); + if (withParent) { + assertEquals(response.getParent().getId(), request.getParentId()); + } + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/service/ExpenseServiceTest.java b/src/test/java/de/whiteo/mylfa/service/ExpenseServiceTest.java new file mode 100644 index 0000000..8f63497 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/service/ExpenseServiceTest.java @@ -0,0 +1,118 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.ExpenseBuilder; +import de.whiteo.mylfa.builder.ExpenseCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.ExpenseCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.expense.ExpenseCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.expense.ExpenseResponse; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.ExpenseCategoryRepository; +import de.whiteo.mylfa.repository.UserRepository; +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import jakarta.transaction.Transactional; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ExpenseServiceTest { + + @Autowired + private ExpenseCategoryRepository expenseCategoryRepository; + @Autowired + private CurrencyTypeRepository currencyTypeRepository; + private ExpenseCategoryBuilder expenseCategoryBuilder; + private CurrencyTypeBuilder currencyTypeBuilder; + @Autowired + private UserRepository userRepository; + @Autowired + private ExpenseService service; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + expenseCategoryBuilder = new ExpenseCategoryBuilder(expenseCategoryRepository); + currencyTypeBuilder = new CurrencyTypeBuilder(currencyTypeRepository); + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create() { + User user = userBuilder.buildUser(); + + ExpenseCreateOrUpdateRequest request = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + ExpenseResponse response = service.create(user.getEmail(), request); + asserts(response, request); + } + + @Test + @Transactional + void update() { + User user = userBuilder.buildUser(); + + String randomCurrencyTypeName = RandomStringUtils.randomAlphabetic(20); + + ExpenseCreateOrUpdateRequest request = prepareTest(randomCurrencyTypeName, user.getId()); + + ExpenseResponse createResponse = service.create(user.getEmail(), request); + ExpenseCreateOrUpdateRequest updateRequest = prepareTest(randomCurrencyTypeName, user.getId()); + ExpenseResponse response = service.update(user.getEmail(), createResponse.getId(), updateRequest); + asserts(response, updateRequest); + } + + @Test + @Transactional + void findById() { + User user = userBuilder.buildUser(); + + ExpenseCreateOrUpdateRequest request = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + ExpenseResponse createResponse = service.create(user.getEmail(), request); + ExpenseResponse response = service.findById(user.getEmail(), createResponse.getId()); + asserts(response, request); + } + + @Test + @Transactional + void delete() { + User user = userBuilder.buildUser(); + + ExpenseCreateOrUpdateRequest request = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + ExpenseResponse createResponse = service.create(user.getEmail(), request); + service.delete(user.getEmail(), createResponse.getId()); + } + + private void asserts(ExpenseResponse response, ExpenseCreateOrUpdateRequest request) { + assertEquals(response.getCategory().getId(), request.getCategoryId()); + assertEquals(response.getDescription(), request.getDescription()); + assertEquals(response.getAmount(), request.getAmount()); + assertEquals(response.getCurrencyType().getId(), request.getCurrencyTypeId()); + } + + private ExpenseCreateOrUpdateRequest prepareTest(String currencyName, UUID userId) { + CurrencyType currencyType = currencyTypeBuilder.buildEntity(currencyName, userId); + + ExpenseCategory expenseCategory = expenseCategoryBuilder.buildEntity( + RandomStringUtils.randomAlphabetic(20), userId, null); + + return ExpenseBuilder.buildRequest(expenseCategory.getId(), currencyType.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/service/IncomeCategoryServiceTest.java b/src/test/java/de/whiteo/mylfa/service/IncomeCategoryServiceTest.java new file mode 100644 index 0000000..d164c3e --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/service/IncomeCategoryServiceTest.java @@ -0,0 +1,153 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.incomecategory.IncomeCategoryResponse; +import de.whiteo.mylfa.repository.UserRepository; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class IncomeCategoryServiceTest { + + @Autowired + private UserRepository userRepository; + @Autowired + private IncomeCategoryService service; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create() { + User user = userBuilder.buildUser(); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + IncomeCategoryResponse response = service.create(user.getEmail(), request); + asserts(response, request, false); + } + + @Test + @Transactional + void update() { + User user = userBuilder.buildUser(); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + IncomeCategoryResponse createResponse = service.create(user.getEmail(), request); + + IncomeCategoryCreateOrUpdateRequest updateRequest = IncomeCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + IncomeCategoryResponse response = service.update(user.getEmail(), createResponse.getId(), updateRequest); + asserts(response, updateRequest, false); + } + + @Test + @Transactional + void find_by_id() { + User user = userBuilder.buildUser(); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + IncomeCategoryResponse createResponse = service.create(user.getEmail(), request); + IncomeCategoryResponse response = service.findById(user.getEmail(), createResponse.getId()); + asserts(response, request, false); + } + + @Test + @Transactional + void create_with_parent() { + User user = userBuilder.buildUser(); + + IncomeCategoryCreateOrUpdateRequest parentRequest = + IncomeCategoryBuilder.buildRequest(RandomStringUtils.randomAlphabetic(20)); + IncomeCategoryResponse parentResponse = service.create(user.getEmail(), parentRequest); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), parentResponse.getId()); + IncomeCategoryResponse response = service.create(user.getEmail(), request); + asserts(response, request, true); + } + + @Test + @Transactional + void update_with_parent() { + User user = userBuilder.buildUser(); + + IncomeCategoryCreateOrUpdateRequest parentRequest = + IncomeCategoryBuilder.buildRequest(RandomStringUtils.randomAlphabetic(20)); + IncomeCategoryResponse parentResponse = service.create(user.getEmail(), parentRequest); + + IncomeCategoryCreateOrUpdateRequest createRequest = IncomeCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), parentResponse.getId()); + IncomeCategoryResponse createResponse = service.create(user.getEmail(), createRequest); + + IncomeCategoryCreateOrUpdateRequest updateParentRequest = + IncomeCategoryBuilder.buildRequest(RandomStringUtils.randomAlphabetic(20)); + IncomeCategoryResponse updateParentResponse = service.create(user.getEmail(), updateParentRequest); + + IncomeCategoryCreateOrUpdateRequest updateRequest = IncomeCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), updateParentResponse.getId()); + IncomeCategoryResponse response = service.update(user.getEmail(), createResponse.getId(), updateRequest); + asserts(response, updateRequest, true); + } + + @Test + @Transactional + void find_by_id_with_parent() { + User user = userBuilder.buildUser(); + + IncomeCategoryCreateOrUpdateRequest parentRequest = + IncomeCategoryBuilder.buildRequest(RandomStringUtils.randomAlphabetic(20)); + IncomeCategoryResponse parentResponse = service.create(user.getEmail(), parentRequest); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequestWithParentId( + RandomStringUtils.randomAlphabetic(20), parentResponse.getId()); + IncomeCategoryResponse createResponse = service.create(user.getEmail(), request); + + IncomeCategoryResponse response = service.findById(user.getEmail(), createResponse.getId()); + asserts(response, request, true); + } + + @SuppressWarnings("java:S2699") + @Test + @Transactional + void delete() { + User user = userBuilder.buildUser(); + + IncomeCategoryCreateOrUpdateRequest request = IncomeCategoryBuilder.buildRequest( + RandomStringUtils.randomAlphabetic(20)); + + IncomeCategoryResponse createResponse = service.create(user.getEmail(), request); + service.delete(user.getEmail(), createResponse.getId()); + } + + private void asserts(IncomeCategoryResponse response, IncomeCategoryCreateOrUpdateRequest request, + boolean withParent) { + assertEquals(response.getName(), request.getName()); + assertEquals(response.isHide(), request.isHide()); + if (withParent) { + assertEquals(response.getParent().getId(), request.getParentId()); + } + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/service/IncomeServiceTest.java b/src/test/java/de/whiteo/mylfa/service/IncomeServiceTest.java new file mode 100644 index 0000000..bc4142e --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/service/IncomeServiceTest.java @@ -0,0 +1,118 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.builder.CurrencyTypeBuilder; +import de.whiteo.mylfa.builder.IncomeBuilder; +import de.whiteo.mylfa.builder.IncomeCategoryBuilder; +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.domain.CurrencyType; +import de.whiteo.mylfa.domain.IncomeCategory; +import de.whiteo.mylfa.domain.User; +import de.whiteo.mylfa.dto.income.IncomeCreateOrUpdateRequest; +import de.whiteo.mylfa.dto.income.IncomeResponse; +import de.whiteo.mylfa.repository.CurrencyTypeRepository; +import de.whiteo.mylfa.repository.IncomeCategoryRepository; +import de.whiteo.mylfa.repository.UserRepository; +import jakarta.annotation.PostConstruct; +import jakarta.transaction.Transactional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class IncomeServiceTest { + + @Autowired + private IncomeCategoryRepository incomeCategoryRepository; + @Autowired + private CurrencyTypeRepository currencyTypeRepository; + private IncomeCategoryBuilder incomeCategoryBuilder; + private CurrencyTypeBuilder currencyTypeBuilder; + @Autowired + private UserRepository userRepository; + @Autowired + private IncomeService service; + private UserBuilder userBuilder; + + @PostConstruct + void initialize() { + incomeCategoryBuilder = new IncomeCategoryBuilder(incomeCategoryRepository); + currencyTypeBuilder = new CurrencyTypeBuilder(currencyTypeRepository); + userBuilder = new UserBuilder(userRepository); + } + + @Test + @Transactional + void create() { + User user = userBuilder.buildUser(); + + IncomeCreateOrUpdateRequest request = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + IncomeResponse response = service.create(user.getEmail(), request); + asserts(response, request); + } + + @Test + @Transactional + void update() { + User user = userBuilder.buildUser(); + + String randomCurrencyTypeName = RandomStringUtils.randomAlphabetic(20); + + IncomeCreateOrUpdateRequest request = prepareTest(randomCurrencyTypeName, user.getId()); + + IncomeResponse createResponse = service.create(user.getEmail(), request); + IncomeCreateOrUpdateRequest updateRequest = prepareTest(randomCurrencyTypeName, user.getId()); + IncomeResponse response = service.update(user.getEmail(), createResponse.getId(), updateRequest); + asserts(response, updateRequest); + } + + @Test + @Transactional + void findById() { + User user = userBuilder.buildUser(); + + IncomeCreateOrUpdateRequest request = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + IncomeResponse createResponse = service.create(user.getEmail(), request); + IncomeResponse response = service.findById(user.getEmail(), createResponse.getId()); + asserts(response, request); + } + + @Test + @Transactional + void delete() { + User user = userBuilder.buildUser(); + + IncomeCreateOrUpdateRequest request = prepareTest(RandomStringUtils.randomAlphabetic(20), user.getId()); + + IncomeResponse createResponse = service.create(user.getEmail(), request); + service.delete(user.getEmail(), createResponse.getId()); + } + + private void asserts(IncomeResponse response, IncomeCreateOrUpdateRequest request) { + assertEquals(response.getCategory().getId(), request.getCategoryId()); + assertEquals(response.getDescription(), request.getDescription()); + assertEquals(response.getCurrencyType().getId(), request.getCurrencyTypeId()); + assertEquals(response.getAmount(), request.getAmount()); + } + + private IncomeCreateOrUpdateRequest prepareTest(String currencyName, UUID userId) { + CurrencyType currencyType = currencyTypeBuilder.buildEntity(currencyName, userId); + + IncomeCategory incomeCategory = incomeCategoryBuilder. + buildEntity(RandomStringUtils.randomAlphabetic(20), userId, null); + + return IncomeBuilder.buildRequest(incomeCategory.getId(), currencyType.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/de/whiteo/mylfa/service/UserServiceTest.java b/src/test/java/de/whiteo/mylfa/service/UserServiceTest.java new file mode 100644 index 0000000..6e840e4 --- /dev/null +++ b/src/test/java/de/whiteo/mylfa/service/UserServiceTest.java @@ -0,0 +1,95 @@ +package de.whiteo.mylfa.service; + +import de.whiteo.mylfa.builder.UserBuilder; +import de.whiteo.mylfa.dto.user.UserCreateRequest; +import de.whiteo.mylfa.dto.user.UserResponse; +import de.whiteo.mylfa.dto.user.UserUpdatePasswordRequest; +import de.whiteo.mylfa.dto.user.UserUpdateRequest; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import jakarta.transaction.Transactional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Leo Tanas (github) + */ + +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class UserServiceTest { + + @Autowired + private UserService service; + private final static String MAIL = "@test.com"; + + @Test + @Transactional + void create() { + UserCreateRequest request = UserBuilder.buildUserCreateRequest( + RandomStringUtils.randomAlphabetic(5) + MAIL, RandomStringUtils.randomAlphabetic(20)); + + UserResponse response = service.create(request); + asserts(response, request.getEmail()); + } + + @Test + @Transactional + void update() { + String passwd = RandomStringUtils.randomAlphabetic(20); + + UserCreateRequest createRequest = UserBuilder.buildUserCreateRequest( + RandomStringUtils.randomAlphabetic(5) + MAIL, passwd); + UserResponse createResponse = service.create(createRequest); + + UserUpdateRequest updateRequest = UserBuilder.buildUserUpdateRequest( + RandomStringUtils.randomAlphabetic(5) + MAIL); + UserResponse response = service.update(createResponse.getId(), updateRequest); + asserts(response, updateRequest.getEmail()); + } + + @Test + @Transactional + void updatePassword() { + String passwd = RandomStringUtils.randomAlphabetic(20); + + UserCreateRequest createRequest = UserBuilder.buildUserCreateRequest( + RandomStringUtils.randomAlphabetic(5) + MAIL, passwd); + UserResponse createResponse = service.create(createRequest); + + UserUpdatePasswordRequest updateRequest = UserBuilder.buildUserPasswordUpdateRequest( + RandomStringUtils.randomAlphabetic(20), passwd); + service.updatePassword(createResponse.getId(), updateRequest); + } + + @Test + @Transactional + void findById() { + UserCreateRequest request = UserBuilder.buildUserCreateRequest( + RandomStringUtils.randomAlphabetic(5) + MAIL, + RandomStringUtils.randomAlphabetic(20)); + + UserResponse createResponse = service.create(request); + UserResponse response = service.find(createResponse.getId()); + asserts(response, createResponse.getEmail()); + } + + @SuppressWarnings("java:S2699") + @Test + @Transactional + void delete() { + UserCreateRequest request = UserBuilder.buildUserCreateRequest( + RandomStringUtils.randomAlphabetic(5) + MAIL, + RandomStringUtils.randomAlphabetic(20)); + + UserResponse createRequest = service.create(request); + service.delete(createRequest.getId()); + } + + private void asserts(UserResponse response, String email) { + assertEquals(response.getEmail(), email); + } +} \ No newline at end of file