Persistance: Note can now be put in the trash
This commit is contained in:
parent
4e2fe463e0
commit
15de81394c
@ -55,10 +55,10 @@ class NoteController(
|
||||
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||
|
||||
if (request.method == Method.POST && request.form("delete") != null) {
|
||||
return if (noteService.delete(jwtPayload.userId, noteUuid))
|
||||
Response.redirect("/notes") // FIXME: flash cookie to show success ?
|
||||
return if (noteService.trash(jwtPayload.userId, noteUuid))
|
||||
Response.redirect("/notes") // TODO: flash cookie to show success ?
|
||||
else
|
||||
Response(NOT_FOUND) // FIXME: show an error
|
||||
Response(NOT_FOUND) // TODO: show an error
|
||||
}
|
||||
|
||||
val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND)
|
||||
|
||||
@ -38,7 +38,10 @@ class NoteService(
|
||||
}
|
||||
|
||||
fun find(userId: Int, uuid: UUID) = noteRepository.find(userId, uuid)
|
||||
fun delete(userId: Int, uuid: UUID) = noteRepository.delete(userId, uuid)
|
||||
fun trash(userId: Int, uuid: UUID) = noteRepository.delete(userId, uuid, permanent = false)
|
||||
fun restore(userId: Int, uuid: UUID) = noteRepository.restore(userId, uuid)
|
||||
fun delete(userId: Int, uuid: UUID) = noteRepository.delete(userId, uuid, permanent = true)
|
||||
|
||||
}
|
||||
|
||||
data class PaginatedNotes(val pages: Int, val notes: List<PersistedNoteMetadata>)
|
||||
|
||||
@ -6,12 +6,23 @@ import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import java.util.*
|
||||
|
||||
interface NoteRepository {
|
||||
fun findAll(userId: Int, limit: Int = 20, offset: Int = 0, tag: String? = null): List<PersistedNoteMetadata>
|
||||
|
||||
fun findAll(
|
||||
userId: Int,
|
||||
limit: Int = 20,
|
||||
offset: Int = 0,
|
||||
tag: String? = null,
|
||||
deleted: Boolean = false
|
||||
): List<PersistedNoteMetadata>
|
||||
|
||||
fun count(userId: Int, tag: String? = null, deleted: Boolean = false): Int
|
||||
fun delete(userId: Int, uuid: UUID, permanent: Boolean = false): Boolean
|
||||
fun restore(userId: Int, uuid: UUID): Boolean
|
||||
|
||||
// These methods only access notes where `Notes.deleted = false`
|
||||
fun getTags(userId: Int): List<String>
|
||||
fun exists(userId: Int, uuid: UUID): Boolean
|
||||
fun create(userId: Int, note: Note): PersistedNote
|
||||
fun find(userId: Int, uuid: UUID): PersistedNote?
|
||||
fun update(userId: Int, uuid: UUID, note: Note): PersistedNote?
|
||||
fun delete(userId: Int, uuid: UUID): Boolean
|
||||
fun getTags(userId: Int): List<String>
|
||||
fun count(userId: Int, tag: String? = null): Int
|
||||
}
|
||||
|
||||
@ -4,8 +4,7 @@ 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.database.Database
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import java.time.LocalDateTime
|
||||
@ -15,7 +14,13 @@ 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<PersistedNoteMetadata> {
|
||||
override fun findAll(
|
||||
userId: Int,
|
||||
limit: Int,
|
||||
offset: Int,
|
||||
tag: String?,
|
||||
deleted: Boolean
|
||||
): List<PersistedNoteMetadata> {
|
||||
require(limit > 0) { "limit should be positive" }
|
||||
require(offset >= 0) { "offset should not be negative" }
|
||||
|
||||
@ -23,13 +28,13 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
||||
db.from(Tags)
|
||||
.leftJoin(Notes, on = Notes.uuid eq Tags.noteUuid)
|
||||
.select(Notes.uuid)
|
||||
.where { (Notes.userId eq userId) and (Tags.name eq tag) }
|
||||
.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 }
|
||||
.filter { (it.userId eq userId) and (it.deleted eq deleted) }
|
||||
|
||||
if (uuids1 != null) query = query.filter { it.uuid inList uuids1 }
|
||||
|
||||
@ -55,7 +60,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
||||
}
|
||||
|
||||
override fun exists(userId: Int, uuid: UUID): Boolean {
|
||||
return db.notes.any { (it.userId eq userId) and (it.uuid eq uuid) }
|
||||
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 {
|
||||
@ -78,17 +83,17 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
||||
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 }
|
||||
.find { (it.userId eq userId) and (it.deleted eq false) }
|
||||
?: return null
|
||||
|
||||
val tags = db.tags
|
||||
.filter { it.noteUuid eq uuid }
|
||||
.mapColumns { it.name } as List<String>
|
||||
val tags = db.from(Tags)
|
||||
.select(Tags.name)
|
||||
.where { Tags.noteUuid eq uuid }
|
||||
.map { it[Tags.name]!! }
|
||||
|
||||
return note.toPersistedNote(tags)
|
||||
}
|
||||
@ -96,7 +101,7 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
||||
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) }
|
||||
.find { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) }
|
||||
?: return null
|
||||
|
||||
currentNote.title = note.meta.title
|
||||
@ -121,20 +126,45 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(userId: Int, uuid: UUID): Boolean = db.useTransaction {
|
||||
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
|
||||
override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean {
|
||||
if (!permanent) {
|
||||
return 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
|
||||
}
|
||||
|
||||
return db.useTransaction {
|
||||
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun getTags(userId: Int): List<String> {
|
||||
return db.sequenceOf(Tags)
|
||||
.filter { it.note.userId eq userId }
|
||||
.mapColumns(isDistinct = true) { it.name } as List<String>
|
||||
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 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) }
|
||||
override fun getTags(userId: Int): List<String> =
|
||||
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 {
|
||||
if (tag == null) return db.notes.count { (it.userId eq userId) and (Notes.deleted eq deleted) }
|
||||
|
||||
return db.sequenceOf(Tags).count {
|
||||
(it.name eq tag) and (it.note.userId eq userId) and (Notes.deleted eq deleted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ internal open class Notes(alias: String?) : Table<NoteEntity>("Notes", alias) {
|
||||
val html = text("html").bindTo { it.html }
|
||||
val userId = int("user_id").references(Users) { it.user }
|
||||
val updatedAt = datetime("updated_at").bindTo { it.updatedAt }
|
||||
val deleted = boolean("deleted").bindTo { it.deleted }
|
||||
val user get() = userId.referenceTable as Users
|
||||
}
|
||||
|
||||
@ -37,6 +38,7 @@ internal interface NoteEntity : Entity<NoteEntity> {
|
||||
var markdown: String
|
||||
var html: String
|
||||
var updatedAt: LocalDateTime
|
||||
var deleted: Boolean
|
||||
|
||||
var user: UserEntity
|
||||
}
|
||||
@ -52,6 +54,7 @@ internal fun Note.toEntity(uuid: UUID, userId: Int): NoteEntity {
|
||||
this.markdown = note.markdown
|
||||
this.html = note.html
|
||||
this.uuid = uuid
|
||||
this.deleted = false
|
||||
this.user["id"] = userId
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
alter table Notes
|
||||
add column deleted bool not null default false
|
||||
@ -0,0 +1,2 @@
|
||||
alter table Notes
|
||||
add column deleted bool not null default false
|
||||
@ -224,9 +224,6 @@ internal class NoteRepositoryImplTest {
|
||||
val note = createNote(user1.id, "1", listOf("a", "b"))
|
||||
assertThat(noteRepo.delete(user1.id, note.uuid))
|
||||
.isTrue
|
||||
|
||||
assertThat(noteRepo.delete(user1.id, note.uuid))
|
||||
.isFalse
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -289,4 +286,42 @@ internal class NoteRepositoryImplTest {
|
||||
.isEqualToComparingOnlyGivenFields(newNote2, "meta", "markdown", "html")
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Trash {
|
||||
|
||||
@Test
|
||||
fun `trashed noted should be restored`() {
|
||||
val note1 = createNote(user1.id, "1", listOf("a", "b"))
|
||||
|
||||
assertThat(noteRepo.delete(user1.id, note1.uuid, permanent = false))
|
||||
.isTrue
|
||||
|
||||
val isDeleted = db.notes
|
||||
.find { it.uuid eq note1.uuid }
|
||||
?.deleted
|
||||
|
||||
assertThat(isDeleted).`as`("Check that Notes.deleted is true").isTrue
|
||||
|
||||
assertThat(noteRepo.restore(user1.id, note1.uuid)).isTrue
|
||||
|
||||
val isDeleted2 = db.notes
|
||||
.find { it.uuid eq note1.uuid }
|
||||
?.deleted
|
||||
|
||||
assertThat(isDeleted2).`as`("Check that Notes.deleted is false after restore()").isFalse
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `permanent delete`() {
|
||||
val note1 = createNote(user1.id, "1", listOf("a", "b"))
|
||||
assertThat(noteRepo.delete(user1.id, note1.uuid, permanent = true))
|
||||
.isTrue
|
||||
|
||||
assertThat(noteRepo.restore(user1.id, note1.uuid)).isFalse
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user