This commit is contained in:
xds
2025-10-16 15:06:20 +03:00
commit 040da34ff7
78 changed files with 3934 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
package space.luminic.finance.api
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
import jakarta.ws.rs.GET
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 space.luminic.finance.dtos.AccountDTO
import space.luminic.finance.dtos.TransactionDTO
import space.luminic.finance.mappers.AccountMapper.toDto
import space.luminic.finance.mappers.TransactionMapper.toDto
import space.luminic.finance.models.Account
import space.luminic.finance.services.AccountService
@RestController
@RequestMapping("/spaces/{spaceId}/accounts")
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
class AccountController(
private val accountService: AccountService
) {
@GetMapping
suspend fun getAccounts(@PathVariable spaceId: String): List<AccountDTO> {
return accountService.getAccounts(spaceId).map { it.toDto() }
}
@GetMapping("/{accountId}")
suspend fun getAccount(@PathVariable spaceId: String, @PathVariable accountId: String): AccountDTO {
return accountService.getAccount(accountId, spaceId).toDto()
}
@GetMapping("/{accountId}/transactions")
suspend fun getAccountTransactions(
@PathVariable spaceId: String,
@PathVariable accountId: String
): List<TransactionDTO> {
return accountService.getAccountTransactions(spaceId, accountId).map { it.toDto() }
}
@PostMapping
suspend fun createAccount(
@PathVariable spaceId: String,
@RequestBody accountDTO: AccountDTO.CreateAccountDTO
): AccountDTO {
return accountService.createAccount(spaceId, accountDTO).toDto()
}
@PutMapping("/{accountId}")
suspend fun updateAccount(
@PathVariable spaceId: String,
@PathVariable accountId: String,
@RequestBody accountDTO: AccountDTO.UpdateAccountDTO
): AccountDTO {
return accountService.updateAccount(spaceId, accountDTO).toDto()
}
@DeleteMapping("/{accountId}")
suspend fun deleteAccount(@PathVariable spaceId: String, @PathVariable accountId: String) {
accountService.deleteAccount(accountId, spaceId)
}
}

View File

@@ -0,0 +1,56 @@
package space.luminic.finance.api
import kotlinx.coroutines.reactive.awaitSingle
import org.slf4j.LoggerFactory
import org.springframework.security.core.context.ReactiveSecurityContextHolder
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.bind.annotation.*
import space.luminic.finance.dtos.UserDTO.*
import space.luminic.finance.dtos.UserDTO
import space.luminic.finance.mappers.UserMapper.toDto
import space.luminic.finance.services.AuthService
import space.luminic.finance.services.UserService
import kotlin.jvm.javaClass
import kotlin.to
@RestController
@RequestMapping("/auth")
class AuthController(
private val userService: UserService,
private val authService: AuthService
) {
private val logger = LoggerFactory.getLogger(javaClass)
@GetMapping("/test")
fun test(): String {
val authentication = SecurityContextHolder.getContext().authentication
logger.info("SecurityContext in controller: $authentication")
return "Hello, ${authentication.name}"
}
@PostMapping("/login")
suspend fun login(@RequestBody request: AuthUserDTO): Map<String, String> {
val token = authService.login(request.username, request.password)
return mapOf("token" to token)
}
@PostMapping("/register")
suspend fun register(@RequestBody request: RegisterUserDTO): UserDTO {
return authService.register(request.username, request.password, request.firstName).toDto()
}
@PostMapping("/tgLogin")
suspend fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Map<String, String> {
val token = authService.tgLogin(tgId)
return mapOf("token" to token)
}
@GetMapping("/me")
suspend fun getMe(): UserDTO {
val securityContext = ReactiveSecurityContextHolder.getContext().awaitSingle()
return userService.getByUsername(securityContext.authentication.name).toDto()
}
}

View File

