Skip to content

Commit 534dcb6

Browse files
committed
implement LWW (Last-Write-Wins) conflict check
1 parent 8b29fde commit 534dcb6

5 files changed

Lines changed: 82 additions & 2 deletions

File tree

src/main/kotlin/com/sakethh/linkora/data/Database.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fun configureDatabase() {
2020
try {
2121
database = connectToADatabase(serverConfig)
2222
transaction {
23-
println("Connected to the database at ${this.db.url}")
23+
println("Connected to the database at ${this.db.url.substringAfter("jdbc:")}")
2424
SchemaUtils.createDatabase("linkora")
2525
createRequiredTables()
2626
}

src/main/kotlin/com/sakethh/linkora/data/repository/FoldersImplementation.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package com.sakethh.linkora.data.repository
22

3-
import com.sakethh.linkora.domain.model.Folder
3+
import com.sakethh.linkora.domain.LWWConflictException
44
import com.sakethh.linkora.domain.dto.IDBasedDTO
55
import com.sakethh.linkora.domain.dto.NewItemResponseDTO
66
import com.sakethh.linkora.domain.dto.TimeStampBasedResponse
77
import com.sakethh.linkora.domain.dto.folder.*
8+
import com.sakethh.linkora.domain.model.Folder
89
import com.sakethh.linkora.domain.model.WebSocketEvent
910
import com.sakethh.linkora.domain.repository.FoldersRepository
1011
import com.sakethh.linkora.domain.routes.FolderRoute
@@ -21,6 +22,19 @@ import org.jetbrains.exposed.sql.transactions.transaction
2122
import java.time.Instant
2223

2324
class FoldersImplementation : FoldersRepository {
25+
26+
private fun checkForLWWConflictAndThrow(id: Long, timeStamp: Long) {
27+
transaction {
28+
FoldersTable.select(FoldersTable.lastModified).where {
29+
FoldersTable.id.eq(id)
30+
}.let {
31+
if (it.single()[FoldersTable.lastModified] > timeStamp) {
32+
throw LWWConflictException()
33+
}
34+
}
35+
}
36+
}
37+
2438
override suspend fun createFolder(addFolderDTO: AddFolderDTO): Result<NewItemResponseDTO> {
2539
return try {
2640
val eventTimestamp = Instant.now().epochSecond
@@ -148,6 +162,7 @@ class FoldersImplementation : FoldersRepository {
148162
override suspend fun markAsArchive(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
149163
val folderId = idBasedDTO.id
150164
return try {
165+
checkForLWWConflictAndThrow(id = idBasedDTO.id, timeStamp = idBasedDTO.eventTimestamp)
151166
val eventTimestamp = Instant.now().epochSecond
152167
transaction {
153168
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
@@ -173,6 +188,7 @@ class FoldersImplementation : FoldersRepository {
173188
override suspend fun markAsRegularFolder(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
174189
val folderId = idBasedDTO.id
175190
return try {
191+
checkForLWWConflictAndThrow(id = idBasedDTO.id, timeStamp = idBasedDTO.eventTimestamp)
176192
val eventTimestamp = Instant.now().epochSecond
177193
transaction {
178194
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
@@ -199,6 +215,7 @@ class FoldersImplementation : FoldersRepository {
199215
val folderId = changeParentFolderDTO.folderId
200216
val newParentFolderId = changeParentFolderDTO.newParentFolderId
201217
return try {
218+
checkForLWWConflictAndThrow(id = changeParentFolderDTO.folderId, timeStamp = changeParentFolderDTO.eventTimestamp)
202219
val eventTimestamp = Instant.now().epochSecond
203220
transaction {
204221
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
@@ -229,6 +246,7 @@ class FoldersImplementation : FoldersRepository {
229246
val folderId = updateFolderNameDTO.folderId
230247
val newFolderName = updateFolderNameDTO.newFolderName
231248
return try {
249+
checkForLWWConflictAndThrow(id = updateFolderNameDTO.folderId, timeStamp = updateFolderNameDTO.eventTimestamp)
232250
val eventTimestamp = Instant.now().epochSecond
233251
transaction {
234252
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
@@ -261,6 +279,7 @@ class FoldersImplementation : FoldersRepository {
261279
val folderId = updateFolderNoteDTO.folderId
262280
val newNote = updateFolderNoteDTO.newNote
263281
return try {
282+
checkForLWWConflictAndThrow(id = updateFolderNoteDTO.folderId, timeStamp = updateFolderNoteDTO.eventTimestamp)
264283
val eventTimestamp = Instant.now().epochSecond
265284
transaction {
266285
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {

src/main/kotlin/com/sakethh/linkora/data/repository/LinksImplementation.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.sakethh.linkora.data.repository
22

3+
import com.sakethh.linkora.domain.LWWConflictException
34
import com.sakethh.linkora.domain.LinkType
45
import com.sakethh.linkora.domain.dto.IDBasedDTO
56
import com.sakethh.linkora.domain.dto.NewItemResponseDTO
@@ -23,6 +24,17 @@ import org.jetbrains.exposed.sql.update
2324
import java.time.Instant
2425

2526
class LinksImplementation : LinksRepository {
27+
private fun checkForLWWConflictAndThrow(id: Long, timeStamp: Long) {
28+
transaction {
29+
LinksTable.select(LinksTable.lastModified).where {
30+
LinksTable.id.eq(id)
31+
}.let {
32+
if (it.single()[LinksTable.lastModified] > timeStamp) {
33+
throw LWWConflictException()
34+
}
35+
}
36+
}
37+
}
2638
override suspend fun createANewLink(addLinkDTO: AddLinkDTO): Result<NewItemResponseDTO> {
2739
return try {
2840
val eventTimestamp = Instant.now().epochSecond
@@ -105,6 +117,10 @@ class LinksImplementation : LinksRepository {
105117

106118
override suspend fun updateLinkedFolderIdOfALink(updateLinkedFolderIDDto: UpdateLinkedFolderIDDto): Result<TimeStampBasedResponse> {
107119
return try {
120+
checkForLWWConflictAndThrow(
121+
id = updateLinkedFolderIDDto.linkId,
122+
timeStamp = updateLinkedFolderIDDto.eventTimestamp
123+
)
108124
val eventTimestamp = Instant.now().epochSecond
109125
transaction {
110126
LinksTable.update(where = {
@@ -132,6 +148,10 @@ class LinksImplementation : LinksRepository {
132148

133149
override suspend fun updateTitleOfTheLink(updateTitleOfTheLinkDTO: UpdateTitleOfTheLinkDTO): Result<TimeStampBasedResponse> {
134150
return try {
151+
checkForLWWConflictAndThrow(
152+
id = updateTitleOfTheLinkDTO.linkId,
153+
timeStamp = updateTitleOfTheLinkDTO.eventTimestamp
154+
)
135155
val eventTimestamp = Instant.now().epochSecond
136156
transaction {
137157
LinksTable.update(where = {
@@ -158,6 +178,10 @@ class LinksImplementation : LinksRepository {
158178

159179
override suspend fun updateNote(updateNoteOfALinkDTO: UpdateNoteOfALinkDTO): Result<TimeStampBasedResponse> {
160180
return try {
181+
checkForLWWConflictAndThrow(
182+
id = updateNoteOfALinkDTO.linkId,
183+
timeStamp = updateNoteOfALinkDTO.eventTimestamp
184+
)
161185
val eventTimestamp = Instant.now().epochSecond
162186
transaction {
163187
LinksTable.update(where = {
@@ -210,6 +234,10 @@ class LinksImplementation : LinksRepository {
210234

211235
override suspend fun archiveALink(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
212236
return try {
237+
checkForLWWConflictAndThrow(
238+
id = idBasedDTO.id,
239+
timeStamp = idBasedDTO.eventTimestamp
240+
)
213241
val eventTimestamp = Instant.now().epochSecond
214242
transaction {
215243
LinksTable.update(where = {
@@ -235,6 +263,10 @@ class LinksImplementation : LinksRepository {
235263

236264
override suspend fun unArchiveALink(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
237265
return try {
266+
checkForLWWConflictAndThrow(
267+
id = idBasedDTO.id,
268+
timeStamp = idBasedDTO.eventTimestamp
269+
)
238270
val eventTimestamp = Instant.now().epochSecond
239271
transaction {
240272
LinksTable.update(where = {
@@ -261,6 +293,10 @@ class LinksImplementation : LinksRepository {
261293

262294
override suspend fun markALinkAsImp(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
263295
return try {
296+
checkForLWWConflictAndThrow(
297+
id = idBasedDTO.id,
298+
timeStamp = idBasedDTO.eventTimestamp
299+
)
264300
val eventTimestamp = Instant.now().epochSecond
265301
transaction {
266302
LinksTable.update(where = {
@@ -286,6 +322,10 @@ class LinksImplementation : LinksRepository {
286322

287323
override suspend fun markALinkAsNonImp(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
288324
return try {
325+
checkForLWWConflictAndThrow(
326+
id = idBasedDTO.id,
327+
timeStamp = idBasedDTO.eventTimestamp
328+
)
289329
val eventTimestamp = Instant.now().epochSecond
290330
transaction {
291331
LinksTable.update(where = {
@@ -311,6 +351,10 @@ class LinksImplementation : LinksRepository {
311351

312352
override suspend fun updateLink(linkDTO: LinkDTO): Result<TimeStampBasedResponse> {
313353
return try {
354+
checkForLWWConflictAndThrow(
355+
id = linkDTO.id,
356+
timeStamp = linkDTO.eventTimestamp
357+
)
314358
val eventTimestamp = Instant.now().epochSecond
315359
transaction {
316360
LinksTable.update(where = {

src/main/kotlin/com/sakethh/linkora/data/repository/PanelsRepoImpl.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.sakethh.linkora.data.repository
22

3+
import com.sakethh.linkora.domain.LWWConflictException
34
import com.sakethh.linkora.domain.dto.IDBasedDTO
45
import com.sakethh.linkora.domain.dto.NewItemResponseDTO
56
import com.sakethh.linkora.domain.dto.TimeStampBasedResponse
@@ -23,6 +24,18 @@ import org.jetbrains.exposed.sql.update
2324
import java.time.Instant
2425

2526
class PanelsRepoImpl : PanelsRepository {
27+
private fun checkForPanelsLWWConflictAndThrow(id: Long, timeStamp: Long) {
28+
transaction {
29+
PanelsTable.select(PanelsTable.lastModified).where {
30+
PanelsTable.id.eq(id)
31+
}.let {
32+
if (it.single()[PanelsTable.lastModified] > timeStamp) {
33+
throw LWWConflictException()
34+
}
35+
}
36+
}
37+
}
38+
2639
override suspend fun addANewPanel(addANewPanelDTO: AddANewPanelDTO): Result<NewItemResponseDTO> {
2740
return try {
2841
val eventTimestamp = Instant.now().epochSecond
@@ -130,6 +143,7 @@ class PanelsRepoImpl : PanelsRepository {
130143

131144
override suspend fun updateAPanelName(updatePanelNameDTO: UpdatePanelNameDTO): Result<TimeStampBasedResponse> {
132145
return try {
146+
checkForPanelsLWWConflictAndThrow(updatePanelNameDTO.panelId, updatePanelNameDTO.eventTimestamp)
133147
val eventTimestamp = Instant.now().epochSecond
134148
transaction {
135149
PanelsTable.update(where = {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.sakethh.linkora.domain
2+
3+
class LWWConflictException : Exception("This row already contains the latest data.")

0 commit comments

Comments
 (0)