Skip to content

Commit

Permalink
Check Entity type on saving
Browse files Browse the repository at this point in the history
Preventing saving an entity of a different type
Added tests for it
Broke up ProjectRepository exceptions out to their own file
  • Loading branch information
Wavesonics committed Jul 8, 2024
1 parent 63361e3 commit ebf236a
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,4 @@ interface ProjectDatasource {
userId: Long,
projectDef: ProjectDefinition
): ApiProjectEntity.Type?

fun getEntityType(
userId: Long,
projectDef: ProjectDefinition,
entityId: Int
): ApiProjectEntity.Type?
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,30 +115,6 @@ class ProjectFilesystemDatasource(
return null
}

override fun getEntityType(
userId: Long,
projectDef: ProjectDefinition,
entityId: Int
): ApiProjectEntity.Type? {
ensureEntityDir(userId, projectDef)

val entityDir = getEntityDirectory(userId, projectDef, fileSystem)
val files = fileSystem.list(entityDir)
for (entityPath in files) {
ServerEntitySynchronizer.ENTITY_FILENAME_REGEX.matchEntire(entityPath.name)
?.let { match ->
val id = match.groupValues[1].toInt()
if (id == entityId) {
val typeStr = match.groupValues[2]
ApiProjectEntity.Type.fromString(typeStr)?.let { type ->
return type
}
}
}
}
return null
}

