+ notifications
This commit is contained in:
@@ -38,7 +38,8 @@ class SpaceRepoImpl(
|
||||
owner = User(
|
||||
rs.getInt("s_owner_id"),
|
||||
rs.getString("s_owner_username"),
|
||||
rs.getString("s_owner_firstname")
|
||||
rs.getString("s_owner_firstname"),
|
||||
tgId = rs.getLong("s_owner_tg_id"),
|
||||
),
|
||||
participant = User(rs.getInt("sp_uid"), rs.getString("sp_username"), rs.getString("sp_first_name")),
|
||||
createdAt = rs.getTimestamp("s_created_at").toInstant(),
|
||||
@@ -93,6 +94,7 @@ class SpaceRepoImpl(
|
||||
s.owner_id as s_owner_id,
|
||||
ou.username as s_owner_username,
|
||||
ou.first_name as s_owner_firstname,
|
||||
ou.tg_id as s_owner_tg_id,
|
||||
sp.participants_id as sp_uid,
|
||||
u.username as sp_username,
|
||||
u.first_name as sp_first_name,
|
||||
@@ -114,7 +116,7 @@ class SpaceRepoImpl(
|
||||
where (s.owner_id = :user_id
|
||||
or sp.participants_id = :user_id)
|
||||
and s.is_deleted = false
|
||||
group by s.id, ou.username, ou.first_name, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||
group by s.id, ou.username, ou.first_name, ou.tg_id, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||
uau.username, uau.first_name;
|
||||
""".trimMargin()
|
||||
val params = mapOf(
|
||||
@@ -133,6 +135,7 @@ class SpaceRepoImpl(
|
||||
s.owner_id as s_owner_id,
|
||||
ou.username as s_owner_username,
|
||||
ou.first_name as s_owner_firstname,
|
||||
ou.tg_id as s_owner_tg_id,
|
||||
sp.participants_id as sp_uid,
|
||||
u.username as sp_username,
|
||||
u.first_name as sp_first_name,
|
||||
@@ -154,7 +157,7 @@ from finance.spaces s
|
||||
where (s.owner_id = :user_id
|
||||
or sp.participants_id = :user_id)
|
||||
and s.is_deleted = false and s.id = :spaceId
|
||||
group by s.id, ou.username, ou.first_name, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||
group by s.id, ou.username, ou.first_name, ou.tg_id, sp.participants_id, u.username, u.first_name, cau.username, cau.first_name,
|
||||
uau.username, uau.first_name;
|
||||
""".trimMargin()
|
||||
val params = mapOf(
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import com.github.kotlintelegrambot.entities.ReplyMarkup
|
||||
import com.github.kotlintelegrambot.entities.inputmedia.MediaGroup
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.models.Transaction
|
||||
|
||||
interface NotificationService {
|
||||
fun sendDailyReminder()
|
||||
fun sendTXNotification(action: TxActionType, space: Space, userId: Int, tx: Transaction, tx2: Transaction? = null)
|
||||
fun sendTextMessage(chatId: Long, message: String, replyMarkup: ReplyMarkup? = null)
|
||||
fun sendMediaGroup(chatId: Long, group: MediaGroup)
|
||||
}
|
||||
|
||||
enum class TxActionType {
|
||||
CREATE,
|
||||
UPDATE,
|
||||
DELETE,
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import com.github.kotlintelegrambot.Bot
|
||||
import com.github.kotlintelegrambot.entities.ChatId
|
||||
import com.github.kotlintelegrambot.entities.InlineKeyboardMarkup
|
||||
import com.github.kotlintelegrambot.entities.ReplyMarkup
|
||||
import com.github.kotlintelegrambot.entities.inputmedia.MediaGroup
|
||||
import com.github.kotlintelegrambot.entities.keyboard.InlineKeyboardButton
|
||||
import com.github.kotlintelegrambot.entities.keyboard.WebAppInfo
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.models.Transaction
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class NotificationServiceImpl(private val userService: UserService, private val bot: Bot,) : NotificationService {
|
||||
private val logger = LoggerFactory.getLogger(this.javaClass)
|
||||
|
||||
|
||||
private fun createWebAppButton(spaceId: Int? = null, txId: Int? = null): InlineKeyboardMarkup =
|
||||
spaceId?.let { spaceId ->
|
||||
txId?.let { txId ->
|
||||
InlineKeyboardMarkup.create(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
"Открыть в WebApp",
|
||||
WebAppInfo("https://app.luminic.space/transactions/${txId}/edit?mode=from_bot&space=${spaceId}")
|
||||
)
|
||||
)
|
||||
)
|
||||
} ?: InlineKeyboardMarkup.create(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
"Открыть в WebApp",
|
||||
WebAppInfo("https://app.luminic.space/transactions?mode=from_bot&space=${spaceId}")
|
||||
)
|
||||
)
|
||||
)
|
||||
} ?: InlineKeyboardMarkup.create(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
"Открыть в WebApp",
|
||||
WebAppInfo("https://app.luminic.space/transactions")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
override fun sendDailyReminder() {
|
||||
val text = "🤑 Время заполнять траты!"
|
||||
val users = userService.getUsers()
|
||||
|
||||
for (user in users) {
|
||||
user.tgId?.let {
|
||||
sendTextMessage(it, text, createWebAppButton())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendTXNotification(
|
||||
action: TxActionType,
|
||||
space: Space,
|
||||
userId: Int,
|
||||
tx: Transaction,
|
||||
tx2: Transaction?
|
||||
) {
|
||||
val user = userService.getById(userId)
|
||||
when (action) {
|
||||
TxActionType.CREATE -> {
|
||||
val text = "${user.firstName} создал транзакцию ${tx.comment} c суммой ${tx.amount} и датой ${tx.date}"
|
||||
space.owner.tgId?.let { sendTextMessage(it, text, createWebAppButton(space.id, tx.id)) }
|
||||
|
||||
}
|
||||
|
||||
TxActionType.UPDATE -> {
|
||||
tx2?.let { tx2 ->
|
||||
val changes = mutableListOf<String>()
|
||||
if (tx.type != tx2.type) {
|
||||
changes.add("Тип: ${tx.type.name} → ${tx2.type.name}")
|
||||
}
|
||||
if (tx.kind != tx2.kind) {
|
||||
changes.add("Вид: ${tx.kind.name} → ${tx2.kind.name}")
|
||||
}
|
||||
if (tx.category != tx2.category) {
|
||||
tx.category?.let { oldCategory ->
|
||||
tx2.category?.let { newCategory ->
|
||||
if (oldCategory.id != newCategory.id) {
|
||||
changes.add("Категория: ${oldCategory.name} → ${newCategory.name}")
|
||||
}
|
||||
} ?: changes.add("Удалена категория. Прежняя: ${oldCategory.name}")
|
||||
} ?: {
|
||||
tx2.category?.let { newCategory ->
|
||||
changes.add("Установлена новая категория ${newCategory.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tx.comment != tx2.comment) {
|
||||
changes.add("Комментарий: ${tx.comment} → ${tx2.comment}")
|
||||
}
|
||||
if (tx.amount != tx2.amount) {
|
||||
changes.add("Сумма: ${tx.amount} → ${tx2.amount}")
|
||||
}
|
||||
if (tx.date.toEpochDay() != tx2.date.toEpochDay()) {
|
||||
changes.add(
|
||||
"Сумма: ${
|
||||
tx.date.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"))
|
||||
} → ${
|
||||
tx2.date.format(
|
||||
DateTimeFormatter.ofPattern("dd.MM.yyyy")
|
||||
)
|
||||
}"
|
||||
)
|
||||
}
|
||||
var text = "${user.firstName} обновил транзакцию ${tx.comment}\n\n"
|
||||
text += changes.joinToString("\n") { it }
|
||||
space.owner.tgId?.let { sendTextMessage(it, text, createWebAppButton(space.id, tx.id)) }
|
||||
|
||||
} ?: logger.warn("No tx2 provided when update")
|
||||
}
|
||||
|
||||
TxActionType.DELETE -> {
|
||||
val text = "${user.firstName} удалил транзакцию ${tx.comment} c суммой ${tx.amount} и датой ${tx.date}"
|
||||
space.owner.tgId?.let { sendTextMessage(it, text, createWebAppButton(space.id, tx.id)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun sendTextMessage(
|
||||
chatId: Long,
|
||||
message: String,
|
||||
replyMarkup: ReplyMarkup?
|
||||
) {
|
||||
bot.sendMessage(ChatId.fromId(chatId), message, replyMarkup = replyMarkup)
|
||||
}
|
||||
|
||||
override fun sendMediaGroup(
|
||||
chatId: Long,
|
||||
group: MediaGroup
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ import org.springframework.stereotype.Service
|
||||
@EnableScheduling
|
||||
@Service
|
||||
class Scheduler(
|
||||
private val recurrentOperationService: RecurrentOperationService
|
||||
private val recurrentOperationService: RecurrentOperationService,
|
||||
private val notificationService: NotificationService
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(Scheduler::class.java)
|
||||
|
||||
@@ -17,4 +18,10 @@ class Scheduler(
|
||||
log.info("Creating recurrent after 13 month")
|
||||
recurrentOperationService.createRecurrentTransactions()
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 30 19 * * *")
|
||||
fun sendDailyReminders() {
|
||||
log.info("Sending daily reminders")
|
||||
notificationService.sendDailyReminder()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package space.luminic.finance.services
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.repos.TransactionRepo
|
||||
import space.luminic.finance.services.gpt.CategorizeService
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class TransactionServiceImpl(
|
||||
@@ -14,7 +20,11 @@ class TransactionServiceImpl(
|
||||
private val transactionRepo: TransactionRepo,
|
||||
private val authService: AuthService,
|
||||
private val categorizeService: CategorizeService,
|
||||
private val notificationService: NotificationService,
|
||||
) : TransactionService {
|
||||
private val logger = LoggerFactory.getLogger(this.javaClass)
|
||||
private val serviceScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
override fun getTransactions(
|
||||
spaceId: Int,
|
||||
filter: TransactionService.TransactionsFilter,
|
||||
@@ -53,7 +63,17 @@ class TransactionServiceImpl(
|
||||
date = transaction.date,
|
||||
recurrentId = transaction.recurrentId,
|
||||
)
|
||||
return transactionRepo.create(transaction, userId)
|
||||
val createdTx = transactionRepo.create(transaction, userId)
|
||||
serviceScope.launch {
|
||||
runCatching {
|
||||
if (space.owner.id != userId) {
|
||||
notificationService.sendTXNotification(TxActionType.CREATE, space, userId, transaction)
|
||||
}
|
||||
}.onFailure {
|
||||
logger.error("Error while creating transaction", it)
|
||||
}
|
||||
}
|
||||
return createdTx
|
||||
}
|
||||
|
||||
override fun batchCreate(spaceId: Int, transactions: List<TransactionDTO.CreateTransactionDTO>, createdById: Int?) {
|
||||
@@ -85,6 +105,7 @@ class TransactionServiceImpl(
|
||||
transactionId: Int,
|
||||
transaction: TransactionDTO.UpdateTransactionDTO
|
||||
): Int {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceService.getSpace(spaceId, null)
|
||||
val existingTransaction = getTransaction(space.id!!, transactionId)
|
||||
val newCategory = transaction.categoryId?.let { categoryService.getCategory(spaceId, it) }
|
||||
@@ -109,14 +130,34 @@ class TransactionServiceImpl(
|
||||
if ((existingTransaction.category == null && updatedTransaction.category != null) || (existingTransaction.category?.id != updatedTransaction.category?.id)) {
|
||||
categorizeService.notifyThatCategorySelected(updatedTransaction)
|
||||
}
|
||||
val updatedTx = transactionRepo.update(updatedTransaction)
|
||||
serviceScope.launch {
|
||||
runCatching {
|
||||
|
||||
return transactionRepo.update(updatedTransaction)
|
||||
notificationService.sendTXNotification(
|
||||
TxActionType.UPDATE,
|
||||
space,
|
||||
userId,
|
||||
existingTransaction,
|
||||
updatedTransaction
|
||||
)
|
||||
}.onFailure {
|
||||
logger.error("Error while send transaction update notification", it)
|
||||
}
|
||||
}
|
||||
return updatedTx
|
||||
}
|
||||
|
||||
override fun deleteTransaction(spaceId: Int, transactionId: Int) {
|
||||
val userId = authService.getSecurityUserId()
|
||||
val space = spaceService.getSpace(spaceId, null)
|
||||
getTransaction(space.id!!, transactionId)
|
||||
val tx = getTransaction(space.id!!, transactionId)
|
||||
transactionRepo.delete(transactionId)
|
||||
serviceScope.launch {
|
||||
runCatching {
|
||||
notificationService.sendTXNotification(TxActionType.DELETE, space, userId, tx)
|
||||
}.onFailure { logger.error("Error while transaction delete notification", it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteByRecurrentId(spaceId: Int, recurrentId: Int) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.github.kotlintelegrambot.logging.LogLevel
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
@@ -30,10 +31,11 @@ import java.time.LocalDate
|
||||
@Service
|
||||
class BotService(
|
||||
@Value("\${telegram.bot.token}") private val botToken: String,
|
||||
@Value("\${spring.profiles.active}") private val profile: String,
|
||||
private val userService: UserService,
|
||||
@Qualifier("spaceServiceTelegram") private val spaceService: SpaceService,
|
||||
private val botRepo: BotRepo,
|
||||
@Qualifier("transactionsServiceTelegram") private val transactionService: TransactionService
|
||||
@Lazy @Qualifier("transactionsServiceTelegram") private val transactionService: TransactionService
|
||||
) {
|
||||
|
||||
|
||||
@@ -113,13 +115,13 @@ class BotService(
|
||||
)
|
||||
)
|
||||
|
||||
return InlineKeyboardMarkup.Companion.create(keyboard)
|
||||
return InlineKeyboardMarkup.create(keyboard)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun bot(): Bot {
|
||||
val bot = com.github.kotlintelegrambot.bot {
|
||||
logLevel = LogLevel.All()
|
||||
logLevel = if (profile == "proc") LogLevel.None else LogLevel.All()
|
||||
token = botToken
|
||||
dispatch {
|
||||
message(Filter.Text) {
|
||||
|
||||
@@ -4,5 +4,5 @@ import space.luminic.finance.models.Space
|
||||
|
||||
interface SpaceService {
|
||||
fun getSpaces(userId: Int): List<Space>
|
||||
fun getSpace(spaceId: Int, userId: Int): Space?
|
||||
fun getSpace(spaceId: Int, userId: Int): Space
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class SpaceServiceImpl(
|
||||
return spaces
|
||||
}
|
||||
|
||||
override fun getSpace(spaceId: Int, userId: Int): Space? {
|
||||
override fun getSpace(spaceId: Int, userId: Int): Space {
|
||||
val space =
|
||||
spaceRepo.findSpaceById(spaceId, userId) ?: throw NotFoundException("Space with id $spaceId not found")
|
||||
return space
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
package space.luminic.finance.services.telegram
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.repos.TransactionRepo
|
||||
import space.luminic.finance.services.CategoryServiceImpl
|
||||
import space.luminic.finance.services.NotificationService
|
||||
import space.luminic.finance.services.TxActionType
|
||||
|
||||
@Service("transactionsServiceTelegram")
|
||||
class TransactionsServiceImpl(
|
||||
private val transactionRepo: TransactionRepo,
|
||||
@Qualifier("spaceServiceTelegram") private val spaceService: SpaceService,
|
||||
private val categoryService: CategoryServiceImpl
|
||||
private val categoryService: CategoryServiceImpl,
|
||||
private val notificationService: NotificationService
|
||||
) : TransactionService {
|
||||
private val logger = LoggerFactory.getLogger(this.javaClass)
|
||||
private val serviceScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
|
||||
override fun createTransaction(
|
||||
spaceId: Int,
|
||||
@@ -35,9 +46,17 @@ class TransactionsServiceImpl(
|
||||
tgChatId = chatId,
|
||||
tgMessageId = messageId,
|
||||
)
|
||||
serviceScope.launch {
|
||||
runCatching {
|
||||
if (space.owner.id != userId) {
|
||||
notificationService.sendTXNotification(TxActionType.CREATE, space, userId, transaction)
|
||||
}
|
||||
}.onFailure {
|
||||
logger.error("Error while transaction notification", it)
|
||||
}
|
||||
}
|
||||
return transactionRepo.create(transaction, userId)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user