Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(user): add user project (DEV-586) #2063

Merged
merged 23 commits into from Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 79 additions & 0 deletions build.sbt
Expand Up @@ -240,6 +240,18 @@ lazy val apiMain = project
)
.dependsOn(schemaCore, schemaRepo, schemaApi)

// Value Objects project

lazy val valueObjects = project
.in(file("dsp-value-objects"))
.settings(
name := "valueObjects",
libraryDependencies ++= Dependencies.valueObjectsLibraryDependencies,
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
)

// Schema projects

lazy val schemaApi = project
.in(file("dsp-schema/api"))
.settings(
Expand Down Expand Up @@ -284,6 +296,73 @@ lazy val schemaRepoSearchService = project
)
.dependsOn(schemaRepo)

// User projects

lazy val userInterface = project
.in(file("dsp-user/interface"))
.settings(
scalacOptions ++= Seq(
"-feature",
"-unchecked",
"-deprecation",
"-Yresolve-term-conflict:package",
"-Ymacro-annotations"
),
name := "userInterface",
libraryDependencies ++= Dependencies.userInterfaceLibraryDependencies,
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
)
.dependsOn(shared, userHandler)

lazy val userHandler = project
.in(file("dsp-user/handler"))
.settings(
scalacOptions ++= Seq(
"-feature",
"-unchecked",
"-deprecation",
"-Yresolve-term-conflict:package",
"-Ymacro-annotations"
),
name := "userHandler",
libraryDependencies ++= Dependencies.userHandlerLibraryDependencies,
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
)
.dependsOn(shared, userCore, userRepo % "test->test") //userHandler tests need mock implementation of UserRepo

lazy val userCore = project
.in(file("dsp-user/core"))
.settings(
scalacOptions ++= Seq(
"-feature",
"-unchecked",
"-deprecation",
"-Yresolve-term-conflict:package",
"-Ymacro-annotations"
),
name := "userCore",
libraryDependencies ++= Dependencies.userCoreLibraryDependencies,
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
)
.dependsOn(shared)

lazy val userRepo = project
.in(file("dsp-user/repo"))
.settings(
scalacOptions ++= Seq(
"-feature",
"-unchecked",
"-deprecation",
"-Yresolve-term-conflict:package",
"-Ymacro-annotations"
),
name := "userRepo",
libraryDependencies ++= Dependencies.userRepoLibraryDependencies,
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
)
.dependsOn(shared, userCore)
//.dependsOn(userHandler % "compile->compile;test->test", userDomain)

lazy val shared = project
.in(file("dsp-shared"))
.settings(
Expand Down
70 changes: 70 additions & 0 deletions dsp-user/core/src/main/scala/dsp/user/api/UserRepo.scala
@@ -0,0 +1,70 @@
/*
* Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package dsp.user.api

import dsp.errors._
import dsp.user.domain._
import zio._
import zio.macros.accessible

import java.util.UUID

/**
* The trait (interface) for the user repository. The user repository is responsible for storing and retrieving users.
* Needs to be used by the user repository implementations.
*/
@accessible // with this annotation we don't have to write the companion object ourselves
trait UserRepo {

/**
* Writes a user to the repository (used for both create and update).
* If this fails (e.g. the triplestore is not available), it's a non-recovable error. That's why we need UIO.
* When used, we should do it like: ...store(...).orDie
*
* @param user the user to write
* @return Unit
*/
def storeUser(user: User): UIO[UserId]

/**
* Gets all users from the repository.
*
* @return a list of [[User]]
*/
def getUsers(): UIO[List[User]]

/**
* Retrieves the user from the repository by ID.
*
* @param id the user's ID
* @return an optional [[User]]
*/
def getUserById(id: UserId): IO[Option[Nothing], User]

/**
* Retrieves the user from the repository by username or email.
*
* @param usernameOrEmail username or email of the user.
* @return an optional [[User]].
*/
def getUserByUsernameOrEmail(usernameOrEmail: String): IO[Option[Nothing], User]

/**
* Checks if a username or email exists in the repo.
*
* @param usernameOrEmail username or email of the user.
* @return Unit in case of success
*/
def checkUsernameOrEmailExists(usernameOrEmail: String): IO[Option[Nothing], Unit]

/**
* Deletes a [[User]] from the repository by its [[UserId]].
*
* @param id the user ID
* @return Unit or None if not found
*/
def deleteUser(id: UserId): IO[Option[Nothing], UserId]
}
142 changes: 142 additions & 0 deletions dsp-user/core/src/main/scala/dsp/user/domain/UserDomain.scala
@@ -0,0 +1,142 @@
/*
* Copyright © 2021 - 2022 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package dsp.user.domain

import dsp.valueobjects.User._
import zio.prelude.Validation

import java.util.UUID

// move this to shared value objects project once we have it
sealed trait Iri
irinaschubert marked this conversation as resolved.
Show resolved Hide resolved
object Iri {

/**
* UserIri value object.
*/
sealed abstract case class UserIri private (value: String) extends Iri
object UserIri { self =>

def make(value: String): UserIri = new UserIri(value) {}
}
// ...

}

