diff --git a/learningpath-api/src/main/resources/no/ndla/learningpathapi/db/migration/V30__ConvertConfigToSpecificType.sql b/learningpath-api/src/main/resources/no/ndla/learningpathapi/db/migration/V30__ConvertConfigToSpecificType.sql new file mode 100644 index 0000000000..48715dac5d --- /dev/null +++ b/learningpath-api/src/main/resources/no/ndla/learningpathapi/db/migration/V30__ConvertConfigToSpecificType.sql @@ -0,0 +1,6 @@ +UPDATE configtable c +SET value=( + SELECT value || jsonb_build_object('value', jsonb_build_object('value', (c2.value->>'value')::jsonb)) + FROM configtable c2 + WHERE c2.configkey = c.configkey +) diff --git a/learningpath-api/src/main/resources/no/ndla/learningpathapi/db/migration/V32__AddArenaEnabledToUser.sql b/learningpath-api/src/main/resources/no/ndla/learningpathapi/db/migration/V32__AddArenaEnabledToUser.sql new file mode 100644 index 0000000000..5d6fa6fa04 --- /dev/null +++ b/learningpath-api/src/main/resources/no/ndla/learningpathapi/db/migration/V32__AddArenaEnabledToUser.sql @@ -0,0 +1,6 @@ +UPDATE my_ndla_users mnu +SET document=( + SELECT document || jsonb_build_object('arenaEnabled', false) + FROM my_ndla_users mnu2 + WHERE mnu.id = mnu2.id +) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/DBMigrator.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/DBMigrator.scala index e64200a9ae..2eebecc70e 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/DBMigrator.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/DBMigrator.scala @@ -12,7 +12,8 @@ import no.ndla.learningpathapi.db.migrationwithdependencies.{ V11__CreatedByNdlaStatusForOwnersWithRoles, V13__StoreNDLAStepsAsIframeTypes, V14__ConvertLanguageUnknown, - V15__MergeDuplicateLanguageFields + V15__MergeDuplicateLanguageFields, + V31__ArenaDefaultEnabledOrgs } import no.ndla.learningpathapi.integration.DataSource import org.flywaydb.core.Flyway @@ -31,7 +32,8 @@ trait DBMigrator { new V11__CreatedByNdlaStatusForOwnersWithRoles(props), new V13__StoreNDLAStepsAsIframeTypes(props), new V14__ConvertLanguageUnknown(props), - new V15__MergeDuplicateLanguageFields(props) + new V15__MergeDuplicateLanguageFields(props), + new V31__ArenaDefaultEnabledOrgs(props) ) .locations("no/ndla/learningpathapi/db/migration") .dataSource(dataSource) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/ConfigController.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/ConfigController.scala index 89b6d756da..18e03883a8 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/ConfigController.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/ConfigController.scala @@ -9,7 +9,7 @@ package no.ndla.learningpathapi.controller import no.ndla.common.model.NDLADate import no.ndla.learningpathapi.Props -import no.ndla.learningpathapi.model.api.config.{ConfigMeta, ConfigMetaRestricted, UpdateConfigValue} +import no.ndla.learningpathapi.model.api.config.{ConfigMeta, ConfigMetaRestricted, ConfigMetaValue} import no.ndla.learningpathapi.model.api.{Error, ValidationError} import no.ndla.learningpathapi.model.domain.config.ConfigKey import no.ndla.learningpathapi.service.{ReadService, UpdateService} @@ -56,7 +56,9 @@ trait ConfigController { val configKeyString = params("config_key") ConfigKey.valueOf(configKeyString) match { case None => - BadRequest(s"No such config key was found. Must be one of '${ConfigKey.values.mkString("', '")}'") + BadRequest( + s"No such config key was found. Must be one of '${ConfigKey.all.mkString("', '")}'" + ) case Some(configKey) => callback(configKey) } } @@ -86,7 +88,7 @@ trait ConfigController { .parameters( asHeaderParam(correlationId), asPathParam(configKeyPathParam), - bodyParam[UpdateConfigValue] + bodyParam[ConfigMetaValue] ) .responseMessages(response400, response404, response403, response500) .authorizations("oauth2") @@ -94,7 +96,8 @@ trait ConfigController { ) { requireUserId { userInfo => withConfigKey(configKey => { - val newConfigValue = extract[UpdateConfigValue](request.body) + val inpString = request.body + val newConfigValue = extract[ConfigMetaValue](inpString) updateService.updateConfig(configKey, newConfigValue, userInfo) match { case Success(c) => c case Failure(ex) => errorHandler(ex) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/UserController.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/UserController.scala index a02ed2403f..5bd7cf480a 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/UserController.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/controller/UserController.scala @@ -11,6 +11,7 @@ package no.ndla.learningpathapi.controller import no.ndla.common.model.NDLADate import no.ndla.learningpathapi.model.api.{Error, ExportedUserData, MyNDLAUser, UpdatedMyNDLAUser, ValidationError} import no.ndla.learningpathapi.service.{ConverterService, ReadService, UpdateService} +import no.ndla.network.tapir.auth.Permission.LEARNINGPATH_API_ADMIN import org.json4s.ext.JavaTimeSerializers import org.json4s.{DefaultFormats, Formats} import org.scalatra.NoContent @@ -40,6 +41,7 @@ trait UserController { val response500: ResponseMessage = ResponseMessage(500, "Unknown error", Some("Error")) private val feideToken = Param[Option[String]]("FeideAuthorization", "Header containing FEIDE access token.") + private val feideId = Param[Option[String]]("feide-id", "FeideID of user") private def requestFeideToken(implicit request: HttpServletRequest): Option[String] = { request.header(this.feideToken.paramName).map(_.replaceFirst("Bearer ", "")) @@ -79,6 +81,27 @@ trait UserController { updateService.updateMyNDLAUserData(updatedUserData, requestFeideToken) }: Unit + patch( + "/update-other-user/?", + operation( + apiOperation[MyNDLAUser]("AdminUpdateMyNDLAUser") + .summary("Update some one elses user data") + .description("Update some one elses user data") + .parameters( + asQueryParam(feideId), + bodyParam[UpdatedMyNDLAUser] + ) + .responseMessages(response403, response404, response500) + .authorizations("oauth2") + ) + ) { + requirePermissionOrAccessDeniedWithUser(LEARNINGPATH_API_ADMIN) { user => + val updatedUserData = extract[UpdatedMyNDLAUser](request.body) + val feideId = paramOrNone(this.feideId.paramName) + updateService.adminUpdateMyNDLAUserData(updatedUserData, feideId, user) + } + }: Unit + delete( "/delete-personal-data/?", operation( diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migrationwithdependencies/V31__ArenaDefaultEnabledOrgs.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migrationwithdependencies/V31__ArenaDefaultEnabledOrgs.scala new file mode 100644 index 0000000000..d423887486 --- /dev/null +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/db/migrationwithdependencies/V31__ArenaDefaultEnabledOrgs.scala @@ -0,0 +1,67 @@ +/* + * Part of NDLA learningpath-api + * Copyright (C) 2023 NDLA + * + * See LICENSE + */ + +package no.ndla.learningpathapi.db.migrationwithdependencies + +import io.circe.Json +import io.circe.syntax._ +import no.ndla.common.model.NDLADate +import no.ndla.learningpathapi.LearningpathApiProperties +import org.flywaydb.core.api.migration.{BaseJavaMigration, Context} +import org.postgresql.util.PGobject +import scalikejdbc.{DB, DBSession, _} + +class V31__ArenaDefaultEnabledOrgs(properties: LearningpathApiProperties) extends BaseJavaMigration { + + override def migrate(context: Context): Unit = DB(context.getConnection) + .autoClose(false) + .withinTx { implicit session => + insertConfig + } + + def insertConfig(implicit session: DBSession): Unit = { + val document = Json.obj( + "key" -> Json.fromString("ARENA_ENABLED_ORGS"), + "value" -> Json.obj("value" -> Json.fromValues(orgs.map(Json.fromString))), + "updatedAt" -> NDLADate.now().asJson, + "updatedBy" -> Json.fromString("System") + ) + + val dataObject = new PGobject() + dataObject.setType("jsonb") + dataObject.setValue(document.noSpaces) + + val inserted = sql""" + INSERT INTO configtable(configkey, value) + VALUES ( + 'ARENA_ENABLED_ORGS', + $dataObject + ) + """.update.apply() + + if (inserted != 1) throw new RuntimeException("Failed to insert ARENA_ENABLED_ORGS") + } + + private def orgs: List[String] = properties.Environment match { + case "local" | "test" => + List( + "Agder fylkeskommune", + "Innlandet fylkeskommune", + "Møre og Romsdal fylkeskommune", + "Nordland fylkeskommune", + "Rogaland fylkeskommune", + "Troms og Finnmark fylkeskommune", + "Trøndelag fylkeskommune", + "Vestfold og Telemark fylkeskommune", + "Vestland fylkeskommune", + "Viken fylkeskommune", + "Universitetet i Rogn" + ) + case _ => List.empty + } + +} diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/MyNDLAUser.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/MyNDLAUser.scala index 7e5cd8094f..c8c9c9b78e 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/MyNDLAUser.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/MyNDLAUser.scala @@ -15,9 +15,12 @@ case class MyNDLAUser( @(ApiModelProperty @field)(description = "ID of the user") id: Long, @(ApiModelProperty @field)(description = "Favorite subjects of the user") favoriteSubjects: Seq[String], @(ApiModelProperty @field)(description = "User role") role: String, - @(ApiModelProperty @field)(description = "User organization") organization: String + @(ApiModelProperty @field)(description = "User organization") organization: String, + @(ApiModelProperty @field)(description = "Whether arena is explicitly enabled for the user") arenaEnabled: Boolean ) +// format: off case class UpdatedMyNDLAUser( - @(ApiModelProperty @field)(description = "Favorite subjects of the user") favoriteSubjects: Option[Seq[String]] + @(ApiModelProperty @field)(description = "Favorite subjects of the user") favoriteSubjects: Option[Seq[String]], + @(ApiModelProperty @field)(description = "Whether arena should explicitly be enabled for the user") arenaEnabled: Option[Boolean] ) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMeta.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMeta.scala index 5f288a11bb..dc153bc003 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMeta.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMeta.scala @@ -11,14 +11,10 @@ import no.ndla.common.model.NDLADate import org.scalatra.swagger.annotations.ApiModelProperty import org.scalatra.swagger.runtime.annotations.ApiModel -import scala.annotation.meta.field - @ApiModel(description = "Describes configuration value.") case class ConfigMeta( - @(ApiModelProperty @field)(description = "Configuration key") key: String, - @(ApiModelProperty @field)(description = "Configuration value.") value: String, - @(ApiModelProperty @field)(description = "Date of when configuration was last updated") updatedAt: NDLADate, - @(ApiModelProperty @field)( - description = "UserId of who last updated the configuration parameter." - ) updatedBy: String + @ApiModelProperty(description = "Configuration key") key: String, + @ApiModelProperty(description = "Configuration value.") value: Either[Boolean, List[String]], + @ApiModelProperty(description = "Date of when configuration was last updated") updatedAt: NDLADate, + @ApiModelProperty(description = "UserId of who last updated the configuration parameter.") updatedBy: String ) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMetaRestricted.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMetaRestricted.scala index 10b67b86da..aafabe99d6 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMetaRestricted.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMetaRestricted.scala @@ -15,5 +15,5 @@ import scala.annotation.meta.field @ApiModel(description = "Describes configuration value.") case class ConfigMetaRestricted( @(ApiModelProperty @field)(description = "Configuration key") key: String, - @(ApiModelProperty @field)(description = "Configuration value.") value: String + @(ApiModelProperty @field)(description = "Configuration value.") value: Either[Boolean, List[String]] ) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMetaValue.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMetaValue.scala new file mode 100644 index 0000000000..fbf87fd2de --- /dev/null +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/ConfigMetaValue.scala @@ -0,0 +1,30 @@ +/* + * Part of NDLA learningpath-api. + * Copyright (C) 2023 NDLA + * + * See LICENSE + */ + +package no.ndla.learningpathapi.model.api.config + +import no.ndla.learningpathapi.model.domain +import org.scalatra.swagger.annotations.ApiModelProperty + +import scala.annotation.meta.field + +case class ConfigMetaValue( + @(ApiModelProperty @field)(description = "Value to set configuration param to.") + value: Either[Boolean, List[String]] +) + +object ConfigMetaValue { + def from(value: domain.config.ConfigMetaValue): ConfigMetaValue = { + value match { + case domain.config.BooleanValue(value) => ConfigMetaValue(Left(value)) + case domain.config.StringListValue(value) => ConfigMetaValue(Right(value)) + } + } + + def apply(value: Boolean): ConfigMetaValue = ConfigMetaValue(Left(value)) + def apply(value: List[String]): ConfigMetaValue = ConfigMetaValue(Right(value)) +} diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/UpdateConfigValue.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/UpdateConfigValue.scala deleted file mode 100644 index c097010b37..0000000000 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/api/config/UpdateConfigValue.scala +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Part of NDLA learningpath-api. - * Copyright (C) 2019 NDLA - * - * See LICENSE - */ - -package no.ndla.learningpathapi.model.api.config - -import org.scalatra.swagger.annotations.{ApiModel, ApiModelProperty} - -import scala.annotation.meta.field - -@ApiModel(description = "Info for updating a configuration parameter") -case class UpdateConfigValue( - @(ApiModelProperty @field)(description = "Value to set configuration param to.") value: String -) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/MyNDLAUser.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/MyNDLAUser.scala index e21fdf8e3e..7419cfc802 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/MyNDLAUser.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/MyNDLAUser.scala @@ -21,7 +21,8 @@ case class MyNDLAUserDocument( userRole: UserRole.Value, lastUpdated: NDLADate, organization: String, - email: String + email: String, + arenaEnabled: Boolean ) { def toFullUser( id: Long, @@ -34,7 +35,8 @@ case class MyNDLAUserDocument( userRole = userRole, lastUpdated = lastUpdated, organization = organization, - email = email + email = email, + arenaEnabled = arenaEnabled ) } } @@ -46,16 +48,9 @@ case class MyNDLAUser( userRole: UserRole.Value, lastUpdated: NDLADate, organization: String, - email: String + email: String, + arenaEnabled: Boolean ) { - def toDocument: MyNDLAUserDocument = MyNDLAUserDocument( - favoriteSubjects = favoriteSubjects, - userRole = userRole, - lastUpdated = lastUpdated, - organization = organization, - email = email - ) - // Keeping FEIDE and our data in sync def wasUpdatedLast24h: Boolean = NDLADate.now().isBefore(lastUpdated.minusSeconds(10)) diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigKey.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigKey.scala index 90cd3f2478..64b694d271 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigKey.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigKey.scala @@ -11,14 +11,14 @@ import enumeratum._ sealed abstract class ConfigKey(override val entryName: String) extends EnumEntry -object ConfigKey extends Enum[ConfigKey] { +object ConfigKey extends Enum[ConfigKey] with CirceEnum[ConfigKey] { case object LearningpathWriteRestricted extends ConfigKey("LEARNINGPATH_WRITE_RESTRICTED") case object MyNDLAWriteRestricted extends ConfigKey("MY_NDLA_WRITE_RESTRICTED") + case object ArenaEnabledOrgs extends ConfigKey("ARENA_ENABLED_ORGS") val values: IndexedSeq[ConfigKey] = findValues val all: Seq[String] = values.map(_.entryName) def valueOf(s: String): Option[ConfigKey] = ConfigKey.values.find(_.entryName == s) - } diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigMeta.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigMeta.scala index 82f3039298..95448d3875 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigMeta.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/model/domain/config/ConfigMeta.scala @@ -7,28 +7,67 @@ package no.ndla.learningpathapi.model.domain.config +import cats.implicits.toFunctorOps +import com.typesafe.scalalogging.StrictLogging import enumeratum.Json4s +import io.circe.{Decoder, Encoder} +import io.circe.generic.semiauto._ +import io.circe.parser.parse +import io.circe.syntax._ import no.ndla.common.errors.{ValidationException, ValidationMessage} import no.ndla.common.model.NDLADate import no.ndla.learningpathapi.Props +import no.ndla.learningpathapi.model.api import org.json4s.Formats import org.json4s.ext.JavaTimeSerializers -import org.json4s.native.Serialization._ import scalikejdbc._ import scala.util.{Failure, Success, Try} +sealed trait ConfigMetaValue +case class BooleanValue(value: Boolean) extends ConfigMetaValue +case class StringListValue(value: List[String]) extends ConfigMetaValue + +object ConfigMetaValue { + + import io.circe.{Decoder, Encoder}, io.circe.generic.auto._ + + implicit def encoder: Encoder[ConfigMetaValue] = Encoder.instance { + case bool @ BooleanValue(_) => bool.asJson + case strList @ StringListValue(_) => strList.asJson + } + implicit def decoder: Decoder[ConfigMetaValue] = { + List[Decoder[ConfigMetaValue]]( + Decoder[BooleanValue].widen, + Decoder[StringListValue].widen + ).reduceLeft(_ or _) + } + + def from(configMetaValue: api.config.ConfigMetaValue): ConfigMetaValue = configMetaValue.value match { + case Left(value) => BooleanValue(value) + case Right(value) => StringListValue(value) + } + +} + case class ConfigMeta( key: ConfigKey, - value: String, + value: ConfigMetaValue, updatedAt: NDLADate, updatedBy: String ) { + def valueToEither: Either[Boolean, List[String]] = { + value match { + case BooleanValue(value) => Left(value) + case StringListValue(value) => Right(value) + } + } + private def validateBooleanKey(configKey: ConfigKey): Try[ConfigMeta] = { - Try(value.toBoolean) match { - case Success(_) => Success(this) - case Failure(_) => + value match { + case BooleanValue(_) => Success(this) + case _ => val validationMessage = ValidationMessage( "value", s"Value of '${configKey.entryName}' must be a boolean string ('true' or 'false')" @@ -36,16 +75,28 @@ case class ConfigMeta( Failure(new ValidationException(s"Invalid config value specified.", Seq(validationMessage))) } } + private def validateStringListKey(orgs: ConfigKey): Try[ConfigMeta] = { + value match { + case StringListValue(_) => Success(this) + case _ => + val validationMessage = ValidationMessage( + "value", + s"Value of '${orgs.entryName}' must be a list of strings" + ) + Failure(new ValidationException(s"Invalid config value specified.", Seq(validationMessage))) + } + } def validate: Try[ConfigMeta] = key match { case ConfigKey.LearningpathWriteRestricted => validateBooleanKey(ConfigKey.LearningpathWriteRestricted) case ConfigKey.MyNDLAWriteRestricted => validateBooleanKey(ConfigKey.MyNDLAWriteRestricted) + case ConfigKey.ArenaEnabledOrgs => validateStringListKey(ConfigKey.ArenaEnabledOrgs) } } trait DBConfigMeta { this: Props => - object DBConfigMeta extends SQLSyntaxSupport[ConfigMeta] { + object DBConfigMeta extends SQLSyntaxSupport[ConfigMeta] with StrictLogging { implicit val formats: Formats = org.json4s.DefaultFormats + Json4s.serializer(ConfigKey) ++ @@ -56,10 +107,21 @@ trait DBConfigMeta { override val schemaName = Some(props.MetaSchema) def fromResultSet(c: SyntaxProvider[ConfigMeta])(rs: WrappedResultSet): ConfigMeta = fromResultSet(c.resultName)(rs) + import ConfigMetaValue._ + + implicit val enc: Encoder[ConfigMeta] = deriveEncoder[ConfigMeta] + implicit val dec: Decoder[ConfigMeta] = deriveDecoder[ConfigMeta] def fromResultSet(c: ResultName[ConfigMeta])(rs: WrappedResultSet): ConfigMeta = { - val meta = read[ConfigMeta](rs.string(c.column("value"))) - meta + val dbStr = rs.string(c.column("value")) + val parsed = parse(dbStr) + parsed.flatMap(_.as[ConfigMeta]) match { + case Right(json) => json + case Left(err) => + logger.error(s"Could not parse json from database: '$dbStr'", err) + // TODO: Would love to propagate these errors via `Try/Either` instead of throwing an exception + throw new RuntimeException("Could not parse json from database") + } } } diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/repository/ConfigRepository.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/repository/ConfigRepository.scala index 3686a8f897..fc99f9ffa0 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/repository/ConfigRepository.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/repository/ConfigRepository.scala @@ -8,12 +8,9 @@ package no.ndla.learningpathapi.repository import com.typesafe.scalalogging.StrictLogging -import no.ndla.common.model.NDLADate +import io.circe.syntax._ import no.ndla.learningpathapi.integration.DataSource import no.ndla.learningpathapi.model.domain.config.{ConfigKey, ConfigMeta, DBConfigMeta} -import org.json4s.Formats -import org.json4s.ext.JavaTimeSerializers -import org.json4s.native.Serialization._ import org.postgresql.util.PGobject import scalikejdbc._ import sqls.count @@ -24,14 +21,15 @@ trait ConfigRepository { this: DataSource with DBConfigMeta => val configRepository: ConfigRepository + import DBConfigMeta._ + class ConfigRepository extends StrictLogging { - implicit val formats: Formats = DBConfigMeta.formats ++ JavaTimeSerializers.all + NDLADate.Json4sSerializer implicit val configValueParameterBinderFactory: ParameterBinderFactory[ConfigMeta] = ParameterBinderFactory[ConfigMeta] { value => (stmt, idx) => { val dataObject = new PGobject() dataObject.setType("jsonb") - dataObject.setValue(write(value)) + dataObject.setValue(value.asJson.noSpaces) stmt.setObject(idx, dataObject) } } diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala index 6767d841a1..91d9755a4e 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ConverterService.scala @@ -14,7 +14,7 @@ import no.ndla.common.{Clock, errors} import no.ndla.common.errors.ValidationException import no.ndla.common.model.domain.learningpath import no.ndla.common.model.domain.learningpath.{EmbedType, EmbedUrl} -import no.ndla.common.model.{domain => common, api => commonApi} +import no.ndla.common.model.{api => commonApi, domain => common} import no.ndla.language.Language.{ AllLanguages, UnknownLanguage, @@ -33,6 +33,7 @@ import no.ndla.learningpathapi.repository.LearningPathRepositoryComponent import no.ndla.learningpathapi.validation.{LanguageValidator, LearningPathValidator} import no.ndla.mapping.License.getLicense import no.ndla.network.ApplicationUrl +import no.ndla.network.tapir.auth.Permission.LEARNINGPATH_API_ADMIN import no.ndla.network.tapir.auth.TokenUser import java.util.UUID @@ -670,14 +671,18 @@ trait ConverterService { def asApiConfig(configValue: ConfigMeta): api.config.ConfigMeta = { api.config.ConfigMeta( configValue.key.entryName, - configValue.value, + configValue.valueToEither, configValue.updatedAt, configValue.updatedBy ) } - def asApiConfigRestricted(configValue: ConfigMeta): api.config.ConfigMetaRestricted = - api.config.ConfigMetaRestricted(key = configValue.key.entryName, value = configValue.value) + def asApiConfigRestricted(configValue: ConfigMeta): api.config.ConfigMetaRestricted = { + api.config.ConfigMetaRestricted( + key = configValue.key.entryName, + value = configValue.valueToEither + ) + } def toUUIDValidated(maybeValue: Option[String], paramName: String): Try[UUID] = { val maybeUUID = maybeValue.map(value => Try(UUID.fromString(value))) @@ -824,17 +829,27 @@ trait ConverterService { ) } - def toApiUserData(domainUserData: domain.MyNDLAUser): api.MyNDLAUser = { + def toApiUserData(domainUserData: domain.MyNDLAUser, arenaEnabledOrgs: List[String]): api.MyNDLAUser = { api.MyNDLAUser( id = domainUserData.id, favoriteSubjects = domainUserData.favoriteSubjects, role = domainUserData.userRole.toString, - organization = domainUserData.organization + organization = domainUserData.organization, + arenaEnabled = domainUserData.arenaEnabled || arenaEnabledOrgs.contains(domainUserData.organization) ) } - def mergeUserData(domainUserData: domain.MyNDLAUser, updatedUser: api.UpdatedMyNDLAUser): domain.MyNDLAUser = { + def mergeUserData( + domainUserData: domain.MyNDLAUser, + updatedUser: api.UpdatedMyNDLAUser, + user: Option[TokenUser] + ): domain.MyNDLAUser = { val favoriteSubjects = updatedUser.favoriteSubjects.getOrElse(domainUserData.favoriteSubjects) + val arenaEnabled = { + if (user.exists(_.hasPermission(LEARNINGPATH_API_ADMIN))) + updatedUser.arenaEnabled.getOrElse(domainUserData.arenaEnabled) + else domainUserData.arenaEnabled + } domain.MyNDLAUser( id = domainUserData.id, @@ -843,7 +858,8 @@ trait ConverterService { userRole = domainUserData.userRole, lastUpdated = domainUserData.lastUpdated, organization = domainUserData.organization, - email = domainUserData.email + email = domainUserData.email, + arenaEnabled = arenaEnabled ) } diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ReadService.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ReadService.scala index ed3e25acba..87c08882d6 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ReadService.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/ReadService.scala @@ -198,18 +198,28 @@ trait ReadService { } def isWriteRestricted: Boolean = - Try( - configRepository - .getConfigWithKey(ConfigKey.LearningpathWriteRestricted) - .map(_.value.toBoolean) - ).toOption.flatten.getOrElse(false) + configRepository + .getConfigWithKey(ConfigKey.LearningpathWriteRestricted) + .map(_.value) + .collectFirst { case domain.config.BooleanValue(value) => value } + .getOrElse(false) def isMyNDLAWriteRestricted: Boolean = - Try( + configRepository + .getConfigWithKey(ConfigKey.MyNDLAWriteRestricted) + .map(_.value) + .collectFirst { case domain.config.BooleanValue(value) => value } + .getOrElse(false) + + def getMyNDLAEnabledOrgs: Try[List[String]] = { + Try { configRepository - .getConfigWithKey(ConfigKey.MyNDLAWriteRestricted) - .map(_.value.toBoolean) - ).toOption.flatten.getOrElse(false) + .getConfigWithKey(ConfigKey.ArenaEnabledOrgs) + .map(_.value) + .collectFirst { case domain.config.StringListValue(value) => value } + .getOrElse(List.empty) + } + } def getConfig(configKey: ConfigKey): Try[api.config.ConfigMetaRestricted] = { configRepository.getConfigWithKey(configKey) match { @@ -392,7 +402,8 @@ trait ReadService { userRole = if (feideExtendedUserData.isTeacher) UserRole.TEACHER else UserRole.STUDENT, lastUpdated = clock.now().plusDays(1), organization = organization, - email = feideExtendedUserData.email + email = feideExtendedUserData.email, + arenaEnabled = false ) inserted <- userRepository.insertUser(feideId, newUser)(session) } yield inserted @@ -414,7 +425,8 @@ trait ReadService { userRole = if (feideUser.isTeacher) UserRole.TEACHER else UserRole.STUDENT, lastUpdated = clock.now().plusDays(1), organization = organization, - email = feideUser.email + email = feideUser.email, + arenaEnabled = userData.arenaEnabled ) userRepository.updateUser(feideId, updatedMyNDLAUser)(session) } @@ -435,15 +447,18 @@ trait ReadService { private def getFeideUserDataAuthenticated( feideId: FeideID, feideAccessToken: Option[FeideAccessToken] - ): Try[api.MyNDLAUser] = { - getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(AutoSession).map(converterService.toApiUserData) - } + ): Try[api.MyNDLAUser] = + for { + user <- getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(AutoSession) + orgs <- readService.getMyNDLAEnabledOrgs + } yield converterService.toApiUserData(user, orgs) def getMyNDLAUserData(feideAccessToken: Option[FeideAccessToken]): Try[api.MyNDLAUser] = { for { feideId <- feideApiClient.getFeideID(feideAccessToken) userData <- getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(AutoSession) - api = converterService.toApiUserData(userData) + orgs <- readService.getMyNDLAEnabledOrgs + api = converterService.toApiUserData(userData, orgs) } yield api } diff --git a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala index 6dd2bc49b2..49f40a4d18 100644 --- a/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala +++ b/learningpath-api/src/main/scala/no/ndla/learningpathapi/service/UpdateService.scala @@ -14,7 +14,6 @@ import no.ndla.common.errors.{AccessDeniedException, ValidationException} import no.ndla.common.implicits._ import no.ndla.learningpathapi.Props import no.ndla.learningpathapi.integration.{SearchApiClient, TaxonomyApiClient} -import no.ndla.learningpathapi.model.api.config.UpdateConfigValue import no.ndla.learningpathapi.model.api.{config, _} import no.ndla.learningpathapi.model.{api, domain} import no.ndla.learningpathapi.model.domain.FolderSortObject.{FolderSorting, ResourceSorting, RootFolderSorting} @@ -371,12 +370,17 @@ trait UpdateService { } } - def updateConfig(configKey: ConfigKey, value: UpdateConfigValue, userInfo: TokenUser): Try[config.ConfigMeta] = { - + def updateConfig( + configKey: ConfigKey, + value: api.config.ConfigMetaValue, + userInfo: TokenUser + ): Try[config.ConfigMeta] = { writeOrAccessDenied(userInfo.isAdmin, "Only administrators can edit configuration.") { - ConfigMeta(configKey, value.value, clock.now(), userInfo.id).validate.flatMap(newConfigValue => { - configRepository.updateConfigParam(newConfigValue).map(converterService.asApiConfig) - }) + val config = ConfigMeta(configKey, domain.config.ConfigMetaValue.from(value), clock.now(), userInfo.id) + for { + validated <- config.validate + stored <- configRepository.updateConfigParam(validated) + } yield converterService.asApiConfig(stored) } } @@ -820,6 +824,24 @@ trait UpdateService { .flatMap(feideId => updateFeideUserDataAuthenticated(updatedUser, feideId, feideAccessToken)(AutoSession)) } + def adminUpdateMyNDLAUserData( + updatedUser: api.UpdatedMyNDLAUser, + feideId: Option[String], + user: TokenUser + ): Try[api.MyNDLAUser] = { + feideId match { + case None => Failure(ValidationException("feideId", "You need to supply a feideId to update a user.")) + case Some(id) => + for { + existing <- getMyNDLAUserOrFail(id) + converted = converterService.mergeUserData(existing, updatedUser, Some(user)) + updated <- userRepository.updateUser(id, converted) + enabledOrgs <- readService.getMyNDLAEnabledOrgs + api = converterService.toApiUserData(updated, enabledOrgs) + } yield api + } + } + private def updateFeideUserDataAuthenticated( updatedUser: api.UpdatedMyNDLAUser, feideId: FeideID, @@ -828,9 +850,10 @@ trait UpdateService { for { _ <- canWriteDuringMyNDLAWriteRestrictionsOrAccessDenied(feideId, feideAccessToken) existingUserData <- getMyNDLAUserOrFail(feideId) - combined = converterService.mergeUserData(existingUserData, updatedUser) - updated <- userRepository.updateUser(feideId, combined) - api = converterService.toApiUserData(updated) + combined = converterService.mergeUserData(existingUserData, updatedUser, None) + updated <- userRepository.updateUser(feideId, combined) + enabledOrgs <- readService.getMyNDLAEnabledOrgs + api = converterService.toApiUserData(updated, enabledOrgs) } yield api } @@ -1034,7 +1057,7 @@ trait UpdateService { for { existingUser <- readService.getOrCreateMyNDLAUserIfNotExist(feideId, feideAccessToken)(session) newFavorites = (existingUser.favoriteSubjects ++ userData.favoriteSubjects).distinct - updatedFeideUser = api.UpdatedMyNDLAUser(favoriteSubjects = Some(newFavorites)) + updatedFeideUser = api.UpdatedMyNDLAUser(favoriteSubjects = Some(newFavorites), arenaEnabled = None) updated <- updateFeideUserDataAuthenticated(updatedFeideUser, feideId, feideAccessToken)(session) } yield updated diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala index 8161a6397d..a60b59fe6c 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/TestData.scala @@ -23,7 +23,7 @@ import no.ndla.learningpathapi.model.domain.{ SearchSettings, Sort } -import no.ndla.learningpathapi.model.domain.config.{ConfigKey, ConfigMeta} +import no.ndla.learningpathapi.model.domain.config.{BooleanValue, ConfigKey, ConfigMeta} object TestData { @@ -47,7 +47,7 @@ object TestData { val testConfigMeta: ConfigMeta = domain.config.ConfigMeta( ConfigKey.LearningpathWriteRestricted, - value = "true", + value = BooleanValue(true), today, "EnKulFyr" ) @@ -172,7 +172,8 @@ object TestData { userRole = domain.UserRole.TEACHER, lastUpdated = today, organization = "", - email = "" + email = "", + arenaEnabled = false ) } diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/controller/ConfigControllerTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/controller/ConfigControllerTest.scala index fe9f464bba..fd77998e7a 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/controller/ConfigControllerTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/controller/ConfigControllerTest.scala @@ -9,18 +9,17 @@ package no.ndla.learningpathapi.controller import no.ndla.common.model.NDLADate import no.ndla.learningpathapi.TestData._ -import no.ndla.learningpathapi.model.api.config.{ConfigMeta, UpdateConfigValue} +import no.ndla.learningpathapi.model.api.config.{ConfigMeta, ConfigMetaValue} import no.ndla.learningpathapi.model.domain.config.ConfigKey import no.ndla.learningpathapi.{TestEnvironment, UnitSuite} import no.ndla.network.tapir.auth.TokenUser -import org.json4s.DefaultFormats +import org.json4s.Formats import org.scalatra.test.scalatest.ScalatraFunSuite import scala.util.Success class ConfigControllerTest extends UnitSuite with TestEnvironment with ScalatraFunSuite { - - implicit val formats: DefaultFormats.type = org.json4s.DefaultFormats + implicit val formats: Formats = org.json4s.DefaultFormats implicit val swagger: LearningpathSwagger = new LearningpathSwagger lazy val controller = new ConfigController @@ -33,14 +32,21 @@ class ConfigControllerTest extends UnitSuite with TestEnvironment with ScalatraF } test("That updating config returns 200 if all is good") { - when(updateService.updateConfig(any[ConfigKey], any[UpdateConfigValue], any[TokenUser])) + when(updateService.updateConfig(any[ConfigKey], any[ConfigMetaValue], any[TokenUser])) .thenReturn( - Success(ConfigMeta(ConfigKey.LearningpathWriteRestricted.entryName, "true", NDLADate.now(), "someoneCool")) + Success( + ConfigMeta( + ConfigKey.LearningpathWriteRestricted.entryName, + Left(true), + NDLADate.now(), + "someoneCool" + ) + ) ) post( s"/${ConfigKey.LearningpathWriteRestricted.entryName}", - body = "{\"value\": \"true\"}", + body = "{\"value\": true}", headers = Map("Authorization" -> s"Bearer $adminScopeClientToken") ) { status should be(200) @@ -48,7 +54,7 @@ class ConfigControllerTest extends UnitSuite with TestEnvironment with ScalatraF post( s"/${ConfigKey.LearningpathWriteRestricted.entryName}", - body = "{\"value\": \"true\"}", + body = "{\"value\": true}", headers = Map("Authorization" -> s"Bearer $adminAndWriteScopeClientToken") ) { status should be(200) diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/model/domain/config/ConfigMetaTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/model/domain/config/ConfigMetaTest.scala index d1eb2d3e47..aad20bdfc3 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/model/domain/config/ConfigMetaTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/model/domain/config/ConfigMetaTest.scala @@ -16,7 +16,7 @@ class ConfigMetaTest extends UnitSuite with UnitTestEnvironment { try { ConfigMeta( key = key, - value = "", + value = BooleanValue(true), updatedAt = TestData.today, updatedBy = "OneCoolKid" ).validate diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/ConfigRepositoryTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/ConfigRepositoryTest.scala index 805cd2b709..1cc4f27e81 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/ConfigRepositoryTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/repository/ConfigRepositoryTest.scala @@ -9,7 +9,7 @@ package no.ndla.learningpathapi.repository import com.zaxxer.hikari.HikariDataSource import no.ndla.common.model.NDLADate -import no.ndla.learningpathapi.model.domain.config.{ConfigKey, ConfigMeta} +import no.ndla.learningpathapi.model.domain.config.{BooleanValue, ConfigKey, ConfigMeta} import no.ndla.learningpathapi.{TestEnvironment, UnitSuite} import no.ndla.scalatestsuite.IntegrationSuite import no.ndla.tag.IntegrationTest @@ -71,7 +71,7 @@ class ConfigRepositoryTest test("That updating configKey from empty database inserts config") { val newConfig = ConfigMeta( key = ConfigKey.LearningpathWriteRestricted, - value = "true", + value = BooleanValue(true), updatedAt = NDLADate.fromUnixTime(0), updatedBy = "ndlaUser1" ) @@ -85,7 +85,7 @@ class ConfigRepositoryTest test("That updating config works as expected") { val originalConfig = ConfigMeta( key = ConfigKey.LearningpathWriteRestricted, - value = "true", + value = BooleanValue(true), updatedAt = NDLADate.fromUnixTime(0), updatedBy = "ndlaUser1" ) @@ -96,7 +96,7 @@ class ConfigRepositoryTest val updatedConfig = ConfigMeta( key = ConfigKey.LearningpathWriteRestricted, - value = "false", + value = BooleanValue(false), updatedAt = NDLADate.fromUnixTime(10000), updatedBy = "ndlaUser2" ) diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala index 7761e9f42c..b1ae6340b9 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ConverterServiceTest.scala @@ -873,12 +873,19 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false ) val expectedUserData = - api.MyNDLAUser(id = 42, favoriteSubjects = Seq("a", "b"), role = "student", organization = "oslo") + api.MyNDLAUser( + id = 42, + favoriteSubjects = Seq("a", "b"), + role = "student", + organization = "oslo", + arenaEnabled = false + ) - service.toApiUserData(domainUserData) should be(expectedUserData) + service.toApiUserData(domainUserData, List.empty) should be(expectedUserData) } test("That mergeUserData works correctly") { @@ -889,11 +896,12 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false ) - val updatedUserData1 = api.UpdatedMyNDLAUser(favoriteSubjects = None) - val updatedUserData2 = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq.empty)) - val updatedUserData3 = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq("x", "y", "z"))) + val updatedUserData1 = api.UpdatedMyNDLAUser(favoriteSubjects = None, arenaEnabled = None) + val updatedUserData2 = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq.empty), arenaEnabled = None) + val updatedUserData3 = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq("x", "y", "z")), arenaEnabled = None) val expectedUserData1 = domain.MyNDLAUser( id = 42, @@ -902,7 +910,8 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false ) val expectedUserData2 = domain.MyNDLAUser( id = 42, @@ -911,7 +920,8 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false ) val expectedUserData3 = domain.MyNDLAUser( id = 42, @@ -920,11 +930,12 @@ class ConverterServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false ) - service.mergeUserData(domainUserData, updatedUserData1) should be(expectedUserData1) - service.mergeUserData(domainUserData, updatedUserData2) should be(expectedUserData2) - service.mergeUserData(domainUserData, updatedUserData3) should be(expectedUserData3) + service.mergeUserData(domainUserData, updatedUserData1, None) should be(expectedUserData1) + service.mergeUserData(domainUserData, updatedUserData2, None) should be(expectedUserData2) + service.mergeUserData(domainUserData, updatedUserData3, None) should be(expectedUserData3) } } diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala index aba5c1f7cb..6dc444d88a 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/ReadServiceTest.scala @@ -611,15 +611,23 @@ class ReadServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false + ) + val apiUserData = api.MyNDLAUser( + id = 42, + favoriteSubjects = Seq("r", "e"), + role = "student", + organization = "oslo", + arenaEnabled = false ) - val apiUserData = api.MyNDLAUser(id = 42, favoriteSubjects = Seq("r", "e"), role = "student", organization = "oslo") val feideUserInfo = FeideExtendedUserInfo( displayName = "David", eduPersonAffiliation = Seq("student"), eduPersonPrincipalName = "example@email.com" ) + when(readService.getMyNDLAEnabledOrgs).thenReturn(Success(List.empty)) when(feideApiClient.getFeideID(any)).thenReturn(Success(feideId)) when(feideApiClient.getFeideAccessTokenOrFail(any)).thenReturn(Success(feideId)) when(feideApiClient.getFeideExtendedUser(any)).thenReturn(Success(feideUserInfo)) @@ -648,10 +656,18 @@ class ReadServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now().plusDays(1), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false + ) + val apiUserData = api.MyNDLAUser( + id = 42, + favoriteSubjects = Seq("r", "e"), + role = "student", + organization = "oslo", + arenaEnabled = false ) - val apiUserData = api.MyNDLAUser(id = 42, favoriteSubjects = Seq("r", "e"), role = "student", organization = "oslo") + when(readService.getMyNDLAEnabledOrgs).thenReturn(Success(List.empty)) when(feideApiClient.getFeideID(Some(feideId))).thenReturn(Success(feideId)) when(userRepository.userWithFeideId(eqTo(feideId))(any)).thenReturn(Success(Some(domainUserData))) @@ -674,15 +690,23 @@ class ReadServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now().minusDays(1), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false ) val updatedFeideUser = FeideExtendedUserInfo( displayName = "name", eduPersonAffiliation = Seq.empty, eduPersonPrincipalName = "example@email.com" ) - val apiUserData = api.MyNDLAUser(id = 42, favoriteSubjects = Seq("r", "e"), role = "student", organization = "oslo") + val apiUserData = api.MyNDLAUser( + id = 42, + favoriteSubjects = Seq("r", "e"), + role = "student", + organization = "oslo", + arenaEnabled = false + ) + when(readService.getMyNDLAEnabledOrgs).thenReturn(Success(List.empty)) when(feideApiClient.getFeideID(Some(feideId))).thenReturn(Success(feideId)) when(feideApiClient.getFeideExtendedUser(Some(feideId))).thenReturn(Success(updatedFeideUser)) when(feideApiClient.getOrganization(Some(feideId))).thenReturn(Success("oslo")) diff --git a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala index 8188b2f6e7..18a75565a1 100644 --- a/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala +++ b/learningpath-api/src/test/scala/no/ndla/learningpathapi/service/UpdateServiceTest.scala @@ -8,14 +8,14 @@ package no.ndla.learningpathapi.service -import no.ndla.common.model.{NDLADate, domain => common, api => commonApi} +import no.ndla.common.model.{NDLADate, api => commonApi, domain => common} import no.ndla.common.errors.{AccessDeniedException, ValidationException} import no.ndla.common.model.domain.{Author, Title} import no.ndla.common.model.domain.learningpath.LearningpathCopyright import no.ndla.learningpathapi.TestData._ import no.ndla.learningpathapi._ import no.ndla.learningpathapi.model._ -import no.ndla.learningpathapi.model.api.config.UpdateConfigValue +import no.ndla.learningpathapi.model.api.config.ConfigMetaValue import no.ndla.learningpathapi.model.api.{ FolderSortRequest, NewCopyLearningPathV2, @@ -1444,7 +1444,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { .thenReturn(Success(TestData.testConfigMeta)) val Failure(ex) = service.updateConfig( ConfigKey.LearningpathWriteRestricted, - UpdateConfigValue("true"), + ConfigMetaValue(true), TokenUser("Kari", Set(LEARNINGPATH_API_PUBLISH), None) ) ex.isInstanceOf[AccessDeniedException] should be(true) @@ -1455,7 +1455,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { .thenReturn(Success(TestData.testConfigMeta)) val Success(_) = service.updateConfig( ConfigKey.LearningpathWriteRestricted, - UpdateConfigValue("true"), + ConfigMetaValue(true), TokenUser("Kari", Set(LEARNINGPATH_API_ADMIN), None) ) } @@ -1465,7 +1465,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { .thenReturn(Success(TestData.testConfigMeta)) val Failure(ex) = service.updateConfig( ConfigKey.LearningpathWriteRestricted, - UpdateConfigValue("123"), + ConfigMetaValue(List("123")), TokenUser("Kari", Set(LEARNINGPATH_API_ADMIN), None) ) @@ -1477,7 +1477,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { .thenReturn(Success(TestData.testConfigMeta)) val res = service.updateConfig( ConfigKey.LearningpathWriteRestricted, - UpdateConfigValue("true"), + ConfigMetaValue(true), TokenUser("Kari", Set(LEARNINGPATH_API_ADMIN), None) ) res.isSuccess should be(true) @@ -2177,9 +2177,10 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false ) - val updatedUserData = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq("r", "e"))) + val updatedUserData = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq("r", "e")), arenaEnabled = None) val userAfterMerge = domain.MyNDLAUser( id = 42, feideId = feideId, @@ -2187,12 +2188,20 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { userRole = UserRole.STUDENT, lastUpdated = clock.now(), organization = "oslo", - email = "example@email.com" + email = "example@email.com", + arenaEnabled = false + ) + val expected = api.MyNDLAUser( + id = 42, + favoriteSubjects = Seq("r", "e"), + role = "student", + organization = "oslo", + arenaEnabled = false ) - val expected = api.MyNDLAUser(id = 42, favoriteSubjects = Seq("r", "e"), role = "student", organization = "oslo") when(feideApiClient.getFeideID(any)).thenReturn(Success(feideId)) when(readService.getOrCreateMyNDLAUserIfNotExist(any, any)(any)).thenReturn(Success(emptyMyNDLAUser)) + when(readService.getMyNDLAEnabledOrgs).thenReturn(Success(List.empty)) when(userRepository.userWithFeideId(eqTo(feideId))(any)).thenReturn(Success(Some(userBefore))) when(userRepository.updateUser(eqTo(feideId), any)(any)).thenReturn(Success(userAfterMerge)) @@ -2204,7 +2213,7 @@ class UpdateServiceTest extends UnitSuite with UnitTestEnvironment { test("That updateUserData fails if user does not exist") { val feideId = "feide" - val updatedUserData = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq("r", "e"))) + val updatedUserData = api.UpdatedMyNDLAUser(favoriteSubjects = Some(Seq("r", "e")), arenaEnabled = None) when(feideApiClient.getFeideID(any)).thenReturn(Success(feideId)) when(readService.getOrCreateMyNDLAUserIfNotExist(any, any)(any)).thenReturn(Success(emptyMyNDLAUser)) diff --git a/typescript/learningpath-api.ts b/typescript/learningpath-api.ts index 3941db6d32..f7cd99af11 100644 --- a/typescript/learningpath-api.ts +++ b/typescript/learningpath-api.ts @@ -12,14 +12,14 @@ export interface IBreadcrumb { export interface IConfigMeta { key: string - value: string + value: (boolean | string[]) updatedAt: string updatedBy: string } export interface IConfigMetaRestricted { key: string - value: string + value: (boolean | string[]) } export interface ICopyright { @@ -179,6 +179,7 @@ export interface IMyNDLAUser { favoriteSubjects: string[] role: string organization: string + arenaEnabled: boolean } export interface INewFolder {