diff --git a/build.gradle.kts b/build.gradle.kts index 4018b17..9827a7c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/src/main/kotlin/space/luminic/finance/api/AuthController.kt b/src/main/kotlin/space/luminic/finance/api/AuthController.kt index 195ff5d..4d16e64 100644 --- a/src/main/kotlin/space/luminic/finance/api/AuthController.kt +++ b/src/main/kotlin/space/luminic/finance/api/AuthController.kt @@ -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 { + 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") + } + } } diff --git a/src/main/kotlin/space/luminic/finance/api/TransactionController.kt b/src/main/kotlin/space/luminic/finance/api/TransactionController.kt index 3a20233..0943296 100644 --- a/src/main/kotlin/space/luminic/finance/api/TransactionController.kt +++ b/src/main/kotlin/space/luminic/finance/api/TransactionController.kt @@ -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 { + 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() diff --git a/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt b/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt index 3e6d5f8..cd0a014 100644 --- a/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt +++ b/src/main/kotlin/space/luminic/finance/dtos/UserDTO.kt @@ -48,6 +48,9 @@ data class UserDTO( ) + data class GoogleAuthDTO( + val code: String + ) } diff --git a/src/main/kotlin/space/luminic/finance/models/User.kt b/src/main/kotlin/space/luminic/finance/models/User.kt index 8a92984..76db0d8 100644 --- a/src/main/kotlin/space/luminic/finance/models/User.kt +++ b/src/main/kotlin/space/luminic/finance/models/User.kt @@ -20,6 +20,7 @@ data class User( @CreatedDate val createdAt: Instant? = null, @LastModifiedDate var updatedAt: Instant? = null, var roles: List = listOf(), + var googleRefreshToken: String? = null, ) diff --git a/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt b/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt index 1e9d65e..18a89bb 100644 --- a/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt +++ b/src/main/kotlin/space/luminic/finance/repos/SpaceRepo.kt @@ -6,6 +6,7 @@ import java.time.LocalDateTime @Repository interface SpaceRepo { + fun findAll(): List fun findSpacesForScheduling(lastRun: LocalDateTime): List fun findSpacesAvailableForUser(userId: Int): List fun findSpaceById(id: Int, userId: Int): Space? diff --git a/src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt index 9bcf431..83c9b18 100644 --- a/src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt +++ b/src/main/kotlin/space/luminic/finance/repos/SpaceRepoImpl.kt @@ -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 { + 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 { val sql = """ select s.id as s_id, diff --git a/src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt b/src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt index 5511388..bce5b11 100644 --- a/src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt +++ b/src/main/kotlin/space/luminic/finance/repos/UserRepoImpl.kt @@ -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)?.toList() ?: emptyList() + roles = (rs.getArray("roles")?.array as? Array)?.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) { diff --git a/src/main/kotlin/space/luminic/finance/services/NotificationServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/NotificationServiceImpl.kt index 375258d..f3e3955 100644 --- a/src/main/kotlin/space/luminic/finance/services/NotificationServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/NotificationServiceImpl.kt @@ -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") } diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionService.kt b/src/main/kotlin/space/luminic/finance/services/TransactionService.kt index 7a92489..ce9f7fb 100644 --- a/src/main/kotlin/space/luminic/finance/services/TransactionService.kt +++ b/src/main/kotlin/space/luminic/finance/services/TransactionService.kt @@ -37,6 +37,11 @@ interface TransactionService { filter: TransactionsFilter ): List + 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, createdById: Int?) diff --git a/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt b/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt index 986e2a1..a446c5e 100644 --- a/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt +++ b/src/main/kotlin/space/luminic/finance/services/TransactionServiceImpl.kt @@ -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, diff --git a/src/main/kotlin/space/luminic/finance/services/UserService.kt b/src/main/kotlin/space/luminic/finance/services/UserService.kt index 8abf154..b5d395d 100644 --- a/src/main/kotlin/space/luminic/finance/services/UserService.kt +++ b/src/main/kotlin/space/luminic/finance/services/UserService.kt @@ -40,4 +40,7 @@ class UserService(val userRepo: UserRepo) { return userRepo.findAll() } + fun update(user: User): User { + return userRepo.update(user) + } } \ No newline at end of file diff --git a/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt b/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt index fcd3b3b..5633125 100644 --- a/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt +++ b/src/main/kotlin/space/luminic/finance/services/gpt/CategorizeService.kt @@ -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? = 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"), ) ) ), diff --git a/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt b/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt index 7eb7cb0..17905dc 100644 --- a/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt +++ b/src/main/kotlin/space/luminic/finance/services/gpt/DeepSeekCategorizationService.kt @@ -34,10 +34,10 @@ class DeepSeekCategorizationService( override fun suggestCategory(tx: Transaction, categories: List): 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. diff --git a/src/main/kotlin/space/luminic/finance/services/gpt/QwenCategorizationService.kt b/src/main/kotlin/space/luminic/finance/services/gpt/QwenCategorizationService.kt index 26f69ae..6354b60 100644 --- a/src/main/kotlin/space/luminic/finance/services/gpt/QwenCategorizationService.kt +++ b/src/main/kotlin/space/luminic/finance/services/gpt/QwenCategorizationService.kt @@ -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): 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): 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 – Другое (вероятность)". Ответ должен быть кратким, одной строкой, без дополнительных пояснений. diff --git a/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt b/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt index 6361a28..45cf546 100644 --- a/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt +++ b/src/main/kotlin/space/luminic/finance/services/telegram/BotService.kt @@ -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 сумма комментарий\n\n Первой обязательно должна быть сумма!", + text = "Успешно!\n\nМы готовы принимать Ваши транзакции.\n\nПросто пишите их в формате:\n\n сумма комментарий (расходы)\n\n +сумма комментарий (пополнения)\n\n Первой обязательно должна быть сумма!", parseMode = ParseMode.HTML, replyMarkup = buildMenu(callbackQuery.from.id) ) diff --git a/src/main/kotlin/space/luminic/finance/services/telegram/TransactionService.kt b/src/main/kotlin/space/luminic/finance/services/telegram/TransactionService.kt index 8693318..d8a56ab 100644 --- a/src/main/kotlin/space/luminic/finance/services/telegram/TransactionService.kt +++ b/src/main/kotlin/space/luminic/finance/services/telegram/TransactionService.kt @@ -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 } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 542a0e6..e3253ef 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 \ No newline at end of file +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 \ No newline at end of file