Implement concept of user permissions for a budget
This commit is contained in:
parent
d2bd1ac301
commit
882108c006
12 changed files with 233 additions and 97 deletions
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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>
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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=*
|
||||
|
|
Loading…
Reference in a new issue