diff --git a/app/models/daos/slick/DBTableDefinitions.scala b/app/models/daos/slick/DBTableDefinitions.scala index f26ee62abe..4149ae95c9 100644 --- a/app/models/daos/slick/DBTableDefinitions.scala +++ b/app/models/daos/slick/DBTableDefinitions.scala @@ -6,13 +6,15 @@ import java.util.UUID object DBTableDefinitions { - case class DBUser (userId: String, username: String, email: String ) + case class DBUser(userId: String, username: String, email: String, tutorialCompleted: Boolean = false) + class UserTable(tag: Tag) extends Table[DBUser](tag, "sidewalk_user") { def userId = column[String]("user_id", O.PrimaryKey) def username = column[String]("username") def email = column[String]("email") - def * = (userId, username, email) <> (DBUser.tupled, DBUser.unapply) + def tutorialCompleted = column[Boolean]("tutorial_completed") + def * = (userId, username, email, tutorialCompleted) <> (DBUser.tupled, DBUser.unapply) } case class DBLoginInfo (id: Option[Long], providerID: String, providerKey: String ) diff --git a/app/models/mission/MissionTable.scala b/app/models/mission/MissionTable.scala index 258f9f17a2..c20784a420 100644 --- a/app/models/mission/MissionTable.scala +++ b/app/models/mission/MissionTable.scala @@ -217,8 +217,14 @@ object MissionTable { * Check if the user has completed onboarding. */ def hasCompletedAuditOnboarding(userId: UUID): Boolean = db.withSession { implicit session => - selectCompletedMissions(userId, includeOnboarding = true, includeSkipped = true) - .exists(_.missionTypeId == MissionTypeTable.missionTypeToId("auditOnboarding")) + users.filter(_.userId === userId.toString).map(_.tutorialCompleted).first + } + + /** + * Updates the user's profile to mark the tutorial as completed. + */ + def markTutorialCompleted(userId: UUID): Int = db.withSession { implicit session => + users.filter(_.userId === userId.toString).map(_.tutorialCompleted).update(true) } /** @@ -613,17 +619,32 @@ object MissionTable { } /** - * Marks the specified mission as complete, filling in mission_end timestamp. - * - * NOTE only call from queryMissionTable or queryMissionTableValidationMissions funcs to prevent race conditions. - * - * @return Int number of rows updated (should always be 1). - */ + * Marks the specified mission as complete, filling in mission_end timestamp. + * If the mission is an audit onboarding mission, also marks the tutorial as completed + * in the user's profile to prevent it from appearing again in any city. + * + * NOTE only call from queryMissionTable or queryMissionTableValidationMissions funcs to prevent race conditions. + * + * @return Int number of rows updated (should always be 1). + */ def updateComplete(missionId: Int): Int = db.withSession { implicit session => val now: Timestamp = new Timestamp(Instant.now.toEpochMilli) val missionToUpdate = for { m <- missions if m.missionId === missionId } yield (m.completed, m.missionEnd) val rowsUpdated: Int = missionToUpdate.update((true, now)) if (rowsUpdated == 0) Logger.error("Tried to mark a mission as complete, but no mission exists with that ID.") + + try { + // Check if this is an audit onboarding mission and mark tutorial as completed if it is + val missionType = getMissionType(missionId) + if (missionType.exists(_ == "auditOnboarding")) { + val userId = missions.filter(_.missionId === missionId).map(_.userId).first + markTutorialCompleted(UUID.fromString(userId)) + } + } catch { + case e: Exception => + Logger.error(s"Error updating tutorial completion status: ${e.getMessage}") + } + rowsUpdated } diff --git a/conf/evolutions/default/269.sql b/conf/evolutions/default/269.sql new file mode 100644 index 0000000000..991a1ce9fd --- /dev/null +++ b/conf/evolutions/default/269.sql @@ -0,0 +1,16 @@ +# --- !Ups +ALTER TABLE sidewalk_user ADD COLUMN IF NOT EXISTS tutorial_completed BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE sidewalk_user AS su +SET tutorial_completed = TRUE +WHERE EXISTS ( + SELECT 1 + FROM mission AS m + JOIN mission_type AS mt ON m.mission_type_id = mt.mission_type_id + WHERE m.user_id = su.user_id + AND mt.mission_type = 'auditOnboarding' + AND m.completed = TRUE +); + +# --- !Downs +ALTER TABLE sidewalk_user DROP COLUMN IF EXISTS tutorial_completed;