Add Transaction routes
This commit is contained in:
parent
f6e0157560
commit
9fc3d1ac1c
12 changed files with 212 additions and 288 deletions
|
@ -10,6 +10,7 @@ import io.ktor.auth.*
|
|||
import io.ktor.http.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.util.pipeline.*
|
||||
import java.time.Instant
|
||||
|
||||
suspend inline fun PipelineContext<Unit, ApplicationCall>.requireBudgetWithPermission(
|
||||
permissionRepository: PermissionRepository,
|
||||
|
@ -56,3 +57,5 @@ suspend inline fun PipelineContext<Unit, ApplicationCall>.errorResponse(
|
|||
call.respond(httpStatusCode, ErrorResponse(message))
|
||||
}?: call.respond(httpStatusCode)
|
||||
}
|
||||
|
||||
fun String.toInstant(): Instant = Instant.parse(this)
|
|
@ -36,7 +36,7 @@ fun Application.budgetRoutes(
|
|||
}
|
||||
}
|
||||
|
||||
post("/{id}") {
|
||||
post("/") {
|
||||
val session = call.principal<Session>()!!
|
||||
val request = call.receive<BudgetRequest>()
|
||||
if (request.name.isNullOrBlank()) {
|
||||
|
|
|
@ -41,7 +41,7 @@ fun Application.categoryRoutes(
|
|||
).map { CategoryResponse(it) })
|
||||
}
|
||||
|
||||
post("/{id}") {
|
||||
post("/") {
|
||||
val session = call.principal<Session>()!!
|
||||
val request = call.receive<CategoryRequest>()
|
||||
if (request.title.isNullOrBlank()) {
|
||||
|
|
|
@ -2,17 +2,7 @@ package com.wbrawner.twigs
|
|||
|
||||
import com.wbrawner.twigs.model.Transaction
|
||||
|
||||
data class NewTransactionRequest(
|
||||
val title: String,
|
||||
val description: String? = null,
|
||||
val date: String,
|
||||
val amount: Long,
|
||||
val categoryId: String? = null,
|
||||
val expense: Boolean,
|
||||
val budgetId: String
|
||||
)
|
||||
|
||||
data class UpdateTransactionRequest(
|
||||
data class TransactionRequest(
|
||||
val title: String? = null,
|
||||
val description: String? = null,
|
||||
val date: String? = null,
|
||||
|
@ -20,7 +10,6 @@ data class UpdateTransactionRequest(
|
|||
val categoryId: String? = null,
|
||||
val expense: Boolean? = null,
|
||||
val budgetId: String? = null,
|
||||
val createdBy: String? = null
|
||||
)
|
||||
|
||||
data class TransactionResponse(
|
||||
|
@ -33,16 +22,18 @@ data class TransactionResponse(
|
|||
val budgetId: String,
|
||||
val categoryId: String?,
|
||||
val createdBy: String
|
||||
) {
|
||||
constructor(transaction: Transaction) : this(
|
||||
transaction.id,
|
||||
transaction.title,
|
||||
transaction.description,
|
||||
transaction.date.toString(),
|
||||
transaction.amount,
|
||||
transaction.expense,
|
||||
transaction.budget!!.id,
|
||||
transaction.category?.id,
|
||||
transaction.createdBy!!.id
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
data class BalanceResponse(val balance: Long)
|
||||
|
||||
fun Transaction.asResponse(): TransactionResponse = TransactionResponse(
|
||||
id = id,
|
||||
title = title,
|
||||
description = description,
|
||||
date = date.toString(),
|
||||
amount = amount,
|
||||
expense = expense,
|
||||
budgetId = budgetId,
|
||||
categoryId = categoryId,
|
||||
createdBy = createdBy
|
||||
)
|
162
api/src/main/kotlin/com/wbrawner/twigs/TransactionRoutes.kt
Normal file
162
api/src/main/kotlin/com/wbrawner/twigs/TransactionRoutes.kt
Normal file
|
@ -0,0 +1,162 @@
|
|||
package com.wbrawner.twigs
|
||||
|
||||
import com.wbrawner.twigs.model.Permission
|
||||
import com.wbrawner.twigs.model.Transaction
|
||||
import com.wbrawner.twigs.storage.PermissionRepository
|
||||
import com.wbrawner.twigs.storage.Session
|
||||
import com.wbrawner.twigs.storage.TransactionRepository
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import java.time.Instant
|
||||
|
||||
fun Application.transactionRoutes(
|
||||
transactionRepository: TransactionRepository,
|
||||
permissionRepository: PermissionRepository
|
||||
) {
|
||||
routing {
|
||||
route("/api/transactions") {
|
||||
authenticate(optional = false) {
|
||||
get("/") {
|
||||
val session = call.principal<Session>()!!
|
||||
call.respond(transactionRepository.findAll(
|
||||
budgetIds = permissionRepository.findAll(
|
||||
budgetIds = call.request.queryParameters.getAll("budgetIds"),
|
||||
userId = session.userId
|
||||
).map { it.budgetId },
|
||||
categoryIds = call.request.queryParameters.getAll("categoryIds"),
|
||||
from = call.request.queryParameters["from"]?.let { Instant.parse(it) },
|
||||
to = call.request.queryParameters["to"]?.let { Instant.parse(it) },
|
||||
expense = call.request.queryParameters["expense"]?.toBoolean(),
|
||||
).map { it.asResponse() })
|
||||
}
|
||||
|
||||
get("/{id}") {
|
||||
val session = call.principal<Session>()!!
|
||||
val transaction = transactionRepository.findAll(
|
||||
ids = call.parameters.getAll("id"),
|
||||
budgetIds = permissionRepository.findAll(
|
||||
userId = session.userId
|
||||
)
|
||||
.map { it.budgetId }
|
||||
)
|
||||
.map { it.asResponse() }
|
||||
.firstOrNull()
|
||||
transaction?.let {
|
||||
call.respond(it)
|
||||
} ?: errorResponse()
|
||||
}
|
||||
|
||||
get("/sum") {
|
||||
val categoryId = call.request.queryParameters["categoryId"]
|
||||
val budgetId = call.request.queryParameters["budgetId"]
|
||||
val from = call.request.queryParameters["from"]?.toInstant() ?: firstOfMonth.toInstant()
|
||||
val to = call.request.queryParameters["to"]?.toInstant() ?: endOfMonth.toInstant()
|
||||
val balance = if (!categoryId.isNullOrBlank()) {
|
||||
if (!budgetId.isNullOrBlank()) {
|
||||
errorResponse(HttpStatusCode.BadRequest, "budgetId and categoryId cannot be provided together")
|
||||
return@get
|
||||
}
|
||||
transactionRepository.sumByCategory(categoryId, from, to)
|
||||
} else if (!budgetId.isNullOrBlank()) {
|
||||
transactionRepository.sumByBudget(budgetId, from, to)
|
||||
} else {
|
||||
errorResponse(HttpStatusCode.BadRequest, "budgetId or categoryId must be provided to sum")
|
||||
return@get
|
||||
}
|
||||
call.respond(BalanceResponse(balance))
|
||||
}
|
||||
|
||||
post("/") {
|
||||
val session = call.principal<Session>()!!
|
||||
val request = call.receive<TransactionRequest>()
|
||||
if (request.title.isNullOrBlank()) {
|
||||
errorResponse(HttpStatusCode.BadRequest, "Title cannot be null or empty")
|
||||
return@post
|
||||
}
|
||||
if (request.budgetId.isNullOrBlank()) {
|
||||
errorResponse(HttpStatusCode.BadRequest, "Budget ID cannot be null or empty")
|
||||
return@post
|
||||
}
|
||||
requireBudgetWithPermission(
|
||||
permissionRepository,
|
||||
session.userId,
|
||||
request.budgetId,
|
||||
Permission.WRITE
|
||||
) {
|
||||
return@post
|
||||
}
|
||||
call.respond(
|
||||
transactionRepository.save(
|
||||
Transaction(
|
||||
title = request.title,
|
||||
description = request.description,
|
||||
amount = request.amount ?: 0L,
|
||||
expense = request.expense ?: true,
|
||||
budgetId = request.budgetId,
|
||||
categoryId = request.categoryId,
|
||||
createdBy = session.userId,
|
||||
date = request.date?.let { Instant.parse(it) } ?: Instant.now()
|
||||
)
|
||||
).asResponse()
|
||||
)
|
||||
}
|
||||
|
||||
put("/{id}") {
|
||||
val session = call.principal<Session>()!!
|
||||
val request = call.receive<TransactionRequest>()
|
||||
val transaction = transactionRepository.findAll(ids = call.parameters.getAll("id"))
|
||||
.firstOrNull()
|
||||
?: run {
|
||||
errorResponse()
|
||||
return@put
|
||||
}
|
||||
requireBudgetWithPermission(
|
||||
permissionRepository,
|
||||
session.userId,
|
||||
transaction.budgetId,
|
||||
Permission.WRITE
|
||||
) {
|
||||
return@put
|
||||
}
|
||||
call.respond(
|
||||
transactionRepository.save(
|
||||
Transaction(
|
||||
title = request.title ?: transaction.title,
|
||||
description = request.description ?: transaction.description,
|
||||
amount = request.amount ?: transaction.amount,
|
||||
expense = request.expense ?: transaction.expense,
|
||||
date = request.date?.let { Instant.parse(it) } ?: transaction.date,
|
||||
categoryId = request.categoryId ?: transaction.categoryId,
|
||||
budgetId = request.budgetId ?: transaction.budgetId,
|
||||
createdBy = transaction.createdBy,
|
||||
)
|
||||
).asResponse()
|
||||
)
|
||||
}
|
||||
|
||||
delete("/{id}") {
|
||||
val session = call.principal<Session>()!!
|
||||
val transaction = transactionRepository.findAll(ids = call.parameters.getAll("id"))
|
||||
.firstOrNull()
|
||||
?: run {
|
||||
errorResponse()
|
||||
return@delete
|
||||
}
|
||||
requireBudgetWithPermission(
|
||||
permissionRepository,
|
||||
session.userId,
|
||||
transaction.budgetId,
|
||||
Permission.WRITE
|
||||
) {
|
||||
return@delete
|
||||
}
|
||||
transactionRepository.delete(transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
package com.wbrawner.twigs.server
|
||||
|
||||
import com.wbrawner.twigs.budgetRoutes
|
||||
import com.wbrawner.twigs.categoryRoutes
|
||||
import com.wbrawner.twigs.*
|
||||
import com.wbrawner.twigs.model.Transaction
|
||||
import com.wbrawner.twigs.storage.*
|
||||
import com.wbrawner.twigs.twoWeeksFromNow
|
||||
import com.wbrawner.twigs.userRoutes
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.sessions.*
|
||||
|
@ -23,6 +21,7 @@ fun Application.module(
|
|||
categoryRepository: CategoryRepository,
|
||||
permissionRepository: PermissionRepository,
|
||||
sessionRepository: SessionRepository,
|
||||
transactionRepository: TransactionRepository,
|
||||
userRepository: UserRepository
|
||||
) {
|
||||
install(Sessions) {
|
||||
|
@ -43,6 +42,7 @@ fun Application.module(
|
|||
}
|
||||
budgetRoutes(budgetRepository, permissionRepository)
|
||||
categoryRoutes(categoryRepository, permissionRepository)
|
||||
transactionRoutes(transactionRepository, permissionRepository)
|
||||
userRoutes(permissionRepository, sessionRepository, userRepository)
|
||||
launch {
|
||||
while (currentCoroutineContext().isActive) {
|
||||
|
|
|
@ -1,209 +0,0 @@
|
|||
package com.wbrawner.twigs.server.transaction
|
||||
|
||||
import com.wbrawner.twigs.ErrorResponse
|
||||
import com.wbrawner.twigs.server.category.Category
|
||||
import com.wbrawner.twigs.server.category.CategoryRepository
|
||||
import com.wbrawner.twigs.server.currentUser
|
||||
import com.wbrawner.twigs.server.endOfMonth
|
||||
import com.wbrawner.twigs.server.firstOfMonth
|
||||
import com.wbrawner.twigs.server.permission.Permission
|
||||
import com.wbrawner.twigs.server.permission.UserPermission
|
||||
import com.wbrawner.twigs.server.permission.UserPermissionRepository
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import java.time.Instant
|
||||
import java.util.stream.Collectors
|
||||
import javax.transaction.Transactional
|
||||
import kotlin.math.min
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = ["/transactions"])
|
||||
@Transactional
|
||||
open class TransactionController(
|
||||
private val categoryRepository: CategoryRepository,
|
||||
private val transactionRepository: TransactionRepository,
|
||||
private val userPermissionsRepository: UserPermissionRepository
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(TransactionController::class.java)
|
||||
@GetMapping(path = [""], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
open fun getTransactions(
|
||||
@RequestParam(value = "categoryIds", required = false) categoryIds: List<String>?,
|
||||
@RequestParam(value = "budgetIds", required = false) budgetIds: List<String>?,
|
||||
@RequestParam(value = "from", required = false) from: String?,
|
||||
@RequestParam(value = "to", required = false) to: String?,
|
||||
@RequestParam(value = "count", required = false) count: Int?,
|
||||
@RequestParam(value = "page", required = false) page: Int?,
|
||||
@RequestParam(value = "sortBy", required = false) sortBy: String?,
|
||||
@RequestParam(value = "sortOrder", required = false) sortOrder: Sort.Direction?
|
||||
): ResponseEntity<List<TransactionResponse>> {
|
||||
val userPermissions: List<UserPermission> = if (budgetIds != null && budgetIds.isNotEmpty()) {
|
||||
userPermissionsRepository.findAllByUserAndBudget_IdIn(
|
||||
currentUser,
|
||||
budgetIds,
|
||||
PageRequest.of(page ?: 0, count ?: 1000)
|
||||
)
|
||||
} else {
|
||||
userPermissionsRepository.findAllByUser(currentUser, null)
|
||||
}
|
||||
val budgets = userPermissions.stream()
|
||||
.map { obj: UserPermission -> obj.budget }
|
||||
.collect(Collectors.toList())
|
||||
var categories: List<Category?>? = null
|
||||
if (categoryIds != null && categoryIds.isNotEmpty()) {
|
||||
categories = categoryRepository.findAllByBudgetInAndIdIn(budgets, categoryIds, null)
|
||||
}
|
||||
val pageRequest = PageRequest.of(
|
||||
min(0, if (page != null) page - 1 else 0),
|
||||
count ?: 1000,
|
||||
sortOrder ?: Sort.Direction.DESC,
|
||||
sortBy ?: "date"
|
||||
)
|
||||
val fromInstant: Instant = try {
|
||||
Instant.parse(from)
|
||||
} catch (e: Exception) {
|
||||
if (e !is NullPointerException) logger.error("Failed to parse '$from' to Instant for 'from' parameter", e)
|
||||
firstOfMonth.toInstant()
|
||||
}
|
||||
val toInstant: Instant = try {
|
||||
Instant.parse(to)
|
||||
} catch (e: Exception) {
|
||||
if (e !is NullPointerException) logger.error("Failed to parse '$to' to Instant for 'to' parameter", e)
|
||||
endOfMonth.toInstant()
|
||||
}
|
||||
val query = if (categories == null) {
|
||||
transactionRepository.findAllByBudgetInAndDateGreaterThanAndDateLessThan(
|
||||
budgets,
|
||||
fromInstant,
|
||||
toInstant,
|
||||
pageRequest
|
||||
)
|
||||
} else {
|
||||
transactionRepository.findAllByBudgetInAndCategoryInAndDateGreaterThanAndDateLessThan(
|
||||
budgets,
|
||||
categories,
|
||||
fromInstant,
|
||||
toInstant,
|
||||
pageRequest
|
||||
)
|
||||
}
|
||||
val transactions = query.map { transaction: Transaction -> TransactionResponse(transaction) }
|
||||
return ResponseEntity.ok(transactions)
|
||||
}
|
||||
|
||||
@GetMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
open fun getTransaction(@PathVariable id: String?): ResponseEntity<TransactionResponse> {
|
||||
val budgets = userPermissionsRepository.findAllByUser(currentUser, null)
|
||||
.stream()
|
||||
.map { obj: UserPermission -> obj.budget }
|
||||
.collect(Collectors.toList())
|
||||
val transaction = transactionRepository.findByIdAndBudgetIn(id, budgets).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
return ResponseEntity.ok(TransactionResponse(transaction))
|
||||
}
|
||||
|
||||
@PostMapping(
|
||||
path = [""],
|
||||
consumes = [MediaType.APPLICATION_JSON_VALUE],
|
||||
produces = [MediaType.APPLICATION_JSON_VALUE]
|
||||
)
|
||||
open fun newTransaction(@RequestBody request: NewTransactionRequest): ResponseEntity<Any> {
|
||||
val userResponse = userPermissionsRepository.findByUserAndBudget_Id(currentUser, request.budgetId)
|
||||
.orElse(null) ?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid budget ID"))
|
||||
if (userResponse.permission.isNotAtLeast(Permission.WRITE)) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build()
|
||||
}
|
||||
val budget = userResponse.budget
|
||||
var category: Category? = null
|
||||
if (request.categoryId != null) {
|
||||
category = categoryRepository.findByBudgetAndId(budget, request.categoryId).orElse(null)
|
||||
}
|
||||
return ResponseEntity.ok(
|
||||
TransactionResponse(
|
||||
transactionRepository.save(
|
||||
Transaction(
|
||||
title = request.title,
|
||||
description = request.description,
|
||||
date = Instant.parse(request.date),
|
||||
amount = request.amount,
|
||||
category = category,
|
||||
expense = request.expense,
|
||||
createdBy = currentUser,
|
||||
budget = budget
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@PutMapping(
|
||||
path = ["/{id}"],
|
||||
consumes = [MediaType.APPLICATION_JSON_VALUE],
|
||||
produces = [MediaType.APPLICATION_JSON_VALUE]
|
||||
)
|
||||
open fun updateTransaction(
|
||||
@PathVariable id: String,
|
||||
@RequestBody request: UpdateTransactionRequest
|
||||
): ResponseEntity<Any> {
|
||||
val transaction = transactionRepository.findById(id).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
userPermissionsRepository.findByUserAndBudget_Id(
|
||||
currentUser,
|
||||
transaction.budget!!.id
|
||||
).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
if (request.title != null) {
|
||||
transaction.title = request.title
|
||||
}
|
||||
if (request.description != null) {
|
||||
transaction.description = request.description
|
||||
}
|
||||
if (request.date != null) {
|
||||
transaction.date = Instant.parse(request.date)
|
||||
}
|
||||
if (request.amount != null) {
|
||||
transaction.amount = request.amount
|
||||
}
|
||||
if (request.expense != null) {
|
||||
transaction.expense = request.expense
|
||||
}
|
||||
if (request.budgetId != null) {
|
||||
val newUserPermission =
|
||||
userPermissionsRepository.findByUserAndBudget_Id(currentUser, request.budgetId).orElse(null)
|
||||
if (newUserPermission == null || newUserPermission.permission.isNotAtLeast(Permission.WRITE)) {
|
||||
return ResponseEntity
|
||||
.badRequest()
|
||||
.body(ErrorResponse("Invalid budget"))
|
||||
}
|
||||
transaction.budget = newUserPermission.budget
|
||||
}
|
||||
if (request.categoryId != null) {
|
||||
val category = categoryRepository.findByBudgetAndId(transaction.budget, request.categoryId).orElse(null)
|
||||
?: return ResponseEntity
|
||||
.badRequest()
|
||||
.body(ErrorResponse("Invalid category"))
|
||||
transaction.category = category
|
||||
}
|
||||
return ResponseEntity.ok(TransactionResponse(transactionRepository.save(transaction)))
|
||||
}
|
||||
|
||||
@DeleteMapping(path = ["/{id}"], produces = [MediaType.TEXT_PLAIN_VALUE])
|
||||
open fun deleteTransaction(@PathVariable id: String): ResponseEntity<Void> {
|
||||
val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
// Check that the transaction belongs to an budget that the user has access to before deleting it
|
||||
val userPermission = userPermissionsRepository.findByUserAndBudget_Id(
|
||||
currentUser,
|
||||
transaction.budget!!.id
|
||||
).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
if (userPermission.permission.isNotAtLeast(Permission.WRITE)) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build()
|
||||
}
|
||||
transactionRepository.delete(transaction)
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.wbrawner.twigs.server.transaction
|
||||
|
||||
import com.wbrawner.twigs.server.budget.Budget
|
||||
import com.wbrawner.twigs.server.category.Category
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.PagingAndSortingRepository
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
interface TransactionRepository : PagingAndSortingRepository<Transaction, String> {
|
||||
fun findByIdAndBudgetIn(id: String?, budgets: List<Budget?>?): Optional<Transaction>
|
||||
fun findAllByBudgetInAndCategoryInAndDateGreaterThanAndDateLessThan(
|
||||
budgets: List<Budget?>?,
|
||||
categories: List<Category?>?,
|
||||
start: Instant?,
|
||||
end: Instant?,
|
||||
pageable: Pageable?
|
||||
): List<Transaction>
|
||||
|
||||
fun findAllByBudgetInAndDateGreaterThanAndDateLessThan(
|
||||
budgets: List<Budget?>?,
|
||||
start: Instant?,
|
||||
end: Instant?,
|
||||
pageable: Pageable?
|
||||
): List<Transaction>
|
||||
|
||||
fun findAllByBudgetAndCategory(budget: Budget?, category: Category?): List<Transaction>
|
||||
|
||||
@Query(
|
||||
nativeQuery = true,
|
||||
value = "SELECT (COALESCE((SELECT SUM(amount) from transaction WHERE Budget_id = :BudgetId AND expense = 0 AND date >= :from AND date <= :to), 0)) - (COALESCE((SELECT SUM(amount) from transaction WHERE Budget_id = :BudgetId AND expense = 1 AND date >= :from AND date <= :to), 0));"
|
||||
)
|
||||
fun sumBalanceByBudgetId(BudgetId: String?, from: Instant?, to: Instant?): Long
|
||||
|
||||
@Query(
|
||||
nativeQuery = true,
|
||||
value = "SELECT (COALESCE((SELECT SUM(amount) from transaction WHERE category_id = :categoryId AND expense = 0 AND date > :start), 0)) - (COALESCE((SELECT SUM(amount) from transaction WHERE category_id = :categoryId AND expense = 1 AND date > :start), 0));"
|
||||
)
|
||||
fun sumBalanceByCategoryId(categoryId: String?, start: Date?): Long
|
||||
}
|
|
@ -11,7 +11,7 @@ private val CALENDAR_FIELDS = intArrayOf(
|
|||
)
|
||||
|
||||
val firstOfMonth: Date
|
||||
get() = GregorianCalendar().run {
|
||||
get() = GregorianCalendar(TimeZone.getTimeZone("UTC")).run {
|
||||
for (calField in CALENDAR_FIELDS) {
|
||||
set(calField, getActualMinimum(calField))
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ val firstOfMonth: Date
|
|||
}
|
||||
|
||||
val endOfMonth: Date
|
||||
get() = GregorianCalendar().run {
|
||||
get() = GregorianCalendar(TimeZone.getTimeZone("UTC")).run {
|
||||
for (calField in CALENDAR_FIELDS) {
|
||||
set(calField, getActualMaximum(calField))
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ val endOfMonth: Date
|
|||
}
|
||||
|
||||
val twoWeeksFromNow: Date
|
||||
get() = GregorianCalendar().run {
|
||||
get() = GregorianCalendar(TimeZone.getTimeZone("UTC")).run {
|
||||
add(Calendar.DATE, 14)
|
||||
time
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ data class Transaction(
|
|||
val description: String? = null,
|
||||
val date: Instant? = null,
|
||||
val amount: Long? = null,
|
||||
val category: Category? = null,
|
||||
val categoryId: String? = null,
|
||||
val expense: Boolean? = null,
|
||||
val createdBy: User? = null,
|
||||
val budget: Budget? = null
|
||||
val createdBy: String,
|
||||
val budgetId: String
|
||||
)
|
||||
|
|
|
@ -11,4 +11,3 @@ data class Session(
|
|||
val token: String = randomString(255),
|
||||
var expiration: Date = twoWeeksFromNow
|
||||
) : Principal
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package com.wbrawner.twigs.storage
|
||||
|
||||
import com.wbrawner.twigs.model.Transaction
|
||||
import java.time.Instant
|
||||
|
||||
interface TransactionRepository : Repository<Transaction> {
|
||||
fun findAll(
|
||||
ids: List<String>? = null,
|
||||
budgetIds: List<String>? = null,
|
||||
categoryIds: List<String>? = null,
|
||||
expense: Boolean? = null,
|
||||
from: Instant? = null,
|
||||
to: Instant? = null,
|
||||
): List<Transaction>
|
||||
|
||||
fun sumByBudget(budgetId: String, from: Instant, to: Instant): Long
|
||||
|
||||
fun sumByCategory(categoryId: String, from: Instant, to: Instant): Long
|
||||
}
|
Loading…
Reference in a new issue