Files
luminic-back/src/main/kotlin/space/luminic/budgerapp/controllers/SpaceController.kt
2025-04-07 18:26:23 +03:00

402 lines
14 KiB
Kotlin

package space.luminic.budgerapp.controllers
import com.opencsv.CSVWriter
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import org.apache.commons.io.IOUtils.writer
import org.bson.Document
import org.slf4j.LoggerFactory
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import space.luminic.budgerapp.controllers.BudgetController.LimitValue
import space.luminic.budgerapp.controllers.dtos.BudgetCreationDTO
import space.luminic.budgerapp.models.*
import space.luminic.budgerapp.services.*
import java.io.ByteArrayOutputStream
import java.io.OutputStreamWriter
import java.time.LocalDate
@RestController
@RequestMapping("/spaces")
class SpaceController(
private val spaceService: SpaceService,
private val financialService: FinancialService,
private val categoryService: CategoryService,
private val recurrentService: RecurrentService,
private val authService: AuthService
) {
private val log = LoggerFactory.getLogger(SpaceController::class.java)
data class SpaceCreateDTO(
val name: String,
val description: String,
val createCategories: Boolean,
)
@GetMapping
suspend fun getSpaces(): List<Space> {
return spaceService.getSpaces()
}
@PostMapping
suspend fun createSpace(@RequestBody space: SpaceCreateDTO): Space {
return spaceService.createSpace(
Space(name = space.name, description = space.description),
space.createCategories
)
}
@GetMapping("{spaceId}")
suspend fun getSpace(@PathVariable spaceId: String): Space {
return spaceService.getSpace(spaceId)
}
@DeleteMapping("/{spaceId}")
suspend fun deleteSpace(@PathVariable spaceId: String) {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return spaceService.deleteSpace(space)
}
@PostMapping("/{spaceId}/invite")
suspend fun inviteSpace(@PathVariable spaceId: String): SpaceInvite {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return spaceService.createInviteSpace(spaceId)
}
@PostMapping("/invite/{code}")
suspend fun acceptInvite(@PathVariable code: String): Space {
return spaceService.acceptInvite(code)
}
@DeleteMapping("/{spaceId}/leave")
suspend fun leaveSpace(@PathVariable spaceId: String) {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return spaceService.leaveSpace(spaceId)
}
@DeleteMapping("/{spaceId}/members/kick/{username}")
suspend fun kickMembers(@PathVariable spaceId: String, @PathVariable username: String) {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return spaceService.kickMember(spaceId, username)
}
//
//Budgets API
//
@GetMapping("/{spaceId}/budgets")
suspend fun getBudgets(@PathVariable spaceId: String): List<Budget> {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return financialService.getBudgets(spaceId).awaitSingleOrNull().orEmpty()
}
@GetMapping("/{spaceId}/budgets/{id}")
suspend fun getBudget(@PathVariable spaceId: String, @PathVariable id: String): BudgetDTO? {
log.info("Getting budget for spaceId=$spaceId, id=$id")
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return financialService.getBudget(spaceId, id)
}
@PostMapping("/{spaceId}/budgets")
suspend fun createBudget(
@PathVariable spaceId: String,
@RequestBody budgetCreationDTO: BudgetCreationDTO,
): Budget? {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return financialService.createBudget(
space,
budgetCreationDTO.budget,
budgetCreationDTO.createRecurrent
)
}
@DeleteMapping("/{spaceId}/budgets/{id}")
suspend fun deleteBudget(@PathVariable spaceId: String, @PathVariable id: String) {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
financialService.deleteBudget(spaceId, id)
}
@PostMapping("/{spaceId}/budgets/{budgetId}/categories/{catId}/limit")
suspend fun setCategoryLimit(
@PathVariable spaceId: String,
@PathVariable budgetId: String,
@PathVariable catId: String,
@RequestBody limit: LimitValue,
): BudgetCategory {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return financialService.setCategoryLimit(spaceId, budgetId, catId, limit.limit)
}
//
// Transactions API
//
@GetMapping("/{spaceId}/transactions")
fun getTransactions(
@PathVariable spaceId: String,
@RequestParam(value = "transaction_type") transactionType: String? = null,
@RequestParam(value = "category_type") categoryType: String? = null,
@RequestParam(value = "user_id") userId: String? = null,
@RequestParam(value = "is_child") isChild: Boolean? = null,
@RequestParam(value = "limit") limit: Int = 10,
@RequestParam(value = "offset") offset: Int = 0
): ResponseEntity<Any> {
try {
return ResponseEntity.ok(
financialService.getTransactions(
spaceId = spaceId,
transactionType = transactionType,
categoryType = categoryType,
userId = userId,
isChild = isChild,
limit = limit,
offset = offset
)
)
} catch (e: Exception) {
e.printStackTrace()
return ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR)
}
}
@GetMapping("/{spaceId}/transactions/csv")
suspend fun getTransactionsCSV(
@PathVariable spaceId: String,
@RequestParam(value = "transaction_type") transactionType: String? = null,
@RequestParam(value = "category_type") categoryType: String? = null,
@RequestParam(value = "user_id") userId: String? = null,
@RequestParam(value = "is_child") isChild: Boolean? = null,
@RequestParam(value = "limit") limit: Int = 20000,
@RequestParam(value = "offset") offset: Int = 0
): ResponseEntity<Any> {
try {
val bos = ByteArrayOutputStream()
val writer = CSVWriter(OutputStreamWriter(bos))
val CSVHeaders = arrayOf("id", "name", "category")
writer.writeNext(CSVHeaders)
financialService.getTransactions(
spaceId = spaceId,
transactionType = transactionType,
categoryType = categoryType,
userId = userId,
isChild = isChild,
limit = limit,
offset = offset
).awaitSingle().map {
val data = arrayOf(it.id, it.comment, it.category.name)
writer.writeNext(data)
}
writer.close()
val csvData = bos.toByteArray()
val headers = HttpHeaders()
headers.contentType = MediaType.parseMediaType("text/csv")
headers.setContentDispositionFormData("attachment", "pojos.csv")
return ResponseEntity(csvData, headers, HttpStatus.OK)
} catch (e: Exception) {
e.printStackTrace()
return ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR)
}
}
@GetMapping("/{spaceId}/category-predict")
suspend fun getTransactionCategoryPredict(
@PathVariable spaceId: String,
@RequestParam comment: String
): List<Category> {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return categoryService.getCategories(
"67af3c0f652da946a7dd9931",
"EXPENSE",
sortBy = "name",
direction = "ASC",
predict = comment
)
}
@GetMapping("/{spaceId}/transactions/{id}")
suspend fun getTransaction(
@PathVariable spaceId: String,
@PathVariable id: String
): Transaction {
return financialService.getTransactionById(id)
}
@PostMapping("/{spaceId}/transactions")
suspend fun createTransaction(@PathVariable spaceId: String, @RequestBody transaction: Transaction): Transaction {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return financialService.createTransaction(space, transaction)
}
@PutMapping("/{spaceId}/transactions/{id}")
suspend fun editTransaction(
@PathVariable spaceId: String, @PathVariable id: String, @RequestBody transaction: Transaction
): Transaction {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
transaction.space = space
return financialService.editTransaction(transaction, user)
}
@DeleteMapping("/{spaceId}/transactions/{id}")
suspend fun deleteTransaction(@PathVariable spaceId: String, @PathVariable id: String) {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
val transaction = financialService.getTransactionById(id)
financialService.deleteTransaction(transaction)
}
//
// Categories API
//
@GetMapping("/{spaceId}/categories")
suspend fun getCategories(
@PathVariable spaceId: String,
@RequestParam("type") type: String? = null,
@RequestParam("sort") sortBy: String = "name",
@RequestParam("direction") direction: String = "ASC"
): List<Category> {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return categoryService.getCategories(spaceId, type, sortBy, direction)
}
@GetMapping("/{spaceId}/categories/types")
fun getCategoriesTypes(@PathVariable spaceId: String): List<CategoryType> {
return categoryService.getCategoryTypes()
}
@PostMapping("/{spaceId}/categories")
suspend fun createCategory(
@PathVariable spaceId: String, @RequestBody category: Category
): Category {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return financialService.createCategory(space, category).awaitSingle()
}
@PutMapping("/{spaceId}/categories/{categoryId}")
suspend fun editCategory(
@PathVariable categoryId: String,
@RequestBody category: Category,
@PathVariable spaceId: String
): Category {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return categoryService.editCategory(space, category)
}
@DeleteMapping("/{spaceId}/categories/{categoryId}")
suspend fun deleteCategory(@PathVariable categoryId: String, @PathVariable spaceId: String) {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
categoryService.deleteCategory(space, categoryId, user)
}
@GetMapping("/{spaceId}/categories/tags")
suspend fun getTags(@PathVariable spaceId: String): List<Tag> {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return spaceService.getTags(space)
}
@PostMapping("/{spaceId}/categories/tags")
suspend fun createTags(@PathVariable spaceId: String, @RequestBody tag: Tag): Tag {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return spaceService.createTag(space, tag)
}
@DeleteMapping("/{spaceId}/categories/tags/{tagId}")
suspend fun deleteTags(@PathVariable spaceId: String, @PathVariable tagId: String) {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return spaceService.deleteTag(space, tagId)
}
@GetMapping("/{spaceId}/analytics/by-month")
suspend fun getCategoriesSumsByMonthsV2(@PathVariable spaceId: String): List<Document> {
return financialService.getCategorySummaries(spaceId, LocalDate.now().minusMonths(6))
}
//
// Recurrents API
//
@GetMapping("/{spaceId}/recurrents")
suspend fun getRecurrents(@PathVariable spaceId: String): List<Recurrent> {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return recurrentService.getRecurrents(spaceId)
}
@GetMapping("/{spaceId}/recurrents/{id}")
suspend fun getRecurrent(@PathVariable spaceId: String, @PathVariable id: String): Recurrent {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return recurrentService.getRecurrentById(space, id).awaitSingle()
}
@PostMapping("/{spaceId}/recurrent")
suspend fun createRecurrent(@PathVariable spaceId: String, @RequestBody recurrent: Recurrent): Recurrent {
val user = authService.getSecurityUser()
val space = spaceService.isValidRequest(spaceId, user)
return recurrentService.createRecurrent(space, recurrent).awaitSingle()
}
@PutMapping("/{spaceId}/recurrent/{id}")
suspend fun editRecurrent(
@PathVariable spaceId: String,
@PathVariable id: String,
@RequestBody recurrent: Recurrent
): Recurrent {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
return recurrentService.editRecurrent(recurrent).awaitSingle()
}
@DeleteMapping("/{spaceId}/recurrent/{id}")
suspend fun deleteRecurrent(@PathVariable spaceId: String, @PathVariable id: String) {
val user = authService.getSecurityUser()
spaceService.isValidRequest(spaceId, user)
recurrentService.deleteRecurrent(id).awaitSingle()
}
// @GetMapping("/regen")
// fun regenSpaces(): Mono<Category> {
// return spaceService.regenSpaceCategory()
// }
}