package be.simplenotes.persistence.repositories import be.simplenotes.persistence.* import be.simplenotes.persistence.converters.NoteConverter import be.simplenotes.persistence.extensions.arrayContains import be.simplenotes.types.Note import be.simplenotes.types.PersistedNote import org.ktorm.database.Database import org.ktorm.dsl.* import org.ktorm.entity.* import java.time.LocalDateTime import java.util.* import jakarta.inject.Singleton @Singleton internal class NoteRepositoryImpl( private val db: Database, private val converter: NoteConverter, ) : NoteRepository { override fun findAll( userId: Int, limit: Int, offset: Int, tag: String?, deleted: Boolean, ) = db.noteWithTags .filterColumns { listOf(it.uuid, it.title, it.updatedAt, it.tags) } .filter { (it.userId eq userId) and (it.deleted eq deleted) } .runIf(tag != null) { filter { it.tags.arrayContains(tag!!) } } .sortedByDescending { it.updatedAt } .take(limit) .drop(offset) .map { converter.toPersistedNoteMetadata(it)!! } override fun exists(userId: Int, uuid: UUID) = db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) and (it.deleted eq false) } override fun create(userId: Int, note: Note): PersistedNote = db.useTransaction { val uuid = UUID.randomUUID() val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now()) db.notes.add(entity) note.tags.takeIf { it.isNotEmpty() }?.run { db.batchInsert(Tags) { forEach { tagName -> item { set(it.noteUuid, uuid) set(it.name, tagName) } } } } return find(userId, uuid) ?: error("Note not found") } override fun find(userId: Int, uuid: UUID) = db.noteWithTags .filterColumns { NotesWithTags.columns - NotesWithTags.userId - NotesWithTags.deleted } .find { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) } .let { converter.toPersistedNote(it) } override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? = db.useTransaction { val now = LocalDateTime.now() val count = db.update(Notes) { set(it.title, note.title) set(it.markdown, note.markdown) set(it.html, note.html) set(it.updatedAt, now) where { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) } } if (count == 0) return null // delete all tags db.delete(Tags) { it.noteUuid eq uuid } // put new ones note.tags.forEach { tagName -> db.insert(Tags) { set(it.name, tagName) set(it.noteUuid, uuid) } } return find(userId, uuid) } override fun delete(userId: Int, uuid: UUID, permanent: Boolean) = if (!permanent) { db.update(Notes) { set(it.deleted, true) set(it.updatedAt, LocalDateTime.now()) where { it.userId eq userId and (it.uuid eq uuid) } } == 1 } else db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1 override fun restore(userId: Int, uuid: UUID) = db.update(Notes) { set(it.deleted, false) where { (it.userId eq userId) and (it.uuid eq uuid) } } == 1 override fun getTags(userId: Int): List = db.from(Tags) .leftJoin(Notes, on = Notes.uuid eq Tags.noteUuid) .selectDistinct(Tags.name) .where { (Notes.userId eq userId) and (Notes.deleted eq false) } .map { it.getString(1)!! } override fun count(userId: Int, tag: String?, deleted: Boolean) = db.from(Notes) .runIf(tag != null) { leftJoin(Tags, on = Tags.noteUuid eq Notes.uuid) } .select(count()) .whereWithConditions { it += Notes.userId eq userId it += Notes.deleted eq deleted tag?.let { tag -> it += Tags.name eq tag } } .map { it.getInt(1) } .first() override fun export(userId: Int) = db.noteWithTags .filterColumns { it.columns - it.userId - it.public } .filter { it.userId eq userId } .sortedByDescending { it.updatedAt } .map { converter.toExportedNote(it)!! } override fun findAllDetails(userId: Int) = db.noteWithTags .filterColumns { it.columns - it.userId - it.deleted } .filter { (it.userId eq userId) and (it.deleted eq false) } .map { converter.toPersistedNote(it)!! } override fun makePublic(userId: Int, uuid: UUID) = db.update(Notes) { set(it.public, true) where { Notes.userId eq userId and (Notes.uuid eq uuid) and (it.deleted eq false) } } == 1 override fun makePrivate(userId: Int, uuid: UUID) = db.update(Notes) { set(it.public, false) where { Notes.userId eq userId and (Notes.uuid eq uuid) and (it.deleted eq false) } } == 1 override fun findPublic(uuid: UUID) = db.noteWithTags .filterColumns { it.columns - it.userId - it.deleted } .find { (it.uuid eq uuid) and (it.public eq true) and (it.deleted eq false) } .let { converter.toPersistedNote(it) } } private inline fun T.runIf(condition: Boolean, block: T.() -> T) = run { if (condition) block(this) else this }