package be.simplenotes.domain import be.simplenotes.persistence.repositories.NoteRepository import be.simplenotes.types.ExportedNote import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import jakarta.inject.Singleton interface ExportService { fun exportAsJson(userId: Int): String fun exportAsZip(userId: Int): InputStream } @Singleton internal class ExportServiceImpl( private val noteRepository: NoteRepository, private val json: Json, ) : ExportService { override fun exportAsJson(userId: Int): String { val notes = noteRepository.export(userId) return json.encodeToString(ListSerializer(ExportedNote.serializer()), notes) } private fun sanitizeFilename(inputName: String): String = inputName.replace("[^a-zA-Z0-9-_.]".toRegex(), "_") override fun exportAsZip(userId: Int): InputStream { val notes = noteRepository.export(userId) val zipOutput = ZipOutput() zipOutput.use { zip -> notes.forEach { val name = sanitizeFilename(it.title) zip.write("notes/$name.md", it.markdown) } } return ByteArrayInputStream(zipOutput.outputStream.toByteArray()) } } private class ZipOutput : AutoCloseable { val outputStream = ByteArrayOutputStream() private val zipOutputStream = ZipArchiveOutputStream(outputStream) fun write(path: String, content: String) { val entry = ZipArchiveEntry(path) zipOutputStream.putArchiveEntry(entry) zipOutputStream.write(content.toByteArray()) zipOutputStream.closeArchiveEntry() } override fun close() { zipOutputStream.finish() zipOutputStream.close() } }