/**
* Stores the user ID, i.e. UUID and IRI of the user
*
* @param uuid the UUID of the user
* @param iri the IRI of the user
*/
abstract case class UserId private (
uuid: UUID,
iri: Iri.UserIri
)

/**
* Companion object for UserId. Contains factory methods for creating UserId instances.
*/
object UserId {

/**
* Generates a UserId instance from a given string (either UUID or IRI).
*
* @param value the string to parse (either UUID or IRI)
* @return a new UserId instance
*/
// TODO not sure if we need this
// def fromString(value: String): UserId = {
// val uuidPattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$".r
// val iriPattern = "^http*".r

// value match {
// case uuidPattern(value) => new UserId(UUID.fromString(value), Iri.UserIri.make(value)) {}
// case iriPattern(value) =>
// new UserId(UUID.fromString(value.substring(value.lastIndexOf("/") + 1)), Iri.UserIri.make(value)) {}
// //case _ => ???
// }
// }

/**
* Generates a UserId instance with a new (random) UUID and an IRI which is created from a prefix and the UUID.
*
* @return a new UserId instance
*/
def fromIri(iri: Iri.UserIri): UserId = {
val uuid: UUID = UUID.fromString(iri.value.split("/").last)
new UserId(uuid, iri) {}
}

/**
* Generates a UserId instance with a new (random) UUID and an IRI which is created from a prefix and the UUID.
*
* @return a new UserId instance
*/
def fromUuid(uuid: UUID): UserId = {
val iri: Iri.UserIri = Iri.UserIri.make("http://rdfh.ch/users/" + uuid.toString)
new UserId(uuid, iri) {}
}

/**
* Generates a UserId instance with a new (random) UUID and an IRI which is created from a prefix and the UUID.
*
* @return a new UserId instance
*/
// TODO should this return a Validation[Throwable, UserId]
def make(): UserId = {
val uuid: UUID = UUID.randomUUID()
val iri: Iri.UserIri = Iri.UserIri.make("http://rdfh.ch/users/" + uuid.toString)
new UserId(uuid, iri) {}
}
}

/**
* Represents the user domain object.
*
* @param id the ID of the user
* @param givenName the given name of the user
* @param familyName the family name of the user
* @param username the username of the user
* @param email the email of the user
* @param password the password of the user
* @param language the user's preferred language
* @param role the user's role
*/
sealed abstract case class User private (
id: UserId,
givenName: GivenName,
familyName: FamilyName,
username: Username,
email: Email,
password: Option[Password],
language: LanguageCode
//role: Role
) extends Ordered[User] { self =>

/**
* Allows to sort collections of [[User]]s. Sorting is done by the IRI.
*/
def compare(that: User): Int = self.id.iri.toString().compareTo(that.id.iri.toString())

def updateUsername(value: Username): User =
new User(self.id, self.givenName, self.familyName, value, self.email, self.password, self.language) {}
}
object User {
def make(
givenName: GivenName,
familyName: FamilyName,
username: Username,
email: Email,
password: Password,
language: LanguageCode
//role: Role
): User = {
val id = UserId.make()
new User(id, givenName, familyName, username, email, Some(password), language) {}
}

}
6 changes: 6 additions & 0 deletions dsp-user/core/src/test/scala/dsp/user/api/UserApiSpec.scala
@@ -0,0 +1,6 @@
/*
* Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package dsp.user.api
@@ -0,0 +1,6 @@
/*
* Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package dsp.user.domain