package be.simplenotes.persistance.notes import be.simplenotes.domain.model.Note import be.simplenotes.domain.model.PersistedNote import be.simplenotes.domain.model.PersistedNoteMetadata import be.simplenotes.domain.usecases.repositories.NoteRepository import be.simplenotes.persistance.extensions.uuidBinary import me.liuwj.ktorm.database.* import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.entity.* import java.time.LocalDateTime import java.util.* import kotlin.collections.HashMap internal class NoteRepositoryImpl(private val db: Database) : NoteRepository { @Throws(IllegalArgumentException::class) override fun findAll(userId: Int, limit: Int, offset: Int, tag: String?): List { require(limit > 0) { "limit should be positive" } require(offset >= 0) { "offset should not be negative" } val uuids1: List? = if (tag != null) { db.from(Tags) .leftJoin(Notes, on = Notes.uuid eq Tags.noteUuid) .select(Notes.uuid) .where { (Notes.userId eq userId) and (Tags.name eq tag) } .map { it[Notes.uuid]!! } } else null var query = db.notes .filterColumns { listOf(it.uuid, it.title, it.updatedAt) } .filter { it.userId eq userId } if (uuids1 != null) query = query.filter { it.uuid inList uuids1 } val notes = query .sortedByDescending { it.updatedAt } .take(limit) .drop(offset) .toList() if (notes.isEmpty()) return emptyList() val uuids = notes.map { note -> note.uuid } val tagsByUuid = db.tags .filterColumns { listOf(it.noteUuid, it.name) } .filter { it.noteUuid inList uuids } .groupByTo(HashMap(), { it.note.uuid }, { it.name }) return notes.map { note -> val tags = tagsByUuid[note.uuid] ?: emptyList() note.toPersistedMetadata(tags) } } override fun exists(userId: Int, uuid: UUID): Boolean { return db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) } } override fun create(userId: Int, note: Note): PersistedNote { val uuid = UUID.randomUUID() val entity = note.toEntity(uuid, userId).apply { this.updatedAt = LocalDateTime.now() } db.useTransaction { db.notes.add(entity) db.batchInsert(Tags) { note.meta.tags.forEach { tagName -> item { it.noteUuid to uuid it.name to tagName } } } } return entity.toPersistedNote(note.meta.tags) } @Suppress("UNCHECKED_CAST") override fun find(userId: Int, uuid: UUID): PersistedNote? { val note = db.notes .filterColumns { it.columns - it.userId } .filter { it.uuid eq uuid } .find { it.userId eq userId } ?: return null val tags = db.tags .filter { it.noteUuid eq uuid } .mapColumns { it.name } as List return note.toPersistedNote(tags) } override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? { db.useTransaction { val currentNote = db.notes .find { it.uuid eq uuid and (it.userId eq userId) } ?: return null currentNote.title = note.meta.title currentNote.markdown = note.markdown currentNote.html = note.html currentNote.updatedAt = LocalDateTime.now() currentNote.flushChanges() // delete all tags db.delete(Tags) { it.noteUuid eq uuid } // put new ones note.meta.tags.forEach { tagName -> db.insert(Tags) { it.name to tagName it.noteUuid to uuid } } return currentNote.toPersistedNote(note.meta.tags) } } override fun delete(userId: Int, uuid: UUID): Boolean = db.useTransaction { db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1 } @Suppress("UNCHECKED_CAST") override fun getTags(userId: Int): List { return db.sequenceOf(Tags) .filter { it.note.userId eq userId } .mapColumns(isDistinct = true) { it.name } as List } override fun count(userId: Int, tag: String?): Int { if (tag == null) return db.notes.count { it.userId eq userId } return db.sequenceOf(Tags) .count { it.name eq tag and (it.note.userId eq userId) } } }