Implement concept of user permissions for a budget

This commit is contained in:
William Brawner 2020-02-18 18:48:02 -07:00
parent d2bd1ac301
commit 882108c006
12 changed files with 233 additions and 97 deletions

View file

@ -1,9 +1,10 @@
package com.wbrawner.budgetserver.budget
import com.wbrawner.budgetserver.category.Category
import com.wbrawner.budgetserver.permission.UserPermission
import com.wbrawner.budgetserver.permission.UserPermissionRequest
import com.wbrawner.budgetserver.permission.UserPermissionResponse
import com.wbrawner.budgetserver.transaction.Transaction
import com.wbrawner.budgetserver.user.User
import com.wbrawner.budgetserver.user.UserResponse
import java.util.*
import javax.persistence.*
@ -13,20 +14,22 @@ data class Budget(
val name: String = "",
val description: String? = null,
val currencyCode: String? = null,
@OneToMany(mappedBy = "budget") val transactions: Set<Transaction> = TreeSet(),
@OneToMany(mappedBy = "budget") val categories: Set<Category> = TreeSet(),
@ManyToMany val users: Set<User> = mutableSetOf(),
@JoinColumn(nullable = false)
@ManyToOne
val owner: User? = null
@OneToMany(mappedBy = "budget") val transactions: MutableSet<Transaction> = TreeSet(),
@OneToMany(mappedBy = "budget") val categories: MutableSet<Category> = TreeSet(),
@OneToMany(mappedBy = "budget") val users: MutableSet<UserPermission> = mutableSetOf()
)
data class NewBudgetRequest(val name: String, val description: String?, val userIds: Set<Long>)
data class NewBudgetRequest(val name: String, val description: String?, val users: Set<UserPermissionRequest>)
data class UpdateBudgetRequest(val name: String?, val description: String?, val userIds: Set<Long>?)
data class UpdateBudgetRequest(val name: String?, val description: String?, val users: Set<UserPermissionRequest>?)
data class BudgetResponse(val id: Long, val name: String, val description: String?, val users: List<UserResponse>) {
constructor(budget: Budget) : this(budget.id!!, budget.name, budget.description, budget.users.map { UserResponse(it) })
data class BudgetResponse(val id: Long, val name: String, val description: String?, val users: List<UserPermissionResponse>) {
constructor(budget: Budget, users: List<UserPermission>) : this(
budget.id!!,
budget.name,
budget.description,
users.map { UserPermissionResponse(it) }
)
}
data class BudgetBalanceResponse(val id: Long, val balance: Long)

View file

@ -1,6 +1,9 @@
package com.wbrawner.budgetserver.budget
import com.wbrawner.budgetserver.getCurrentUser
import com.wbrawner.budgetserver.permission.Permission
import com.wbrawner.budgetserver.permission.UserPermission
import com.wbrawner.budgetserver.permission.UserPermissionRepository
import com.wbrawner.budgetserver.transaction.TransactionRepository
import com.wbrawner.budgetserver.user.UserRepository
import io.swagger.annotations.Api
@ -8,7 +11,6 @@ import io.swagger.annotations.ApiOperation
import io.swagger.annotations.Authorization
import org.hibernate.Hibernate
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@ -21,34 +23,37 @@ import javax.transaction.Transactional
open class BudgetController(
private val budgetRepository: BudgetRepository,
private val transactionRepository: TransactionRepository,
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val userPermissionsRepository: UserPermissionRepository
) {
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getBudgets", nickname = "getBudgets", tags = ["Budgets"])
open fun getBudgets(page: Int?, count: Int?): ResponseEntity<List<BudgetResponse>> = ResponseEntity.ok(
budgetRepository.findAllByUsersContainsOrOwner(
userPermissionsRepository.findAllByUser(
user = getCurrentUser()!!,
pageable = PageRequest.of(page ?: 0, count ?: 1000, Sort.by("name")))
pageable = PageRequest.of(page ?: 0, count ?: 1000))
.map {
Hibernate.initialize(it.users)
BudgetResponse(it)
Hibernate.initialize(it.budget)
BudgetResponse(it.budget!!, userPermissionsRepository.findAllByUserAndBudget(getCurrentUser()!!, it.budget, null))
}
)
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getBudget", nickname = "getBudget", tags = ["Budgets"])
open fun getBudget(@PathVariable id: Long): ResponseEntity<BudgetResponse> = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id)
.orElse(null)
open fun getBudget(@PathVariable id: Long): ResponseEntity<BudgetResponse> = userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, id, null)
.firstOrNull()
?.budget
?.let {
Hibernate.initialize(it.users)
ResponseEntity.ok(BudgetResponse(it))
} ?: ResponseEntity.notFound().build()
ResponseEntity.ok(BudgetResponse(it, userPermissionsRepository.findAllByBudget(it, null)))
}
?: ResponseEntity.notFound().build()
@GetMapping("/{id}/balance", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getBudgetBalance", nickname = "getBudgetBalance", tags = ["Budgets"])
open fun getBudgetBalance(@PathVariable id: Long): ResponseEntity<BudgetBalanceResponse> =
budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id)
.orElse(null)
userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, id, null)
.firstOrNull()
?.budget
?.let {
ResponseEntity.ok(BudgetBalanceResponse(it.id!!, transactionRepository.sumBalanceByBudgetId(it.id)))
} ?: ResponseEntity.notFound().build()
@ -56,30 +61,53 @@ open class BudgetController(
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "newBudget", nickname = "newBudget", tags = ["Budgets"])
open fun newBudget(@RequestBody request: NewBudgetRequest): ResponseEntity<BudgetResponse> {
val users = request.userIds
.map { id -> userRepository.findById(id).orElse(null) }
.filterNotNull()
val budget = budgetRepository.save(Budget(name = request.name, description = request.description))
val users = request.users
.mapNotNull {
userRepository.findById(it.user).orElse(null)?.let { user ->
userPermissionsRepository.save(
UserPermission(budget = budget, user = user, permission = it.permission)
)
}
}
.toMutableSet()
.apply { this.add(getCurrentUser()!!) }
val budget = budgetRepository.save(Budget(name = request.name, description = request.description, users = users, owner = getCurrentUser()!!))
return ResponseEntity.ok(BudgetResponse(budget))
if (users.firstOrNull { it.user?.id == getCurrentUser()!!.id } == null) {
users.add(
userPermissionsRepository.save(
UserPermission(budget = budget, user = getCurrentUser(), permission = Permission.OWNER)
)
)
}
return ResponseEntity.ok(BudgetResponse(budget, users.toList()))
}
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "updateBudget", nickname = "updateBudget", tags = ["Budgets"])
open fun updateBudget(@PathVariable id: Long, request: UpdateBudgetRequest): ResponseEntity<BudgetResponse> {
var budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null)
var budget = userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, id, null)
.firstOrNull()
?.budget
?: return ResponseEntity.notFound().build()
if (request.name != null) budget = budget.copy(name = request.name)
if (request.description != null) budget = budget.copy(description = request.description)
if (request.userIds != null) budget = budget.copy(users = userRepository.findAllById(request.userIds).toSet())
return ResponseEntity.ok(BudgetResponse(budgetRepository.save(budget)))
request.name?.let {
budget = budget.copy(name = it)
}
request.description?.let {
budget = budget.copy(description = request.description)
}
val users = request.users?.mapNotNull { req ->
userRepository.findById(req.user).orElse(null)?.let {
userPermissionsRepository.save(UserPermission(budget = budget, user = it, permission = req.permission))
}
} ?: userPermissionsRepository.findAllByUserAndBudget(getCurrentUser()!!, budget, null)
return ResponseEntity.ok(BudgetResponse(budgetRepository.save(budget), users))
}
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
@ApiOperation(value = "deleteBudget", nickname = "deleteBudget", tags = ["Budgets"])
open fun deleteBudget(@PathVariable id: Long): ResponseEntity<Unit> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null)
val budget = userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, id, null)
.firstOrNull()
?.budget
?: return ResponseEntity.notFound().build()
budgetRepository.delete(budget)
return ResponseEntity.ok().build()

