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 }