+ google drive
This commit is contained in:
@@ -35,6 +35,14 @@ repositories {
|
||||
|
||||
|
||||
dependencies {
|
||||
// Excel
|
||||
implementation("org.apache.poi:poi-ooxml:5.2.3")
|
||||
|
||||
// Google API
|
||||
implementation("com.google.api-client:google-api-client:2.4.0")
|
||||
implementation("com.google.apis:google-api-services-drive:v3-rev20240123-2.0.0")
|
||||
implementation("com.google.auth:google-auth-library-oauth2-http:1.23.0")
|
||||
|
||||
// Spring
|
||||
implementation("org.springframework.boot:spring-boot-starter-cache")
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
|
||||
@@ -13,6 +13,8 @@ import space.luminic.finance.dtos.UserDTO.RegisterUserDTO
|
||||
import space.luminic.finance.mappers.UserMapper.toDto
|
||||
import space.luminic.finance.mappers.UserMapper.toTelegramMap
|
||||
import space.luminic.finance.services.AuthService
|
||||
import space.luminic.finance.services.GoogleDriveService
|
||||
import space.luminic.finance.services.UserService
|
||||
import java.net.URLDecoder
|
||||
import java.security.MessageDigest
|
||||
import java.time.Instant
|
||||
@@ -23,6 +25,8 @@ import javax.crypto.spec.SecretKeySpec
|
||||
@RequestMapping("/auth")
|
||||
class AuthController(
|
||||
private val authService: AuthService,
|
||||
private val googleDriveService: GoogleDriveService,
|
||||
private val userService: UserService,
|
||||
@Value("\${telegram.bot.token}") private val botToken: String
|
||||
) {
|
||||
|
||||
@@ -183,4 +187,18 @@ class AuthController(
|
||||
|
||||
return authService.getSecurityUser().toDto()
|
||||
}
|
||||
|
||||
@PostMapping("/me/google-drive")
|
||||
fun linkGoogleDrive(@RequestBody request: UserDTO.GoogleAuthDTO): Map<String, String> {
|
||||
val user = authService.getSecurityUser()
|
||||
val refreshToken = googleDriveService.exchangeCodeForRefreshToken(request.code)
|
||||
|
||||
if (refreshToken.isNotEmpty()) {
|
||||
user.googleRefreshToken = refreshToken
|
||||
userService.update(user)
|
||||
return mapOf("status" to "success", "message" to "Google Drive linked successfully")
|
||||
} else {
|
||||
throw IllegalArgumentException("Failed to exchange code for refresh token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package space.luminic.finance.api
|
||||
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||
import org.springframework.web.bind.annotation.*
|
||||
@@ -26,6 +29,18 @@ class TransactionController (
|
||||
return transactionService.getTransactions(spaceId, filter).map { it.toDto() }
|
||||
}
|
||||
|
||||
@PostMapping("/_export")
|
||||
fun exportExcel(@PathVariable spaceId: Int, @RequestBody filter: TransactionService.TransactionsFilter): ResponseEntity<ByteArray> {
|
||||
val excelBytes = transactionService.generateExcel(spaceId, filter)
|
||||
val headers = HttpHeaders()
|
||||
headers.contentType = MediaType.APPLICATION_OCTET_STREAM
|
||||
headers.setContentDispositionFormData("attachment", "transactions.xlsx")
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(excelBytes)
|
||||
}
|
||||
|
||||
@GetMapping("/{transactionId}")
|
||||
fun getTransaction(@PathVariable spaceId: Int, @PathVariable transactionId: Int): TransactionDTO {
|
||||
return transactionService.getTransaction(spaceId, transactionId).toDto()
|
||||
|
||||
@@ -48,6 +48,9 @@ data class UserDTO(
|
||||
)
|
||||
|
||||
|
||||
data class GoogleAuthDTO(
|
||||
val code: String
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ data class User(
|
||||
@CreatedDate val createdAt: Instant? = null,
|
||||
@LastModifiedDate var updatedAt: Instant? = null,
|
||||
var roles: List<String> = listOf(),
|
||||
var googleRefreshToken: String? = null,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.time.LocalDateTime
|
||||
|
||||
@Repository
|
||||
interface SpaceRepo {
|
||||
fun findAll(): List<Space>
|
||||
fun findSpacesForScheduling(lastRun: LocalDateTime): List<Space>
|
||||
fun findSpacesAvailableForUser(userId: Int): List<Space>
|
||||
fun findSpaceById(id: Int, userId: Int): Space?
|
||||
|
||||
@@ -23,6 +23,7 @@ class SpaceRepoImpl(
|
||||
username = rs.getString("username"),
|
||||
firstName = rs.getString("first_name"),
|
||||
password = rs.getString("password"),
|
||||
googleRefreshToken = rs.getString("google_refresh_token")
|
||||
),
|
||||
participants = userRepo.findParticipantsBySpace(rs.getInt("id")).toSet(),
|
||||
isDeleted = rs.getBoolean("is_deleted"),
|
||||
@@ -85,6 +86,21 @@ class SpaceRepoImpl(
|
||||
return spaceMap.map { it.value }
|
||||
}
|
||||
|
||||
override fun findAll(): List<Space> {
|
||||
val sql = """
|
||||
select s.*,
|
||||
u.id as owner_id,
|
||||
u.username as username,
|
||||
u.first_name as first_name,
|
||||
u.password as password,
|
||||
u.google_refresh_token as google_refresh_token
|
||||
from finance.spaces s
|
||||
join finance.users u on u.id = s.owner_id
|
||||
where s.is_deleted = false
|
||||
""".trimIndent()
|
||||
return jdbcTemplate.query(sql, spaceRowMapper())
|
||||
}
|
||||
|
||||
override fun findSpacesForScheduling(lastRun: LocalDateTime): List<Space> {
|
||||
val sql = """
|
||||
select s.id as s_id,
|
||||
|
||||
@@ -23,7 +23,8 @@ class UserRepoImpl(
|
||||
regDate = rs.getDate("reg_date").toLocalDate(),
|
||||
createdAt = rs.getTimestamp("created_at").toInstant(),
|
||||
updatedAt = rs.getTimestamp("updated_at").toInstant(),
|
||||
roles = (rs.getArray("roles")?.array as? Array<String>)?.toList() ?: emptyList()
|
||||
roles = (rs.getArray("roles")?.array as? Array<String>)?.toList() ?: emptyList(),
|
||||
googleRefreshToken = rs.getString("google_refresh_token")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ class UserRepoImpl(
|
||||
|
||||
override fun create(user: User): User {
|
||||
val sql =
|
||||
"insert into finance.users(username, first_name, tg_id, tg_user_name, photo_url, password, is_active, reg_date) values (:username, :firstname, :tg_id, :tg_user_name, :photo_url, :password, :isActive, :regDate) returning ID"
|
||||
"insert into finance.users(username, first_name, tg_id, tg_user_name, photo_url, password, is_active, reg_date, google_refresh_token) values (:username, :firstname, :tg_id, :tg_user_name, :photo_url, :password, :isActive, :regDate, :googleRefreshToken) returning ID"
|
||||
val params = mapOf(
|
||||
"username" to user.username,
|
||||
"firstname" to user.firstName,
|
||||
@@ -68,6 +69,7 @@ class UserRepoImpl(
|
||||
"password" to user.password,
|
||||
"isActive" to user.isActive,
|
||||
"regDate" to user.regDate,
|
||||
"googleRefreshToken" to user.googleRefreshToken
|
||||
)
|
||||
val savedId = jdbcTemplate.queryForObject(sql, params, Int::class.java)
|
||||
user.id = savedId
|
||||
@@ -75,7 +77,33 @@ class UserRepoImpl(
|
||||
}
|
||||
|
||||
override fun update(user: User): User {
|
||||
TODO("Not yet implemented")
|
||||
val sql = """
|
||||
update finance.users
|
||||
set username = :username,
|
||||
first_name = :firstname,
|
||||
tg_id = :tg_id,
|
||||
tg_user_name = :tg_user_name,
|
||||
photo_url = :photo_url,
|
||||
password = :password,
|
||||
is_active = :isActive,
|
||||
reg_date = :regDate,
|
||||
google_refresh_token = :googleRefreshToken
|
||||
where id = :id
|
||||
""".trimIndent()
|
||||
val params = mapOf(
|
||||
"id" to user.id,
|
||||
"username" to user.username,
|
||||
"firstname" to user.firstName,
|
||||
"tg_id" to user.tgId,
|
||||
"tg_user_name" to user.tgUserName,
|
||||
"photo_url" to user.photoUrl,
|
||||
"password" to user.password,
|
||||
"isActive" to user.isActive,
|
||||
"regDate" to user.regDate,
|
||||
"googleRefreshToken" to user.googleRefreshToken
|
||||
)
|
||||
jdbcTemplate.update(sql, params)
|
||||
return user
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import space.luminic.finance.models.Transaction
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class NotificationServiceImpl(private val userService: UserService, private val bot: Bot,) : NotificationService {
|
||||
class NotificationServiceImpl(private val userService: UserService, private val bot: Bot) : NotificationService {
|
||||
private val logger = LoggerFactory.getLogger(this.javaClass)
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class NotificationServiceImpl(private val userService: UserService, private val
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
"Открыть в WebApp",
|
||||
WebAppInfo("https://app.luminic.space/transactions")
|
||||
WebAppInfo("https://app.luminic.space/transactions?mode=from_bot")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -113,10 +113,11 @@ class NotificationServiceImpl(private val userService: UserService, private val
|
||||
}"
|
||||
)
|
||||
}
|
||||
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)) }
|
||||
|
||||
if (changes.isNotEmpty()) {
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ interface TransactionService {
|
||||
filter: TransactionsFilter
|
||||
): List<Transaction>
|
||||
|
||||
fun generateExcel(
|
||||
spaceId: Int,
|
||||
filter: TransactionsFilter
|
||||
): ByteArray
|
||||
|
||||
fun getTransaction(spaceId: Int, transactionId: Int): Transaction
|
||||
fun createTransaction(spaceId: Int, transaction: TransactionDTO.CreateTransactionDTO): Int
|
||||
fun batchCreate(spaceId: Int, transactions: List<TransactionDTO.CreateTransactionDTO>, createdById: Int?)
|
||||
|
||||
@@ -11,8 +11,10 @@ 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.io.ByteArrayOutputStream
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
|
||||
@Service
|
||||
class TransactionServiceImpl(
|
||||
@@ -34,6 +36,45 @@ class TransactionServiceImpl(
|
||||
return transactions
|
||||
}
|
||||
|
||||
override fun generateExcel(
|
||||
spaceId: Int,
|
||||
filter: TransactionService.TransactionsFilter
|
||||
): ByteArray {
|
||||
val transactions = getTransactions(spaceId, filter)
|
||||
val workbook = XSSFWorkbook()
|
||||
val sheet = workbook.createSheet("Transactions")
|
||||
|
||||
val headerRow = sheet.createRow(0)
|
||||
headerRow.createCell(0).setCellValue("ID")
|
||||
headerRow.createCell(1).setCellValue("Date")
|
||||
headerRow.createCell(2).setCellValue("Type")
|
||||
headerRow.createCell(3).setCellValue("Kind")
|
||||
headerRow.createCell(4).setCellValue("Category")
|
||||
headerRow.createCell(5).setCellValue("Amount")
|
||||
headerRow.createCell(6).setCellValue("Comment")
|
||||
|
||||
var rowNum = 1
|
||||
for (transaction in transactions) {
|
||||
val row = sheet.createRow(rowNum++)
|
||||
row.createCell(0).setCellValue(transaction.id?.toDouble() ?: 0.0)
|
||||
row.createCell(1).setCellValue(transaction.date.toString())
|
||||
row.createCell(2).setCellValue(transaction.type.displayName)
|
||||
row.createCell(3).setCellValue(transaction.kind.displayName)
|
||||
row.createCell(4).setCellValue(transaction.category?.name ?: "")
|
||||
row.createCell(5).setCellValue(transaction.amount.toDouble())
|
||||
row.createCell(6).setCellValue(transaction.comment)
|
||||
}
|
||||
|
||||
for (i in 0..6) {
|
||||
sheet.autoSizeColumn(i)
|
||||
}
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
workbook.write(outputStream)
|
||||
workbook.close()
|
||||
return outputStream.toByteArray()
|
||||
}
|
||||
|
||||
override fun getTransaction(
|
||||
spaceId: Int,
|
||||
transactionId: Int
|
||||
@@ -112,7 +153,7 @@ class TransactionServiceImpl(
|
||||
val newKind = if (
|
||||
!existingTransaction.isDone &&
|
||||
transaction.isDone &&
|
||||
(today.isAfter(transaction.date) || today.isEqual(transaction.date))
|
||||
(today.isAfter(transaction.date) || today.isEqual(transaction.date) || today.isBefore(transaction.date))
|
||||
) Transaction.TransactionKind.INSTANT else transaction.kind
|
||||
val updatedTransaction = Transaction(
|
||||
id = existingTransaction.id,
|
||||
|
||||
@@ -40,4 +40,7 @@ class UserService(val userRepo: UserRepo) {
|
||||
return userRepo.findAll()
|
||||
}
|
||||
|
||||
fun update(user: User): User {
|
||||
return userRepo.update(user)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.github.kotlintelegrambot.types.TelegramBotResult
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.models.Category
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.repos.CategoryRepo
|
||||
import space.luminic.finance.repos.TransactionRepo
|
||||
@@ -38,9 +39,18 @@ class CategorizeService(
|
||||
runCatching {
|
||||
val tx = transactionRepo.findBySpaceIdAndId(job.spaceId, job.txId)
|
||||
?: throw IllegalArgumentException("Transaction ${job.txId} not found")
|
||||
|
||||
val categories = categoriesRepo.findBySpaceId(job.spaceId).filter {
|
||||
it.type == when (tx.type) {
|
||||
Transaction.TransactionType.INCOME -> Category.CategoryType.INCOME
|
||||
Transaction.TransactionType.EXPENSE -> Category.CategoryType.EXPENSE
|
||||
else -> it.type
|
||||
}
|
||||
}
|
||||
|
||||
val res = gpt.suggestCategory(
|
||||
tx,
|
||||
categoriesRepo.findBySpaceId(job.spaceId)
|
||||
categories
|
||||
) // тут твой вызов GPT
|
||||
var message: TelegramBotResult<Message>? = null
|
||||
|
||||
@@ -60,7 +70,7 @@ class CategorizeService(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
"Открыть в WebApp",
|
||||
WebAppInfo("https://app.luminic.space/transactions/${tx.id}/edit")
|
||||
WebAppInfo("https://app.luminic.space/transactions/${tx.id}/edit?mode=from_bot")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -85,7 +95,7 @@ class CategorizeService(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
"Открыть в WebApp",
|
||||
WebAppInfo("https://app.luminic.space/transactions/${tx.id}/edit")
|
||||
WebAppInfo("https://app.luminic.space/transactions/${tx.id}/edit?mode=from_bot"),
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -131,7 +141,7 @@ class CategorizeService(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
"Открыть в WebApp",
|
||||
WebAppInfo("https://app.luminic.space/transactions/${tx.id}/edit")
|
||||
WebAppInfo("https://app.luminic.space/transactions/${tx.id}/edit?mode=from_bot"),
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
@@ -34,10 +34,10 @@ class DeepSeekCategorizationService(
|
||||
override fun suggestCategory(tx: Transaction, categories: List<Category>): CategorySuggestion {
|
||||
val catList = categories.joinToString("\n") { "- ${it.id}: ${it.name}" }
|
||||
val txInfo = """
|
||||
{ \"amount\": ${tx.amount}, \"comment\": \"${tx.comment}\", \"date\":${tx.date}\" }
|
||||
{ \"type\": \"${tx.type.displayName}\", \"amount\": ${tx.amount}, \"comment\": \"${tx.comment}\", \"date\":${tx.date}\" }
|
||||
""".trimIndent()
|
||||
val prompt = """
|
||||
Пользователь имеет следующие категории:
|
||||
Пользователь имеет следующие категории (${tx.type.displayName}):
|
||||
$catList
|
||||
|
||||
Задача:
|
||||
@@ -88,7 +88,7 @@ class DeepSeekCategorizationService(
|
||||
override fun analyzePeriod(startDate: LocalDate, endDate: LocalDate, dashboardData: DashboardData): String {
|
||||
mapper.registerModule(JavaTimeModule());
|
||||
if (dashboardData.totalIncome == 0 && dashboardData.totalExpense == 0){
|
||||
return "Начните записывать траты и поступления и мы начнем их анализировать!"
|
||||
return """{ "common": "Начните записывать траты и поступления и мы начнем их анализировать!", "categoryAnalysis": "", "keyInsights": "", "recommendations": "" }"""
|
||||
} else {
|
||||
var prompt = """
|
||||
You are a personal finance analyst.
|
||||
|
||||
@@ -22,19 +22,19 @@ class QwenCategorizationService(
|
||||
private val mapper = jacksonObjectMapper()
|
||||
private val client = OkHttpClient()
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
override fun suggestCategory(tx: Transaction, categories: List<Category>): CategorySuggestion {
|
||||
val catList = categories.joinToString("\n") { "- ${it.id}: ${it.name}" }
|
||||
val txInfo = """
|
||||
{ \"type\": \"${tx.type.displayName}\", \"amount\": ${tx.amount}, \"comment\": \"${tx.comment}\", \"date\":${tx.date}\" }
|
||||
""".trimIndent()
|
||||
val prompt = """
|
||||
Пользователь имеет следующие категории (${tx.type.displayName}):
|
||||
$catList
|
||||
|
||||
override fun suggestCategory(tx: Transaction, categories: List<Category>): CategorySuggestion {
|
||||
val catList = categories.joinToString("\n") { "- ${it.id}: ${it.name}" }
|
||||
val txInfo = """
|
||||
{ \"amount\": ${tx.amount}, \"comment\": \"${tx.comment}\", \"date\":${tx.date}\" }
|
||||
""".trimIndent()
|
||||
val prompt = """
|
||||
Пользователь имеет следующие категории:
|
||||
$catList
|
||||
|
||||
Задача:
|
||||
1. Определите наиболее подходящую категорию из списка выше для транзакции пользователя.
|
||||
2. Верните ответ в формате: "ID категории – имя категории (вероятность)", например "3 – Продукты (0.87)".
|
||||
Задача:
|
||||
1. Определите наиболее подходящую категорию из списка выше для транзакции пользователя.
|
||||
2. Верните ответ в формате: "ID категории – имя категории (вероятность)", например "3 – Продукты (0.87)".
|
||||
...
|
||||
3. Если ни одна категория из списка не подходит, верните: "0 – Другое (вероятность)".
|
||||
|
||||
Ответ должен быть кратким, одной строкой, без дополнительных пояснений.
|
||||
|
||||
@@ -110,7 +110,7 @@ class BotService(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
text = "Открыть WebApp",
|
||||
webApp = WebAppInfo(url = "https://app.luminic.space")
|
||||
webApp = WebAppInfo(url = "https://app.luminic.space?mode=from_bot")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -131,11 +131,18 @@ class BotService(
|
||||
when (state?.state) {
|
||||
State.StateCode.SPACE_SELECTED -> {
|
||||
try {
|
||||
val parts = message.text!!.trim().split(" ", limit = 2)
|
||||
if (parts.isEmpty()) {
|
||||
var text = message.text!!.trim()
|
||||
var type = Transaction.TransactionType.EXPENSE
|
||||
if (text.startsWith("+")) {
|
||||
type = Transaction.TransactionType.INCOME
|
||||
text = text.substring(1).trim()
|
||||
}
|
||||
|
||||
val parts = text.split(" ", limit = 2)
|
||||
if (parts.isEmpty() || parts[0].isEmpty()) {
|
||||
bot.sendMessage(
|
||||
chatId = ChatId.fromId(message.chat.id),
|
||||
text = "Введите сумму и комментарий, например: `250 обед`",
|
||||
text = "Введите сумму и комментарий, например: `250 обед` или `+1000 зп` ",
|
||||
parseMode = ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
@@ -161,7 +168,7 @@ class BotService(
|
||||
?: throw IllegalArgumentException("selected space is empty"),
|
||||
user.id!!,
|
||||
TransactionDTO.CreateTransactionDTO(
|
||||
Transaction.TransactionType.EXPENSE,
|
||||
type,
|
||||
Transaction.TransactionKind.INSTANT,
|
||||
comment = comment,
|
||||
amount = amount.toBigDecimal(),
|
||||
@@ -203,7 +210,7 @@ class BotService(
|
||||
bot.editMessageText(
|
||||
chatId = ChatId.Companion.fromId(callbackQuery.message!!.chat.id),
|
||||
messageId = callbackQuery.message!!.messageId,
|
||||
text = "Успешно!\n\nМы готовы принимать Ваши транзакции.\n\nПросто пишите их в формате:\n\n <i>сумма комментарий</i>\n\n <b>Первой обязательно должна быть сумма!</b>",
|
||||
text = "Успешно!\n\nМы готовы принимать Ваши транзакции.\n\nПросто пишите их в формате:\n\n <i>сумма комментарий</i> (расходы)\n\n <i>+сумма комментарий</i> (пополнения)\n\n <b>Первой обязательно должна быть сумма!</b>",
|
||||
parseMode = ParseMode.HTML,
|
||||
replyMarkup = buildMenu(callbackQuery.from.id)
|
||||
)
|
||||
|
||||
@@ -2,7 +2,8 @@ package space.luminic.finance.services.telegram
|
||||
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
|
||||
interface TransactionService {
|
||||
interface
|
||||
TransactionService {
|
||||
|
||||
fun createTransaction(spaceId: Int, userId: Int, transaction: TransactionDTO.CreateTransactionDTO, chatId: Long, messageId: Long ): Int
|
||||
}
|
||||
@@ -35,4 +35,7 @@ spring.flyway.schemas=finance
|
||||
spring.jpa.properties.hibernate.default_schema=finance
|
||||
spring.jpa.properties.hibernate.default_batch_fetch_size=50
|
||||
qwen.api_key=sk-991942d15b424cc89513498bb2946045
|
||||
ds.api_key=sk-b5949728e79747f08af0a1d65bc6a7a2
|
||||
ds.api_key=sk-b5949728e79747f08af0a1d65bc6a7a2\ngoogle.client-id=\ngoogle.client-secret=\ngoogle.redirect-uri=
|
||||
google.client-id=112729998586-q39qsptu67lqeej0356m01e1ghptuajk.apps.googleusercontent.com
|
||||
google.client-secret=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
|
||||
google.redirect-uri=https://app.luminic.space
|
||||
Reference in New Issue
Block a user