@@ -0,0 +1,78 @@
package space.luminic.finance.api
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
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.RequestParam
import org.springframework.web.bind.annotation.RestController
import space.luminic.finance.dtos.BudgetDTO
import space.luminic.finance.dtos.TransactionDTO
import space.luminic.finance.mappers.BudgetMapper.toDto
import space.luminic.finance.mappers.BudgetMapper.toShortDto
import space.luminic.finance.mappers.TransactionMapper.toDto
import space.luminic.finance.models.Budget
import space.luminic.finance.services.BudgetService
@RestController
@RequestMapping("/spaces/{spaceId}/budgets")
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
class BudgetController(
private val budgetService: BudgetService
) {
@GetMapping
suspend fun getBudgets(
@PathVariable spaceId: String,
@RequestParam(value = "sort", defaultValue = "dateFrom") sortBy: String,
@RequestParam("direction", defaultValue = "DESC") sortDirection: String
): List<BudgetDTO.BudgetShortInfoDTO> {
return budgetService.getBudgets(spaceId, sortBy, sortDirection).map { it.toShortDto() }
}
@GetMapping("/{budgetId}")
suspend fun getBudgetById(@PathVariable spaceId: String, @PathVariable budgetId: String): BudgetDTO {
return budgetService.getBudget(spaceId, budgetId).toDto()
}
@GetMapping("/{budgetId}/transactions")
suspend fun getBudgetTransactions(
@PathVariable spaceId: String,
@PathVariable budgetId: String
): BudgetDTO.BudgetTransactionsDTO {
return budgetService.getBudgetTransactions(spaceId, budgetId).toDto()
}
@PostMapping
suspend fun createBudget(
@PathVariable spaceId: String,
@RequestBody createBudgetDTO: BudgetDTO.CreateBudgetDTO
): BudgetDTO {
return budgetService.createBudget(spaceId, Budget.BudgetType.SPECIAL, createBudgetDTO).toDto()
}
@PutMapping("/{budgetId}")
suspend fun updateBudget(
@PathVariable spaceId: String,
@PathVariable budgetId: String,
@RequestBody updateBudgetDTO: BudgetDTO.UpdateBudgetDTO
): BudgetDTO {
return budgetService.updateBudget(spaceId, updateBudgetDTO).toDto()
}
@DeleteMapping("/{budgetId}")
suspend fun deleteBudget(@PathVariable spaceId: String, @PathVariable budgetId: String) {
budgetService.deleteBudget(spaceId, budgetId)
}
}

View File

@@ -0,0 +1,65 @@
package space.luminic.finance.api
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
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 space.luminic.finance.dtos.CategoryDTO
import space.luminic.finance.mappers.CategoryMapper.toDto
import space.luminic.finance.services.CategoryService
@RestController
@RequestMapping("/spaces/{spaceId}/categories")
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
class CategoryController(
private val categoryService: CategoryService,
service: CategoryService
) {
@GetMapping
suspend fun getCategories(@PathVariable spaceId: String): List<CategoryDTO> {
return categoryService.getCategories(spaceId).map { it.toDto() }
}
@GetMapping("/{categoryId}")
suspend fun getCategory(@PathVariable spaceId: String, @PathVariable categoryId: String): CategoryDTO {
return categoryService.getCategory(spaceId, categoryId).toDto()
}
@PostMapping
suspend fun createCategory(
@PathVariable spaceId: String,
@RequestBody categoryDTO: CategoryDTO.CreateCategoryDTO
): CategoryDTO {
return categoryService.createCategory(spaceId, categoryDTO).toDto()
}
@PutMapping("/{categoryId}")
suspend fun updateCategory(
@PathVariable spaceId: String,
@PathVariable categoryId: String,
@RequestBody categoryDTO: CategoryDTO.UpdateCategoryDTO
): CategoryDTO {
return categoryService.updateCategory(spaceId, categoryDTO).toDto()
}
@DeleteMapping("/{categoryId}")
suspend fun deleteCategory(@PathVariable spaceId: String, @PathVariable categoryId: String) {
categoryService.deleteCategory(spaceId, categoryId)
}
}

