Skip to content

Commit bfe28c5

Browse files
committed
Merge branch 'dev'
2 parents a6d8d2c + c279914 commit bfe28c5

35 files changed

Lines changed: 825 additions & 673 deletions

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ val kotlin_version: String by project
22
val logback_version: String by project
33

44
plugins {
5-
kotlin("jvm") version "2.0.21"
5+
kotlin("jvm") version "2.2.21"
66
id("io.ktor.plugin") version "3.0.1"
77
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.21"
88
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
kotlin.code.style=official
2-
kotlin_version=2.0.21
2+
kotlin_version=2.2.21
33
ktor_version=3.0.1
44
logback_version=1.4.14

gradlew

100644100755
File mode changed.

src/main/kotlin/com/sakethh/linkora/Application.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.sakethh.linkora.utils.SysEnvKey
1111
import com.sakethh.linkora.utils.useSysEnvValues
1212
import io.ktor.http.*
1313
import io.ktor.network.tls.certificates.*
14+
import io.ktor.network.tls.extensions.*
1415
import io.ktor.server.application.*
1516
import io.ktor.server.engine.*
1617
import io.ktor.server.netty.*
@@ -24,6 +25,7 @@ import java.io.FileOutputStream
2425
import java.io.FileWriter
2526
import java.net.Inet4Address
2627
import java.net.InetAddress
28+
import java.net.NetworkInterface
2729
import java.net.URI
2830
import java.nio.file.Files
2931
import java.nio.file.Paths
@@ -47,7 +49,7 @@ fun main() {
4749
factory = Netty, configure = {
4850
sslConnector(builder = {
4951
this.port = serverConfig.httpsPort
50-
this.host = Inet4Address.getLocalHost().hostAddress
52+
this.host = hostIp?.hostAddress ?: localhost.hostAddress
5153
enabledProtocols = listOf("TLSv1.3", "TLSv1.2")
5254
}, keyStore = serverKeyStore, keyAlias = Constants.KEY_STORE_ALIAS, keyStorePassword = {
5355
serverConfig.keyStorePassword.toCharArray()
@@ -63,6 +65,17 @@ fun main() {
6365
}, module = Application::module).start(wait = true)
6466
}
6567

68+
private val hostIp = try {
69+
NetworkInterface.getNetworkInterfaces().asSequence().flatMap { it.inetAddresses.asSequence() }.firstOrNull {
70+
!it.isLoopbackAddress && it is Inet4Address && it.isSiteLocalAddress
71+
}
72+
} catch (e: Exception) {
73+
e.printStackTrace()
74+
null
75+
}
76+
77+
private val localhost = Inet4Address.getLocalHost()
78+
6679
object ServerConfiguration {
6780
private val json = Json {
6881
prettyPrint = true
@@ -202,10 +215,13 @@ object ServerConfiguration {
202215
this.certificate(
203216
alias = Constants.KEY_STORE_ALIAS, block = {
204217
this.password = serverConfig.keyStorePassword
205-
this.domains = listOf(Inet4Address.getLocalHost().hostAddress)
206-
this.ipAddresses = listOf(Inet4Address.getLocalHost())
218+
this.domains = listOf(hostIp?.hostAddress ?: localhost.hostAddress)
219+
this.ipAddresses = listOf(hostIp ?: localhost)
207220
this.daysValid = 365
208221
this.subject = X500Principal("CN=linkora-sync, OU=Linkora, O=Linkora, C=West Elizabeth")
222+
this.keySizeInBits = 2048
223+
this.hash = HashAlgorithm.SHA256
224+
this.sign = SignatureAlgorithm.RSA
209225
})
210226
}
211227
return jarDir.resolve("linkoraServerCert.jks").run {
@@ -249,8 +265,8 @@ fun Application.module() {
249265
maxFrameSize = Long.MAX_VALUE
250266
}
251267
configureEventsWebSocket()
252-
val serverConfiguredPage =
253-
"https://" + Inet4Address.getLocalHost().hostAddress + ":" + serverConfig.httpsPort + "/" + Route.Sync.SERVER_IS_CONFIGURED.name
268+
val serverConfiguredPage = "https://" + (hostIp?.hostAddress
269+
?: localhost?.hostAddress) + ":" + serverConfig.httpsPort + "/" + Route.Sync.SERVER_IS_CONFIGURED.name
254270
if (useSysEnvValues().not() && Desktop.isDesktopSupported() && Desktop.getDesktop()
255271
.isSupported(Desktop.Action.BROWSE)
256272
) {

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import org.jetbrains.exposed.v1.jdbc.transactions.transaction
1010

1111
private fun connectToADatabase(serverConfig: ServerConfig): Database {
1212
return Database.connect(
13-
url = serverConfig.databaseUrl, user = serverConfig.databaseUser, password = serverConfig.databasePassword
13+
url = if (serverConfig.databaseUrl.startsWith("jdbc:sqlite:", ignoreCase = true)) {
14+
"${serverConfig.databaseUrl}?foreign_keys=on"
15+
} else {
16+
serverConfig.databaseUrl
17+
}, user = serverConfig.databaseUser, password = serverConfig.databasePassword
1418
)
1519
}
1620

@@ -81,4 +85,6 @@ private fun createRequiredTables() {
8185
SchemaUtils.create(*linkoraTables())
8286
}
8387

84-
fun linkoraTables() = arrayOf(FoldersTable, LinksTable, TombstoneTable, PanelsTable, PanelFoldersTable)
88+
fun linkoraTables() = arrayOf(
89+
FoldersTable, LinksTable, TombstoneTable, PanelsTable, PanelFoldersTable, TagsTable, LinkTagTable
90+
)

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

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.sakethh.linkora.domain.tables.LinksTable
1515
import com.sakethh.linkora.domain.tables.PanelFoldersTable
1616
import com.sakethh.linkora.domain.tables.helper.TombStoneHelper
1717
import com.sakethh.linkora.utils.checkForLWWConflictAndThrow
18+
import com.sakethh.linkora.utils.getSystemEpochSeconds
1819
import kotlinx.serialization.encodeToString
1920
import kotlinx.serialization.json.Json
2021
import kotlinx.serialization.json.encodeToJsonElement
@@ -31,7 +32,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
3132

3233
override suspend fun createFolder(addFolderDTO: AddFolderDTO): Result<NewItemResponseDTO> {
3334
return try {
34-
val eventTimestamp = Instant.now().epochSecond
35+
val eventTimestamp = getSystemEpochSeconds()
3536
transaction {
3637
FoldersTable.insertAndGetId { folder ->
3738
folder[lastModified] = eventTimestamp
@@ -44,14 +45,11 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
4445
Result.Success(
4546
response = NewItemResponseDTO(
4647
timeStampBasedResponse = TimeStampBasedResponse(
47-
message = "Folder created successfully with id = $it",
48-
eventTimestamp = eventTimestamp
49-
),
50-
id = it,
51-
correlation = addFolderDTO.correlation
52-
),
53-
webSocketEvent = WebSocketEvent(
54-
operation = Route.Folder.CREATE_FOLDER.name, payload = Json.encodeToJsonElement(
48+
message = "Folder created successfully with id = $it", eventTimestamp = eventTimestamp
49+
), id = it, correlation = addFolderDTO.correlation
50+
), webSocketEvent = WebSocketEvent(
51+
operation = Route.Folder.CREATE_FOLDER.name,
52+
payload = Json.encodeToJsonElement(
5553
FolderDTO(
5654
id = it,
5755
name = addFolderDTO.name,
@@ -70,9 +68,40 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
7068
}
7169
}
7270

71+
override suspend fun updateFolder(folderDTO: FolderDTO): Result<TimeStampBasedResponse> {
72+
return try {
73+
FoldersTable.checkForLWWConflictAndThrow(
74+
id = folderDTO.id, timeStamp = folderDTO.eventTimestamp, lastModifiedColumn = FoldersTable.lastModified
75+
)
76+
val eventTimestamp = getSystemEpochSeconds()
77+
transaction {
78+
FoldersTable.update(where = {
79+
FoldersTable.id eq folderDTO.id
80+
}) { folder ->
81+
folder[lastModified] = eventTimestamp
82+
folder[folderName] = folderDTO.name
83+
folder[note] = folderDTO.note
84+
folder[parentFolderID] = folderDTO.parentFolderId
85+
folder[isFolderArchived] = folderDTO.isArchived
86+
}
87+
}.let {
88+
Result.Success(
89+
response = TimeStampBasedResponse(
90+
message = "Folder updated successfully.", eventTimestamp = eventTimestamp
91+
), webSocketEvent = WebSocketEvent(
92+
operation = Route.Folder.UPDATE_FOLDER.name,
93+
payload = Json.encodeToJsonElement(folderDTO.copy(eventTimestamp = eventTimestamp)),
94+
)
95+
)
96+
}
97+
} catch (e: Exception) {
98+
Result.Failure(e)
99+
}
100+
}
101+
73102
override suspend fun deleteFolder(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
74103
return try {
75-
val eventTimestamp = Instant.now().epochSecond
104+
val eventTimestamp = getSystemEpochSeconds()
76105
when (val childFolders = getChildFolders(idBasedDTO)) {
77106
is Result.Failure -> {
78107
throw childFolders.exception
@@ -114,8 +143,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
114143
}
115144
Result.Success(
116145
response = TimeStampBasedResponse(
117-
message = "Folder and its contents have been successfully deleted.",
118-
eventTimestamp = eventTimestamp
146+
message = "Folder and its contents have been successfully deleted.", eventTimestamp = eventTimestamp
119147
), webSocketEvent = WebSocketEvent(
120148
operation = Route.Folder.DELETE_FOLDER.name,
121149
payload = Json.encodeToJsonElement(idBasedDTO.copy(eventTimestamp = eventTimestamp)),
@@ -180,7 +208,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
180208
timeStamp = idBasedDTO.eventTimestamp,
181209
lastModifiedColumn = FoldersTable.lastModified
182210
)
183-
val eventTimestamp = Instant.now().epochSecond
211+
val eventTimestamp = getSystemEpochSeconds()
184212
transaction {
185213
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
186214
it[lastModified] = eventTimestamp
@@ -189,8 +217,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
189217
}.let {
190218
Result.Success(
191219
response = TimeStampBasedResponse(
192-
eventTimestamp = eventTimestamp,
193-
message = "Number of rows affected by the update = $it"
220+
eventTimestamp = eventTimestamp, message = "Number of rows affected by the update = $it"
194221
), webSocketEvent = WebSocketEvent(
195222
operation = Route.Folder.MARK_FOLDER_AS_ARCHIVE.name,
196223
payload = Json.encodeToJsonElement(idBasedDTO.copy(eventTimestamp = eventTimestamp)),
@@ -210,7 +237,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
210237
timeStamp = idBasedDTO.eventTimestamp,
211238
lastModifiedColumn = FoldersTable.lastModified
212239
)
213-
val eventTimestamp = Instant.now().epochSecond
240+
val eventTimestamp = getSystemEpochSeconds()
214241
transaction {
215242
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
216243
it[lastModified] = eventTimestamp
@@ -219,8 +246,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
219246
}.let {
220247
Result.Success(
221248
response = TimeStampBasedResponse(
222-
eventTimestamp = eventTimestamp,
223-
message = "Number of rows affected by the update = $it"
249+
eventTimestamp = eventTimestamp, message = "Number of rows affected by the update = $it"
224250
), webSocketEvent = WebSocketEvent(
225251
operation = Route.Folder.MARK_AS_REGULAR_FOLDER.name,
226252
payload = Json.encodeToJsonElement(idBasedDTO.copy(eventTimestamp = eventTimestamp)),
@@ -241,7 +267,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
241267
timeStamp = updateFolderNameDTO.eventTimestamp,
242268
lastModifiedColumn = FoldersTable.lastModified
243269
)
244-
val eventTimestamp = Instant.now().epochSecond
270+
val eventTimestamp = getSystemEpochSeconds()
245271
transaction {
246272
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
247273
it[lastModified] = eventTimestamp
@@ -256,8 +282,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
256282
}.let {
257283
Result.Success(
258284
response = TimeStampBasedResponse(
259-
eventTimestamp = eventTimestamp,
260-
message = "Number of rows affected by the update = $it"
285+
eventTimestamp = eventTimestamp, message = "Number of rows affected by the update = $it"
261286
), webSocketEvent = WebSocketEvent(
262287
operation = Route.Folder.UPDATE_FOLDER_NAME.name,
263288
payload = Json.encodeToJsonElement(updateFolderNameDTO.copy(eventTimestamp = eventTimestamp)),
@@ -278,7 +303,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
278303
timeStamp = updateFolderNoteDTO.eventTimestamp,
279304
lastModifiedColumn = FoldersTable.lastModified
280305
)
281-
val eventTimestamp = Instant.now().epochSecond
306+
val eventTimestamp = getSystemEpochSeconds()
282307
transaction {
283308
FoldersTable.update(where = { FoldersTable.id.eq(folderId) }) {
284309
it[lastModified] = eventTimestamp
@@ -287,8 +312,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
287312
}.let {
288313
Result.Success(
289314
response = TimeStampBasedResponse(
290-
message = "Number of rows affected by the update = $it",
291-
eventTimestamp = eventTimestamp
315+
message = "Number of rows affected by the update = $it", eventTimestamp = eventTimestamp
292316
), webSocketEvent = WebSocketEvent(
293317
operation = Route.Folder.UPDATE_FOLDER_NOTE.name,
294318
payload = Json.encodeToJsonElement(updateFolderNoteDTO.copy(eventTimestamp = eventTimestamp)),
@@ -302,7 +326,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
302326

303327
override suspend fun deleteFolderNote(idBasedDTO: IDBasedDTO): Result<TimeStampBasedResponse> {
304328
return try {
305-
val eventTimestamp = Instant.now().epochSecond
329+
val eventTimestamp = getSystemEpochSeconds()
306330
transaction {
307331
FoldersTable.update(where = { FoldersTable.id.eq(idBasedDTO.id) }) {
308332
it[note] = ""
@@ -311,8 +335,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
311335
}.let {
312336
Result.Success(
313337
response = TimeStampBasedResponse(
314-
eventTimestamp = eventTimestamp,
315-
message = "Number of rows affected by the update = $it"
338+
eventTimestamp = eventTimestamp, message = "Number of rows affected by the update = $it"
316339
), webSocketEvent = WebSocketEvent(
317340
operation = Route.Folder.DELETE_FOLDER_NOTE.name,
318341
payload = Json.encodeToJsonElement(idBasedDTO.copy(eventTimestamp = eventTimestamp)),
@@ -331,7 +354,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
331354
timeStamp = markSelectedFoldersAsRootDTO.eventTimestamp,
332355
lastModifiedColumn = FoldersTable.lastModified
333356
)
334-
val eventTimeStamp = Instant.now().epochSecond
357+
val eventTimeStamp = getSystemEpochSeconds()
335358
transaction {
336359
FoldersTable.update(where = {
337360
FoldersTable.id.inList(markSelectedFoldersAsRootDTO.folderIds)
@@ -342,8 +365,7 @@ class FoldersRepoImpl(private val panelsRepo: PanelsRepo) : FoldersRepo {
342365
}.let {
343366
Result.Success(
344367
response = TimeStampBasedResponse(
345-
eventTimestamp = eventTimeStamp,
346-
message = "Marked $it folders as root."
368+
eventTimestamp = eventTimeStamp, message = "Marked $it folders as root."
347369
), webSocketEvent = WebSocketEvent(
348370
operation = Route.Folder.MARK_FOLDERS_AS_ROOT.name,
349371
payload = Json.encodeToJsonElement(markSelectedFoldersAsRootDTO.copy(eventTimestamp = eventTimeStamp))

0 commit comments

Comments
 (0)