1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2021 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.api.demographics
21 
22 import org.make.api.technical.{IdGeneratorComponent, MakeRandom}
23 import org.make.api.technical.security.AESEncryptionComponent
24 import org.make.core.demographics.{DemographicsCard, DemographicsCardId, LabelsValue}
25 import org.make.core.question.QuestionId
26 import org.make.core.reference.Language
27 import org.make.core.technical.Multilingual
28 import org.make.core.technical.Pagination.{End, Offset}
29 import org.make.core.{CirceFormatters, DateHelperComponent, Order}
30 
31 import cats.data.NonEmptyList
32 import cats.implicits._
33 
34 import java.time.ZonedDateTime
35 import scala.concurrent.ExecutionContext.Implicits.global
36 import scala.concurrent.Future
37 
38 trait DefaultDemographicsCardServiceComponent extends DemographicsCardServiceComponent {
39   self: AESEncryptionComponent
40     with DateHelperComponent
41     with IdGeneratorComponent
42     with PersistentDemographicsCardServiceComponent =>
43 
44   override lazy val demographicsCardService: DemographicsCardService = new DemographicsCardService {
45 
46     override def get(id: DemographicsCardId): Future[Option[DemographicsCard]] =
47       persistentDemographicsCardService.get(id)
48 
49     override def list(
50       offset: Option[Offset],
51       end: Option[End],
52       sort: Option[String],
53       order: Option[Order],
54       languages: Option[List[Language]],
55       dataType: Option[String],
56       questionId: Option[QuestionId]
57     ): Future[Seq[DemographicsCard]] =
58       persistentDemographicsCardService.list(offset, end, sort, order, languages, dataType, questionId)
59 
60     override def create(
61       name: String,
62       layout: DemographicsCard.Layout,
63       dataType: String,
64       languages: NonEmptyList[Language],
65       titles: Multilingual[String],
66       parameters: NonEmptyList[LabelsValue],
67       questionId: QuestionId
68     ): Future[DemographicsCard] =
69       persistentDemographicsCardService.persist(
70         DemographicsCard(
71           id = idGenerator.nextDemographicsCardId(),
72           name = name,
73           questionId = questionId,
74           layout = layout,
75           dataType = dataType,
76           languages = languages,
77           titles = titles,
78           parameters = parameters,
79           createdAt = dateHelper.now(),
80           updatedAt = dateHelper.now()
81         )
82       )
83 
84     override def update(
85       id: DemographicsCardId,
86       name: String,
87       layout: DemographicsCard.Layout,
88       dataType: String,
89       languages: NonEmptyList[Language],
90       titles: Multilingual[String],
91       parameters: NonEmptyList[LabelsValue],
92       questionId: QuestionId
93     ): Future[Option[DemographicsCard]] = {
94       get(id).flatMap(
95         _.traverse(
96           existing =>
97             persistentDemographicsCardService
98               .modify(
99                 existing.copy(
100                   name = name,
101                   questionId = questionId,
102                   layout = layout,
103                   dataType = dataType,
104                   languages = languages,
105                   titles = titles,
106                   parameters = parameters,
107                   updatedAt = dateHelper.now()
108                 )
109               )
110         )
111       )
112     }
113 
114     override def delete(id: DemographicsCardId): Future[Unit] =
115       persistentDemographicsCardService.delete(id)
116 
117     override def count(
118       languages: Option[List[Language]],
119       dataType: Option[String],
120       questionId: Option[QuestionId]
121     ): Future[Int] =
122       persistentDemographicsCardService.count(languages, dataType, questionId)
123 
124     override def generateToken(id: DemographicsCardId, questionId: QuestionId): String = {
125       val nowDate: ZonedDateTime = dateHelper.now()
126       val token = DemographicToken(nowDate, id, questionId)
127       aesEncryption.encryptAndEncode(token.toTokenizedString)
128     }
129 
130     override def isTokenValid(token: String, id: DemographicsCardId, questionId: QuestionId): Boolean = {
131       aesEncryption.decodeAndDecrypt(token).toOption.exists { decryptedToken =>
132         val nowDate: ZonedDateTime = dateHelper.now()
133         val demoToken = DemographicToken.fromString(decryptedToken)
134         demoToken.createdAt.plusHours(1).isAfter(nowDate) && demoToken.id == id && demoToken.questionId == questionId
135       }
136     }
137 
138     override def getOneRandomCardByQuestion(questionId: QuestionId): Future[Option[DemographicsCardResponse]] = {
139       demographicsCardService.list(questionId = Some(questionId)).map { cards =>
140         MakeRandom
141           .shuffleSeq(cards)
142           .headOption
143           .map(card => DemographicsCardResponse(card, generateToken(card.id, questionId)))
144       }
145     }
146 
147     override def getOrPickRandom(
148       maybeId: Option[DemographicsCardId],
149       maybeToken: Option[String],
150       questionId: QuestionId
151     ): Future[Option[DemographicsCardResponse]] = {
152       (maybeId, maybeToken) match {
153         case (Some(id), Some(token)) if isTokenValid(token, id, questionId) =>
154           get(id).map(_.map(card => DemographicsCardResponse(card, token)))
155         case _ => getOneRandomCardByQuestion(questionId)
156       }
157     }
158 
159   }
160 }
161 
162 final case class DemographicToken(createdAt: ZonedDateTime, id: DemographicsCardId, questionId: QuestionId)
163     extends CirceFormatters {
164 
165   private val SEPARATOR: Char = DemographicToken.SEPARATOR
166 
167   def toTokenizedString: String = {
168     s"${createdAt}${SEPARATOR}${id.value}${SEPARATOR}${questionId.value}"
169   }
170 }
171 
172 object DemographicToken {
173 
174   private val SEPARATOR: Char = '|'
175 
176   def fromString(token: String): DemographicToken = {
177     val Array(date, id, question) = token.split(SEPARATOR)
178     DemographicToken(
179       createdAt = ZonedDateTime.parse(date),
180       id = DemographicsCardId(id),
181       questionId = QuestionId(question)
182     )
183   }
184 
185 }
Line Stmt Id Pos Tree Symbol Tests Code
165 25229 5941 - 5967 Select org.make.api.demographics.DemographicToken.SEPARATOR DemographicToken.SEPARATOR
174 22659 6145 - 6148 Literal <nosymbol> '|'
177 23353 6228 - 6228 Select scala.Tuple3._3 org.make.api.demographics.demographicscardservicetest x$3._3
177 24244 6224 - 6224 Select scala.Tuple3._2 org.make.api.demographics.demographicscardservicetest x$3._2
177 26626 6218 - 6218 Select scala.Tuple3._1 org.make.api.demographics.demographicscardservicetest x$3._1
178 27402 6267 - 6410 Apply org.make.api.demographics.DemographicToken.apply org.make.api.demographics.demographicscardservicetest DemographicToken.apply(java.time.ZonedDateTime.parse(date), org.make.core.demographics.DemographicsCardId.apply(id), org.make.core.question.QuestionId.apply(question))
179 27098 6303 - 6328 Apply java.time.ZonedDateTime.parse org.make.api.demographics.demographicscardservicetest java.time.ZonedDateTime.parse(date)
180 24763 6341 - 6363 Apply org.make.core.demographics.DemographicsCardId.apply org.make.api.demographics.demographicscardservicetest org.make.core.demographics.DemographicsCardId.apply(id)
181 22488 6384 - 6404 Apply org.make.core.question.QuestionId.apply org.make.api.demographics.demographicscardservicetest org.make.core.question.QuestionId.apply(question)