From 87b96cc81a06d2ac7260f2f119aed441d85589bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vitor=20Mar=C3=A7al?= Date: Tue, 23 Jan 2024 19:41:57 -0300 Subject: [PATCH] feat: enable delete chat (#107) --- README.md | 38 ++++----- .../bucket_service/BucketService.kt | 1 + .../kotlin/dev/marcal/chatvault/model/Chat.kt | 4 + .../chatvault/repository/ChatRepository.kt | 1 + .../bucket_service/BucketServiceImpl.kt | 74 ++++++++++------- .../src/main/resources/appservice.properties | 4 +- .../email/config/EmailHandlerConfig.kt | 16 ++-- .../email/listener/EmailMessageHandler.kt | 6 +- .../email/src/main/resources/email.properties | 16 ++-- .../persistence/ChatRepositoryImpl.kt | 11 +++ .../repository/MessageCrudRepository.kt | 2 + .../marcal/chatvault/web/ChatController.kt | 81 ++++++++++--------- .../marcal/chatvault/service/ChatDeleter.kt | 5 ++ .../chatvault/usecase/ChatDeleterUseCase.kt | 19 +++++ frontend/components/ChatConfig.vue | 9 ++- frontend/components/ChatDeleter.vue | 56 +++++++++++++ frontend/components/IconTrash.vue | 14 ++++ frontend/nuxt.config.ts | 1 + frontend/pages/index.vue | 1 + 19 files changed, 254 insertions(+), 105 deletions(-) create mode 100644 backend/service/src/main/kotlin/dev/marcal/chatvault/service/ChatDeleter.kt create mode 100644 backend/usecase/src/main/kotlin/dev/marcal/chatvault/usecase/ChatDeleterUseCase.kt create mode 100644 frontend/components/ChatDeleter.vue create mode 100644 frontend/components/IconTrash.vue diff --git a/README.md b/README.md index 06fdc84..7f43bc6 100644 --- a/README.md +++ b/README.md @@ -58,25 +58,25 @@ There are docker image packages on github. You can download the latest image wit For docker, the variables must be in upper case and where is "." it must be "_": `some.environment.variable` is like `SOME_ENVIRONMENT_VARIABLE` in docker -| Environment variables | obs | example | -|-------------------------------------------|--------------------------------|----------------------------------------------------| -| Database | required | | -| spring.datasource.url | required | jdbc:postgresql://database_host:5432/database_name | -| spring.datasource.username | required | user | -| spring.datasource.password | required | secret | -| -------------------------- | -------------------------- | --------- | -| Email import | feat not required | | -| app.email.enabled | not required | true | -| app.email.host | required to feat | imap.server.com | -| app.email.password | required to feat | secret | -| app.email.port | required to feat | 993 | -| app.email.username | required to feat | someuser | -| app.email.debug | not required | true | -| -------------------------- | | -------------------------- | -| chatvault.host | not required | https://somehost.com ,http://localhost:3000 | -| spring.servlet.multipart.max-file-size | not required | 500MB | -| spring.servlet.multipart.max-request-size | not required | 500MB | -| chatvault.msgparser.dateformat | not required but recommended | dd/MM/yyyy HH:mm | +| Environment variables | obs | example | +|-------------------------------------------|------------------------------|----------------------------------------------------| +| Database | required | | +| spring.datasource.url | required | jdbc:postgresql://database_host:5432/database_name | +| spring.datasource.username | required | user | +| spring.datasource.password | required | secret | +| -------------------------- | -------------------------- | --------- | +| Email import | feat not required | | +| chatvault.email.enabled | not required | true | +| chatvault.email.host | required to feat | imap.server.com | +| chatvault.email.password | required to feat | secret | +| chatvault.email.port | required to feat | 993 | +| chatvault.email.username | required to feat | someuser | +| chatvault.email.debug | not required | true | +| -------------------------- | | -------------------------- | +| chatvault.host | not required | https://somehost.com ,http://localhost:3000 | +| spring.servlet.multipart.max-file-size | not required | 500MB | +| spring.servlet.multipart.max-request-size | not required | 500MB | +| chatvault.msgparser.dateformat | not required but recommended | dd/MM/yyyy HH:mm | ------ * If not defined chatvault.msgparser.dateformat, the application will not be able to resolve ambiguities in certain situations \ No newline at end of file diff --git a/backend/domain/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketService.kt b/backend/domain/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketService.kt index f817735..fd9c4fc 100644 --- a/backend/domain/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketService.kt +++ b/backend/domain/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketService.kt @@ -11,4 +11,5 @@ interface BucketService { fun saveToImportDir(bucketFile: BucketFile) fun saveTextToBucket(bucketFile: BucketFile, messages: Sequence) fun loadBucketAsZip(path: String): Resource + fun delete(bucketFile: BucketFile) } \ No newline at end of file diff --git a/backend/domain/src/main/kotlin/dev/marcal/chatvault/model/Chat.kt b/backend/domain/src/main/kotlin/dev/marcal/chatvault/model/Chat.kt index c58cdea..314c636 100644 --- a/backend/domain/src/main/kotlin/dev/marcal/chatvault/model/Chat.kt +++ b/backend/domain/src/main/kotlin/dev/marcal/chatvault/model/Chat.kt @@ -53,4 +53,8 @@ data class Bucket( fun withPath(path: String): Bucket { return this.copy(path = this.path + path) } + + fun toBucketFile(): BucketFile { + return BucketFile( fileName = "/", address = this) + } } diff --git a/backend/domain/src/main/kotlin/dev/marcal/chatvault/repository/ChatRepository.kt b/backend/domain/src/main/kotlin/dev/marcal/chatvault/repository/ChatRepository.kt index 27de2a1..a16f185 100644 --- a/backend/domain/src/main/kotlin/dev/marcal/chatvault/repository/ChatRepository.kt +++ b/backend/domain/src/main/kotlin/dev/marcal/chatvault/repository/ChatRepository.kt @@ -30,4 +30,5 @@ interface ChatRepository { fun setChatNameByChatId(chatId: Long, chatName: String) fun findAttachmentMessageIdsByChatId(chatId: Long): Sequence fun findLastMessageByChatId(chatId: Long): Message? + fun deleteChat(chatId: Long) } \ No newline at end of file diff --git a/backend/infra/app-service/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketServiceImpl.kt b/backend/infra/app-service/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketServiceImpl.kt index 10aa40e..4e8f832 100644 --- a/backend/infra/app-service/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketServiceImpl.kt +++ b/backend/infra/app-service/src/main/kotlin/dev/marcal/chatvault/app_service/bucket_service/BucketServiceImpl.kt @@ -22,8 +22,8 @@ import java.nio.file.StandardCopyOption @Service @PropertySource("classpath:appservice.properties") class BucketServiceImpl( - @Value("\${app.bucket.root}") val bucketRootPath: String, - @Value("\${app.bucket.import}") val bucketImportPath: String + @Value("\${chatvault.bucket.root}") val bucketRootPath: String, + @Value("\${chatvault.bucket.import}") val bucketImportPath: String ) : BucketService { private val logger = LoggerFactory.getLogger(this.javaClass) @@ -87,41 +87,59 @@ class BucketServiceImpl( override fun loadBucketAsZip(path: String): Resource { try { return File(bucketRootPath).getDirectoriesWithContentAndZipFiles() - .first { path == it.name } - .let { dir -> - DirectoryZipper.zip(dir).let { resource -> - InputStreamResource(object : FileInputStream(resource.file) { - @Throws(IOException::class) - override fun close() { - super.close() - val isDeleted: Boolean = resource.file.delete() - logger.info( - "export:'{}':" + if (isDeleted) "deleted" else "preserved", resource.file.name - ) - } - }) + .first { path == it.name } + .let { dir -> + DirectoryZipper.zip(dir).let { resource -> + InputStreamResource(object : FileInputStream(resource.file) { + @Throws(IOException::class) + override fun close() { + super.close() + val isDeleted: Boolean = resource.file.delete() + logger.info( + "export:'{}':" + if (isDeleted) "deleted" else "preserved", resource.file.name + ) + } + }) + } } - } } catch (e: Exception) { throw BucketServiceException(message = "Fail to zip bucket", throwable = e) } } + override fun delete(bucketFile: BucketFile) { + val file = bucketFile.file(bucketRootPath) + logger.info("start to delete bucket at: $file") + + + if (!file.exists()) { + return + } + + Files.walk(file.toPath()) + .sorted(reverseOrder()) + .forEach { + logger.info("item to delete: {}", it) + Files.delete(it) + } + + } + override fun zipPendingImports(chatName: String?): Sequence { try { return File(bucketImportPath) - .getDirectoriesWithContentAndZipFiles() - .asSequence() - .filter { chatName == null || chatName == it.name } - .map { chatGroupDir -> - if (chatGroupDir.name.endsWith(".zip")) { - UrlResource(chatGroupDir.toURI()) - } else { - DirectoryZipper.zipAndDeleteSource(chatGroupDir) - } + .getDirectoriesWithContentAndZipFiles() + .asSequence() + .filter { chatName == null || chatName == it.name } + .map { chatGroupDir -> + if (chatGroupDir.name.endsWith(".zip")) { + UrlResource(chatGroupDir.toURI()) + } else { + DirectoryZipper.zipAndDeleteSource(chatGroupDir) + } - } + } } catch (e: Exception) { throw BucketServiceException(message = "Fail to zip pending imports", throwable = e) } @@ -130,8 +148,8 @@ class BucketServiceImpl( override fun deleteZipImported(filename: String) { val toDelete = BucketFile( - fileName = filename, - address = Bucket(path = "/") + fileName = filename, + address = Bucket(path = "/") ).file(root = bucketImportPath) toDelete.delete() } diff --git a/backend/infra/app-service/src/main/resources/appservice.properties b/backend/infra/app-service/src/main/resources/appservice.properties index cc7af15..6d9ff79 100644 --- a/backend/infra/app-service/src/main/resources/appservice.properties +++ b/backend/infra/app-service/src/main/resources/appservice.properties @@ -1,2 +1,2 @@ -app.bucket.root=/opt/chatvault/archive -app.bucket.import=/opt/chatvault/import \ No newline at end of file +chatvault.bucket.root=/opt/chatvault/archive +chatvault.bucket.import=/opt/chatvault/import \ No newline at end of file diff --git a/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/config/EmailHandlerConfig.kt b/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/config/EmailHandlerConfig.kt index fb694aa..ca330ce 100644 --- a/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/config/EmailHandlerConfig.kt +++ b/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/config/EmailHandlerConfig.kt @@ -16,15 +16,15 @@ import org.springframework.integration.support.PropertiesBuilder @Configuration @PropertySource("classpath:email.properties") -@ConditionalOnProperty(prefix = "app.email", name = ["enabled"], havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "chatvault.email", name = ["enabled"], havingValue = "true", matchIfMissing = true) class EmailHandlerConfig( - @Value("\${app.email.host}") private val host: String, - @Value("\${app.email.port}") private val port: String, - @Value("\${app.email.username}") private val username: String, - @Value("\${app.email.password}") private val password: String, - @Value("\${app.email.fixed-delay-mlss}") private val fixedDelay: Long, - @Value("\${app.email.debug}") private val emailDebug: Boolean, - @Value("\${app.email.subject-starts-with}") private val subjectStartsWithList: List, + @Value("\${chatvault.email.host}") private val host: String, + @Value("\${chatvault.email.port}") private val port: String, + @Value("\${chatvault.email.username}") private val username: String, + @Value("\${chatvault.email.password}") private val password: String, + @Value("\${chatvault.email.fixed-delay-mlss}") private val fixedDelay: Long, + @Value("\${chatvault.email.debug}") private val emailDebug: Boolean, + @Value("\${chatvault.email.subject-starts-with}") private val subjectStartsWithList: List, ) { private val logger = LoggerFactory.getLogger(this::class.java) diff --git a/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/listener/EmailMessageHandler.kt b/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/listener/EmailMessageHandler.kt index ab2594d..2304a29 100644 --- a/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/listener/EmailMessageHandler.kt +++ b/backend/infra/email/src/main/kotlin/dev/marcal/chatvault/email/listener/EmailMessageHandler.kt @@ -16,11 +16,11 @@ import java.io.InputStream import java.time.LocalDateTime @Component -@ConditionalOnProperty(prefix = "app.email", name = ["enabled"], havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "chatvault.email", name = ["enabled"], havingValue = "true", matchIfMissing = true) class EmailMessageHandler( private val chatFileImporter: ChatFileImporter, - @Value("\${app.email.subject-starts-with}") private val subjectStartsWithList: List, - @Value("\${app.email.debug}") private val emailDebug: Boolean, + @Value("\${chatvault.email.subject-starts-with}") private val subjectStartsWithList: List, + @Value("\${chatvault.email.debug}") private val emailDebug: Boolean, private val bucketDiskImporter: BucketDiskImporter ) { diff --git a/backend/infra/email/src/main/resources/email.properties b/backend/infra/email/src/main/resources/email.properties index 4a7ac46..2a145c9 100644 --- a/backend/infra/email/src/main/resources/email.properties +++ b/backend/infra/email/src/main/resources/email.properties @@ -1,8 +1,8 @@ -app.email.host= -app.email.port= -app.email.username= -app.email.password= -app.email.fixed-delay-mlss=10000 -app.email.subject-starts-with=chat-vault,Conversa do WhatsApp com -app.email.enabled=false -app.email.debug=false \ No newline at end of file +chatvault.email.host= +chatvault.email.port= +chatvault.email.username= +chatvault.email.password= +chatvault.email.fixed-delay-mlss=10000 +chatvault.email.subject-starts-with=chat-vault,Conversa do WhatsApp com +chatvault.email.enabled=false +chatvault.email.debug=false \ No newline at end of file diff --git a/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/ChatRepositoryImpl.kt b/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/ChatRepositoryImpl.kt index c411c53..455e0c8 100644 --- a/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/ChatRepositoryImpl.kt +++ b/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/ChatRepositoryImpl.kt @@ -10,6 +10,7 @@ import dev.marcal.chatvault.persistence.repository.ChatCrudRepository import dev.marcal.chatvault.persistence.repository.EventSourceCrudRepository import dev.marcal.chatvault.persistence.repository.MessageCrudRepository import dev.marcal.chatvault.repository.ChatRepository +import org.slf4j.LoggerFactory import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort @@ -25,6 +26,8 @@ class ChatRepositoryImpl( private val objectMapper: ObjectMapper ) : ChatRepository { + private val logger = LoggerFactory.getLogger(this.javaClass) + @Transactional override fun saveNewMessages(payload: MessagePayload, eventSource: Boolean) { if (eventSource) { @@ -109,6 +112,14 @@ class ChatRepositoryImpl( return messageCrudRepository.findTopByChatIdOrderByIdDesc(chatId)?.toMessageDomain() } + @Transactional + override fun deleteChat(chatId: Long) { + logger.info("start to remove messages from $chatId") + messageCrudRepository.deleteAllByChatId(chatId) + logger.info("start to remove chat from $chatId") + chatCrudRepository.deleteById(chatId) + } + override fun countChatMessages(chatId: Long): Long { return messageCrudRepository.countByChatId(chatId) } diff --git a/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/repository/MessageCrudRepository.kt b/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/repository/MessageCrudRepository.kt index b69286f..6fb9860 100644 --- a/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/repository/MessageCrudRepository.kt +++ b/backend/infra/persistence/src/main/kotlin/dev/marcal/chatvault/persistence/repository/MessageCrudRepository.kt @@ -19,4 +19,6 @@ interface MessageCrudRepository : JpaRepository { @Query("SELECT new dev.marcal.chatvault.persistence.dto.AttachmentInfoDTO(m.id, m.attachmentName) FROM MessageEntity m WHERE m.chatId = :chatId AND m.attachmentPath IS NOT NULL") fun findMessageIdByChatIdAndAttachmentExists(chatId: Long): List + + fun deleteAllByChatId(chatId: Long) } \ No newline at end of file diff --git a/backend/infra/web/src/main/kotlin/dev/marcal/chatvault/web/ChatController.kt b/backend/infra/web/src/main/kotlin/dev/marcal/chatvault/web/ChatController.kt index 7065708..ff5b5f0 100644 --- a/backend/infra/web/src/main/kotlin/dev/marcal/chatvault/web/ChatController.kt +++ b/backend/infra/web/src/main/kotlin/dev/marcal/chatvault/web/ChatController.kt @@ -18,12 +18,13 @@ import java.util.concurrent.TimeUnit @RestController @RequestMapping("/api/chats") class ChatController( - private val chatLister: ChatLister, - private val messageFinderByChatId: MessageFinderByChatId, - private val attachmentFinder: AttachmentFinder, - private val chatNameUpdater: ChatNameUpdater, - private val attachmentInfoFinderByChatId: AttachmentInfoFinderByChatId, - private val profileImageManager: ProfileImageManager + private val chatLister: ChatLister, + private val messageFinderByChatId: MessageFinderByChatId, + private val attachmentFinder: AttachmentFinder, + private val chatNameUpdater: ChatNameUpdater, + private val attachmentInfoFinderByChatId: AttachmentInfoFinderByChatId, + private val profileImageManager: ProfileImageManager, + private val chatDeleter: ChatDeleter ) { @GetMapping @@ -33,22 +34,30 @@ class ChatController( @GetMapping("{chatId}") fun findChatMessages( - @PathVariable("chatId") chatId: Long, - @SortDefault( - sort = ["createdAt", "id"], - direction = Sort.Direction.DESC - ) pageable: Pageable + @PathVariable("chatId") chatId: Long, + @SortDefault( + sort = ["createdAt", "id"], + direction = Sort.Direction.DESC + ) pageable: Pageable ): Page { return messageFinderByChatId.execute( - chatId = chatId, - pageable = pageable + chatId = chatId, + pageable = pageable ) } + @DeleteMapping("{chatId}") + fun deleteChatAndAssets( + @PathVariable("chatId") chatId: Long, + ) { + return chatDeleter.execute(chatId) + + } + @PatchMapping("{chatId}/chatName/{chatName}") fun update( - @PathVariable chatId: Long, - @PathVariable chatName: String, + @PathVariable chatId: Long, + @PathVariable chatName: String, ): ResponseEntity { chatNameUpdater.execute(chatId, chatName) return ResponseEntity.noContent().build() @@ -56,40 +65,40 @@ class ChatController( @GetMapping("{chatId}/messages/{messageId}/attachment") fun downloadAttachment( - @PathVariable("chatId") chatId: Long, - @PathVariable("messageId") messageId: Long + @PathVariable("chatId") chatId: Long, + @PathVariable("messageId") messageId: Long ): ResponseEntity { val resource = attachmentFinder.execute( - AttachmentCriteriaInput( - chatId = chatId, - messageId = messageId - ) + AttachmentCriteriaInput( + chatId = chatId, + messageId = messageId + ) ) val cacheControl = CacheControl.maxAge(1, TimeUnit.DAYS) return ResponseEntity.ok() - .contentType(resource.getMediaType()) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${resource.filename}\"") - .cacheControl(cacheControl) - .body(resource) + .contentType(resource.getMediaType()) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${resource.filename}\"") + .cacheControl(cacheControl) + .body(resource) } @GetMapping("{chatId}/attachments") fun downloadAttachment( - @PathVariable("chatId") chatId: Long + @PathVariable("chatId") chatId: Long ): ResponseEntity> { val cacheControl = CacheControl.maxAge(5, TimeUnit.MINUTES) return ResponseEntity.ok() - .cacheControl(cacheControl) - .body(attachmentInfoFinderByChatId.execute(chatId)) + .cacheControl(cacheControl) + .body(attachmentInfoFinderByChatId.execute(chatId)) } @PostMapping("{chatId}/profile-image") fun putProfileImage( - @PathVariable("chatId") chatId: Long, - @RequestParam("profile-image") file: MultipartFile + @PathVariable("chatId") chatId: Long, + @RequestParam("profile-image") file: MultipartFile ): ResponseEntity { profileImageManager.updateImage(file.inputStream, chatId) @@ -98,22 +107,22 @@ class ChatController( @GetMapping("{chatId}/profile-image") fun getProfileImage( - @PathVariable("chatId") chatId: Long + @PathVariable("chatId") chatId: Long ): ResponseEntity { val image = profileImageManager.getImage(chatId) val cacheControl = CacheControl.maxAge(5, TimeUnit.MINUTES) return ResponseEntity.ok() - .contentType(image.getMediaType()) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${image.filename}\"") - .cacheControl(cacheControl) - .body(image) + .contentType(image.getMediaType()) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${image.filename}\"") + .cacheControl(cacheControl) + .body(image) } } fun Resource.getMediaType(): MediaType { return MediaTypeFactory.getMediaTypes(this.filename).firstOrNull() - ?: MediaType.APPLICATION_OCTET_STREAM + ?: MediaType.APPLICATION_OCTET_STREAM } \ No newline at end of file diff --git a/backend/service/src/main/kotlin/dev/marcal/chatvault/service/ChatDeleter.kt b/backend/service/src/main/kotlin/dev/marcal/chatvault/service/ChatDeleter.kt new file mode 100644 index 0000000..818d8b3 --- /dev/null +++ b/backend/service/src/main/kotlin/dev/marcal/chatvault/service/ChatDeleter.kt @@ -0,0 +1,5 @@ +package dev.marcal.chatvault.service + +interface ChatDeleter { + fun execute(chatId: Long) +} \ No newline at end of file diff --git a/backend/usecase/src/main/kotlin/dev/marcal/chatvault/usecase/ChatDeleterUseCase.kt b/backend/usecase/src/main/kotlin/dev/marcal/chatvault/usecase/ChatDeleterUseCase.kt new file mode 100644 index 0000000..08811d5 --- /dev/null +++ b/backend/usecase/src/main/kotlin/dev/marcal/chatvault/usecase/ChatDeleterUseCase.kt @@ -0,0 +1,19 @@ +package dev.marcal.chatvault.usecase + +import dev.marcal.chatvault.app_service.bucket_service.BucketService +import dev.marcal.chatvault.repository.ChatRepository +import dev.marcal.chatvault.service.ChatDeleter +import org.springframework.stereotype.Service + +@Service +class ChatDeleterUseCase( + private val chatRepository: ChatRepository, + private val buckService: BucketService +) : ChatDeleter { + override fun execute(chatId: Long) { + chatRepository.findChatBucketInfoByChatId(chatId)?.let { chatBucketInfo -> + buckService.delete(chatBucketInfo.bucket.toBucketFile()) + chatRepository.deleteChat(chatId) + } + } +} \ No newline at end of file diff --git a/frontend/components/ChatConfig.vue b/frontend/components/ChatConfig.vue index 246956e..f97b42e 100644 --- a/frontend/components/ChatConfig.vue +++ b/frontend/components/ChatConfig.vue @@ -1,6 +1,8 @@ + + + + \ No newline at end of file diff --git a/frontend/components/IconTrash.vue b/frontend/components/IconTrash.vue new file mode 100644 index 0000000..33b603b --- /dev/null +++ b/frontend/components/IconTrash.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index 0c48e8c..c531d95 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -15,6 +15,7 @@ export default defineNuxtConfig({ updateChatNameByChatId: `${host}/chats/:chatId/chatName/:chatName`, getProfileImage: `${host}/chats/:chatId/profile-image`, exportChatById: `${host}/chats/:chatId/export`, + deleteChatById: `${host}/chats/:chatId`, importChatByName: `${host}/chats/import/:chatName`, importFromDisk: `${host}/chats/disk-import` } diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 9f19761..1eac7b4 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -24,6 +24,7 @@ :mobile="isMobile" />