package be.simplenotes.persistance.notes import be.simplenotes.domain.model.* import be.simplenotes.domain.usecases.repositories.NoteRepository import me.liuwj.ktorm.database.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?, deleted: Boolean ): 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) and (Notes.deleted eq deleted) } .map { it[Notes.uuid]!! } } else null var query = db.notes .filterColumns { listOf(it.uuid, it.title, it.updatedAt) } .filter { (it.userId eq userId) and (it.deleted eq deleted) } if (uuids1 != null) query = query.filter { it.uuid inList uuids1 } val notes = query .sortedByDescending { it.updatedAt } .take(limit) .drop(offset) .toList() val tagsByUuid = notes.tagsByUuid() 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) and (it.deleted eq false) } } 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) } 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) and (it.deleted eq false) } ?: return null val tags = db.from(Tags) .select(Tags.name) .where { Tags.noteUuid eq uuid } .map { it[Tags.name]!! } return note.toPersistedNote(tags) } override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? { db.useTransaction { val now = LocalDateTime.now() val count = db.update(Notes) { it.title to note.meta.title it.markdown to note.markdown it.html to note.html it.updatedAt to 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.meta.tags.forEach { tagName -> db.insert(Tags) { it.name to tagName it.noteUuid to uuid } } return PersistedNote( meta = note.meta, markdown = note.markdown, html = note.html, updatedAt = now, uuid = uuid, public = false, // TODO ) } } override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean { return if (!permanent) { db.useTransaction { db.update(Notes) { it.deleted to true it.updatedAt to LocalDateTime.now() where { it.userId eq userId and (it.uuid eq uuid) } } } == 1 } else db.useTransaction { db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1 } } override fun restore(userId: Int, uuid: UUID): Boolean { return db.useTransaction { db.update(Notes) { it.deleted to 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[Tags.name]!! } override fun count(userId: Int, tag: String?, deleted: Boolean): Int { return if (tag == null) db.notes.count { (it.userId eq userId) and (Notes.deleted eq deleted) } else db.sequenceOf(Tags).count { (it.name eq tag) and (it.note.userId eq userId) and (it.note.deleted eq deleted) } } override fun export(userId: Int): List { val notes = db.notes .filterColumns { it.columns - it.userId } .filter { it.userId eq userId } .sortedByDescending { it.updatedAt } .toList() val tagsByUuid = notes.tagsByUuid() return notes.map { note -> ExportedNote( title = note.title, tags = tagsByUuid[note.uuid] ?: emptyList(), markdown = note.markdown, html = note.html, updatedAt = note.updatedAt, trash = note.deleted, ) } } override fun findAllDetails(userId: Int): List { val notes = db.notes .filterColumns { it.columns - it.deleted } .filter { (it.userId eq userId) and (it.deleted eq false) } .toList() val tagsByUuid = notes.tagsByUuid() return notes.map { note -> val tags = tagsByUuid[note.uuid] ?: emptyList() note.toPersistedNote(tags) } } override fun makePublic(userId: Int, uuid: UUID) = db.update(Notes) { it.public to 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) { it.public to false where { Notes.userId eq userId and (Notes.uuid eq uuid) and (it.deleted eq false) } } == 1 override fun findPublic(uuid: UUID): PersistedNote? { val note = db.notes .filterColumns { it.columns - it.userId } .filter { it.uuid eq uuid } .filter { it.public eq true } .find { it.deleted eq false } ?: return null val tags = db.from(Tags) .select(Tags.name) .where { Tags.noteUuid eq uuid } .map { it[Tags.name]!! } return note.toPersistedNote(tags) } private fun List.tagsByUuid(): Map> { return if (isEmpty()) emptyMap() else db.tags .filterColumns { listOf(it.noteUuid, it.name) } .filter { it.noteUuid inList map { note -> note.uuid } } .groupByTo(HashMap(), { it.note.uuid }, { it.name }) } }