+ bot + notifications
This commit is contained in:
@@ -56,6 +56,8 @@ 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")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,17 +2,20 @@ package space.luminic.budgerapp
|
||||
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
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.TelegramBotProperties
|
||||
import java.util.*
|
||||
|
||||
@SpringBootApplication(scanBasePackages = ["space.luminic.budgerapp"])
|
||||
@EnableCaching
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@EnableConfigurationProperties(TelegramBotProperties::class)
|
||||
@EnableMongoRepositories(basePackages = ["space.luminic.budgerapp.repos"])
|
||||
class BudgerAppApplication
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -10,10 +10,7 @@ import org.springframework.web.bind.annotation.*
|
||||
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.time.LocalDate
|
||||
|
||||
@RestController
|
||||
@@ -22,7 +19,8 @@ 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)
|
||||
@@ -57,13 +55,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)
|
||||
}
|
||||
|
||||
@@ -74,13 +75,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)
|
||||
}
|
||||
|
||||
@@ -90,7 +93,8 @@ 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()
|
||||
|
||||
}
|
||||
@@ -98,7 +102,8 @@ class SpaceController(
|
||||
@GetMapping("/{spaceId}/budgets/{id}")
|
||||
suspend fun getBudget(@PathVariable spaceId: String, @PathVariable id: String): BudgetDTO? {
|
||||
log.info("Getting budget for spaceId=$spaceId, id=$id")
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return financialService.getBudget(spaceId, id)
|
||||
}
|
||||
|
||||
@@ -107,8 +112,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
|
||||
)
|
||||
@@ -116,7 +123,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)
|
||||
|
||||
}
|
||||
@@ -128,7 +136,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)
|
||||
}
|
||||
|
||||
@@ -174,7 +183,8 @@ class SpaceController(
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
@@ -184,14 +194,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)
|
||||
}
|
||||
@@ -206,7 +218,8 @@ class SpaceController(
|
||||
@RequestParam("sort") sortBy: String = "name",
|
||||
@RequestParam("direction") direction: String = "ASC"
|
||||
): List<Category> {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return categoryService.getCategories(spaceId, type, sortBy, direction).awaitSingleOrNull().orEmpty()
|
||||
}
|
||||
|
||||
@@ -219,7 +232,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()
|
||||
}
|
||||
|
||||
@@ -229,33 +243,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)
|
||||
}
|
||||
|
||||
@@ -271,7 +290,8 @@ class SpaceController(
|
||||
|
||||
@GetMapping("/{spaceId}/recurrents")
|
||||
suspend fun getRecurrents(@PathVariable spaceId: String): List<Recurrent> {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
return recurrentService.getRecurrents(spaceId)
|
||||
|
||||
}
|
||||
@@ -279,13 +299,15 @@ class SpaceController(
|
||||
|
||||
@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")
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -295,14 +317,16 @@ class SpaceController(
|
||||
@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}")
|
||||
suspend fun deleteRecurrent(@PathVariable spaceId: String, @PathVariable id: String) {
|
||||
spaceService.isValidRequest(spaceId)
|
||||
val user = authService.getSecurityUser()
|
||||
spaceService.isValidRequest(spaceId, user)
|
||||
recurrentService.deleteRecurrent(id).awaitSingle()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
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(),
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
|
||||
}
|
||||
@@ -4,10 +4,10 @@ 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
|
||||
@@ -28,6 +28,16 @@ class AuthService(
|
||||
) {
|
||||
private val passwordEncoder = BCryptPasswordEncoder()
|
||||
|
||||
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("Пользователь не найден")
|
||||
|
||||
308
src/main/kotlin/space/luminic/budgerapp/services/BotService.kt
Normal file
308
src/main/kotlin/space/luminic/budgerapp/services/BotService.kt
Normal file
@@ -0,0 +1,308 @@
|
||||
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.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,
|
||||
) : TelegramLongPollingBot(telegramBotProperties.token) {
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
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, "Введите сумму и комментарий когда будете готовы.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 categories =
|
||||
categoriesService.getCategories(
|
||||
"67af3c0f652da946a7dd9931",
|
||||
"EXPENSE",
|
||||
sortBy = "name",
|
||||
direction = "ASC"
|
||||
)
|
||||
.awaitSingle()
|
||||
val keyboard = InlineKeyboardMarkup()
|
||||
val buttonLines = mutableListOf<MutableList<InlineKeyboardButton>>()
|
||||
categories.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 backButton = InlineKeyboardButton.builder().text("Отмена").callbackData("cancel").build()
|
||||
|
||||
buttonLines.add(mutableListOf(backButton))
|
||||
keyboard.keyboard = buttonLines
|
||||
|
||||
val message = SendMessage()
|
||||
message.chatId = chatId
|
||||
val msg = "Выберите категорию"
|
||||
message.text = msg
|
||||
message.replyMarkup = keyboard
|
||||
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"
|
||||
)
|
||||
.awaitSingle()
|
||||
.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() }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,10 +17,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
|
||||
|
||||
@@ -133,7 +130,7 @@ 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()) {
|
||||
@@ -154,7 +151,7 @@ class CategoryService(
|
||||
|
||||
transactions.map { transaction ->
|
||||
transaction.category = otherCategory
|
||||
financialService.editTransaction(transaction)
|
||||
financialService.editTransaction(transaction, author)
|
||||
}
|
||||
}
|
||||
val budgets = financialService.findProjectedBudgets(
|
||||
|
||||
@@ -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,8 @@ class FinancialService(
|
||||
val reactiveMongoTemplate: ReactiveMongoTemplate,
|
||||
private val categoryRepo: CategoryRepo,
|
||||
val transactionsMapper: TransactionsMapper,
|
||||
val budgetMapper: BudgetMapper
|
||||
val budgetMapper: BudgetMapper,
|
||||
private val subscriptionService: SubscriptionService
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(FinancialService::class.java)
|
||||
|
||||
@@ -846,25 +845,45 @@ class FinancialService(
|
||||
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
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 }) {
|
||||
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/"
|
||||
)
|
||||
)
|
||||
}
|
||||
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) {
|
||||
@@ -878,8 +897,57 @@ class FinancialService(
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -37,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)
|
||||
|
||||
// Проверяем доступ пользователя к пространству
|
||||
|
||||
@@ -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
|
||||
)
|
||||
logger.info("Уведомление успешно отправлено на endpoint: $endpoint")
|
||||
|
||||
} catch (e: Exception) {
|
||||
logger.error("Ошибка при отправке уведомления на endpoint $endpoint: ${e.message}")
|
||||
throw e
|
||||
}
|
||||
.doOnSuccess {
|
||||
logger.info("Уведомление успешно отправлено на endpoint: $endpoint")
|
||||
}
|
||||
.doOnError { e ->
|
||||
logger.error("Ошибка при отправке уведомления на endpoint $endpoint: ${e.message}")
|
||||
}
|
||||
.onErrorResume { e ->
|
||||
Mono.error(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 ->
|
||||
sub.isActive = false
|
||||
subscriptionRepo.save(sub).then(Mono.empty())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
sub.isActive = false
|
||||
subscriptionRepo.save(sub).awaitSingle()
|
||||
}
|
||||
.collectList() // Собираем результаты в список
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,12 @@ 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")
|
||||
|
||||
@Cacheable("users", key = "#username")
|
||||
suspend fun getByUserNameWoPass(username: String): User {
|
||||
return userRepo.findByUsernameWOPassword(username).awaitSingleOrNull()
|
||||
?: throw NotFoundException("User with username: $username not found")
|
||||
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,4 @@ 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,8 @@
|
||||
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
|
||||
|
||||
@@ -16,5 +16,5 @@ logging.level.org.springframework.data.mongodb.code = DEBUG
|
||||
#management.endpoint.metrics.access=read_only
|
||||
|
||||
|
||||
|
||||
telegram.bot.token = 6662300972:AAFXjk_h0AUCy4bORC12UcdXbYnh2QSVKAY
|
||||
|
||||
|
||||
@@ -34,3 +34,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