private fun ensureEntityDir(userId: Long, projectDef: ProjectDefinition) {
val entityDir = getEntityDirectory(userId, projectDef, fileSystem)
fileSystem.createDirectories(entityDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ class ProjectRepository(

val syncKey = ProjectSyncKey(userId, projectDef)

return if (projectsSessions.hasActiveSyncSession(userId) || sessionManager.hasActiveSyncSession(syncKey)) {
return if (
projectsSessions.hasActiveSyncSession(userId) ||
sessionManager.hasActiveSyncSession(syncKey)
) {
SResult.failure(
"begin sync failure: existing session",
Msg.r("api.project.sync.begin.error.session", userId)
Expand All @@ -68,14 +71,15 @@ class ProjectRepository(
projectSyncData = projectSyncData.copy(lastId = lastId ?: -1)
}

val newSyncId = sessionManager.createNewSession(syncKey) { key: ProjectSyncKey, sync: String ->
ProjectSynchronizationSession(
userId = key.userId,
projectDef = key.projectDef,
started = clock.now(),
syncId = sync
)
}
val newSyncId =
sessionManager.createNewSession(syncKey) { key: ProjectSyncKey, sync: String ->
ProjectSynchronizationSession(
userId = key.userId,
projectDef = key.projectDef,
started = clock.now(),
syncId = sync
)
}

val updateSequence = getUpdateSequence(userId, projectDef, clientState, lite)
val syncBegan = ProjectSynchronizationBegan(
Expand Down Expand Up @@ -146,7 +150,10 @@ class ProjectRepository(
)
)
} else {
SResult.failure("Project does not exist", Msg.r("api.project.getproject.error.notfound"))
SResult.failure(
"Project does not exist",
Msg.r("api.project.getproject.error.notfound")
)
}
}

Expand All @@ -161,6 +168,18 @@ class ProjectRepository(
if (validateSyncId(userId, projectDef, syncId).not())
return SResult.failure("Invalid SyncId", exception = InvalidSyncIdException())

val existingType = projectDatasource.findEntityType(entity.id, userId, projectDef)
if (existingType != null && existingType != entity.type)
return SResult.failure(
"Entity type mismatch",
exception = EntityTypeConflictException(
id = entity.id,
existingType = existingType,
submittedType = entity.type
),
displayMessage = Msg.r("api.project.saveentity.error.typeconflict")
)

val result = when (entity) {
is ApiProjectEntity.SceneEntity -> sceneSynchronizer.saveEntity(
userId,
Expand Down Expand Up @@ -221,22 +240,42 @@ class ProjectRepository(
}

val entityType: ApiProjectEntity.Type =
projectDatasource.getEntityType(userId, projectDef, entityId) ?: return SResult.failure(
"No type found",
exception = NoEntityTypeFound(entityId)
)
projectDatasource.findEntityType(entityId, userId, projectDef)
?: return SResult.failure(
"No type found",
exception = NoEntityTypeFound(entityId)
)

when (entityType) {
ApiProjectEntity.Type.SCENE -> sceneSynchronizer.deleteEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.NOTE -> noteSynchronizer.deleteEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.TIMELINE_EVENT -> timelineEventSynchronizer.deleteEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.SCENE -> sceneSynchronizer.deleteEntity(
userId,
projectDef,
entityId
)

ApiProjectEntity.Type.NOTE -> noteSynchronizer.deleteEntity(
userId,
projectDef,
entityId
)

ApiProjectEntity.Type.TIMELINE_EVENT -> timelineEventSynchronizer.deleteEntity(
userId,
projectDef,
entityId
)

ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY -> encyclopediaSynchronizer.deleteEntity(
userId,
projectDef,
entityId
)

ApiProjectEntity.Type.SCENE_DRAFT -> sceneDraftSynchronizer.deleteEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.SCENE_DRAFT -> sceneDraftSynchronizer.deleteEntity(
userId,
projectDef,
entityId
)
}

return SResult.success()
Expand All @@ -255,23 +294,41 @@ class ProjectRepository(
?: return SResult.failure("EntityNotFound", exception = EntityNotFound(entityId))

return when (type) {
ApiProjectEntity.Type.SCENE -> sceneSynchronizer.loadEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.SCENE -> sceneSynchronizer.loadEntity(
userId,
projectDef,
entityId
)

ApiProjectEntity.Type.NOTE -> noteSynchronizer.loadEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.TIMELINE_EVENT -> timelineEventSynchronizer.loadEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.TIMELINE_EVENT -> timelineEventSynchronizer.loadEntity(
userId,
projectDef,
entityId
)

ApiProjectEntity.Type.ENCYCLOPEDIA_ENTRY -> encyclopediaSynchronizer.loadEntity(
userId,
projectDef,
entityId
)

ApiProjectEntity.Type.SCENE_DRAFT -> sceneDraftSynchronizer.loadEntity(userId, projectDef, entityId)
ApiProjectEntity.Type.SCENE_DRAFT -> sceneDraftSynchronizer.loadEntity(
userId,
projectDef,
entityId
)
}
}

private suspend fun validateSyncId(userId: Long, projectDef: ProjectDefinition, syncId: String): Boolean {
private suspend fun validateSyncId(
userId: Long,
projectDef: ProjectDefinition,
syncId: String
): Boolean {
val syncKey = ProjectSyncKey(userId, projectDef)
return !projectsSessions.hasActiveSyncSession(userId) &&
sessionManager.validateSyncId(syncKey, syncId, true)
sessionManager.validateSyncId(syncKey, syncId, true)
}

private suspend fun getUpdateSequence(
Expand All @@ -283,18 +340,24 @@ class ProjectRepository(
val updateSequence = mutableSetOf<Int>()
if (lite.not()) {
updateSequence += sceneSynchronizer.getUpdateSequence(userId, projectDef, clientState)
updateSequence += sceneDraftSynchronizer.getUpdateSequence(userId, projectDef, clientState)
updateSequence += sceneDraftSynchronizer.getUpdateSequence(
userId,
projectDef,
clientState
)
updateSequence += noteSynchronizer.getUpdateSequence(userId, projectDef, clientState)
updateSequence += timelineEventSynchronizer.getUpdateSequence(userId, projectDef, clientState)
updateSequence += encyclopediaSynchronizer.getUpdateSequence(userId, projectDef, clientState)
updateSequence += timelineEventSynchronizer.getUpdateSequence(
userId,
projectDef,
clientState
)
updateSequence += encyclopediaSynchronizer.getUpdateSequence(
userId,
projectDef,
clientState
)
}

return updateSequence.toList()
}
}

data class ProjectServerState(val lastSync: Instant, val lastId: Int)

class InvalidSyncIdException : Exception("Invalid sync id")
class NoEntityTypeFound(val id: Int) : Exception("Could not find Type for Entity ID: $id")
class EntityNotFound(val id: Int) : Exception("Entity $id not found on server")
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.darkrockstudios.apps.hammer.project

import com.darkrockstudios.apps.hammer.base.http.ApiProjectEntity

class InvalidSyncIdException : Exception("Invalid sync id")
class NoEntityTypeFound(val id: Int) : Exception("Could not find Type for Entity ID: $id")
class EntityNotFound(val id: Int) : Exception("Entity $id not found on server")
class EntityTypeConflictException(
val id: Int,
val existingType: ApiProjectEntity.Type,
val submittedType: ApiProjectEntity.Type
) :
Exception(
"Entity type conflict for ID: $id\n" +
" Existing Type: $existingType\n" +
" Submitted Type: $submittedType"
)
Loading

0 comments on commit ebf236a

Please sign in to comment.