View File

@@ -0,0 +1,53 @@
package space.luminic.finance.api
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
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 space.luminic.finance.dtos.CurrencyDTO
import space.luminic.finance.mappers.CurrencyMapper.toDto
import space.luminic.finance.services.CurrencyService
@RestController
@RequestMapping("/references")
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
class ReferenceController(
private val currencyService: CurrencyService
) {
@GetMapping("/currencies")
suspend fun getCurrencies(): List<CurrencyDTO> {
return currencyService.getCurrencies().map { it.toDto() }
}
@GetMapping("/currencies/{currencyCode}")
suspend fun getCurrency(@PathVariable currencyCode: String): CurrencyDTO {
return currencyService.getCurrency(currencyCode).toDto()
}
@PostMapping("/currencies")
suspend fun createCurrency(@RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
return currencyService.createCurrency(currencyDTO).toDto()
}
@PutMapping("/currencies/{currencyCode}")
suspend fun updateCurrency(@PathVariable currencyCode: String, @RequestBody currencyDTO: CurrencyDTO): CurrencyDTO {
return currencyService.updateCurrency(currencyDTO).toDto()
}
@DeleteMapping("/currencies/{currencyCode}")
suspend fun deleteCurrency(@PathVariable currencyCode: String) {
currencyService.deleteCurrency(currencyCode)
}
}

View File

@@ -0,0 +1,55 @@
package space.luminic.finance.api
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
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 space.luminic.finance.dtos.SpaceDTO
import space.luminic.finance.mappers.SpaceMapper.toDto
import space.luminic.finance.models.Space
import space.luminic.finance.services.SpaceService
@RestController
@RequestMapping("/spaces")
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
class SpaceController(
private val spaceService: SpaceService,
) {
@GetMapping
suspend fun getSpaces(): List<SpaceDTO> {
return spaceService.getSpaces().map { it.toDto() }
}
@GetMapping("/{spaceId}")
suspend fun getSpace(@PathVariable spaceId: String): SpaceDTO {
return spaceService.getSpace(spaceId).toDto()
}
@PostMapping
suspend fun createSpace(@RequestBody space: SpaceDTO.CreateSpaceDTO): SpaceDTO {
return spaceService.createSpace(space).toDto()
}
@PutMapping("/{spaceId}")
suspend fun updateSpace(@PathVariable spaceId: String, @RequestBody space: SpaceDTO.UpdateSpaceDTO): SpaceDTO {
return spaceService.updateSpace(spaceId, space).toDto()
}
@DeleteMapping("/{spaceId}")
suspend fun deleteSpace(@PathVariable spaceId: String) {
spaceService.deleteSpace(spaceId)
}
}

View File

@@ -0,0 +1,60 @@
package space.luminic.finance.api
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
import io.swagger.v3.oas.annotations.security.SecurityScheme
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 space.luminic.finance.dtos.TransactionDTO
import space.luminic.finance.mappers.TransactionMapper.toDto
import space.luminic.finance.services.TransactionService
@RestController
@RequestMapping("/spaces/{spaceId}/transactions")
@SecurityScheme(
name = "bearerAuth",
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
class TransactionController (
private val transactionService: TransactionService,
service: TransactionService,
transactionService1: TransactionService,
){
@GetMapping
suspend fun getTransactions(@PathVariable spaceId: String) : List<TransactionDTO>{
return transactionService.getTransactions(spaceId, TransactionService.TransactionsFilter(),"date", "DESC").map { it.toDto() }
}
@GetMapping("/{transactionId}")
suspend fun getTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String): TransactionDTO {
return transactionService.getTransaction(spaceId, transactionId).toDto()
}
@PostMapping
suspend fun createTransaction(@PathVariable spaceId: String, @RequestBody transactionDTO: TransactionDTO.CreateTransactionDTO): TransactionDTO {
return transactionService.createTransaction(spaceId, transactionDTO).toDto()
}
@PutMapping("/{transactionId}")
suspend fun updateTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String, @RequestBody transactionDTO: TransactionDTO.UpdateTransactionDTO): TransactionDTO {
return transactionService.updateTransaction(spaceId, transactionDTO).toDto()
}
@DeleteMapping("/{transactionId}")
suspend fun deleteTransaction(@PathVariable spaceId: String, @PathVariable transactionId: String) {
transactionService.deleteTransaction(spaceId, transactionId)
}
}

