Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package org.example

import kotlinx.coroutines.runBlocking
import logic.usecase.auth.CreateDefaultAdminUseCase
import org.example.data.DataProvider
import org.example.di.appModule
import org.example.di.mongoModule
import org.example.di.uiModule
import org.example.di.useCaseModule
import org.example.entity.UserEntity
import org.example.presentation.PlanMateConsoleUI
import org.koin.core.context.startKoin
import org.koin.core.qualifier.named
import org.koin.java.KoinJavaComponent.getKoin


Expand All @@ -24,14 +20,7 @@ fun main() {
)
}
val console: PlanMateConsoleUI = getKoin().get()
val usersData: DataProvider<UserEntity> = getKoin().get(named("userDataProviderMongo"))
val createDefaultAdminUseCase = CreateDefaultAdminUseCase(usersData)
runBlocking {
try {
createDefaultAdminUseCase.invoke()
} catch (e: Exception) {
e.printStackTrace()
}
console.start()
}
}
6 changes: 3 additions & 3 deletions src/main/kotlin/data/mongo/AuditLogMongoDbImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import org.example.utils.PlanMateException
import java.util.*

class AuditLogMongoDbImpl(
private val mongoDBClient: MongoDBClient
mongoDBClient: MongoDBClient
) : DataProvider<AuditLogEntity> {
private val auditLogCollection = mongoDBClient.getDatabase().getCollection<Document>("audit_log")

override suspend fun add(auditLog: AuditLogEntity) {
override suspend fun add(item: AuditLogEntity) {
MongoExceptionHandler.handleOperation("adding audit log") {
val document = toDocument(auditLog)
val document = toDocument(item)
auditLogCollection.insertOne(document)
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/main/kotlin/data/mongo/AuthMongoImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ import org.example.utils.PlanMateException
import java.util.*

class AuthMongoImpl(
private val mongoDBClient: MongoDBClient
mongoDBClient: MongoDBClient
) : AuthProvider {
private val currentUserCollection = mongoDBClient.getDatabase().getCollection<Document>("current_users")

override suspend fun addCurrentUser(user: UserEntity) {
MongoExceptionHandler.handleOperation("adding current user") {
// First delete any existing current user
deleteCurrentUser()

// Then add the new user
val document = toDocument(user)
currentUserCollection.insertOne(document)
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/data/mongo/MongoDBClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import com.mongodb.kotlin.client.coroutine.MongoDatabase
import org.bson.UuidRepresentation

class MongoDBClient(
private val username: String,
private val password: String,
private val clusterUrl: String = "cluster0.watzb0c.mongodb.net",
private val databaseName: String = "PlanMate"
username: String,
password: String,
clusterUrl: String = "cluster0.watzb0c.mongodb.net",
databaseName: String = "PlanMate"
) {

private val client: MongoClient
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/data/mongo/StateMongoDBImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class StateMongoDBImpl(
}
}

override suspend fun getById(id: UUID): StateEntity? {
override suspend fun getById(id: UUID): StateEntity {
return MongoExceptionHandler.handleOperation("fetching state by ID") {
val filter = Filters.eq("id", id.toString())
val document = collection.find(filter).firstOrNull()
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/data/mongo/UsersMongoImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.example.data.mongo

import com.mongodb.client.model.Filters
import com.mongodb.client.model.Updates
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import org.bson.Document
Expand All @@ -14,7 +14,7 @@ import org.example.utils.PlanMateException
import java.util.*

class UsersMongoImpl(
private val mongoDBClient: MongoDBClient
mongoDBClient: MongoDBClient
) : DataProvider<UserEntity> {
private val usersCollection = mongoDBClient.getDatabase().getCollection<Document>("users")

Expand All @@ -33,10 +33,10 @@ class UsersMongoImpl(
}
}

override suspend fun getById(id: UUID): UserEntity? {
override suspend fun getById(id: UUID): UserEntity {
return MongoExceptionHandler.handleOperation("fetching user by ID") {
val filter = Filters.eq("id", id.toString())
val document = usersCollection.find(filter).first()
val document = usersCollection.find(filter).firstOrNull()
document?.let { fromDocument(it) }
?: throw PlanMateException.ItemNotFoundException("User not found with id: $id")
}
Expand Down
Empty file.
4 changes: 1 addition & 3 deletions src/main/kotlin/data/repository/AuditLogRepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,17 @@ class AuditLogRepositoryImpl(
override suspend fun addAudit(auditLogEntity: AuditLogEntity) {
try {
dataProvider.add(auditLogEntity)
}catch (e: PlanMateException.FileWriteException) {
} catch (e: PlanMateException.FileWriteException) {
throw PlanMateException.FileWriteException("Failed to add audit log due to invalid state.")
}
}

override suspend fun getProjectHistory(projectId: UUID): List<AuditLogEntity> =
getEntityHistory(projectId, AuditedEntityType.PROJECT)


override suspend fun getTaskHistory(taskId: UUID): List<AuditLogEntity> =
getEntityHistory(taskId, AuditedEntityType.TASK)


private suspend fun getEntityHistory(entityId: UUID, entityType: AuditedEntityType): List<AuditLogEntity> {
val history = dataProvider.get().filter {
it.entityType == entityType && it.entityId == entityId
Expand Down
18 changes: 13 additions & 5 deletions src/main/kotlin/data/repository/AuthenticationRepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import org.example.entity.UserEntity
import org.example.logic.repository.AuthenticationRepository
import org.example.logic.repository.UserRepository
import org.example.utils.PlanMateException
import org.example.utils.hasher.PasswordHasher

class AuthenticationRepositoryImpl(
private val authenticationProvider: AuthProvider,
private val userRepository: UserRepository,
private val dataProvider: DataProvider<UserEntity>
private val dataProvider: DataProvider<UserEntity>,
private val passwordHasher: PasswordHasher
) : AuthenticationRepository {

override suspend fun login(username: String, password: String) {
val user = userRepository.getUserByUsername(username)

if (user.password != password) {
if (!isPasswordValid(user, password)) {
throw PlanMateException.ValidationException("Password is not correct.")
}

authenticationProvider.addCurrentUser(user)
}

Expand All @@ -28,7 +29,6 @@ class AuthenticationRepositoryImpl(
userRepository.getUserByUsername(newUser.username)
throw PlanMateException.ValidationException("A user with that username already exists.")
} catch (e: PlanMateException.ItemNotFoundException) {
// User doesn't exist, we can proceed
dataProvider.add(newUser)
}
}
Expand All @@ -44,6 +44,14 @@ class AuthenticationRepositoryImpl(
null
}
}
}

private fun isPasswordValid(user: UserEntity, inputPassword: String): Boolean {
return try {
val hashedInput = passwordHasher.hash(inputPassword)
user.password == hashedInput
} catch (e: Exception) {
false
}
}

}
54 changes: 20 additions & 34 deletions src/main/kotlin/data/repository/ProjectRepositoryImpl.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package org.example.data.repository

import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone.Companion.UTC
import kotlinx.datetime.toLocalDateTime
import org.example.data.DataProvider
import org.example.data.logAudit
import org.example.entity.AuditAction
import org.example.entity.AuditLogEntity
import org.example.entity.AuditedEntityType
import org.example.entity.ProjectEntity
import org.example.logic.repository.ProjectRepository
import java.util.*
Expand All @@ -20,57 +17,46 @@ class ProjectRepositoryImpl(
require(project.name.isNotBlank()) { "Project name cannot be blank" }

projectDataProvider.add(project)
auditDataProvider.add(
AuditLogEntity(
userId = project.createdByAdminId,
entityType = AuditedEntityType.PROJECT,
entityId = project.id,
action = AuditAction.CREATE,
changeDetails = "Created project: ${project.name}",
timestamp = Clock.System.now().toLocalDateTime(UTC)
)
logAudit(
userId = project.createdByAdminId,
entityId = project.id,
action = AuditAction.CREATE,
changeDetails = "Created project: ${project.name}",
auditDataProvider
)
return project
}

override suspend fun updateProject(project: ProjectEntity, currentUserId: UUID): ProjectEntity {
projectDataProvider.update(project)

auditDataProvider.add(
AuditLogEntity(
userId = currentUserId,
entityType = AuditedEntityType.PROJECT,
entityId = project.id,
action = AuditAction.UPDATE,
changeDetails = "Updated project: ${project.name}",
timestamp = Clock.System.now().toLocalDateTime(UTC)
)
logAudit(
userId = currentUserId,
entityId = project.id,
action = AuditAction.UPDATE,
changeDetails = "Updated project: ${project.name}", auditDataProvider
)
return project
}


override suspend fun deleteProject(projectId: UUID, currentUserId: UUID) {
val project = projectDataProvider.getById(projectId)
?: throw NoSuchElementException("Project not found")
projectDataProvider.delete(projectId)

auditDataProvider.add(
AuditLogEntity(
userId = currentUserId,
entityType = AuditedEntityType.PROJECT,
entityId = projectId,
action = AuditAction.DELETE,
changeDetails = "Deleted project: ${project.name}",
timestamp = Clock.System.now().toLocalDateTime(UTC)
)
logAudit(
userId = currentUserId,
entityId = projectId,
action = AuditAction.DELETE,
changeDetails = "Deleted project: ${project.name}",
auditDataProvider
)
}

override suspend fun getAllProjects(): List<ProjectEntity> = projectDataProvider.get()

override suspend fun getProjectById(projectId: String): ProjectEntity {
return projectDataProvider.getById(UUID.fromString(projectId))
override suspend fun getProjectById(projectId: UUID): ProjectEntity {
return projectDataProvider.getById(projectId)
?: throw NoSuchElementException("Project not found")
}
}
10 changes: 4 additions & 6 deletions src/main/kotlin/data/repository/TaskRepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ class TaskRepositoryImpl(
dataProvider.getById(id) ?: throw PlanMateException.ItemNotFoundException("Task $id not found")


override suspend fun getTasksByProjectId(projectId: UUID): List<TaskEntity> =
dataProvider.get().filter { it.projectId == projectId }
override suspend fun getTasksByProjectId(id: UUID): List<TaskEntity> =
dataProvider.get().filter { it.projectId == id }
.takeIf { it.isNotEmpty() }
?: throw PlanMateException.ItemNotFoundException("Project $projectId not found")
?: throw PlanMateException.ItemNotFoundException("Project $id not found")


private suspend fun generateUpdateDetails(
Expand Down Expand Up @@ -99,7 +99,6 @@ class TaskRepositoryImpl(
)
}


private suspend fun audit(
userId: UUID,
entityId: UUID,
Expand All @@ -114,5 +113,4 @@ class TaskRepositoryImpl(
changeDetails = details
)
)
}

}
28 changes: 28 additions & 0 deletions src/main/kotlin/data/utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.example.data

import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone.Companion.UTC
import kotlinx.datetime.toLocalDateTime
import org.example.entity.AuditAction
import org.example.entity.AuditLogEntity
import org.example.entity.AuditedEntityType
import java.util.*

suspend fun logAudit(
userId: UUID,
entityId: UUID,
action: AuditAction,
changeDetails: String,
auditDataProvider:DataProvider<AuditLogEntity>
) {
auditDataProvider.add(
AuditLogEntity(
userId = userId,
entityType = AuditedEntityType.PROJECT,
entityId = entityId,
action = action,
changeDetails = changeDetails,
timestamp = Clock.System.now().toLocalDateTime(UTC)
)
)
}
7 changes: 6 additions & 1 deletion src/main/kotlin/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import org.example.data.mongo.*
import org.example.data.repository.*
import org.example.entity.*
import org.example.logic.repository.*
import org.example.utils.hasher.PasswordHasher
import org.example.utils.hasher.PasswordMD5HasherImpl
import org.koin.core.qualifier.named
import org.koin.dsl.module

val appModule = module {
single <PasswordHasher> { PasswordMD5HasherImpl() }

single(named("projects")) { "projects.csv" }
single(named("states")) { "states.csv" }
single(named("tasks")) { "tasks.csv" }
Expand Down Expand Up @@ -39,7 +43,8 @@ val appModule = module {
AuthenticationRepositoryImpl(
get(named("authProviderMongo")),
get(),
get(qualifier = named("userDataProviderMongo"))
get(qualifier = named("userDataProviderMongo")),
get()
)
}
single<ProjectRepository> {
Expand Down
4 changes: 0 additions & 4 deletions src/main/kotlin/di/UseCaseModule.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.example.di

import logic.usecase.auth.CreateDefaultAdminUseCase
import org.example.logic.usecase.audit.AddAuditLogUseCase
import org.example.logic.usecase.audit.GetAuditLogUseCase
import org.example.logic.usecase.auth.GetCurrentUserUseCase
import org.example.logic.usecase.auth.LoginUseCase
Expand All @@ -23,7 +21,6 @@ val useCaseModule = module {
single { GetUserByIdUseCase(get()) }
single { GetUserByUsernameUseCase(get()) }
single { GetAuditLogUseCase(get()) }
single { AddAuditLogUseCase(get()) }
single { AddTaskUseCase(get()) }
single { DeleteTaskUseCase(get()) }
single { GetTaskByIdUseCase(get()) }
Expand All @@ -42,5 +39,4 @@ val useCaseModule = module {
single { UpdateStateUseCase(get()) }
single { AddStateUseCase(get()) }
single { DeleteStateUseCase(get()) }
single { CreateDefaultAdminUseCase(get()) }
}
Loading