Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9575eb82a2 | |||
| 6e8f5d112b | |||
| 2e054088ce | |||
| 1bcbf5e53a | |||
| 5d9cc167fc | |||
| af7577c65b | |||
| a38d5068e0 | |||
| 711348b386 | |||
| 4d5b89b08c | |||
| ed8965b055 | |||
| 94df5d72c3 | |||
| 98263732ca |
@@ -56,6 +56,9 @@ dependencies {
|
||||
implementation("com.google.code.gson:gson")
|
||||
implementation("io.micrometer:micrometer-registry-prometheus")
|
||||
|
||||
implementation("org.telegram:telegrambots:6.9.7.1")
|
||||
implementation("org.telegram:telegrambots-spring-boot-starter:6.9.7.1")
|
||||
implementation("com.opencsv:opencsv:5.10")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,17 +2,23 @@ package space.luminic.budgerapp
|
||||
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.cache.annotation.EnableCaching
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories
|
||||
import org.springframework.scheduling.annotation.EnableAsync
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
import java.util.TimeZone
|
||||
import space.luminic.budgerapp.configs.NLPConfig
|
||||
import space.luminic.budgerapp.configs.TelegramBotProperties
|
||||
import java.util.*
|
||||
|
||||
@SpringBootApplication(scanBasePackages = ["space.luminic.budgerapp"])
|
||||
@EnableCaching
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
//@EnableConfigurationProperties([TelegramBotProperties::class,)
|
||||
@ConfigurationPropertiesScan(basePackages = ["space.luminic.budgerapp"])
|
||||
@EnableMongoRepositories(basePackages = ["space.luminic.budgerapp.repos"])
|
||||
class BudgerAppApplication
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package space.luminic.budgerapp.configs
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
//@Configuration
|
||||
|
||||
//class CommonConfig {
|
||||
// @Bean
|
||||
// fun httpTraceRepository(): HttpTraceRepository {
|
||||
// return InMemoryHttpTraceRepository()
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
@ConfigurationProperties(prefix = "nlp")
|
||||
data class NLPConfig(
|
||||
val address: String,
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
package space.luminic.budgerapp.configs
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.telegram.telegrambots.meta.TelegramBotsApi
|
||||
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession
|
||||
import space.luminic.budgerapp.services.BotService
|
||||
|
||||
@Configuration
|
||||
class TelegramBotConfig {
|
||||
|
||||
@Bean
|
||||
fun telegramBotsApi(myBot: BotService): TelegramBotsApi {
|
||||
val botsApi = TelegramBotsApi(DefaultBotSession::class.java)
|
||||
botsApi.registerBot(myBot)
|
||||
return botsApi
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "telegram.bot")
|
||||
data class TelegramBotProperties(
|
||||
val username: String,
|
||||
val token: String,
|
||||
)
|
||||
@@ -30,18 +30,19 @@ class AuthController(
|
||||
|
||||
@PostMapping("/login")
|
||||
suspend fun login(@RequestBody request: AuthRequest): Map<String, String> {
|
||||
return authService.login(request.username, request.password)
|
||||
.map { token -> mapOf("token" to token) }.awaitFirst()
|
||||
val token = authService.login(request.username, request.password)
|
||||
return mapOf("token" to token)
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
fun register(@RequestBody request: RegisterRequest): Mono<User> {
|
||||
suspend fun register(@RequestBody request: RegisterRequest): User {
|
||||
return authService.register(request.username, request.password, request.firstName)
|
||||
}
|
||||
|
||||
@PostMapping("/tgLogin")
|
||||
fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Mono<Map<String, String>> {
|
||||
return authService.tgLogin(tgId).map { token -> mapOf("token" to token) }
|
||||
suspend fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Map<String, String> {
|
||||
val token = authService.tgLogin(tgId)
|
||||
return mapOf("token" to token)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
package space.luminic.budgerapp.controllers
|
||||
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import reactor.core.publisher.Mono
|
||||
import space.luminic.budgerapp.models.Recurrent
|
||||
import space.luminic.budgerapp.configs.AuthException
|
||||
import space.luminic.budgerapp.services.AuthService
|
||||
import space.luminic.budgerapp.services.RecurrentService
|
||||
import space.luminic.budgerapp.services.SpaceService
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/recurrents")
|
||||
class RecurrentController (
|
||||
private val recurrentService: RecurrentService
|
||||
@RequestMapping("/spaces/{spaceId}")
|
||||
class RecurrentController(
|
||||
private val recurrentService: RecurrentService,
|
||||
private val authService: AuthService,
|
||||
private val spaceService: SpaceService
|
||||
){
|
||||
|
||||
// @DeleteMapping("/recurrents/{recurrentId}")
|
||||
// suspend fun delete(
|
||||
// @PathVariable spaceId: String,
|
||||
// @PathVariable recurrentId: String): String {
|
||||
// val user = authService.getSecurityUser()
|
||||
// val space = spaceService.isValidRequest(spaceId, user)
|
||||
// if (space.owner?.id == user.id) {
|
||||
// recurrentService.deleteRecurrent(recurrentId)
|
||||
// return "Cool"
|
||||
// } else {
|
||||
// throw AuthException("Only owners allowed")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @GetMapping("/")
|
||||
// fun getRecurrents(): Mono<List<Recurrent>> {
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
package space.luminic.budgerapp.controllers
|
||||
|
||||
import com.opencsv.CSVWriter
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.apache.commons.io.IOUtils.writer
|
||||
import org.bson.Document
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.budgerapp.configs.AuthException
|
||||
import space.luminic.budgerapp.controllers.BudgetController.LimitValue
|
||||
import space.luminic.budgerapp.controllers.dtos.BudgetCreationDTO
|
||||
import space.luminic.budgerapp.models.*
|
||||
import space.luminic.budgerapp.services.CategoryService
|
||||
import space.luminic.budgerapp.services.FinancialService
|
||||
import space.luminic.budgerapp.services.RecurrentService
|
||||
import space.luminic.budgerapp.services.SpaceService
|
||||
import space.luminic.budgerapp.services.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import java.time.LocalDate
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/spaces")
|
||||
class SpaceController(
|
||||
private val spaceService: SpaceService,
|
||||
private val financialService: FinancialService,
|
||||
private val categoryService: CategoryService,
|
||||
private val recurrentService: RecurrentService
|
||||
private val recurrentService: RecurrentService,
|
||||
private val authService: AuthService
|
||||
) {
|
||||
|
||||
private val log = LoggerFactory.getLogger(SpaceController::class.java)
|
||||
|
||||
|
||||
data class SpaceCreateDTO(
|
||||
val name: String,
|
||||
@@ -54,13 +63,16 @@ class SpaceController(
|
||||
|
||||
@DeleteMapping("/{spaceId}")
|
||||
suspend fun deleteSpace(@PathVariable spaceId: String) {
|
||||
return spaceService.deleteSpace(spaceService.isValidRequest(spaceId))
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return spaceService.deleteSpace(space)
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/{spaceId}/invite")
|
||||
suspend fun inviteSpace(@PathVariable spaceId: String): SpaceInvite {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return spaceService.createInviteSpace(spaceId)
|
||||
}
|
||||
|
||||
@@ -71,13 +83,15 @@ class SpaceController(
|
||||
|
||||
@DeleteMapping("/{spaceId}/leave")
|
||||
suspend fun leaveSpace(@PathVariable spaceId: String) {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return spaceService.leaveSpace(spaceId)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{spaceId}/members/kick/{username}")
|
||||
suspend fun kickMembers(@PathVariable spaceId: String, @PathVariable username: String) {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return spaceService.kickMember(spaceId, username)
|
||||
}
|
||||
|
||||
@@ -87,14 +101,17 @@ class SpaceController(
|
||||
//
|
||||
@GetMapping("/{spaceId}/budgets")
|
||||
suspend fun getBudgets(@PathVariable spaceId: String): List<Budget> {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return financialService.getBudgets(spaceId).awaitSingleOrNull().orEmpty()
|
||||
|
||||
}
|
||||
|
||||
@GetMapping("/{spaceId}/budgets/{id}")
|
||||
suspend fun getBudget(@PathVariable spaceId: String, @PathVariable id: String): BudgetDTO? {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
log.info("Getting budget for spaceId=$spaceId, id=$id")
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return financialService.getBudget(spaceId, id)
|
||||
}
|
||||
|
||||
@@ -103,8 +120,10 @@ class SpaceController(
|
||||
@PathVariable spaceId: String,
|
||||
@RequestBody budgetCreationDTO: BudgetCreationDTO,
|
||||
): Budget? {
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return financialService.createBudget(
|
||||
spaceService.isValidRequest(spaceId),
|
||||
space,
|
||||
budgetCreationDTO.budget,
|
||||
budgetCreationDTO.createRecurrent
|
||||
)
|
||||
@@ -112,7 +131,8 @@ class SpaceController(
|
||||
|
||||
@DeleteMapping("/{spaceId}/budgets/{id}")
|
||||
suspend fun deleteBudget(@PathVariable spaceId: String, @PathVariable id: String) {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
financialService.deleteBudget(spaceId, id)
|
||||
|
||||
}
|
||||
@@ -124,7 +144,8 @@ class SpaceController(
|
||||
@PathVariable catId: String,
|
||||
@RequestBody limit: LimitValue,
|
||||
): BudgetCategory {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return financialService.setCategoryLimit(spaceId, budgetId, catId, limit.limit)
|
||||
}
|
||||
|
||||
@@ -159,6 +180,51 @@ class SpaceController(
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/transactions/csv")
|
||||
suspend fun getTransactionsCSV(
|
||||
): ResponseEntity<Any> {
|
||||
try {
|
||||
val bos = ByteArrayOutputStream()
|
||||
val writer = CSVWriter(OutputStreamWriter(bos))
|
||||
val CSVHeaders = arrayOf("id", "comment", "category")
|
||||
writer.writeNext(CSVHeaders)
|
||||
financialService.getAllTransactions(
|
||||
).map {
|
||||
val data = arrayOf(it.id, it.comment, it.category.id)
|
||||
writer.writeNext(data)
|
||||
}
|
||||
writer.close()
|
||||
|
||||
val csvData = bos.toByteArray()
|
||||
val headers = HttpHeaders()
|
||||
headers.contentType = MediaType.parseMediaType("text/csv")
|
||||
headers.setContentDispositionFormData("attachment", "pojos.csv")
|
||||
|
||||
return ResponseEntity(csvData, headers, HttpStatus.OK)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/{spaceId}/category-predict")
|
||||
suspend fun getTransactionCategoryPredict(
|
||||
@PathVariable spaceId: String,
|
||||
@RequestParam comment: String,
|
||||
@RequestParam cloud: Int
|
||||
): List<Category> {
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return categoryService.getCategories(
|
||||
"67af3c0f652da946a7dd9931",
|
||||
"EXPENSE",
|
||||
sortBy = "name",
|
||||
direction = "ASC",
|
||||
predict = comment,
|
||||
cloud = cloud
|
||||
)
|
||||
}
|
||||
|
||||
@GetMapping("/{spaceId}/transactions/{id}")
|
||||
suspend fun getTransaction(
|
||||
@@ -168,9 +234,11 @@ class SpaceController(
|
||||
return financialService.getTransactionById(id)
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/{spaceId}/transactions")
|
||||
suspend fun createTransaction(@PathVariable spaceId: String, @RequestBody transaction: Transaction): Transaction {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return financialService.createTransaction(space, transaction)
|
||||
|
||||
|
||||
@@ -180,14 +248,16 @@ class SpaceController(
|
||||
suspend fun editTransaction(
|
||||
@PathVariable spaceId: String, @PathVariable id: String, @RequestBody transaction: Transaction
|
||||
): Transaction {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
transaction.space = space
|
||||
return financialService.editTransaction(transaction)
|
||||
return financialService.editTransaction(transaction, user)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{spaceId}/transactions/{id}")
|
||||
suspend fun deleteTransaction(@PathVariable spaceId: String, @PathVariable id: String) {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
val transaction = financialService.getTransactionById(id)
|
||||
financialService.deleteTransaction(transaction)
|
||||
}
|
||||
@@ -202,8 +272,9 @@ class SpaceController(
|
||||
@RequestParam("sort") sortBy: String = "name",
|
||||
@RequestParam("direction") direction: String = "ASC"
|
||||
): List<Category> {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
return categoryService.getCategories(spaceId, type, sortBy, direction).awaitSingleOrNull().orEmpty()
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return categoryService.getCategories(spaceId, type, sortBy, direction)
|
||||
}
|
||||
|
||||
@GetMapping("/{spaceId}/categories/types")
|
||||
@@ -215,7 +286,8 @@ class SpaceController(
|
||||
suspend fun createCategory(
|
||||
@PathVariable spaceId: String, @RequestBody category: Category
|
||||
): Category {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return financialService.createCategory(space, category).awaitSingle()
|
||||
}
|
||||
|
||||
@@ -225,33 +297,38 @@ class SpaceController(
|
||||
@RequestBody category: Category,
|
||||
@PathVariable spaceId: String
|
||||
): Category {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return categoryService.editCategory(space, category)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{spaceId}/categories/{categoryId}")
|
||||
suspend fun deleteCategory(@PathVariable categoryId: String, @PathVariable spaceId: String) {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
categoryService.deleteCategory(space, categoryId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
categoryService.deleteCategory(space, categoryId, user)
|
||||
}
|
||||
|
||||
@GetMapping("/{spaceId}/categories/tags")
|
||||
suspend fun getTags(@PathVariable spaceId: String): List<Tag> {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return spaceService.getTags(space)
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/{spaceId}/categories/tags")
|
||||
suspend fun createTags(@PathVariable spaceId: String, @RequestBody tag: Tag): Tag {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return spaceService.createTag(space, tag)
|
||||
|
||||
}
|
||||
|
||||
@DeleteMapping("/{spaceId}/categories/tags/{tagId}")
|
||||
suspend fun deleteTags(@PathVariable spaceId: String, @PathVariable tagId: String) {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return spaceService.deleteTag(space, tagId)
|
||||
}
|
||||
|
||||
@@ -267,39 +344,49 @@ class SpaceController(
|
||||
|
||||
@GetMapping("/{spaceId}/recurrents")
|
||||
suspend fun getRecurrents(@PathVariable spaceId: String): List<Recurrent> {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
return recurrentService.getRecurrents(spaceId).awaitSingleOrNull().orEmpty()
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return recurrentService.getRecurrents(spaceId)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/{spaceId}/recurrents/{id}")
|
||||
suspend fun getRecurrent(@PathVariable spaceId: String, @PathVariable id: String): Recurrent {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return recurrentService.getRecurrentById(space, id).awaitSingle()
|
||||
}
|
||||
|
||||
@PostMapping("/{spaceId}/recurrent")
|
||||
@PostMapping("/{spaceId}/recurrents")
|
||||
suspend fun createRecurrent(@PathVariable spaceId: String, @RequestBody recurrent: Recurrent): Recurrent {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return recurrentService.createRecurrent(space, recurrent).awaitSingle()
|
||||
}
|
||||
|
||||
@PutMapping("/{spaceId}/recurrent/{id}")
|
||||
@PutMapping("/{spaceId}/recurrents/{id}")
|
||||
suspend fun editRecurrent(
|
||||
@PathVariable spaceId: String,
|
||||
@PathVariable id: String,
|
||||
@RequestBody recurrent: Recurrent
|
||||
): Recurrent {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return recurrentService.editRecurrent(recurrent).awaitSingle()
|
||||
}
|
||||
|
||||
|
||||
@DeleteMapping("/{spaceId}/recurrent/{id}")
|
||||
@DeleteMapping("/{spaceId}/recurrents/{id}")
|
||||
suspend fun deleteRecurrent(@PathVariable spaceId: String, @PathVariable id: String) {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
recurrentService.deleteRecurrent(id).awaitSingle()
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
if (space.owner?.id == user.id) {
|
||||
recurrentService.deleteRecurrent(id)
|
||||
} else {
|
||||
throw AuthException("Only owners allowed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @GetMapping("/regen")
|
||||
|
||||
@@ -35,7 +35,7 @@ class SubscriptionController(
|
||||
}
|
||||
|
||||
@PostMapping("/notifyAll")
|
||||
fun notifyAll(@RequestBody payload: PushMessage): ResponseEntity<Any> {
|
||||
suspend fun notifyAll(@RequestBody payload: PushMessage): ResponseEntity<Any> {
|
||||
return try {
|
||||
ResponseEntity.ok(subscriptionService.sendToAll(payload))
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package space.luminic.budgerapp.controllers
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import space.luminic.budgerapp.models.WishList
|
||||
import space.luminic.budgerapp.models.WishListItem
|
||||
import space.luminic.budgerapp.services.AuthService
|
||||
import space.luminic.budgerapp.services.SpaceService
|
||||
import space.luminic.budgerapp.services.WishListService
|
||||
|
||||
@@ -11,24 +12,28 @@ import space.luminic.budgerapp.services.WishListService
|
||||
@RequestMapping("/spaces/{spaceId}/wishlists")
|
||||
class WishListController(
|
||||
private val wishListService: WishListService,
|
||||
private val spaceService: SpaceService
|
||||
private val spaceService: SpaceService,
|
||||
private val authService: AuthService
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
suspend fun findWishList(@PathVariable spaceId: String): List<WishList> {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.findWishLists(spaceId)
|
||||
}
|
||||
|
||||
@GetMapping("/{wishListId}")
|
||||
suspend fun getWishList(@PathVariable spaceId: String, @PathVariable wishListId: String): WishList {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.getList(wishListId)
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
suspend fun createWishList(@PathVariable spaceId: String, @RequestBody wishList: WishList): WishList {
|
||||
val space = spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
val space = spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.createWishList(space, wishList)
|
||||
}
|
||||
|
||||
@@ -38,7 +43,8 @@ class WishListController(
|
||||
@PathVariable wishListId: String,
|
||||
@RequestBody wishList: WishList
|
||||
): WishList {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.updateWishListInfo(wishList)
|
||||
}
|
||||
|
||||
@@ -49,7 +55,8 @@ class WishListController(
|
||||
@PathVariable itemId: String,
|
||||
@RequestBody wishListItem: WishListItem
|
||||
): WishList {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.updateWishListItemInfo(wishListId, wishListItem)
|
||||
}
|
||||
|
||||
@@ -59,7 +66,8 @@ class WishListController(
|
||||
@PathVariable wishListId: String,
|
||||
@RequestBody wishListItem: WishListItem
|
||||
): WishList {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.addItemToWishList(wishListId, wishListItem)
|
||||
}
|
||||
|
||||
@@ -69,13 +77,15 @@ class WishListController(
|
||||
@PathVariable wishListId: String,
|
||||
@PathVariable wishListItemId: String
|
||||
): WishList {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.removeItemFromWishList(wishListId, wishListItemId)
|
||||
}
|
||||
|
||||
@DeleteMapping("/{wishListId}")
|
||||
suspend fun deleteWishList(@PathVariable spaceId: String, @PathVariable wishListId: String) {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
wishListService.deleteWishList(wishListId)
|
||||
}
|
||||
|
||||
@@ -83,8 +93,9 @@ class WishListController(
|
||||
suspend fun cancelReserve(
|
||||
@PathVariable spaceId: String, @PathVariable wishListId: String,
|
||||
@PathVariable wishlistItemId: String
|
||||
) : WishList {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
): WishList {
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return wishListService.cancelReserve(wishListId, wishlistItemId)
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@ class WishListMapper : FromDocumentMapper {
|
||||
reserveDoc.getString("name")
|
||||
) else null,
|
||||
updatedAt = it.getDate("updatedAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(),
|
||||
createdAt = it.getDate("createdAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
|
||||
createdAt = it.getDate("createdAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(),
|
||||
isActive = it.getBoolean("isActive")
|
||||
)
|
||||
}.toMutableList(),
|
||||
updatedAt = document.getDate("updatedAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(),
|
||||
|
||||
22
src/main/kotlin/space/luminic/budgerapp/models/BotStates.kt
Normal file
22
src/main/kotlin/space/luminic/budgerapp/models/BotStates.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package space.luminic.budgerapp.models
|
||||
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef
|
||||
import org.springframework.data.mongodb.core.mapping.Document
|
||||
|
||||
enum class BotStates {
|
||||
WAIT_CATEGORY
|
||||
}
|
||||
|
||||
@Document("bot-user-states")
|
||||
data class BotUserState(
|
||||
@Id val id: String? = null,
|
||||
@DBRef val user: User,
|
||||
var data: MutableList<ChatData> = mutableListOf(),
|
||||
)
|
||||
|
||||
data class ChatData (
|
||||
val chatId: String,
|
||||
var state: BotStates,
|
||||
var data: MutableMap<String, String> = mutableMapOf(),
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
package space.luminic.budgerapp.models
|
||||
|
||||
data class CategoryPrediction(val category: String, val weight: Double) {
|
||||
}
|
||||
@@ -2,3 +2,4 @@ package space.luminic.budgerapp.models
|
||||
|
||||
|
||||
open class NotFoundException(message: String) : Exception(message)
|
||||
open class TelegramBotException(message: String, val chatId: Long) : Exception(message)
|
||||
@@ -27,6 +27,7 @@ data class WishListItem(
|
||||
var price: Double,
|
||||
var link: String,
|
||||
var images: MutableList<String> = mutableListOf(),
|
||||
var isActive: Boolean,
|
||||
var reservedBy: Reserve? = null,
|
||||
var updatedAt: LocalDateTime = LocalDateTime.now(),
|
||||
var createdAt: LocalDateTime = LocalDateTime.now()
|
||||
@@ -35,5 +36,4 @@ data class WishListItem(
|
||||
data class Reserve(
|
||||
val aid: String,
|
||||
val name: String? = null,
|
||||
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package space.luminic.budgerapp.repos
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import space.luminic.budgerapp.models.BotUserState
|
||||
|
||||
interface BotStatesRepo: ReactiveMongoRepository<BotUserState, String> {
|
||||
}
|
||||
@@ -1,10 +1,21 @@
|
||||
package space.luminic.budgerapp.repos
|
||||
|
||||
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.mongodb.repository.Query
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import reactor.core.publisher.Flux
|
||||
import space.luminic.budgerapp.models.Subscription
|
||||
|
||||
@Repository
|
||||
interface SubscriptionRepo : ReactiveMongoRepository<Subscription, String> {
|
||||
|
||||
@Query("{ \$and: [ " +
|
||||
"{ 'user': { '\$ref': 'users', '\$id': ?0 } }, " +
|
||||
"{ 'isActive': true } " +
|
||||
"]}")
|
||||
fun findByUserIdAndIsActive(userId: ObjectId): Flux<Subscription>
|
||||
|
||||
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package space.luminic.budgerapp.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.springframework.cache.annotation.Cacheable
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Mono
|
||||
import space.luminic.budgerapp.configs.AuthException
|
||||
import space.luminic.budgerapp.models.TokenStatus
|
||||
import space.luminic.budgerapp.models.User
|
||||
@@ -25,10 +28,20 @@ class AuthService(
|
||||
) {
|
||||
private val passwordEncoder = BCryptPasswordEncoder()
|
||||
|
||||
fun login(username: String, password: String): Mono<String> {
|
||||
return userRepository.findByUsername(username)
|
||||
.flatMap { user ->
|
||||
if (passwordEncoder.matches(password, user.password)) {
|
||||
suspend fun getSecurityUser(): User {
|
||||
val securityContextHolder = ReactiveSecurityContextHolder.getContext().awaitSingleOrNull()
|
||||
?: throw AuthException("Authentication failed")
|
||||
val authentication = securityContextHolder.authentication
|
||||
|
||||
val username = authentication.name
|
||||
// Получаем пользователя по имени
|
||||
return userService.getByUsername(username)
|
||||
}
|
||||
|
||||
suspend fun login(username: String, password: String): String {
|
||||
val user = userRepository.findByUsername(username).awaitFirstOrNull()
|
||||
?: throw UsernameNotFoundException("Пользователь не найден")
|
||||
return if (passwordEncoder.matches(password, user.password)) {
|
||||
val token = jwtUtil.generateToken(user.username!!)
|
||||
val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10)
|
||||
tokenService.saveToken(
|
||||
@@ -38,18 +51,17 @@ class AuthService(
|
||||
expireAt.toInstant(),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
).thenReturn(token)
|
||||
)
|
||||
token
|
||||
} else {
|
||||
Mono.error(AuthException("Invalid credentials"))
|
||||
}
|
||||
throw IllegalArgumentException("Ошибка логина или пароля")
|
||||
}
|
||||
}
|
||||
|
||||
fun tgLogin(tgId: String): Mono<String> {
|
||||
return userRepository.findByTgId(tgId)
|
||||
.switchIfEmpty(Mono.error(AuthException("Invalid credentials")))
|
||||
.flatMap { user ->
|
||||
println("here")
|
||||
suspend fun tgLogin(tgId: String): String {
|
||||
val user =
|
||||
userRepository.findByTgId(tgId).awaitSingleOrNull() ?: throw UsernameNotFoundException("Пользователь не найден")
|
||||
|
||||
val token = jwtUtil.generateToken(user.username!!)
|
||||
val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10)
|
||||
tokenService.saveToken(
|
||||
@@ -59,35 +71,30 @@ class AuthService(
|
||||
expireAt.toInstant(),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
).thenReturn(token)
|
||||
)
|
||||
return token
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun register(username: String, password: String, firstName: String): Mono<User> {
|
||||
return userRepository.findByUsername(username)
|
||||
.flatMap<User> { Mono.error(IllegalArgumentException("User with username '$username' already exists")) } // Ошибка, если пользователь уже существует
|
||||
.switchIfEmpty(
|
||||
Mono.defer {
|
||||
val newUser = User(
|
||||
suspend fun register(username: String, password: String, firstName: String): User {
|
||||
val user = userRepository.findByUsername(username).awaitSingleOrNull()
|
||||
if (user == null) {
|
||||
var newUser = User(
|
||||
username = username,
|
||||
password = passwordEncoder.encode(password), // Шифрование пароля
|
||||
firstName = firstName,
|
||||
roles = mutableListOf("USER")
|
||||
)
|
||||
userRepository.save(newUser).map { user ->
|
||||
user.password = null
|
||||
user
|
||||
} // Сохранение нового пользователя
|
||||
|
||||
}
|
||||
)
|
||||
newUser = userRepository.save(newUser).awaitSingle()
|
||||
newUser.password = null
|
||||
return newUser
|
||||
} else throw IllegalArgumentException("Пользователь уже зарегистрирован")
|
||||
}
|
||||
|
||||
|
||||
@Cacheable(cacheNames = ["tokens"], key = "#token")
|
||||
suspend fun isTokenValid(token: String): User {
|
||||
val tokenDetails = tokenService.getToken(token).awaitFirstOrNull() ?: throw AuthException("Invalid token")
|
||||
val tokenDetails = tokenService.getToken(token).awaitFirstOrNull() ?: throw AuthException("Токен не валиден")
|
||||
when {
|
||||
tokenDetails.status == TokenStatus.ACTIVE && tokenDetails.expiresAt.isAfter(LocalDateTime.now()) -> {
|
||||
return userService.getByUserNameWoPass(tokenDetails.username)
|
||||
@@ -95,7 +102,7 @@ class AuthService(
|
||||
|
||||
else -> {
|
||||
tokenService.revokeToken(tokenDetails.token)
|
||||
throw AuthException("Token expired or inactive")
|
||||
throw AuthException("Токен истек или не валиден")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
319
src/main/kotlin/space/luminic/budgerapp/services/BotService.kt
Normal file
319
src/main/kotlin/space/luminic/budgerapp/services/BotService.kt
Normal file
@@ -0,0 +1,319 @@
|
||||
package space.luminic.budgerapp.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.bson.Document
|
||||
import org.bson.types.ObjectId
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.*
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.stereotype.Service
|
||||
import org.telegram.telegrambots.bots.TelegramLongPollingBot
|
||||
import org.telegram.telegrambots.meta.api.methods.send.SendMessage
|
||||
import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage
|
||||
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageReplyMarkup
|
||||
import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageText
|
||||
import org.telegram.telegrambots.meta.api.objects.Update
|
||||
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup
|
||||
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton
|
||||
import org.telegram.telegrambots.meta.exceptions.TelegramApiException
|
||||
import space.luminic.budgerapp.configs.TelegramBotConfig
|
||||
import space.luminic.budgerapp.configs.TelegramBotProperties
|
||||
import space.luminic.budgerapp.models.*
|
||||
import space.luminic.budgerapp.repos.BotStatesRepo
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Service
|
||||
class BotService(
|
||||
private val telegramBotProperties: TelegramBotProperties,
|
||||
private val botStatesRepo: BotStatesRepo,
|
||||
private val reactiveMongoTemplate: ReactiveMongoTemplate,
|
||||
private val userService: UserService,
|
||||
private val categoriesService: CategoryService,
|
||||
private val financialService: FinancialService,
|
||||
private val spaceService: SpaceService,
|
||||
private val nlpService: NLPService
|
||||
) : TelegramLongPollingBot(telegramBotProperties.token) {
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
private suspend fun constructCategoriesButtons(
|
||||
nlp: Boolean, text: String? = null, chatId: Long
|
||||
): InlineKeyboardMarkup {
|
||||
val categories = categoriesService.getCategories(
|
||||
"67af3c0f652da946a7dd9931", "EXPENSE", sortBy = "name", direction = "ASC"
|
||||
)
|
||||
|
||||
val keyboard = InlineKeyboardMarkup()
|
||||
val buttonLines = mutableListOf<MutableList<InlineKeyboardButton>>()
|
||||
val filteredCategories = mutableListOf<Category>()
|
||||
if (nlp) {
|
||||
if (text.isNullOrBlank()) {
|
||||
throw TelegramBotException("Текст не может быть пустым", chatId)
|
||||
}
|
||||
val predictedCategories = nlpService.predictCategory(text, 0)
|
||||
|
||||
|
||||
for (category in predictedCategories) {
|
||||
filteredCategories.add(categories.first { it.id == category.category })
|
||||
}
|
||||
} else {
|
||||
filteredCategories.addAll(categories)
|
||||
}
|
||||
filteredCategories.map { category ->
|
||||
val btn = InlineKeyboardButton.builder().text("${category.icon} ${category.name}")
|
||||
.callbackData("category_${category.id}").build()
|
||||
|
||||
if (category.name.length >= 15) {
|
||||
// Если текст длинный, создаём отдельную строку для кнопки
|
||||
buttonLines.add(mutableListOf(btn))
|
||||
} else {
|
||||
var isAdded = false
|
||||
|
||||
// Пытаемся добавить кнопку в существующую строку
|
||||
for (line in buttonLines) {
|
||||
if (line.size < 2 && (line.isEmpty() || line[0].text.length < 14)) {
|
||||
line.add(btn)
|
||||
isAdded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Если не нашли подходящую строку, создаём новую
|
||||
if (!isAdded) {
|
||||
buttonLines.add(mutableListOf(btn))
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
val allCatsBtn = InlineKeyboardButton.builder().text("Все категории").callbackData("all_cats").build()
|
||||
val backButton = InlineKeyboardButton.builder().text("Отмена").callbackData("cancel").build()
|
||||
|
||||
buttonLines.add(mutableListOf(allCatsBtn))
|
||||
buttonLines.add(mutableListOf(backButton))
|
||||
keyboard.keyboard = buttonLines
|
||||
return keyboard
|
||||
}
|
||||
|
||||
|
||||
override fun getBotUsername(): String {
|
||||
return telegramBotProperties.username
|
||||
}
|
||||
|
||||
override fun onUpdateReceived(update: Update) = runBlocking {
|
||||
logger.info("Received message $update")
|
||||
try {
|
||||
if (update.hasCallbackQuery()) {
|
||||
processCallback(update)
|
||||
} else if (update.hasMessage()) {
|
||||
if (update.message.hasText()) {
|
||||
processMessage(update)
|
||||
} else if (update.message.hasPhoto()) {
|
||||
processPhoto(update)
|
||||
} else if (update.message.hasVideo()) {
|
||||
processVideo(update)
|
||||
}
|
||||
}
|
||||
} catch (e: TelegramBotException) {
|
||||
e.printStackTrace()
|
||||
logger.error(e.message)
|
||||
sendMessage(e.chatId.toString(), "${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processCallback(update: Update) {
|
||||
val chatId = update.callbackQuery.message.chatId.toString()
|
||||
val tgUserId = update.callbackQuery.from.id
|
||||
val user = userService.getUserByTelegramId(tgUserId) ?: throw TelegramBotException(
|
||||
"User ${update.callbackQuery.from.userName} not found", chatId = update.callbackQuery.message.chatId
|
||||
)
|
||||
val state = getState(user.id!!)
|
||||
if (state != null) {
|
||||
when (state.data.first { it.chatId == chatId }.state) {
|
||||
BotStates.WAIT_CATEGORY -> if (update.callbackQuery.data.startsWith("category_")) {
|
||||
confirmTransaction(
|
||||
chatId, user = userService.getUserByTelegramId(tgUserId)!!, update
|
||||
)
|
||||
} else if (update.callbackQuery.data == "cancel") {
|
||||
finishState(chatId, user)
|
||||
val deleteMsg = DeleteMessage(chatId, update.callbackQuery.message.messageId)
|
||||
execute(deleteMsg)
|
||||
sendMessage(chatId, "Введите сумму и комментарий когда будете готовы.")
|
||||
} else if (update.callbackQuery.data == "all_cats") {
|
||||
val editMessageReplyMarkup = EditMessageReplyMarkup()
|
||||
editMessageReplyMarkup.chatId = chatId
|
||||
editMessageReplyMarkup.messageId = update.callbackQuery.message.messageId
|
||||
editMessageReplyMarkup.replyMarkup = constructCategoriesButtons(false, chatId = chatId.toLong())
|
||||
execute(editMessageReplyMarkup)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processMessage(update: Update) {
|
||||
val user = userService.getUserByTelegramId(update.message.from.id) ?: throw TelegramBotException(
|
||||
"Мы не знакомы",
|
||||
chatId = update.message.chatId,
|
||||
)
|
||||
getState(user.id!!)?.data?.find { it.chatId == update.message.chatId.toString() }?.let {
|
||||
if (it.state == BotStates.WAIT_CATEGORY) {
|
||||
throw TelegramBotException(
|
||||
"Уже есть открытый выбор категории", update.message.chatId
|
||||
)
|
||||
}
|
||||
}
|
||||
newExpense(
|
||||
update.message.chatId.toString(),
|
||||
user = user,
|
||||
text = update.message.text,
|
||||
)
|
||||
}
|
||||
|
||||
private fun processPhoto(update: Update) {
|
||||
|
||||
}
|
||||
|
||||
private fun processVideo(update: Update) {
|
||||
|
||||
}
|
||||
|
||||
private fun sendMessage(chatId: String, text: String) {
|
||||
val message = SendMessage(chatId, text)
|
||||
try {
|
||||
execute(message)
|
||||
} catch (e: TelegramApiException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun newExpense(chatId: String, user: User, text: String) {
|
||||
|
||||
val splitText = text.split(" ")
|
||||
if (splitText.size < 2) {
|
||||
try {
|
||||
throw TelegramBotException("Сумма или комментарий не введены", chatId.toLong())
|
||||
|
||||
} catch (e: TelegramApiException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} else {
|
||||
val sum = try {
|
||||
splitText[0].toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
throw TelegramBotException("Кажется первый параметр не цифра", chatId.toLong())
|
||||
}
|
||||
val textWOSum = splitText.drop(1)
|
||||
var comment = ""
|
||||
textWOSum.map { word ->
|
||||
comment += "$word "
|
||||
}
|
||||
|
||||
|
||||
val message = SendMessage()
|
||||
message.chatId = chatId
|
||||
val msg = "Выберите категорию"
|
||||
message.text = msg
|
||||
message.replyMarkup = constructCategoriesButtons(true, text, chatId.toLong())
|
||||
val userState = BotUserState(user = user)
|
||||
val chatData =
|
||||
userState.data.find { it.chatId == chatId } ?: ChatData(chatId, state = BotStates.WAIT_CATEGORY)
|
||||
chatData.data["sum"] = sum.toString()
|
||||
chatData.data["comment"] = comment
|
||||
userState.data.add(chatData)
|
||||
try {
|
||||
execute(message)
|
||||
setState(userState)
|
||||
} catch (e: TelegramApiException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun confirmTransaction(chatId: String, user: User, update: Update) {
|
||||
val state = getState(user.id!!)
|
||||
if (state == null) {
|
||||
sendMessage(chatId, "Не можем найти информацию о сумме и комментарии")
|
||||
return
|
||||
}
|
||||
val stateData = state.data.find { it.chatId == chatId }
|
||||
if (stateData == null) {
|
||||
sendMessage(chatId, "Не можем найти информацию о сумме и комментарии")
|
||||
return
|
||||
}
|
||||
|
||||
val category = categoriesService.getCategories(
|
||||
"67af3c0f652da946a7dd9931", "EXPENSE", sortBy = "name", direction = "ASC"
|
||||
).first { it.id == update.callbackQuery.data.split("_")[1] }
|
||||
val space = spaceService.getSpace("67af3c0f652da946a7dd9931")
|
||||
val instantType = financialService.getTransactionTypes().first { it.code == "INSTANT" }
|
||||
val transaction = financialService.createTransaction(
|
||||
space, transaction = Transaction(
|
||||
space = space,
|
||||
type = instantType,
|
||||
user = user,
|
||||
category = category,
|
||||
comment = stateData.data["comment"]!!.trim(),
|
||||
date = LocalDate.now(),
|
||||
amount = stateData.data["sum"]!!.toDouble(),
|
||||
isDone = true,
|
||||
parentId = null,
|
||||
createdAt = LocalDateTime.now()
|
||||
), user = user
|
||||
)
|
||||
val editMsg = EditMessageText()
|
||||
editMsg.chatId = chatId
|
||||
editMsg.messageId = update.callbackQuery.message.messageId
|
||||
editMsg.text = "Успешно создали транзакцию c id ${transaction.id}"
|
||||
try {
|
||||
execute(editMsg)
|
||||
// execute(msg)
|
||||
finishState(chatId, user)
|
||||
} catch (e: TelegramApiException) {
|
||||
e.printStackTrace()
|
||||
logger.error(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun getState(userId: String): BotUserState? {
|
||||
val lookup = lookup("users", "user.\$id", "_id", "userDetails")
|
||||
val unwind = unwind("userDetails")
|
||||
val match = match(Criteria.where("userDetails._id").`is`(ObjectId(userId)))
|
||||
|
||||
val aggregation = newAggregation(lookup, unwind, match)
|
||||
return reactiveMongoTemplate.aggregate(aggregation, "bot-user-states", Document::class.java).next().map { doc ->
|
||||
val dataList = doc.getList("data", Document::class.java)
|
||||
BotUserState(
|
||||
id = doc.getObjectId("_id").toString(),
|
||||
user = User(doc.get("userDetails", Document::class.java).getObjectId("_id").toString()),
|
||||
data = dataList.map {
|
||||
val data = it.get("data", Document::class.java)
|
||||
ChatData(
|
||||
chatId = it.getString("chatId"),
|
||||
state = BotStates.valueOf(it.getString("state")),
|
||||
data = (data.toMap().mapValues { it.value.toString() }.toMutableMap())
|
||||
)
|
||||
}.toMutableList(),
|
||||
)
|
||||
}.awaitSingleOrNull()
|
||||
}
|
||||
|
||||
private suspend fun setState(userState: BotUserState): BotUserState {
|
||||
val stateToSave = userState.user.id?.let { userId ->
|
||||
getState(userId)?.copy(data = userState.data) ?: BotUserState(user = userState.user, data = userState.data)
|
||||
} ?: BotUserState(user = userState.user, data = userState.data)
|
||||
|
||||
return botStatesRepo.save(stateToSave).awaitSingle()
|
||||
}
|
||||
|
||||
private suspend fun finishState(chatId: String, user: User) {
|
||||
val state = getState(user.id!!)
|
||||
state?.data?.removeIf { it.chatId == chatId }
|
||||
state?.let { botStatesRepo.save(state).awaitSingle() }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package space.luminic.budgerapp.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.bson.Document
|
||||
import org.bson.types.ObjectId
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -16,10 +18,7 @@ import org.springframework.data.mongodb.core.query.isEqualTo
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Mono
|
||||
import space.luminic.budgerapp.mappers.CategoryMapper
|
||||
import space.luminic.budgerapp.models.Category
|
||||
import space.luminic.budgerapp.models.CategoryType
|
||||
import space.luminic.budgerapp.models.NotFoundException
|
||||
import space.luminic.budgerapp.models.Space
|
||||
import space.luminic.budgerapp.models.*
|
||||
import space.luminic.budgerapp.repos.BudgetRepo
|
||||
import space.luminic.budgerapp.repos.CategoryRepo
|
||||
|
||||
@@ -30,8 +29,8 @@ class CategoryService(
|
||||
private val mongoTemplate: ReactiveMongoTemplate,
|
||||
private val categoryMapper: CategoryMapper,
|
||||
private val budgetRepo: BudgetRepo,
|
||||
|
||||
) {
|
||||
private val nlpService: NLPService
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
@@ -69,14 +68,15 @@ class CategoryService(
|
||||
}.awaitFirstOrNull() ?: throw NotFoundException("Category not found")
|
||||
}
|
||||
|
||||
|
||||
fun getCategories(
|
||||
suspend fun getCategories(
|
||||
spaceId: String,
|
||||
type: String? = null,
|
||||
sortBy: String,
|
||||
direction: String,
|
||||
tagCode: String? = null
|
||||
): Mono<List<Category>> {
|
||||
tagCode: String? = null,
|
||||
predict: String? = null,
|
||||
cloud: Int? = null
|
||||
): MutableList<Category> {
|
||||
val lookupSpaces = lookup("spaces", "space.\$id", "_id", "spaceDetails")
|
||||
val unwindSpace = unwind("spaceDetails")
|
||||
val matchCriteria = mutableListOf<Criteria>()
|
||||
@@ -101,7 +101,7 @@ class CategoryService(
|
||||
).filterNotNull()
|
||||
|
||||
val aggregation = newAggregation(aggregationBuilder)
|
||||
return mongoTemplate.aggregate(
|
||||
val categories = mongoTemplate.aggregate(
|
||||
aggregation, "categories", Document::class.java
|
||||
)
|
||||
.collectList() // Преобразуем Flux<Transaction> в Mono<List<Transaction>>
|
||||
@@ -109,8 +109,19 @@ class CategoryService(
|
||||
docs.map { doc ->
|
||||
categoryMapper.fromDocument(doc)
|
||||
}
|
||||
}.awaitSingle().toMutableList()
|
||||
|
||||
val predictedCategories = mutableListOf<CategoryPrediction>()
|
||||
if (!predict.isNullOrBlank() && cloud != null) {
|
||||
predictedCategories.addAll(nlpService.predictCategory(predict, cloud))
|
||||
}
|
||||
val filteredCategories = mutableListOf<Category>()
|
||||
for (category in predictedCategories) {
|
||||
categories.find { it.id == category.category }?.let { filteredCategories.add(it) }
|
||||
}
|
||||
return if (filteredCategories.isEmpty()) categories else filteredCategories
|
||||
}
|
||||
|
||||
|
||||
@Cacheable("categoryTypes")
|
||||
fun getCategoryTypes(): List<CategoryType> {
|
||||
@@ -121,7 +132,6 @@ class CategoryService(
|
||||
}
|
||||
|
||||
|
||||
@CacheEvict(cacheNames = ["getAllCategories"], allEntries = true)
|
||||
suspend fun editCategory(space: Space, category: Category): Category {
|
||||
val oldCategory = findCategory(space, id = category.id)
|
||||
|
||||
@@ -132,9 +142,10 @@ class CategoryService(
|
||||
return categoryRepo.save(category).awaitSingle() // Сохраняем категорию, если тип не изменился
|
||||
}
|
||||
|
||||
suspend fun deleteCategory(space: Space, categoryId: String) {
|
||||
suspend fun deleteCategory(space: Space, categoryId: String, author: User) {
|
||||
findCategory(space, categoryId)
|
||||
val transactions = financialService.getTransactions(space.id!!, categoryId = categoryId).awaitSingle()
|
||||
if (transactions.isNotEmpty()) {
|
||||
val otherCategory = try {
|
||||
findCategory(space, name = "Другое")
|
||||
} catch (nfe: NotFoundException) {
|
||||
@@ -148,9 +159,12 @@ class CategoryService(
|
||||
)
|
||||
).awaitSingle()
|
||||
}
|
||||
|
||||
|
||||
transactions.map { transaction ->
|
||||
transaction.category = otherCategory
|
||||
financialService.editTransaction(transaction)
|
||||
financialService.editTransaction(transaction, author)
|
||||
}
|
||||
}
|
||||
val budgets = financialService.findProjectedBudgets(
|
||||
ObjectId(space.id),
|
||||
@@ -171,7 +185,7 @@ class CategoryService(
|
||||
budget.categories.removeIf { it.category.id == categoryId }
|
||||
budgetRepo.save(budget)
|
||||
}
|
||||
categoryRepo.deleteById(categoryId).awaitSingle()
|
||||
categoryRepo.deleteById(categoryId).awaitSingleOrNull()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package space.luminic.budgerapp.services
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.reactive.asFlow
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
@@ -35,6 +32,7 @@ import space.luminic.budgerapp.repos.CategoryRepo
|
||||
import space.luminic.budgerapp.repos.TransactionRepo
|
||||
import space.luminic.budgerapp.repos.WarnRepo
|
||||
import java.time.*
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.TemporalAdjusters
|
||||
import java.util.*
|
||||
|
||||
@@ -48,7 +46,9 @@ class FinancialService(
|
||||
val reactiveMongoTemplate: ReactiveMongoTemplate,
|
||||
private val categoryRepo: CategoryRepo,
|
||||
val transactionsMapper: TransactionsMapper,
|
||||
val budgetMapper: BudgetMapper
|
||||
val budgetMapper: BudgetMapper,
|
||||
private val subscriptionService: SubscriptionService,
|
||||
private val nlpService: NLPService
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(FinancialService::class.java)
|
||||
|
||||
@@ -57,8 +57,15 @@ class FinancialService(
|
||||
val budget = findProjectedBudget(
|
||||
transaction.space!!.id!!, budgetId = null, transaction.date, transaction.date
|
||||
)
|
||||
val budgetCategory = budget.categories.firstOrNull { it.category.id == transaction.category.id }
|
||||
?: throw NotFoundException("Budget category not found in the budget")
|
||||
if (transaction.category.type.code == "EXPENSE") {
|
||||
var budgetCategory = budget.categories.firstOrNull { it.category.id == transaction.category.id }
|
||||
|
||||
if (budgetCategory == null) {
|
||||
budgetCategory = BudgetCategory(0.0, 0.0, 0.0, transaction.category)
|
||||
budget.categories.add(budgetCategory)
|
||||
budgetRepo.save(budget).awaitSingle()
|
||||
|
||||
}
|
||||
if (transaction.category.type.code == "INCOME") {
|
||||
budgetRepo.save(budget).awaitSingle()
|
||||
}
|
||||
@@ -72,6 +79,7 @@ class FinancialService(
|
||||
logger.info("updateBudgetOnCreate end")
|
||||
budgetRepo.save(budget).awaitSingle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun updateBudgetOnEdit(
|
||||
@@ -147,26 +155,22 @@ class FinancialService(
|
||||
oldBudget: Budget,
|
||||
newBudget: Budget
|
||||
) = coroutineScope {
|
||||
val oldCategory = findBudgetCategory(oldTransaction, oldBudget)
|
||||
val newCategory = findBudgetCategory(newTransaction, newBudget)
|
||||
// val oldCategory = findBudgetCategory(oldTransaction, oldBudget)
|
||||
// val newCategory = findBudgetCategory(newTransaction, newBudget)
|
||||
|
||||
|
||||
if (oldCategory.category.id == newCategory.category.id) {
|
||||
async {
|
||||
updateBudgetCategory(oldTransaction, oldBudget, -difference)
|
||||
updateBudgetCategory(newTransaction, newBudget, difference)
|
||||
}
|
||||
} else {
|
||||
async {
|
||||
updateBudgetCategory(
|
||||
oldTransaction,
|
||||
oldBudget,
|
||||
-difference,
|
||||
isCategoryChanged = true,
|
||||
isOldCategory = true
|
||||
isOldCategory = true,
|
||||
isNewBudget = true
|
||||
)
|
||||
updateBudgetCategory(newTransaction, newBudget, difference)
|
||||
}
|
||||
updateBudgetCategory(newTransaction, newBudget, difference, isNewBudget = true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun findBudgetCategory(transaction: Transaction, budget: Budget): BudgetCategory {
|
||||
@@ -176,7 +180,7 @@ class FinancialService(
|
||||
|
||||
} else {
|
||||
budget.incomeCategories.firstOrNull { it.category.id == transaction.category.id }
|
||||
?:addCategoryToBudget(transaction.category, budget)
|
||||
?: addCategoryToBudget(transaction.category, budget)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,8 +206,10 @@ class FinancialService(
|
||||
budget: Budget,
|
||||
difference: Double,
|
||||
isCategoryChanged: Boolean = false,
|
||||
isOldCategory: Boolean = false
|
||||
isOldCategory: Boolean = false,
|
||||
isNewBudget: Boolean = false
|
||||
): Double {
|
||||
return if (transaction.category.type.code == "EXPENSE") {
|
||||
val sums = getBudgetSumsByCategory(transaction.category.id!!, budget)
|
||||
val categoryBudget = budget.categories.firstOrNull { it.category.id == transaction.category.id }
|
||||
?: throw NotFoundException("Not found category in budget")
|
||||
@@ -214,10 +220,13 @@ class FinancialService(
|
||||
if (isOldCategory) {
|
||||
categoryBudget.currentLimit -= transaction.amount
|
||||
} else categoryBudget.currentLimit += transaction.amount
|
||||
} else if (isNewBudget) {
|
||||
categoryBudget.currentLimit += transaction.amount
|
||||
} else categoryBudget.currentLimit += difference
|
||||
}
|
||||
budgetRepo.save(budget).awaitSingle()
|
||||
return 1.0
|
||||
1.0
|
||||
} else 0.0
|
||||
}
|
||||
|
||||
|
||||
@@ -367,6 +376,7 @@ class FinancialService(
|
||||
budgetDTO.plannedExpenses = transactions.get("plannedExpenses") as MutableList
|
||||
budgetDTO.plannedIncomes = transactions["plannedIncomes"] as MutableList
|
||||
budgetDTO.transactions = transactions["instantTransactions"] as MutableList
|
||||
logger.info("got budget for spaceId=$spaceId, id=$id")
|
||||
|
||||
budgetDTO
|
||||
}
|
||||
@@ -752,8 +762,39 @@ class FinancialService(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAllTransactions(): List<Transaction>{
|
||||
|
||||
suspend fun getTransactionByParentId(parentId: String): Transaction {
|
||||
// Сборка агрегации
|
||||
val lookup = lookup("categories", "category.\$id", "_id", "categoryDetails")
|
||||
val unwindCategory = unwind("categoryDetails")
|
||||
val lookupSpaces = lookup("spaces", "space.\$id", "_id", "spaceDetails")
|
||||
val unwindSpace = unwind("spaceDetails")
|
||||
val lookupUsers = lookup("users", "user.\$id", "_id", "userDetails")
|
||||
val unwindUser = unwind("userDetails")
|
||||
val sort =
|
||||
sort(Sort.by(Direction.DESC, "date").and(Sort.by(Direction.DESC, "createdAt")))
|
||||
|
||||
val aggregationBuilder = mutableListOf(
|
||||
lookup,
|
||||
unwindCategory,
|
||||
lookupSpaces,
|
||||
unwindSpace,
|
||||
lookupUsers,
|
||||
unwindUser,
|
||||
sort
|
||||
)
|
||||
|
||||
val aggregation = newAggregation(aggregationBuilder)
|
||||
|
||||
return reactiveMongoTemplate.aggregate(
|
||||
aggregation, "transactions", Document::class.java
|
||||
).collectList().awaitSingle().map { doc ->
|
||||
transactionsMapper.fromDocument(doc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun getTransactionByParentId(parentId: String): Transaction? {
|
||||
// Сборка агрегации
|
||||
val lookup = lookup("categories", "category.\$id", "_id", "categoryDetails")
|
||||
val unwindCategory = unwind("categoryDetails")
|
||||
@@ -780,7 +821,7 @@ class FinancialService(
|
||||
).map { doc ->
|
||||
transactionsMapper.fromDocument(doc)
|
||||
|
||||
}.awaitFirstOrNull() ?: throw NotFoundException("Child transaction with parent id $parentId not found")
|
||||
}.awaitFirstOrNull()
|
||||
}
|
||||
|
||||
|
||||
@@ -843,24 +884,48 @@ class FinancialService(
|
||||
}
|
||||
|
||||
|
||||
suspend fun createTransaction(space: Space, transaction: Transaction): Transaction {
|
||||
val securityContextHolder = ReactiveSecurityContextHolder.getContext().awaitSingle()
|
||||
val user = userService.getByUserNameWoPass(securityContextHolder.authentication.name)
|
||||
if (space.users.none { it.id.toString() == user.id }) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
suspend fun createTransaction(space: Space, transaction: Transaction, user: User? = null): Transaction {
|
||||
val author = user
|
||||
?: userService.getByUserNameWoPass(
|
||||
ReactiveSecurityContextHolder.getContext().awaitSingle().authentication.name
|
||||
)
|
||||
if (space.users.none { it.id.toString() == author.id }) {
|
||||
throw IllegalArgumentException("User does not have access to this Space")
|
||||
}
|
||||
// Привязываем space и user к транзакции
|
||||
transaction.user = user
|
||||
transaction.user = author
|
||||
transaction.space = space
|
||||
|
||||
val savedTransaction = transactionsRepo.save(transaction).awaitSingle()
|
||||
|
||||
updateBudgetOnCreate(savedTransaction)
|
||||
scope.launch {
|
||||
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
||||
val transactionType = if (transaction.type.code == "INSTANT") "текущую" else "плановую"
|
||||
|
||||
subscriptionService.sendToSpaceOwner(
|
||||
space.owner!!.id!!, PushMessage(
|
||||
title = "Новая транзакция в пространстве ${space.name}!",
|
||||
body = "Пользователь ${author.username} создал $transactionType транзакцию на сумму ${transaction.amount.toInt()} с комментарием ${transaction.comment} с датой ${
|
||||
dateFormatter.format(
|
||||
transaction.date
|
||||
)
|
||||
}",
|
||||
url = "https://luminic.space/"
|
||||
)
|
||||
)
|
||||
}
|
||||
scope.launch {
|
||||
nlpService.reteach()
|
||||
}
|
||||
return savedTransaction
|
||||
}
|
||||
|
||||
|
||||
@CacheEvict(cacheNames = ["transactions", "budgets"], allEntries = true)
|
||||
suspend fun editTransaction(transaction: Transaction): Transaction {
|
||||
suspend fun editTransaction(transaction: Transaction, author: User): Transaction {
|
||||
val oldStateOfTransaction = getTransactionById(transaction.id!!)
|
||||
val changed = compareSumDateDoneIsChanged(oldStateOfTransaction, transaction)
|
||||
if (!changed) {
|
||||
@@ -868,39 +933,90 @@ class FinancialService(
|
||||
}
|
||||
val amountDifference = transaction.amount - oldStateOfTransaction.amount
|
||||
|
||||
if (oldStateOfTransaction.isDone && oldStateOfTransaction.type.code == "PLANNED") {
|
||||
if (oldStateOfTransaction.type.code == "PLANNED") {
|
||||
handleChildTransaction(
|
||||
oldStateOfTransaction,
|
||||
transaction
|
||||
)
|
||||
}
|
||||
val space = transaction.space
|
||||
val savedTransaction = transactionsRepo.save(transaction).awaitSingle()
|
||||
updateBudgetOnEdit(oldStateOfTransaction, savedTransaction, amountDifference)
|
||||
scope.launch {
|
||||
var whatChanged = "nothing"
|
||||
val dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
||||
val transactionType = if (transaction.type.code == "INSTANT") "текущую" else "плановую"
|
||||
|
||||
val sb = StringBuilder()
|
||||
if (oldStateOfTransaction.amount != transaction.amount) {
|
||||
sb.append("${oldStateOfTransaction.amount} → ${transaction.amount}\n")
|
||||
whatChanged = "main"
|
||||
}
|
||||
if (oldStateOfTransaction.comment != transaction.comment) {
|
||||
sb.append("${oldStateOfTransaction.comment} → ${transaction.comment}\n")
|
||||
whatChanged = "main"
|
||||
|
||||
}
|
||||
if (oldStateOfTransaction.date != transaction.date) {
|
||||
sb.append("${dateFormatter.format(oldStateOfTransaction.date)} → ${dateFormatter.format(transaction.date)}\n")
|
||||
whatChanged = "main"
|
||||
}
|
||||
if (!oldStateOfTransaction.isDone && transaction.isDone) {
|
||||
whatChanged = "done_true"
|
||||
}
|
||||
if (oldStateOfTransaction.isDone && !transaction.isDone) {
|
||||
whatChanged = "done_false"
|
||||
}
|
||||
|
||||
val body: String = when (whatChanged) {
|
||||
"main" -> {
|
||||
"Пользователь ${author.username} изменил $transactionType транзакцию:\n$sb"
|
||||
}
|
||||
|
||||
"done_true" -> {
|
||||
"Пользователь ${author.username} выполнил ${transaction.comment} с суммой ${transaction.amount.toInt()}"
|
||||
}
|
||||
|
||||
"done_false" -> {
|
||||
"Пользователь ${author.username} отменил выполнение ${transaction.comment} с суммой ${transaction.amount.toInt()}"
|
||||
}
|
||||
|
||||
else -> "Изменения не обнаружены, но что то точно изменилось"
|
||||
}
|
||||
|
||||
subscriptionService.sendToSpaceOwner(
|
||||
space?.owner!!.id!!, PushMessage(
|
||||
title = "Новое действие в пространстве ${space.name}!",
|
||||
body = body,
|
||||
url = "https://luminic.space/"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return savedTransaction
|
||||
}
|
||||
|
||||
|
||||
private suspend fun handleChildTransaction(oldTransaction: Transaction, newTransaction: Transaction) {
|
||||
val childTransaction = getTransactionByParentId(newTransaction.id!!)
|
||||
|
||||
logger.info("Updating child: $childTransaction")
|
||||
val updatedChild = childTransaction.copy(
|
||||
amount = newTransaction.amount,
|
||||
category = newTransaction.category,
|
||||
comment = newTransaction.comment,
|
||||
user = newTransaction.user
|
||||
)
|
||||
transactionsRepo.save(updatedChild).awaitSingle()
|
||||
|
||||
if (!oldTransaction.isDone && newTransaction.isDone) {
|
||||
val newChildTransaction = newTransaction.copy(
|
||||
id = null, type = TransactionType("INSTANT", "Текущие"), parentId = newTransaction.id
|
||||
)
|
||||
transactionsRepo.save(newChildTransaction).awaitSingle()
|
||||
updateBudgetOnCreate(newChildTransaction)
|
||||
|
||||
} else if (oldTransaction.isDone && !newTransaction.isDone) {
|
||||
deleteTransaction(childTransaction)
|
||||
val childTransaction = getTransactionByParentId(newTransaction.id!!)
|
||||
childTransaction?.let { deleteTransaction(it) }
|
||||
} else {
|
||||
val childTransaction = getTransactionByParentId(newTransaction.id!!)
|
||||
childTransaction?.let {
|
||||
logger.info("Updating child: $it")
|
||||
it.amount = newTransaction.amount
|
||||
it.category = newTransaction.category
|
||||
it.comment = newTransaction.comment
|
||||
it.user = newTransaction.user
|
||||
transactionsRepo.save(it).awaitSingle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package space.luminic.budgerapp.services
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import org.springframework.web.reactive.function.client.bodyToMono
|
||||
import space.luminic.budgerapp.configs.NLPConfig
|
||||
import space.luminic.budgerapp.models.CategoryPrediction
|
||||
|
||||
@Service
|
||||
class NLPService(
|
||||
private val nlpConfig: NLPConfig,
|
||||
private val webClient: WebClient = WebClient.builder()
|
||||
.baseUrl(nlpConfig.address)
|
||||
// .defaultHeader("Authorization", "Bearer YOUR_API_KEY")
|
||||
.build(),
|
||||
) {
|
||||
private val logging = LoggerFactory.getLogger(NLPService::class.java)
|
||||
|
||||
suspend fun reteach() {
|
||||
logging.info("Reteaching NLP")
|
||||
webClient.get().uri("/reteach").retrieve().bodyToMono<Void>().awaitSingleOrNull()
|
||||
}
|
||||
|
||||
suspend fun predictCategory(category: String, cloud: Int): List<CategoryPrediction> {
|
||||
val response = webClient.get().uri("/predict?req=$category&cloud=$cloud")
|
||||
.retrieve()
|
||||
.bodyToFlux(CategoryPrediction::class.java)
|
||||
.collectList()
|
||||
.awaitSingle()
|
||||
return response
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package space.luminic.budgerapp.services
|
||||
|
||||
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactive.awaitLast
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
@@ -36,7 +37,7 @@ class RecurrentService(
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
|
||||
fun getRecurrents(spaceId: String): Mono<List<Recurrent>> {
|
||||
suspend fun getRecurrents(spaceId: String): List<Recurrent> {
|
||||
val lookupCategories = lookup("categories", "category.\$id", "_id", "categoryDetails")
|
||||
val unwindCategory = unwind("categoryDetails")
|
||||
|
||||
@@ -48,12 +49,13 @@ class RecurrentService(
|
||||
val aggregation =
|
||||
newAggregation(lookupCategories, unwindCategory, lookupSpace, unwindSpace, matchStage, sort)
|
||||
// Запрос рекуррентных платежей
|
||||
return reactiveMongoTemplate.aggregate(aggregation, "recurrents", Document::class.java).collectList()
|
||||
return reactiveMongoTemplate.aggregate(aggregation, "recurrents", Document::class.java)
|
||||
.collectList()
|
||||
.map { docs ->
|
||||
docs.map { doc ->
|
||||
recurrentMapper.fromDocument(doc)
|
||||
}.toList()
|
||||
}
|
||||
}.awaitSingle()
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +84,8 @@ class RecurrentService(
|
||||
val context = ReactiveSecurityContextHolder.getContext().awaitSingleOrNull()
|
||||
?: throw IllegalStateException("SecurityContext is empty!")
|
||||
val user = userService.getByUserNameWoPass(context.authentication.name)
|
||||
val recurrents = getRecurrents(space.id!!).awaitSingle()
|
||||
val recurrents = getRecurrents(space.id!!)
|
||||
if (recurrents.isNotEmpty()) {
|
||||
val transactions = recurrents.map { recurrent ->
|
||||
val transactionDate = when {
|
||||
recurrent.atDay in budget.dateFrom.dayOfMonth..daysInCurrentMonth -> {
|
||||
@@ -113,6 +116,8 @@ class RecurrentService(
|
||||
transactionRepo.saveAll(transactions).awaitLast()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun editRecurrent(recurrent: Recurrent): Mono<Recurrent> {
|
||||
return if (recurrent.id != null && recurrent.atDay <= 31) recurrentRepo.save(recurrent) else Mono.error(
|
||||
@@ -120,8 +125,8 @@ class RecurrentService(
|
||||
)
|
||||
}
|
||||
|
||||
fun deleteRecurrent(id: String): Mono<Void> {
|
||||
return recurrentRepo.deleteById(id)
|
||||
suspend fun deleteRecurrent(id: String) {
|
||||
recurrentRepo.deleteById(id).awaitFirstOrNull()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.reactive.awaitFirst
|
||||
import kotlinx.coroutines.reactive.awaitFirstOrNull
|
||||
import kotlinx.coroutines.reactive.awaitLast
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.bson.Document
|
||||
@@ -18,6 +19,7 @@ import space.luminic.budgerapp.configs.AuthException
|
||||
import space.luminic.budgerapp.models.*
|
||||
import space.luminic.budgerapp.repos.*
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.util.UUID
|
||||
|
||||
@Service
|
||||
@@ -35,14 +37,9 @@ class SpaceService(
|
||||
private val tagRepo: TagRepo
|
||||
) {
|
||||
|
||||
suspend fun isValidRequest(spaceId: String): Space {
|
||||
val securityContextHolder = ReactiveSecurityContextHolder.getContext().awaitSingleOrNull()
|
||||
?: throw AuthException("Authentication failed")
|
||||
val authentication = securityContextHolder.authentication
|
||||
|
||||
val username = authentication.name
|
||||
// Получаем пользователя по имени
|
||||
val user = userService.getByUsername(username)
|
||||
|
||||
suspend fun isValidRequest(spaceId: String, user: User): Space {
|
||||
val space = getSpace(spaceId)
|
||||
|
||||
// Проверяем доступ пользователя к пространству
|
||||
@@ -92,7 +89,8 @@ class SpaceService(
|
||||
username = userDoc.getString("username"),
|
||||
firstName = userDoc.getString("firstName")
|
||||
)
|
||||
}.toMutableList()
|
||||
}.toMutableList(),
|
||||
createdAt = doc.getDate("createdAt").toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
|
||||
)
|
||||
}
|
||||
}.awaitFirst()
|
||||
@@ -120,7 +118,7 @@ class SpaceService(
|
||||
.map { category ->
|
||||
category.copy(id = null, space = savedSpace) // Создаем новую копию
|
||||
}
|
||||
categoryRepo.saveAll(categories).awaitSingle()
|
||||
categoryRepo.saveAll(categories).awaitLast()
|
||||
savedSpace
|
||||
}
|
||||
|
||||
@@ -142,12 +140,12 @@ class SpaceService(
|
||||
|
||||
launch {
|
||||
val categories =
|
||||
categoryService.getCategories(objectId.toString(), null, "name", "ASC").awaitFirstOrNull().orEmpty()
|
||||
categoryService.getCategories(objectId.toString(), null, "name", "ASC")
|
||||
categoryRepo.deleteAll(categories).awaitFirstOrNull()
|
||||
}
|
||||
|
||||
launch {
|
||||
val recurrents = recurrentService.getRecurrents(objectId.toString()).awaitFirstOrNull().orEmpty()
|
||||
val recurrents = recurrentService.getRecurrents(objectId.toString())
|
||||
recurrentRepo.deleteAll(recurrents).awaitFirstOrNull()
|
||||
}
|
||||
}
|
||||
@@ -282,7 +280,6 @@ class SpaceService(
|
||||
val existedTag = findTag(space, tagCode) ?: throw NoSuchElementException("Tag with code $tagCode not found")
|
||||
val categoriesWithTag =
|
||||
categoryService.getCategories(space.id!!, sortBy = "name", direction = "ASC", tagCode = existedTag.code)
|
||||
.awaitSingleOrNull().orEmpty()
|
||||
categoriesWithTag.map { cat ->
|
||||
cat.tags.removeIf { it.code == tagCode } // Изменяем список тегов
|
||||
cat
|
||||
|
||||
@@ -3,13 +3,16 @@ package space.luminic.budgerapp.services
|
||||
|
||||
import com.interaso.webpush.VapidKeys
|
||||
import com.interaso.webpush.WebPushService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.reactive.awaitSingle
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.bson.types.ObjectId
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.dao.DuplicateKeyException
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Mono
|
||||
import space.luminic.budgerapp.models.PushMessage
|
||||
import space.luminic.budgerapp.models.Subscription
|
||||
import space.luminic.budgerapp.models.SubscriptionDTO
|
||||
@@ -36,38 +39,50 @@ class SubscriptionService(private val subscriptionRepo: SubscriptionRepo) {
|
||||
vapidKeys = VapidKeys.fromUncompressedBytes(VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY)
|
||||
)
|
||||
|
||||
fun sendNotification(endpoint: String, p256dh: String, auth: String, payload: PushMessage): Mono<Void> {
|
||||
return Mono.fromRunnable<Void> {
|
||||
|
||||
suspend fun sendToSpaceOwner(ownerId: String, message: PushMessage) = coroutineScope {
|
||||
val ownerTokens = subscriptionRepo.findByUserIdAndIsActive(ObjectId(ownerId)).collectList().awaitSingle()
|
||||
|
||||
ownerTokens.forEach { token ->
|
||||
launch(Dispatchers.IO) { // Теперь мы точно в корутин скоупе
|
||||
try {
|
||||
sendNotification(token.endpoint, token.p256dh, token.auth, message)
|
||||
} catch (e: Exception) {
|
||||
logger.error("Ошибка при отправке уведомления: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun sendNotification(endpoint: String, p256dh: String, auth: String, payload: PushMessage) {
|
||||
try {
|
||||
pushService.send(
|
||||
payload = Json.encodeToString(payload),
|
||||
endpoint = endpoint,
|
||||
p256dh = p256dh,
|
||||
auth = auth
|
||||
)
|
||||
}
|
||||
.doOnSuccess {
|
||||
logger.info("Уведомление успешно отправлено на endpoint: $endpoint")
|
||||
}
|
||||
.doOnError { e ->
|
||||
|
||||
} catch (e: Exception) {
|
||||
logger.error("Ошибка при отправке уведомления на endpoint $endpoint: ${e.message}")
|
||||
}
|
||||
.onErrorResume { e ->
|
||||
Mono.error(e) // Пробрасываем ошибку дальше, если нужна обработка выше
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun sendToAll(payload: PushMessage): Mono<List<String>> {
|
||||
return subscriptionRepo.findAll()
|
||||
.flatMap { sub ->
|
||||
suspend fun sendToAll(payload: PushMessage) {
|
||||
|
||||
subscriptionRepo.findAll().collectList().awaitSingle().forEach { sub ->
|
||||
|
||||
try {
|
||||
sendNotification(sub.endpoint, sub.p256dh, sub.auth, payload)
|
||||
.then(Mono.just("${sub.user?.username} at endpoint ${sub.endpoint}"))
|
||||
.onErrorResume { e ->
|
||||
} catch (e: Exception) {
|
||||
sub.isActive = false
|
||||
subscriptionRepo.save(sub).then(Mono.empty())
|
||||
subscriptionRepo.save(sub).awaitSingle()
|
||||
}
|
||||
}
|
||||
.collectList() // Собираем результаты в список
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package space.luminic.budgerapp.services
|
||||
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import org.springframework.cache.annotation.CacheEvict
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Mono
|
||||
@@ -12,14 +13,14 @@ import java.time.LocalDateTime
|
||||
class TokenService(private val tokenRepository: TokenRepo) {
|
||||
|
||||
@CacheEvict("tokens", allEntries = true)
|
||||
fun saveToken(token: String, username: String, expiresAt: LocalDateTime): Mono<Token> {
|
||||
suspend fun saveToken(token: String, username: String, expiresAt: LocalDateTime): Token {
|
||||
val newToken = Token(
|
||||
token = token,
|
||||
username = username,
|
||||
issuedAt = LocalDateTime.now(),
|
||||
expiresAt = expiresAt
|
||||
)
|
||||
return tokenRepository.save(newToken)
|
||||
return tokenRepository.save(newToken).awaitSingle()
|
||||
}
|
||||
|
||||
fun getToken(token: String): Mono<Token> {
|
||||
|
||||
@@ -29,6 +29,10 @@ class UserService(val userRepo: UserRepo) {
|
||||
.switchIfEmpty(Mono.error(Exception("User not found"))) // Обрабатываем случай, когда пользователь не найден
|
||||
}
|
||||
|
||||
suspend fun getUserByTelegramId(telegramId: Long): User? {
|
||||
return userRepo.findByTgId(telegramId.toString()).awaitSingleOrNull()
|
||||
}
|
||||
|
||||
|
||||
@Cacheable("users", key = "#username")
|
||||
suspend fun getByUserNameWoPass(username: String): User {
|
||||
|
||||
@@ -48,8 +48,10 @@ class WishListService(
|
||||
val match = match(
|
||||
Criteria.where("spaceDetails._id").`is`(ObjectId(spaceId))
|
||||
.andOperator(
|
||||
Criteria.where("ownerDetails._id").`is`(ObjectId(user.id))
|
||||
.orOperator(Criteria.where("isPrivate").`is`(false))
|
||||
Criteria().orOperator(
|
||||
Criteria.where("ownerDetails._id").`is`(ObjectId(user.id)),
|
||||
Criteria.where("isPrivate").`is`(false)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -60,13 +62,14 @@ class WishListService(
|
||||
}.toList()
|
||||
}
|
||||
|
||||
suspend fun getList(listId: String): WishList {
|
||||
suspend fun getList(listId: String, isActive: Boolean = true): WishList {
|
||||
val user = userService.getByUserNameWoPass(
|
||||
ReactiveSecurityContextHolder.getContext().awaitSingle().authentication.name
|
||||
)
|
||||
val match = match(
|
||||
Criteria.where("_id").`is`(ObjectId(listId))
|
||||
.andOperator(Criteria.where("ownerDetails._id").`is`(ObjectId(user.id)))
|
||||
// .andOperator(Criteria.where("ownerDetails._id").`is`(ObjectId(user.id)))
|
||||
// .andOperator(Criteria.where("isActive").`is`(isActive))
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package space.luminic.budgerapp.services
|
||||
|
||||
import com.mongodb.client.model.Filters.and
|
||||
import com.mongodb.client.model.Filters.eq
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.bson.Document
|
||||
import org.bson.types.ObjectId
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation.*
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
import org.springframework.data.mongodb.core.aggregation.LookupOperation
|
||||
import org.springframework.data.mongodb.core.aggregation.VariableOperators
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.data.mongodb.core.query.Criteria.expr
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.budgerapp.mappers.WishListMapper
|
||||
import space.luminic.budgerapp.models.NotFoundException
|
||||
@@ -27,7 +34,32 @@ class WishlistExternalService(
|
||||
val unwindSpace = unwind("spaceDetails")
|
||||
val lookupOwner = lookup("users", "owner.\$id", "_id", "ownerDetails")
|
||||
val unwindOwner = unwind("ownerDetails")
|
||||
val lookupItems = lookup("wishlistItems", "items.\$id", "_id", "itemsDetails")
|
||||
// val lookupItems = lookup("wishlistItems", "items.\$id", "_id", "itemsDetails")
|
||||
// Расширенный lookup с фильтром isActive == true
|
||||
val lookupItems = AggregationOperation { context ->
|
||||
Document(
|
||||
"\$lookup", Document()
|
||||
.append("from", "wishlistItems")
|
||||
.append("let", Document("itemIds", "\$items.\$id"))
|
||||
.append("pipeline", listOf(
|
||||
Document(
|
||||
"\$match", Document(
|
||||
"\$expr", Document(
|
||||
"\$and", listOf(
|
||||
Document("\$in", listOf("\$_id", Document("\$map", Document()
|
||||
.append("input", "\$\$itemIds")
|
||||
.append("as", "id")
|
||||
.append("in", Document("\$toObjectId", "\$\$id"))
|
||||
))),
|
||||
Document("\$eq", listOf("\$isActive", true))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
.append("as", "itemsDetails")
|
||||
)
|
||||
}
|
||||
val match = match(
|
||||
Criteria.where("_id").`is`(ObjectId(wishListId))
|
||||
)
|
||||
|
||||
@@ -24,14 +24,6 @@ class JWTUtil(private val tokenService: TokenService) {
|
||||
.setExpiration(expireAt) // 10 дней
|
||||
.signWith(key)
|
||||
.compact()
|
||||
tokenService.saveToken(
|
||||
token,
|
||||
username,
|
||||
LocalDateTime.from(
|
||||
expireAt.toInstant().atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime()
|
||||
)
|
||||
)
|
||||
return token
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class ScheduledTasks(private val subscriptionService: SubscriptionService,
|
||||
private val logger = LoggerFactory.getLogger(ScheduledTasks::class.java)
|
||||
|
||||
@Scheduled(cron = "0 30 19 * * *")
|
||||
fun sendNotificationOfMoneyFilling() {
|
||||
suspend fun sendNotificationOfMoneyFilling() {
|
||||
subscriptionService.sendToAll(
|
||||
PushMessage(
|
||||
title = "Время заполнять траты!🤑",
|
||||
@@ -23,13 +23,6 @@ class ScheduledTasks(private val subscriptionService: SubscriptionService,
|
||||
badge = "/apple-touch-icon.png",
|
||||
url = "https://luminic.space/transactions/create"
|
||||
)
|
||||
).doOnNext { responses ->
|
||||
responses.forEach { response ->
|
||||
logger.info("Уведомление отправлено: $response")
|
||||
}
|
||||
}.subscribe(
|
||||
{ logger.info("Все уведомления отправлены.") },
|
||||
{ error -> logger.error("Ошибка при отправке уведомлений: ${error.message}", error) }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,4 @@ spring.datasource.url=jdbc:postgresql://213.183.51.243/familybudget_app
|
||||
spring.datasource.username=familybudget_app
|
||||
spring.datasource.password=FB1q2w3e4r!
|
||||
|
||||
|
||||
# ??????? JDBC
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# ????????? Hibernate
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
|
||||
telegram.bot.token = 6972242509:AAGyXuL3T-BNE4XMoo_qvtaYxw_SuiS_dDs
|
||||
@@ -1,27 +1,9 @@
|
||||
spring.application.name=budger-app
|
||||
|
||||
spring.data.mongodb.host=77.222.32.64
|
||||
spring.data.mongodb.port=27017
|
||||
spring.data.mongodb.database=budger-app
|
||||
spring.data.mongodb.uri=mongodb://budger-app:BA1q2w3e4r!@luminic.space:27017/budger-app?authSource=admin&minPoolSize=10&maxPoolSize=100
|
||||
#spring.data.mongodb.username=budger-app
|
||||
#spring.data.mongodb.password=BA1q2w3e4r!
|
||||
#spring.data.mongodb.authentication-database=admin
|
||||
|
||||
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
|
||||
|
||||
|
||||
spring.datasource.url=jdbc:postgresql://213.183.51.243/familybudget_app
|
||||
spring.datasource.username=familybudget_app
|
||||
spring.datasource.password=FB1q2w3e4r!
|
||||
|
||||
|
||||
# ??????? JDBC
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# ????????? Hibernate
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
|
||||
telegram.bot.token=6972242509:AAGyXuL3T-BNE4XMoo_qvtaYxw_SuiS_dDs
|
||||
nlp.address=http://127.0.0.1:8000
|
||||
@@ -16,5 +16,5 @@ logging.level.org.springframework.data.mongodb.code = DEBUG
|
||||
#management.endpoint.metrics.access=read_only
|
||||
|
||||
|
||||
|
||||
|
||||
telegram.bot.token = 6662300972:AAFXjk_h0AUCy4bORC12UcdXbYnh2QSVKAY
|
||||
nlp.address=https://nlp.luminic.space
|
||||
|
||||
@@ -7,8 +7,6 @@ spring.webflux.base-path=/api
|
||||
spring.profiles.active=prod
|
||||
spring.main.web-application-type=reactive
|
||||
|
||||
|
||||
|
||||
logging.level.org.springframework.web=INFO
|
||||
logging.level.org.springframework.data = INFO
|
||||
logging.level.org.springframework.data.mongodb.core.ReactiveMongoTemplate=INFO
|
||||
@@ -34,3 +32,6 @@ management.endpoints.web.exposure.include=*
|
||||
# Enable Prometheus metrics export
|
||||
management.prometheus.metrics.export.enabled=true
|
||||
|
||||
telegram.bot.username = expenses_diary_bot
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user