View File

@@ -0,0 +1,109 @@
package space.luminic.finance.api.exceptionHandlers
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import space.luminic.finance.configs.AuthException
import space.luminic.finance.models.NotFoundException
@RestControllerAdvice
class GlobalExceptionHandler {
fun constructErrorBody(
e: Exception,
message: String,
status: HttpStatus,
request: ServerHttpRequest
): Map<String, Any?> {
val errorResponse = mapOf(
"timestamp" to System.currentTimeMillis(),
"status" to status.value(),
"error" to message,
"message" to e.message,
"path" to request.path.value()
)
return errorResponse
}
@ExceptionHandler(AuthException::class)
fun handleAuthenticationException(
ex: AuthException,
exchange: ServerWebExchange
): Mono<ResponseEntity<Map<String, Any?>>?> {
ex.printStackTrace()
return Mono.just(
ResponseEntity(
constructErrorBody(
ex,
ex.message.toString(),
HttpStatus.UNAUTHORIZED,
exchange.request
), HttpStatus.UNAUTHORIZED
)
)
}
@ExceptionHandler(NotFoundException::class)
fun handleNotFoundException(
e: NotFoundException,
exchange: ServerWebExchange
): Mono<ResponseEntity<Map<String, Any?>>?> {
e.printStackTrace()
return Mono.just(
ResponseEntity(
constructErrorBody(
e,
e.message.toString(),
HttpStatus.NOT_FOUND,
exchange.request
), HttpStatus.NOT_FOUND
)
)
}
@ExceptionHandler(IllegalArgumentException::class)
fun handleIllegalArgumentException(
e: IllegalArgumentException,
exchange: ServerWebExchange
): Mono<ResponseEntity<Map<String, Any?>>?> {
e.printStackTrace()
return Mono.just(
ResponseEntity(
constructErrorBody(
e,
e.message.toString(),
HttpStatus.BAD_REQUEST,
exchange.request
), HttpStatus.BAD_REQUEST
)
)
}
@ExceptionHandler(Exception::class)
fun handleGenericException(
e: Exception,
exchange: ServerWebExchange
): Mono<out ResponseEntity<out Map<String, Any?>>?> {
e.printStackTrace()
return Mono.just(
ResponseEntity(
constructErrorBody(
e,
e.message.toString(),
HttpStatus.INTERNAL_SERVER_ERROR,
exchange.request
), HttpStatus.INTERNAL_SERVER_ERROR
)
)
}
}

View File

@@ -0,0 +1,49 @@
package space.luminic.finance.api.exceptionHandlers
import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
import org.springframework.boot.web.error.ErrorAttributeOptions
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.context.ApplicationContext
import org.springframework.core.annotation.Order
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerCodecConfigurer
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.server.*
import reactor.core.publisher.Mono
@Component
@Order(-2)
class GlobalErrorWebExceptionHandler(
errorAttributes: ErrorAttributes,
applicationContext: ApplicationContext,
serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(
errorAttributes,
WebProperties.Resources(),
applicationContext
) {
init {
super.setMessageWriters(serverCodecConfigurer.writers)
super.setMessageReaders(serverCodecConfigurer.readers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse)
}
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
val errorAttributesMap = getErrorAttributes(
request,
ErrorAttributeOptions.of(
ErrorAttributeOptions.Include.MESSAGE
)
)
return ServerResponse.status(401)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorAttributesMap))
}
}