View file

@ -1,16 +1,10 @@
package com.wbrawner.budgetserver.budget
import com.wbrawner.budgetserver.category.Category
import com.wbrawner.budgetserver.transaction.Transaction
import com.wbrawner.budgetserver.user.User
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.PagingAndSortingRepository
import java.util.*
interface BudgetRepository: PagingAndSortingRepository<Budget, Long> {
fun findAllByIdIn(ids: List<Long>): List<Budget>
fun findAllByUsersContainsOrOwner(user: User, owner: User = user, pageable: Pageable? = null): List<Budget>
fun findByUsersContainsAndId(user: User, id: Long): Optional<Budget>
fun findByUsersContainsAndTransactionsContains(user: User, transaction: Transaction): Optional<Budget>
fun findByUsersContainsAndCategoriesContains(user: User, category: Category): Optional<Budget>
// fun findByUsersContainsAndId(user: User, id: Long): Optional<Budget>
// fun findByUsersContainsAndTransactionsContains(user: User, transaction: Transaction): Optional<Budget>
// fun findByUsersContainsAndCategoriesContains(user: User, category: Category): Optional<Budget>
}

View file

@ -3,11 +3,11 @@ package com.wbrawner.budgetserver.category
import com.wbrawner.budgetserver.ErrorResponse
import com.wbrawner.budgetserver.budget.BudgetRepository
import com.wbrawner.budgetserver.getCurrentUser
import com.wbrawner.budgetserver.permission.UserPermissionRepository
import com.wbrawner.budgetserver.transaction.TransactionRepository
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import io.swagger.annotations.Authorization
import org.hibernate.Hibernate
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.http.MediaType
@ -23,22 +23,32 @@ import javax.transaction.Transactional
open class CategoryController(
private val budgetRepository: BudgetRepository,
private val categoryRepository: CategoryRepository,
private val transactionRepository: TransactionRepository
private val transactionRepository: TransactionRepository,
private val userPermissionsRepository: UserPermissionRepository
) {
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getCategories", nickname = "getCategories", tags = ["Categories"])
open fun getCategories(budgetId: Long? = null,
isExpense: Boolean? = null,
count: Int?,
page: Int?,
sortBy: String?,
sortOrder: Sort.Direction?
open fun getCategories(
@RequestParam("budgetIds", required = false) budgetIds: List<Long>? = null,
@RequestParam("isExpense", required = false) isExpense: Boolean? = null,
@RequestParam("count", required = false) count: Int?,
@RequestParam("page", required = false) page: Int?,
@RequestParam("false", required = false) sortBy: String?,
@RequestParam("sortOrder", required = false) sortOrder: Sort.Direction?
): ResponseEntity<List<CategoryResponse>> {
val budgets = if (budgetId != null) {
budgetRepository.findAllById(listOf(budgetId))
} else {
budgetRepository.findAllByUsersContainsOrOwner(getCurrentUser()!!)
}.toList()
val budgets = (
budgetIds
?.let {
userPermissionsRepository.findAllByUserAndBudget_IdIn(getCurrentUser()!!, it, null)
}
?: userPermissionsRepository.findAllByUser(
user = getCurrentUser()!!,
pageable = PageRequest.of(page ?: 0, count ?: 1000)
)
)
.mapNotNull {
it.budget
}
val pageRequest = PageRequest.of(
min(0, page?.minus(1) ?: 0),
count ?: 1000,
@ -55,8 +65,11 @@ open class CategoryController(
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getCategory", nickname = "getCategory", tags = ["Categories"])
open fun getCategory(@PathVariable id: Long): ResponseEntity<CategoryResponse> {
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
val budgets = userPermissionsRepository.findAllByUser(getCurrentUser()!!, null)
.mapNotNull { it.budget }
val category = categoryRepository.findByBudgetInAndId(budgets, id)
.orElse(null)
?: return ResponseEntity.notFound().build()
return ResponseEntity.ok(CategoryResponse(category))
}
@ -64,8 +77,10 @@ open class CategoryController(
@GetMapping("/{id}/balance", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getCategoryBalance", nickname = "getCategoryBalance", tags = ["Categories"])
open fun getCategoryBalance(@PathVariable id: Long): ResponseEntity<CategoryBalanceResponse> {
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
val budgets = userPermissionsRepository.findAllByUser(getCurrentUser()!!, null)
.mapNotNull { it.budget }
val category = categoryRepository.findByBudgetInAndId(budgets, id)
.orElse(null)
?: return ResponseEntity.notFound().build()
val transactions = transactionRepository.sumBalanceByCategoryId(category.id!!)
return ResponseEntity.ok(CategoryBalanceResponse(category.id, transactions))
@ -74,9 +89,10 @@ open class CategoryController(
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "newCategory", nickname = "newCategory", tags = ["Categories"])
open fun newCategory(@RequestBody request: NewCategoryRequest): ResponseEntity<Any> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, request.budgetId).orElse(null)
val budget = userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, request.budgetId, null)
.firstOrNull()
?.budget
?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid budget ID"))
Hibernate.initialize(budget.users)
return ResponseEntity.ok(CategoryResponse(categoryRepository.save(Category(
title = request.title,
description = request.description,
@ -88,8 +104,10 @@ open class CategoryController(
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "updateCategory", nickname = "updateCategory", tags = ["Categories"])
open fun updateCategory(@PathVariable id: Long, @RequestBody request: UpdateCategoryRequest): ResponseEntity<CategoryResponse> {
var category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
val budgets = userPermissionsRepository.findAllByUser(getCurrentUser()!!, null)
.mapNotNull { it.budget }
var category = categoryRepository.findByBudgetInAndId(budgets, id)
.orElse(null)
?: return ResponseEntity.notFound().build()
request.title?.let { category = category.copy(title = it) }
request.description?.let { category = category.copy(description = it) }
@ -100,13 +118,17 @@ open class CategoryController(
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
@ApiOperation(value = "deleteCategory", nickname = "deleteCategory", tags = ["Categories"])
open fun deleteCategory(@PathVariable id: Long): ResponseEntity<Unit> {
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
val budget = budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
val budgets = userPermissionsRepository.findAllByUser(getCurrentUser()!!, null)
.mapNotNull { it.budget }
val category = categoryRepository.findByBudgetInAndId(budgets, id)
.orElse(null)
?: return ResponseEntity.notFound().build()
val budget = budgets.first { it.id == category.budget!!.id }
categoryRepository.delete(category)
transactionRepository.findAllByBudgetAndCategory(budget, category).forEach { transaction ->
transactionRepository.save(transaction.copy(category = null))
}
transactionRepository.findAllByBudgetAndCategory(budget, category)
.forEach { transaction ->
transactionRepository.save(transaction.copy(category = null))
}
return ResponseEntity.ok().build()
}
}

View file

@ -8,6 +8,7 @@ import java.util.*
interface CategoryRepository: PagingAndSortingRepository<Category, Long> {
fun findAllByBudget(budget: Budget, pageable: Pageable): List<Category>
fun findAllByBudgetIn(budgets: List<Budget>, pageable: Pageable? = null): List<Category>
fun findByBudgetInAndId(budgets: List<Budget>, id: Long): Optional<Category>
fun findAllByBudgetInAndExpense(budgets: List<Budget>, isExpense: Boolean, pageable: Pageable? = null): List<Category>
fun findByBudgetAndId(budget: Budget, id: Long): Optional<Category>
fun findAllByBudgetInAndIdIn(budgets: List<Budget>, ids: List<Long>, pageable: Pageable? = null): List<Category>

View file

@ -0,0 +1,50 @@
package com.wbrawner.budgetserver.permission
import com.wbrawner.budgetserver.budget.Budget
import com.wbrawner.budgetserver.user.User
import com.wbrawner.budgetserver.user.UserResponse
import java.io.Serializable
import javax.persistence.*
@Entity
data class UserPermission(
@EmbeddedId
val id: UserPermissionKey? = null,
@ManyToOne
@MapsId("budgetId")
@JoinColumn(nullable = false, name = "budget_id")
val budget: Budget? = null,
@ManyToOne
@MapsId("userId")
@JoinColumn(nullable = false, name = "user_id")
val user: User? = null,
@JoinColumn(nullable = false)
@Enumerated(EnumType.STRING)
val permission: Permission? = null
) {
constructor(budget: Budget, user: User, permission: Permission) : this(UserPermissionKey(budget.id, user.id), budget, user, permission)
}
@Embeddable
data class UserPermissionKey(
var budgetId: Long? = null,
var userId: Long? = null
) : Serializable
enum class Permission {
READ,
WRITE,
OWNER
}
data class UserPermissionResponse(
val user: UserResponse,
val permission: Permission
) {
constructor(userPermission: UserPermission) : this(UserResponse(userPermission.user!!), userPermission.permission!!)
}
data class UserPermissionRequest(
val user: Long,
val permission: Permission
)

View file

@ -0,0 +1,14 @@
package com.wbrawner.budgetserver.permission
import com.wbrawner.budgetserver.budget.Budget
import com.wbrawner.budgetserver.user.User
import org.springframework.data.domain.Pageable
import org.springframework.data.repository.PagingAndSortingRepository
interface UserPermissionRepository : PagingAndSortingRepository<UserPermission, UserPermissionKey> {
fun findAllByUserAndBudget_Id(user: User, budgetId: Long, pageable: Pageable?): List<UserPermission>
fun findAllByUser(user: User, pageable: Pageable?): List<UserPermission>
fun findAllByBudget(budget: Budget, pageable: Pageable?): List<UserPermission>
fun findAllByUserAndBudget(user: User, budget: Budget, pageable: Pageable?): List<UserPermission>
fun findAllByUserAndBudget_IdIn(user: User, budgetIds: List<Long>, pageable: Pageable?): List<UserPermission>
}

View file

@ -5,14 +5,13 @@ import com.wbrawner.budgetserver.budget.BudgetRepository
import com.wbrawner.budgetserver.category.Category
import com.wbrawner.budgetserver.category.CategoryRepository
import com.wbrawner.budgetserver.getCurrentUser
import com.wbrawner.budgetserver.permission.UserPermissionRepository
import com.wbrawner.budgetserver.setToEndOfMonth
import com.wbrawner.budgetserver.setToFirstOfMonth
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import io.swagger.annotations.Authorization
import org.hibernate.Hibernate
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.http.MediaType
@ -30,7 +29,8 @@ import javax.transaction.Transactional
open class TransactionController(
private val budgetRepository: BudgetRepository,
private val categoryRepository: CategoryRepository,
private val transactionRepository: TransactionRepository
private val transactionRepository: TransactionRepository,
private val userPermissionsRepository: UserPermissionRepository
) {
private val logger = LoggerFactory.getLogger(TransactionController::class.java)
@ -46,11 +46,15 @@ open class TransactionController(
@RequestParam sortBy: String?,
@RequestParam sortOrder: Sort.Direction?
): ResponseEntity<List<TransactionResponse>> {
val budgets = if (budgetIds?.isNotEmpty() == true) {
budgetRepository.findAllById(budgetIds.toList())
val budgets = if (budgetIds != null) {
userPermissionsRepository.findAllByUser(
user = getCurrentUser()!!,
pageable = PageRequest.of(page ?: 0, count ?: 1000))
} else {
budgetRepository.findAllByUsersContainsOrOwner(getCurrentUser()!!)
}.toList()
userPermissionsRepository.findAllByUser(getCurrentUser()!!, null)
}.mapNotNull {
it.budget
}
val categories = if (categoryIds?.isNotEmpty() == true) {
categoryRepository.findAllByBudgetInAndIdIn(budgets, categoryIds.toList())
} else {
@ -91,8 +95,11 @@ open class TransactionController(
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getTransaction", nickname = "getTransaction", tags = ["Transactions"])
open fun getTransaction(@PathVariable id: Long): ResponseEntity<TransactionResponse> {
val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null)
val budgets = userPermissionsRepository.findAllByUser(getCurrentUser()!!, null)
.mapNotNull {
it.budget
}
val transaction = transactionRepository.findAllByIdAndBudgetIn(id, budgets).firstOrNull()
?: return ResponseEntity.notFound().build()
return ResponseEntity.ok(TransactionResponse(transaction))
}
@ -100,9 +107,10 @@ open class TransactionController(
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "newTransaction", nickname = "newTransaction", tags = ["Transactions"])
open fun newTransaction(@RequestBody request: NewTransactionRequest): ResponseEntity<Any> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, request.budgetId).orElse(null)
val budget = userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, request.budgetId, null)
.firstOrNull()
?.budget
?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid budget ID"))
Hibernate.initialize(budget.users)
val category: Category? = request.categoryId?.let {
categoryRepository.findByBudgetAndId(budget, request.categoryId).orElse(null)
}
@ -122,18 +130,23 @@ open class TransactionController(
@ApiOperation(value = "updateTransaction", nickname = "updateTransaction", tags = ["Transactions"])
open fun updateTransaction(@PathVariable id: Long, @RequestBody request: UpdateTransactionRequest): ResponseEntity<TransactionResponse> {
var transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
var budget = budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction)
.orElse(null) ?: return ResponseEntity.notFound().build()
var budget = userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, transaction.budget!!.id!!, null)
.firstOrNull()
?.budget
?: return ResponseEntity.notFound().build()
request.title?.let { transaction = transaction.copy(title = it) }
request.description?.let { transaction = transaction.copy(description = it) }
request.date?.let { transaction = transaction.copy(date = Instant.parse(it)) }
request.amount?.let { transaction = transaction.copy(amount = it) }
request.expense?.let { transaction = transaction.copy(expense = it) }
request.budgetId?.let { budgetId ->
budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, budgetId).orElse(null)?.let {
budget = it
transaction = transaction.copy(budget = it, category = null)
}
userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, budgetId, null)
.firstOrNull()
?.budget
?.let {
budget = it
transaction = transaction.copy(budget = it, category = null)
}
}
request.categoryId?.let {
categoryRepository.findByBudgetAndId(budget, it).orElse(null)?.let { category ->
@ -148,7 +161,9 @@ open class TransactionController(
open fun deleteTransaction(@PathVariable id: Long): ResponseEntity<Unit> {
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
budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null)
userPermissionsRepository.findAllByUserAndBudget_Id(getCurrentUser()!!, transaction.budget!!.id!!, null)
.firstOrNull()
?.budget
?: return ResponseEntity.notFound().build()
transactionRepository.delete(transaction)
return ResponseEntity.ok().build()

View file

@ -10,6 +10,7 @@ import java.time.Instant
import java.util.*
interface TransactionRepository: PagingAndSortingRepository<Transaction, Long> {
fun findAllByIdAndBudgetIn(id: Long, budgets: List<Budget>): List<Transaction>
fun findAllByBudgetInAndCategoryInAndDateGreaterThanAndDateLessThan(
budgets: List<Budget>,
categories: List<Category>,

View file

@ -36,6 +36,7 @@ data class User(
override fun isAccountNonLocked(): Boolean = !isLocked
}
data class UserResponse(val id: Long, val username: String, val email: String) {
constructor(user: User) : this(user.id!!, user.name, user.email)
}

View file

@ -3,11 +3,11 @@ package com.wbrawner.budgetserver.user
import com.wbrawner.budgetserver.ErrorResponse
import com.wbrawner.budgetserver.budget.BudgetRepository
import com.wbrawner.budgetserver.getCurrentUser
import com.wbrawner.budgetserver.permission.UserPermissionRepository
import com.wbrawner.budgetserver.permission.UserPermissionResponse
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import io.swagger.annotations.Authorization
import org.hibernate.Hibernate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
@ -26,16 +26,23 @@ open class UserController(
private val budgetRepository: BudgetRepository,
private val userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder,
private val userPermissionsRepository: UserPermissionRepository,
private val authenticationProvider: DaoAuthenticationProvider
) {
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getUsers", nickname = "getUsers", tags = ["Users"])
open fun getUsers(budgetId: Long): ResponseEntity<List<UserResponse>> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, budgetId).orElse(null)
open fun getUsers(budgetId: Long): ResponseEntity<List<UserPermissionResponse>> {
val userPermissions = budgetRepository.findById(budgetId)
.orElse(null)
?.run {
userPermissionsRepository.findAllByBudget(this, null)
}
?: return ResponseEntity.notFound().build()
Hibernate.initialize(budget.users)
return ResponseEntity.ok(budget.users.map { UserResponse(it) })
if (userPermissions.none { it.user!!.id == getCurrentUser()!!.id }) {
return ResponseEntity.notFound().build()
}
return ResponseEntity.ok(userPermissions.map { UserPermissionResponse(it) })
}
@PostMapping("/login", produces = [MediaType.APPLICATION_JSON_VALUE])

View file

@ -8,4 +8,4 @@ spring.session.jdbc.initialize-schema=always
spring.datasource.testWhileIdle=true
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.validationQuery=SELECT 1
twigs.cors.domains=*
twigs.cors.domains=*