From 4de09a8f1c9d0f5e92257db902a0ff96471843e3 Mon Sep 17 00:00:00 2001 From: William Brawner Date: Tue, 3 Aug 2021 19:55:03 -0600 Subject: [PATCH] Add category routes --- api/.gitignore | 1 + .../kotlin/com/wbrawner/twigs/BudgetRoutes.kt | 143 ++++++++------- .../kotlin/com/wbrawner/twigs/CategoryApi.kt | 11 +- .../com/wbrawner/twigs/CategoryRoutes.kt | 148 +++++++++++++++ .../com/wbrawner/twigs/server/Application.kt | 2 + .../server/category/CategoryController.kt | 171 ------------------ .../server/category/CategoryRepository.kt | 23 --- .../PasswordResetRequestRepository.kt | 5 - .../permission/UserPermissionRepository.kt | 15 -- .../server/session/UserSessionRepository.kt | 11 -- core/.gitignore | 1 + .../com/wbrawner/twigs/model/Category.kt | 2 +- .../kotlin/com/wbrawner/twigs/model/User.kt | 4 + storage/.gitignore | 1 + .../twigs/storage/CategoryRepository.kt | 12 ++ .../twigs/storage/PermissionRepository.kt | 6 +- 16 files changed, 247 insertions(+), 309 deletions(-) create mode 100644 api/.gitignore create mode 100644 api/src/main/kotlin/com/wbrawner/twigs/CategoryRoutes.kt delete mode 100644 app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryController.kt delete mode 100644 app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryRepository.kt delete mode 100644 app/src/main/kotlin/com/wbrawner/twigs/server/passwordresetrequest/PasswordResetRequestRepository.kt delete mode 100644 app/src/main/kotlin/com/wbrawner/twigs/server/permission/UserPermissionRepository.kt delete mode 100644 app/src/main/kotlin/com/wbrawner/twigs/server/session/UserSessionRepository.kt create mode 100644 core/.gitignore create mode 100644 storage/.gitignore create mode 100644 storage/src/main/kotlin/com/wbrawner/twigs/storage/CategoryRepository.kt diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/api/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/api/src/main/kotlin/com/wbrawner/twigs/BudgetRoutes.kt b/api/src/main/kotlin/com/wbrawner/twigs/BudgetRoutes.kt index ef2b4cf..5c64091 100644 --- a/api/src/main/kotlin/com/wbrawner/twigs/BudgetRoutes.kt +++ b/api/src/main/kotlin/com/wbrawner/twigs/BudgetRoutes.kt @@ -23,8 +23,10 @@ fun Application.budgetRoutes( block: suspend (Budget) -> Unit ) { val session = call.principal()!! - val userPermission = - permissionRepository.findAllByUserId(session.userId).firstOrNull { it.budgetId == budgetId } + val userPermission = permissionRepository.findAll( + userId = session.userId, + budgetIds = listOf(budgetId) + ).firstOrNull() if (userPermission?.permission?.isNotAtLeast(permission) != true) { call.respond(HttpStatusCode.Forbidden) return @@ -33,90 +35,87 @@ fun Application.budgetRoutes( } routing { - authenticate(optional = false) { - get("/") { - val session = call.principal()!! - val budgetIds = permissionRepository.findAllByUserId(session.userId).map { it.budgetId } - val budgets = budgetRepository.findAllByIds(budgetIds).map { - BudgetResponse(it, permissionRepository.findAllByBudgetId(it.id)) + route("/api/budgets") { + authenticate(optional = false) { + get("/") { + val session = call.principal()!! + val budgetIds = permissionRepository.findAll(userId = session.userId).map { it.budgetId } + val budgets = budgetRepository.findAllByIds(budgetIds).map { + BudgetResponse(it, permissionRepository.findAll(budgetIds = listOf(it.id))) + } + call.respond(budgets) } - if (call.request.contentType() == ContentType.Application.Json) { - } else { - call.respondHtml() + get("/{id}") { + budgetWithPermission(budgetId = call.parameters["id"]!!, Permission.READ) { budget -> + val users = permissionRepository.findAll(budgetIds = listOf(budget.id)) + call.respond(BudgetResponse(budget, users)) + } } - call.respond(budgets) - } - get("/{id}") { - budgetWithPermission(budgetId = call.parameters["id"]!!, Permission.READ) { budget -> - val users = permissionRepository.findAllByBudgetId(budget.id) - call.respond(BudgetResponse(budget, users)) - } - } - - post("/{id}") { - val session = call.principal()!! - val request = call.receive() - if (request.name.isNullOrBlank()) { - call.respond(HttpStatusCode.BadRequest, "Name cannot be empty or null") - return@post - } - val budget = budgetRepository.save( - Budget( - name = request.name, - description = request.description - ) - ) - val users = request.users?.map { - permissionRepository.save( - UserPermission( - budgetId = budget.id, - userId = it.user, - permission = it.permission + post("/{id}") { + val session = call.principal()!! + val request = call.receive() + if (request.name.isNullOrBlank()) { + call.respond(HttpStatusCode.BadRequest, "Name cannot be empty or null") + return@post + } + val budget = budgetRepository.save( + Budget( + name = request.name, + description = request.description ) ) - }?.toMutableSet() ?: mutableSetOf() - if (users.none { it.userId == session.userId }) { - users.add( + val users = request.users?.map { permissionRepository.save( UserPermission( budgetId = budget.id, - userId = session.userId, - permission = Permission.OWNER + userId = it.user, + permission = it.permission ) ) - ) - } - call.respond(BudgetResponse(budget, users)) - } - - put("/{id}") { - budgetWithPermission(call.parameters["id"]!!, Permission.MANAGE) { budget -> - val request = call.receive() - val name = request.name ?: budget.name - val description = request.description ?: budget.description - val users = request.users?.map { - permissionRepository.save(UserPermission(budget.id, it.user, it.permission)) - } ?: permissionRepository.findAllByBudgetId(budget.id) - permissionRepository.findAllByBudgetId(budget.id).forEach { - if (it.permission != Permission.OWNER && users.none { userPermission -> userPermission.userId == it.userId }) { - permissionRepository.delete(it) - } - } - call.respond( - BudgetResponse( - budgetRepository.save(budget.copy(name = name, description = description)), - users + }?.toMutableSet() ?: mutableSetOf() + if (users.none { it.userId == session.userId }) { + users.add( + permissionRepository.save( + UserPermission( + budgetId = budget.id, + userId = session.userId, + permission = Permission.OWNER + ) + ) ) - ) + } + call.respond(BudgetResponse(budget, users)) } - } - delete("/{id}") { - budgetWithPermission(budgetId = call.parameters["id"]!!, Permission.READ) { budget -> - budgetRepository.delete(budget) - call.respond(HttpStatusCode.NoContent) + put("/{id}") { + budgetWithPermission(call.parameters["id"]!!, Permission.MANAGE) { budget -> + val request = call.receive() + val name = request.name ?: budget.name + val description = request.description ?: budget.description + val users = request.users?.map { + permissionRepository.save(UserPermission(budget.id, it.user, it.permission)) + } ?: permissionRepository.findAll(budgetIds = listOf(budget.id)) + permissionRepository.findAll(budgetIds = listOf(budget.id)).forEach { + if (it.permission != Permission.OWNER && users.none { userPermission -> userPermission.userId == it.userId }) { + permissionRepository.delete(it) + } + } + call.respond( + BudgetResponse( + budgetRepository.save(budget.copy(name = name, description = description)), + users + ) + ) + } + } + + delete("/{id}") { + budgetWithPermission(budgetId = call.parameters["id"]!!, Permission.OWNER) { budget -> + budgetRepository.delete(budget) + call.respond(HttpStatusCode.NoContent) + } } } } diff --git a/api/src/main/kotlin/com/wbrawner/twigs/CategoryApi.kt b/api/src/main/kotlin/com/wbrawner/twigs/CategoryApi.kt index 38c7789..91f1c63 100644 --- a/api/src/main/kotlin/com/wbrawner/twigs/CategoryApi.kt +++ b/api/src/main/kotlin/com/wbrawner/twigs/CategoryApi.kt @@ -2,18 +2,11 @@ package com.wbrawner.twigs import com.wbrawner.twigs.model.Category -data class NewCategoryRequest( - val title: String, - val description: String? = null, - val amount: Long, - val budgetId: String, - val expense: Boolean -) - -data class UpdateCategoryRequest( +data class CategoryRequest( val title: String? = null, val description: String? = null, val amount: Long? = null, + val budgetId: String? = null, val expense: Boolean? = null, val archived: Boolean? = null ) diff --git a/api/src/main/kotlin/com/wbrawner/twigs/CategoryRoutes.kt b/api/src/main/kotlin/com/wbrawner/twigs/CategoryRoutes.kt new file mode 100644 index 0000000..c109b93 --- /dev/null +++ b/api/src/main/kotlin/com/wbrawner/twigs/CategoryRoutes.kt @@ -0,0 +1,148 @@ +package com.wbrawner.twigs + +import com.wbrawner.twigs.model.Category +import com.wbrawner.twigs.model.Permission +import com.wbrawner.twigs.storage.CategoryRepository +import com.wbrawner.twigs.storage.PermissionRepository +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 io.ktor.util.pipeline.* + +fun Application.categoryRoutes( + categoryRepository: CategoryRepository, + permissionRepository: PermissionRepository +) { + routing { + route("/api/categories") { + authenticate(optional = false) { + get("/") { + val session = call.principal()!! + call.respond(categoryRepository.findAll( + budgetIds = permissionRepository.findAll( + budgetIds = call.request.queryParameters.getAll("budgetIds"), + userId = session.userId + ).map { it.budgetId }, + expense = call.request.queryParameters["expense"]?.toBoolean(), + archived = call.request.queryParameters["archived"]?.toBoolean() + ).map { CategoryResponse(it) }) + } + + get("/{id}") { + val session = call.principal()!! + call.respond(categoryRepository.findAll( + ids = call.parameters.getAll("id"), + budgetIds = permissionRepository.findAll( + userId = session.userId + ).map { it.budgetId } + ).map { CategoryResponse(it) }) + } + + post("/{id}") { + val session = call.principal()!! + val request = call.receive() + if (request.title.isNullOrBlank()) { + call.respond(HttpStatusCode.BadRequest, ErrorResponse("Title cannot be null or empty")) + return@post + } + if (request.budgetId.isNullOrBlank()) { + call.respond(HttpStatusCode.BadRequest, ErrorResponse("Budget ID cannot be null or empty")) + return@post + } + requireBudgetWithPermission( + permissionRepository, + session.userId, + request.budgetId, + Permission.WRITE + ) { + return@post + } + call.respond( + CategoryResponse( + categoryRepository.save( + Category( + title = request.title, + description = request.description, + amount = request.amount ?: 0L, + expense = request.expense ?: true, + ) + ) + ) + ) + } + + put("/{id}") { + val session = call.principal()!! + val request = call.receive() + val category = categoryRepository.findAll(ids = call.parameters.getAll("id")) + .firstOrNull() + ?: run { + call.respond(HttpStatusCode.NotFound) + return@put + } + requireBudgetWithPermission( + permissionRepository, + session.userId, + category.budgetId!!, + Permission.WRITE + ) { + return@put + } + call.respond( + CategoryResponse( + categoryRepository.save( + Category( + title = request.title ?: category.title, + description = request.description ?: category.description, + amount = request.amount ?: category.amount, + expense = request.expense ?: category.expense, + archived = request.archived ?: category.archived + ) + ) + ) + ) + } + + delete("/{id}") { + val session = call.principal()!! + val category = categoryRepository.findAll(ids = call.parameters.getAll("id")) + .firstOrNull() + ?: run { + call.respond(HttpStatusCode.NotFound) + return@delete + } + requireBudgetWithPermission( + permissionRepository, + session.userId, + category.budgetId!!, + Permission.WRITE + ) { + return@delete + } + categoryRepository.delete(category) + } + } + } + } +} + +suspend inline fun PipelineContext.requireBudgetWithPermission( + permissionRepository: PermissionRepository, + userId: String, + budgetId: String, + permission: Permission, + otherwise: () -> Unit +) { + permissionRepository.findAll( + userId = userId, + budgetIds = listOf(budgetId) + ).firstOrNull { + it.permission.isAtLeast(permission) + } ?: run { + call.respond(HttpStatusCode.Forbidden, "Insufficient permissions on budget $budgetId") + otherwise() + } +} diff --git a/app/src/main/kotlin/com/wbrawner/twigs/server/Application.kt b/app/src/main/kotlin/com/wbrawner/twigs/server/Application.kt index 88e4485..f6cba83 100644 --- a/app/src/main/kotlin/com/wbrawner/twigs/server/Application.kt +++ b/app/src/main/kotlin/com/wbrawner/twigs/server/Application.kt @@ -1,6 +1,7 @@ package com.wbrawner.twigs.server import com.wbrawner.twigs.budgetRoutes +import com.wbrawner.twigs.categoryRoutes import io.ktor.application.* import io.ktor.auth.* @@ -10,4 +11,5 @@ fun Application.module(budgetReposi) { install(Authentication) budgetRoutes() + categoryRoutes() } \ No newline at end of file diff --git a/app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryController.kt b/app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryController.kt deleted file mode 100644 index 4c91611..0000000 --- a/app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryController.kt +++ /dev/null @@ -1,171 +0,0 @@ -package com.wbrawner.twigs.server.category - -import com.wbrawner.twigs.ErrorResponse -import com.wbrawner.twigs.server.currentUser -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 com.wbrawner.twigs.server.transaction.Transaction -import com.wbrawner.twigs.server.transaction.TransactionRepository -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.util.function.Consumer -import java.util.stream.Collectors -import javax.transaction.Transactional - -@RestController -@RequestMapping(path = ["/categories"]) -@Transactional -open class CategoryController( - private val categoryRepository: CategoryRepository, - private val transactionRepository: TransactionRepository, - private val userPermissionsRepository: UserPermissionRepository -) { - @GetMapping(path = [""], produces = [MediaType.APPLICATION_JSON_VALUE]) - open fun getCategories( - @RequestParam(name = "budgetIds", required = false) budgetIds: List?, - @RequestParam(name = "isExpense", required = false) isExpense: Boolean?, - @RequestParam(name = "includeArchived", required = false) includeArchived: Boolean?, - @RequestParam(name = "count", required = false) count: Int?, - @RequestParam(name = "page", required = false) page: Int?, - @RequestParam(name = "false", required = false) sortBy: String?, - @RequestParam(name = "sortOrder", required = false) sortOrder: Sort.Direction? - ): ResponseEntity> { - val userPermissions: List - userPermissions = if (budgetIds != null && !budgetIds.isEmpty()) { - 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()) - val pageRequest = PageRequest.of( - Math.min(0, if (page != null) page - 1 else 0), - count ?: 1000, - sortOrder ?: Sort.Direction.ASC, - sortBy ?: "title" - ) - val archived = if (includeArchived == null || includeArchived == false) false else null - val categories = categoryRepository.findAllByBudgetIn(budgets, isExpense, archived, pageRequest) - return ResponseEntity.ok( - categories.stream() - .map { category: Category -> CategoryResponse(category) } - .collect(Collectors.toList()) - ) - } - - @GetMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE]) - open fun getCategory(@PathVariable id: String?): ResponseEntity { - val budgets = userPermissionsRepository.findAllByUser(currentUser, null) - .stream() - .map { obj: UserPermission -> obj.budget } - .collect(Collectors.toList()) - val category = categoryRepository.findByBudgetInAndId(budgets, id).orElse(null) - ?: return ResponseEntity.notFound().build() - return ResponseEntity.ok(CategoryResponse(category)) - } - - @GetMapping(path = ["/{id}/balance"], produces = [MediaType.APPLICATION_JSON_VALUE]) - open fun getCategoryBalance(@PathVariable id: String?): ResponseEntity { - val budgets = userPermissionsRepository.findAllByUser(currentUser, null) - .stream() - .map { obj: UserPermission -> obj.budget } - .collect(Collectors.toList()) - val category = categoryRepository.findByBudgetInAndId(budgets, id).orElse(null) - ?: return ResponseEntity.notFound().build() - val sum = transactionRepository.sumBalanceByCategoryId(category.id, firstOfMonth) - return ResponseEntity.ok(CategoryBalanceResponse(category.id, sum)) - } - - @PostMapping( - path = [""], - consumes = [MediaType.APPLICATION_JSON_VALUE], - produces = [MediaType.APPLICATION_JSON_VALUE] - ) - open fun newCategory(@RequestBody request: NewCategoryRequest): ResponseEntity { - 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 - return ResponseEntity.ok( - CategoryResponse( - categoryRepository.save( - Category( - title = request.title, - description = request.description, - amount = request.amount, - budget = budget, - expense = request.expense - ) - ) - ) - ) - } - - @PutMapping( - path = ["/{id}"], - consumes = [MediaType.APPLICATION_JSON_VALUE], - produces = [MediaType.APPLICATION_JSON_VALUE] - ) - open fun updateCategory( - @PathVariable id: String, - @RequestBody request: UpdateCategoryRequest - ): ResponseEntity { - val category = categoryRepository.findById(id).orElse(null) - ?: return ResponseEntity.notFound().build() - val userPermission = userPermissionsRepository.findByUserAndBudget_Id( - currentUser, - category.budget!!.id - ).orElse(null) - ?: return ResponseEntity.notFound().build() - if (userPermission.permission.isNotAtLeast(Permission.WRITE)) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build() - } - if (request.title != null) { - category.title = request.title - } - if (request.description != null) { - category.description = request.description - } - if (request.amount != null) { - category.amount = request.amount - } - if (request.expense != null) { - category.expense = request.expense - } - if (request.archived != null) { - category.archived = request.archived - } - return ResponseEntity.ok(CategoryResponse(categoryRepository.save(category))) - } - - @DeleteMapping(path = ["/{id}"], produces = [MediaType.TEXT_PLAIN_VALUE]) - open fun deleteCategory(@PathVariable id: String): ResponseEntity { - val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() - val userPermission = - userPermissionsRepository.findByUserAndBudget_Id(currentUser, category.budget!!.id).orElse(null) - ?: return ResponseEntity.notFound().build() - if (userPermission.permission.isNotAtLeast(Permission.WRITE)) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build() - } - transactionRepository.findAllByBudgetAndCategory(userPermission.budget, category) - .forEach(Consumer { transaction: Transaction -> - transaction.category = null - transactionRepository.save(transaction) - }) - categoryRepository.delete(category) - return ResponseEntity.ok().build() - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryRepository.kt b/app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryRepository.kt deleted file mode 100644 index ad5e959..0000000 --- a/app/src/main/kotlin/com/wbrawner/twigs/server/category/CategoryRepository.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.wbrawner.twigs.server.category - -import com.wbrawner.twigs.server.budget.Budget -import org.springframework.data.domain.Pageable -import org.springframework.data.jpa.repository.Query -import org.springframework.data.repository.PagingAndSortingRepository -import java.util.* - -interface CategoryRepository : PagingAndSortingRepository { - fun findAllByBudget(budget: Budget?, pageable: Pageable?): List - - @Query("SELECT c FROM Category c where c.budget IN (:budgets) AND (:expense IS NULL OR c.expense = :expense) AND (:archived IS NULL OR c.archived = :archived)") - fun findAllByBudgetIn( - budgets: List?, - expense: Boolean?, - archived: Boolean?, - pageable: Pageable? - ): List - - fun findByBudgetInAndId(budgets: List?, id: String?): Optional - fun findByBudgetAndId(budget: Budget?, id: String?): Optional - fun findAllByBudgetInAndIdIn(budgets: List?, ids: List?, pageable: Pageable?): List -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/wbrawner/twigs/server/passwordresetrequest/PasswordResetRequestRepository.kt b/app/src/main/kotlin/com/wbrawner/twigs/server/passwordresetrequest/PasswordResetRequestRepository.kt deleted file mode 100644 index c06e869..0000000 --- a/app/src/main/kotlin/com/wbrawner/twigs/server/passwordresetrequest/PasswordResetRequestRepository.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.wbrawner.twigs.server.passwordresetrequest - -import org.springframework.data.repository.PagingAndSortingRepository - -interface PasswordResetRequestRepository : PagingAndSortingRepository \ No newline at end of file diff --git a/app/src/main/kotlin/com/wbrawner/twigs/server/permission/UserPermissionRepository.kt b/app/src/main/kotlin/com/wbrawner/twigs/server/permission/UserPermissionRepository.kt deleted file mode 100644 index f090bf8..0000000 --- a/app/src/main/kotlin/com/wbrawner/twigs/server/permission/UserPermissionRepository.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wbrawner.twigs.server.permission - -import com.wbrawner.twigs.server.budget.Budget -import com.wbrawner.twigs.server.user.User -import org.springframework.data.domain.Pageable -import org.springframework.data.repository.PagingAndSortingRepository -import java.util.* - -interface UserPermissionRepository : PagingAndSortingRepository { - fun findByUserAndBudget_Id(user: User?, budgetId: String?): Optional - fun findAllByUser(user: User?, pageable: Pageable?): List - fun findAllByBudget(budget: Budget?, pageable: Pageable?): List - fun findAllByUserAndBudget(user: User?, budget: Budget?, pageable: Pageable?): List - fun findAllByUserAndBudget_IdIn(user: User?, budgetIds: List?, pageable: Pageable?): List -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/wbrawner/twigs/server/session/UserSessionRepository.kt b/app/src/main/kotlin/com/wbrawner/twigs/server/session/UserSessionRepository.kt deleted file mode 100644 index 7eb396d..0000000 --- a/app/src/main/kotlin/com/wbrawner/twigs/server/session/UserSessionRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.wbrawner.twigs.server.session - -import org.springframework.data.repository.PagingAndSortingRepository -import java.util.* - -interface UserSessionRepository : PagingAndSortingRepository { - fun findByUserId(userId: String?): List? - fun findByToken(token: String?): Optional? - fun findByUserIdAndToken(userId: String?, token: String?): Optional? - fun deleteAllByExpirationBefore(expiration: Date?) -} \ No newline at end of file diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/core/src/main/kotlin/com/wbrawner/twigs/model/Category.kt b/core/src/main/kotlin/com/wbrawner/twigs/model/Category.kt index 2d6607e..0c2eed3 100644 --- a/core/src/main/kotlin/com/wbrawner/twigs/model/Category.kt +++ b/core/src/main/kotlin/com/wbrawner/twigs/model/Category.kt @@ -7,7 +7,7 @@ data class Category( var title: String = "", var description: String? = null, var amount: Long = 0L, - var budget: Budget? = null, + var budgetId: String? = null, var expense: Boolean = true, var archived: Boolean = false ) diff --git a/core/src/main/kotlin/com/wbrawner/twigs/model/User.kt b/core/src/main/kotlin/com/wbrawner/twigs/model/User.kt index dadfcd7..18fb4e8 100644 --- a/core/src/main/kotlin/com/wbrawner/twigs/model/User.kt +++ b/core/src/main/kotlin/com/wbrawner/twigs/model/User.kt @@ -30,6 +30,10 @@ enum class Permission { */ OWNER; + fun isAtLeast(wanted: Permission): Boolean { + return ordinal >= wanted.ordinal + } + fun isNotAtLeast(wanted: Permission): Boolean { return ordinal < wanted.ordinal } diff --git a/storage/.gitignore b/storage/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/storage/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/storage/src/main/kotlin/com/wbrawner/twigs/storage/CategoryRepository.kt b/storage/src/main/kotlin/com/wbrawner/twigs/storage/CategoryRepository.kt new file mode 100644 index 0000000..0adc36e --- /dev/null +++ b/storage/src/main/kotlin/com/wbrawner/twigs/storage/CategoryRepository.kt @@ -0,0 +1,12 @@ +package com.wbrawner.twigs.storage + +import com.wbrawner.twigs.model.Category + +interface CategoryRepository : Repository { + fun findAll( + ids: List? = null, + budgetIds: List? = null, + expense: Boolean? = null, + archived: Boolean? = null + ): List +} \ No newline at end of file diff --git a/storage/src/main/kotlin/com/wbrawner/twigs/storage/PermissionRepository.kt b/storage/src/main/kotlin/com/wbrawner/twigs/storage/PermissionRepository.kt index ebc21a5..1687e74 100644 --- a/storage/src/main/kotlin/com/wbrawner/twigs/storage/PermissionRepository.kt +++ b/storage/src/main/kotlin/com/wbrawner/twigs/storage/PermissionRepository.kt @@ -3,6 +3,8 @@ package com.wbrawner.twigs.storage import com.wbrawner.twigs.model.UserPermission interface PermissionRepository : Repository { - fun findAllByBudgetId(budgetId: String): List - fun findAllByUserId(userId: String): List + fun findAll( + budgetIds: List? = null, + userId: String? = null + ): List } \ No newline at end of file