Repository: hmrc/income-tax-additional-information
Pull request #30: [SASS-11483] Add new UserAnswersRepo which will be used for Different Journey Data
Author: login: MJCallahanPage
Created: 2025-04-09T14:51:08Z | Merged: 2025-04-10T12:22:17Z

**Notes:**
Adds new `UserAnswersRepo` to be used for the `BusinessTaxReliefs` journey. Long term, this will also replace the `GainsUserDataRepo` implementation - but this refactor will be done separately.


Diff:
diff --git a/app/config/AppConfig.scala b/app/config/AppConfig.scala
index ac0c1ce..3b71831 100644
--- a/app/config/AppConfig.scala
+++ b/app/config/AppConfig.scala
@@ -40,6 +40,7 @@ trait AppConfig {
   def useEncryption: Boolean
   def encryptionKey: String
   def mongoTTL: Int
+  def userAnswersReplaceIndexes: Boolean
 
   def authorisationTokenFor(apiVersion: String): String
 
@@ -55,6 +56,7 @@ class BackendAppConfig @Inject()(config: Configuration, servicesConfig: Services
   // Mongo config
   lazy val encryptionKey: String = servicesConfig.getString("mongodb.encryption.key")
   lazy val mongoTTL: Int = Duration(servicesConfig.getString("mongodb.timeToLive")).toDays.toInt
+  lazy val userAnswersReplaceIndexes: Boolean = config.get[Boolean]("mongodb.userAnswersReplaceIndexes")
 
   val auditingEnabled: Boolean = config.get[Boolean]("auditing.enabled")
   val graphiteHost: String = config.get[String]("microservice.metrics.graphite.host")
diff --git a/app/config/Module.scala b/app/config/Module.scala
index 5e57bb5..7bcff45 100644
--- a/app/config/Module.scala
+++ b/app/config/Module.scala
@@ -18,9 +18,12 @@ package config
 
 import com.google.inject.AbstractModule
 
+import java.time.Clock
+
 class Module extends AbstractModule {
 
   override def configure(): Unit = {
     bind(classOf[AppConfig]).asEagerSingleton()
+    bind(classOf[Clock]).toInstance(Clock.systemUTC())
   }
 }
diff --git a/app/models/mongo/Journey.scala b/app/models/mongo/Journey.scala
new file mode 100644
index 0000000..c95c52d
--- /dev/null
+++ b/app/models/mongo/Journey.scala
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package models.mongo
+
+import play.api.libs.json.{Format, JsError, JsResult, JsString, JsSuccess, JsValue}
+
+sealed abstract class Journey(val name: String)
+
+object BusinessTaxReliefs extends Journey("businessTaxReliefs")
+
+object Journey {
+  implicit val format: Format[Journey] = new Format[Journey] {
+    override def writes(model: Journey): JsValue = JsString(model.name)
+
+    override def reads(json: JsValue): JsResult[Journey] = json match {
+      case JsString(BusinessTaxReliefs.name) => JsSuccess(BusinessTaxReliefs)
+      case _ => JsError("Unknown journey")
+    }
+  }
+}
diff --git a/app/models/mongo/UserAnswersModel.scala b/app/models/mongo/UserAnswersModel.scala
new file mode 100644
index 0000000..e63d963
--- /dev/null
+++ b/app/models/mongo/UserAnswersModel.scala
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package models.mongo
+
+import play.api.libs.json.{Format, JsObject, Json, OFormat}
+import uk.gov.hmrc.mongo.play.json.formats.MongoJavatimeFormats
+
+import java.time.Instant
+
+case class UserAnswersModel(mtdItId: String,
+                            nino: String,
+                            taxYear: Int,
+                            journey: Journey,
+                            data: JsObject = Json.obj(),
+                            lastUpdated: Instant = Instant.now)
+
+object UserAnswersModel {
+  implicit val instantFormat: Format[Instant] = MongoJavatimeFormats.instantFormat
+  implicit val format: OFormat[UserAnswersModel] = Json.format[UserAnswersModel]
+}
diff --git a/app/repositories/UserAnswersIndexes.scala b/app/repositories/UserAnswersIndexes.scala
new file mode 100644
index 0000000..c2eb05d
--- /dev/null
+++ b/app/repositories/UserAnswersIndexes.scala
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package repositories
+
+import config.AppConfig
+import org.mongodb.scala.model.Indexes.{ascending, compoundIndex}
+import org.mongodb.scala.model.{IndexModel, IndexOptions}
+
+import java.util.concurrent.TimeUnit
+
+object UserAnswersIndexes {
+
+  def indexes()(implicit appConfig: AppConfig): Seq[IndexModel] = Seq(
+    IndexModel(
+      compoundIndex(
+        ascending("mtdItId"),
+        ascending("nino"),
+        ascending("journey"),
+        ascending("taxYear")
+      ),
+      IndexOptions().name("mtdItId-nino-journey-taxYear-index")
+    ),
+    IndexModel(
+      ascending("lastUpdated"),
+      IndexOptions()
+        .expireAfter(appConfig.mongoTTL, TimeUnit.DAYS)
+        .name("last-updated-index")
+    )
+  )
+
+}
\ No newline at end of file
diff --git a/app/repositories/UserAnswersRepository.scala b/app/repositories/UserAnswersRepository.scala
new file mode 100644
index 0000000..62b9f48
--- /dev/null
+++ b/app/repositories/UserAnswersRepository.scala
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package repositories
+
+import com.mongodb.client.model
+import config.AppConfig
+import models.mongo.{Journey, UserAnswersModel}
+import org.mongodb.scala.bson.conversions.Bson
+import org.mongodb.scala.model.Filters.{and, equal}
+import org.mongodb.scala.model._
+import org.mongodb.scala.result.DeleteResult
+import uk.gov.hmrc.mongo.MongoComponent
+import uk.gov.hmrc.mongo.play.json.Codecs.toBson
+import uk.gov.hmrc.mongo.play.json.PlayMongoRepository
+import uk.gov.hmrc.play.http.logging.Mdc
+
+import java.time.{Clock, Instant}
+import javax.inject.{Inject, Singleton}
+import scala.concurrent.{ExecutionContext, Future}
+
+@Singleton
+class UserAnswersRepository @Inject()(mongoComponent: MongoComponent,
+                                      appConfig: AppConfig,
+                                      clock: Clock)(implicit ec: ExecutionContext)
+  extends PlayMongoRepository[UserAnswersModel](
+    collectionName = "userAnswers",
+    mongoComponent = mongoComponent,
+    domainFormat   = UserAnswersModel.format,
+    indexes = UserAnswersIndexes.indexes()(appConfig),
+    replaceIndexes = appConfig.userAnswersReplaceIndexes
+  ) {
+
+  private def pk(mtdItId: String, nino: String, taxYear: Int, journey: Journey): Bson = and(
+    equal("mtdItId", toBson(mtdItId)),
+    equal("nino", toBson(nino)),
+    equal("taxYear", toBson(taxYear)),
+    equal("journey", toBson(journey))
+  )
+
+  def get(mtdItId: String, nino: String, taxYear: Int, journey: Journey): Future[Option[UserAnswersModel]] =
+    Mdc.preservingMdc(
+      collection
+        .findOneAndUpdate(
+          filter = pk(mtdItId, nino, taxYear, journey),
+          update = Updates.set("lastUpdated", Instant.now(clock)),
+          options = FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
+        )
+        .headOption()
+    )
+
+  def set(userData: UserAnswersModel): Future[UserAnswersModel] =
+    Mdc.preservingMdc(
+      collection
+        .findOneAndReplace(
+          filter = pk(userData.mtdItId, userData.nino, userData.taxYear, userData.journey),
+          replacement = userData.copy(lastUpdated = Instant.now(clock)),
+          options = FindOneAndReplaceOptions()
+            .upsert(true)
+            .returnDocument(model.ReturnDocument.AFTER)
+        )
+        .toFuture()
+    )
+
+  def delete(mtdItId: String, nino: String, taxYear: Int, journey: Journey): Future[DeleteResult] =
+    Mdc.preservingMdc(
+      collection
+        .deleteOne(pk(mtdItId, nino, taxYear, journey))
+        .toFuture()
+    )
+}
+
diff --git a/app/utils/TimeMachine.scala b/app/utils/TimeMachine.scala
new file mode 100644
index 0000000..6f945a5
--- /dev/null
+++ b/app/utils/TimeMachine.scala
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package utils
+
+import java.time.Instant
+
+class TimeMachine {
+
+  val instantNow: Instant = Instant.now()
+
+}
diff --git a/build.sbt b/build.sbt
index c3236f7..62ec360 100755
--- a/build.sbt
+++ b/build.sbt
@@ -19,7 +19,7 @@ import uk.gov.hmrc.DefaultBuildSettings
 val appName = "income-tax-additional-information"
 
 ThisBuild / majorVersion := 0
-ThisBuild / scalaVersion := "2.13.12"
+ThisBuild / scalaVersion := "2.13.16"
 
 lazy val coverageSettings: Seq[Setting[?]] = {
   import scoverage.ScoverageKeys
diff --git a/conf/application.conf b/conf/application.conf
index f43b81e..de42a68 100755
--- a/conf/application.conf
+++ b/conf/application.conf
@@ -70,6 +70,7 @@ mongodb {
   encryption.key = "QmFyMTIzNDVCYXIxMjM0NQ=="
   uri = "mongodb://localhost:27017/"${appName}
   timeToLive = 28days
+  userAnswersReplaceIndexes = true
 }
 
 useEncryption = false
diff --git a/it/test/repositories/UserAnswersRepositoryISpec.scala b/it/test/repositories/UserAnswersRepositoryISpec.scala
new file mode 100644
index 0000000..76bb8db
--- /dev/null
+++ b/it/test/repositories/UserAnswersRepositoryISpec.scala
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2023 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package repositories
+
+import models.User
+import models.mongo._
+import org.mongodb.scala.bson.BsonDocument
+import org.mongodb.scala.result.DeleteResult
+import org.scalatest.matchers.must.Matchers.convertToAnyMustWrapper
+import play.api.libs.json.{JsObject, Json}
+import play.api.mvc.AnyContent
+import play.api.test.{DefaultAwaitTimeout, FakeRequest, FutureAwaits}
+import support.IntegrationTest
+
+class UserAnswersRepositoryISpec extends IntegrationTest with FutureAwaits with DefaultAwaitTimeout {
+
+  val userAnswersRepo: UserAnswersRepository = app.injector.instanceOf[UserAnswersRepository]
+  val testUser: User[AnyContent] = User(mtditid, None, nino, "individual", sessionId)
+
+  private def count: Long = await(userAnswersRepo.collection.countDocuments().toFuture())
+
+  class Fixture {
+    await(userAnswersRepo.collection.deleteMany(BsonDocument()).toFuture())
+    count mustBe 0
+  }
+
+  val userAnswers: UserAnswersModel = UserAnswersModel(
+    mtdItId = mtditid,
+    nino = nino,
+    taxYear = taxYear,
+    journey = BusinessTaxReliefs,
+    data = JsObject.empty
+  )
+
+  implicit val request: FakeRequest[AnyContent] = FakeRequest()
+
+  ".set" when {
+
+    "document does NOT exist" should {
+
+      "add a document to the collection" in new Fixture {
+        val upserted: UserAnswersModel = await(userAnswersRepo.set(userAnswers))
+        count mustBe 1
+        upserted.data mustBe Json.obj()
+      }
+    }
+
+    "document does exist" should {
+
+      "update the document in the collection" in new Fixture {
+        val upserted: UserAnswersModel = await(userAnswersRepo.set(userAnswers))
+        count mustBe 1
+        upserted.data mustBe Json.obj()
+        val updated: UserAnswersModel = await(userAnswersRepo.set(userAnswers.copy(data = Json.obj("foo" -> "bar"))))
+        updated.data mustBe Json.obj("foo" -> "bar")
+      }
+    }
+  }
+
+  ".get" should {
+
+    "return None when no document exists" in new Fixture {
+      await(userAnswersRepo.get(userAnswers.mtdItId, userAnswers.nino, userAnswers.taxYear, userAnswers.journey)) mustBe None
+    }
+
+    "return Some(UserAnswers) when document exists" in new Fixture {
+      await(userAnswersRepo.set(userAnswers))
+      count mustBe 1
+      val result: Option[UserAnswersModel] = await(userAnswersRepo.get(userAnswers.mtdItId, userAnswers.nino, userAnswers.taxYear, userAnswers.journey))
+      result.map(_.data) mustBe Some(Json.obj())
+    }
+  }
+
+  ".delete" should {
+
+    "clear the document for the current user" in new Fixture {
+      await(userAnswersRepo.set(userAnswers))
+      await(userAnswersRepo.set(userAnswers.copy(mtdItId = "other")))
+      await(userAnswersRepo.set(userAnswers.copy(mtdItId = "other2")))
+      count shouldBe 3
+      val deleted: DeleteResult = await(userAnswersRepo.delete(userAnswers.mtdItId, userAnswers.nino, userAnswers.taxYear, userAnswers.journey))
+      deleted.getDeletedCount mustBe 1
+      count shouldBe 2
+    }
+  }
+}
diff --git a/project/build.properties b/project/build.properties
index 04267b1..e97b272 100755
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=1.9.9
+sbt.version=1.10.10
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 33c41da..e1ba371 100755
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -22,5 +22,5 @@ resolvers += Resolver.typesafeRepo("releases")
 addSbtPlugin("uk.gov.hmrc"       % "sbt-auto-build"         % "3.24.0")
 addSbtPlugin("uk.gov.hmrc"       % "sbt-distributables"     % "2.6.0")
 addSbtPlugin("org.playframework" % "sbt-plugin"             % "3.0.6")
-addSbtPlugin("org.scoverage"     % "sbt-scoverage"          % "2.0.9")
+addSbtPlugin("org.scoverage"     % "sbt-scoverage"          % "2.3.1")
 addSbtPlugin("org.scalastyle"    % "scalastyle-sbt-plugin"  % "1.0.0" exclude("org.scala-lang.modules", "scala-xml_2.12"))
diff --git a/test/models/mongo/JourneyModelSpec.scala b/test/models/mongo/JourneyModelSpec.scala
new file mode 100644
index 0000000..f5b0144
--- /dev/null
+++ b/test/models/mongo/JourneyModelSpec.scala
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package models.mongo
+
+import play.api.libs.json.{JsString, Json}
+import testUtils.TestSuite
+
+class JourneyModelSpec extends TestSuite {
+
+  "should serialise to JSON correctly" in {
+    Json.toJson[Journey](BusinessTaxReliefs) mustBe JsString("businessTaxReliefs")
+  }
+
+  "should deserialise from JSON correctly" in {
+    JsString("businessTaxReliefs").as[Journey] mustBe BusinessTaxReliefs
+  }
+}
diff --git a/test/support/stubs/AppConfigStub.scala b/test/support/stubs/AppConfigStub.scala
index c579d84..8a6b04f 100644
--- a/test/support/stubs/AppConfigStub.scala
+++ b/test/support/stubs/AppConfigStub.scala
@@ -42,6 +42,7 @@ class AppConfigStub extends MockFactory {
     override lazy val useEncryption: Boolean = true
     override val encryptionKey: String = "1234556"
     override lazy val mongoTTL: Int = Duration("28days").toDays.toInt
+    override lazy val userAnswersReplaceIndexes = true
   }
 
   def noEncryptionConfig(): AppConfig = new AppConfig() {
@@ -63,6 +64,7 @@ class AppConfigStub extends MockFactory {
     override lazy val useEncryption: Boolean = false
     override lazy val encryptionKey: String = "1234556"
     override lazy val mongoTTL: Int = Duration("28days").toDays.toInt
+    override lazy val userAnswersReplaceIndexes = true
   }
 
   def featureSwitchConfigs(environment: String = "test")(flags: (String, Boolean)*): AppConfig = new AppConfig() {
@@ -85,5 +87,6 @@ class AppConfigStub extends MockFactory {
     override lazy val useEncryption: Boolean = true
     override val encryptionKey: String = "1234556"
     override lazy val mongoTTL: Int = Duration("28days").toDays.toInt
+    override lazy val userAnswersReplaceIndexes = true
   }
 }


Reviews:
user: login: amaalali
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T09:53:13Z
user: login: MJCallahanPage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T10:40:32Z
user: login: MJCallahanPage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T10:47:48Z
user: login: MJCallahanPage
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T10:47:54Z
user: login: amaalali
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T12:07:47Z

Review comments:
diff_hunk: @@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package models.mongo
+
+import play.api.libs.json.{Format, JsError, JsResult, JsString, JsSuccess, JsValue}
+
+sealed trait Journey { val name: String }
+
+object BusinessTaxReliefs extends Journey { override val name: String = "businessTaxReliefs" }
path: app/models/mongo/Journey.scala
user: login: amaalali
body: Minor suggestion, would making this an abstract class here make this more ergonomic? It would look 

```
sealed abstract class Journey(name: String)

object BusinessTaxReliefs extends Journey("businessTaxReliefs")
```
created_at: 2025-04-10T08:36:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,85 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package repositories
+
+import com.mongodb.client.model
+import config.AppConfig
+import models.mongo.{Journey, UserAnswersModel}
+import org.mongodb.scala.bson.conversions.Bson
+import org.mongodb.scala.model.Filters.{and, equal}
+import org.mongodb.scala.model._
+import org.mongodb.scala.result.DeleteResult
+import uk.gov.hmrc.mongo.MongoComponent
+import uk.gov.hmrc.mongo.play.json.Codecs.toBson
+import uk.gov.hmrc.mongo.play.json.PlayMongoRepository
+import uk.gov.hmrc.play.http.logging.Mdc
+import utils.TimeMachine
+
+import javax.inject.{Inject, Singleton}
+import scala.concurrent.{ExecutionContext, Future}
+
+@Singleton
+class UserAnswersRepository @Inject()(mongoComponent: MongoComponent,
+                                      appConfig: AppConfig,
+                                      timeMachine: TimeMachine)(implicit ec: ExecutionContext)
path: app/repositories/UserAnswersRepository.scala
user: login: amaalali
body: In place of the wrapper, we can inject an instance of `Clock` and where we do any `Instant.now` or `instantNow` we can do `Instant.now(clock)`
created_at: 2025-04-10T09:52:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package models.mongo
+
+import play.api.libs.json.{Format, JsError, JsResult, JsString, JsSuccess, JsValue}
+
+sealed trait Journey { val name: String }
+
+object BusinessTaxReliefs extends Journey { override val name: String = "businessTaxReliefs" }
path: app/models/mongo/Journey.scala
user: login: MJCallahanPage
body: Yeah, agreed. Will update that.
created_at: 2025-04-10T10:40:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,85 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package repositories
+
+import com.mongodb.client.model
+import config.AppConfig
+import models.mongo.{Journey, UserAnswersModel}
+import org.mongodb.scala.bson.conversions.Bson
+import org.mongodb.scala.model.Filters.{and, equal}
+import org.mongodb.scala.model._
+import org.mongodb.scala.result.DeleteResult
+import uk.gov.hmrc.mongo.MongoComponent
+import uk.gov.hmrc.mongo.play.json.Codecs.toBson
+import uk.gov.hmrc.mongo.play.json.PlayMongoRepository
+import uk.gov.hmrc.play.http.logging.Mdc
+import utils.TimeMachine
+
+import javax.inject.{Inject, Singleton}
+import scala.concurrent.{ExecutionContext, Future}
+
+@Singleton
+class UserAnswersRepository @Inject()(mongoComponent: MongoComponent,
+                                      appConfig: AppConfig,
+                                      timeMachine: TimeMachine)(implicit ec: ExecutionContext)
path: app/repositories/UserAnswersRepository.scala
user: login: MJCallahanPage
body: Changed on [e242888](https://github.com/hmrc/income-tax-additional-information/pull/30/commits/e242888c53d163b6fd4ae1d312a3c56e16c14647)
created_at: 2025-04-10T10:47:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,34 @@
+/*
+ * Copyright 2025 HM Revenue & Customs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package models.mongo
+
+import play.api.libs.json.{Format, JsError, JsResult, JsString, JsSuccess, JsValue}
+
+sealed trait Journey { val name: String }
+
+object BusinessTaxReliefs extends Journey { override val name: String = "businessTaxReliefs" }
path: app/models/mongo/Journey.scala
user: login: MJCallahanPage
body: Pushed on [e242888](https://github.com/hmrc/income-tax-additional-information/pull/30/commits/e242888c53d163b6fd4ae1d312a3c56e16c14647)
created_at: 2025-04-10T10:47:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: platops-pr-bot
created_at: 2025-04-09T14:57:18Z
body: - `SBT` can be updated to `1.10.10` in [project/build.properties](https://github.com/hmrc/income-tax-additional-information/blob/SASS-11483/project/build.properties#L1)
- `Scala` can be updated from `2.13.12` to `2.13.16` in [build.sbt](https://github.com/hmrc/income-tax-additional-information/blob/SASS-11483/build.sbt#L22)
- Looks like `@Singleton` is missing on [app/repositories/UserAnswersRepository.scala](https://github.com/hmrc/income-tax-additional-information/blob/SASS-11483/app/repositories/UserAnswersRepository.scala#L38). Only one instance is preferable as indexes would be reinitalised - see https://github.com/hmrc/hmrc-mongo#playmongorepository
author_association: NONE
user: login: platops-pr-bot
created_at: 2025-04-09T15:08:44Z
body: - ~~`SBT` can be updated to `1.10.10` in [project/build.properties](https://github.com/hmrc/income-tax-additional-information/blob/SASS-11483/project/build.properties#L1)~~
- ~~`Scala` can be updated from `2.13.12` to `2.13.16` in [build.sbt](https://github.com/hmrc/income-tax-additional-information/blob/SASS-11483/build.sbt#L22)~~
- ~~Looks like `@Singleton` is missing on [app/repositories/UserAnswersRepository.scala](https://github.com/hmrc/income-tax-additional-information/blob/SASS-11483/app/repositories/UserAnswersRepository.scala#L38). Only one instance is preferable as indexes would be reinitalised - see https://github.com/hmrc/hmrc-mongo#playmongorepository~~
author_association: NONE