1 /*
2 * Make.org Core API
3 * Copyright (C) 2018 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.proposal
21
22 import java.time.ZonedDateTime
23 import akka.actor.typed.ActorRef
24 import org.make.api.proposal.ProposalActorResponse.{Envelope, VotesActorResponse}
25 import org.make.api.proposal.ProposalActorResponse.Error._
26 import org.make.api.technical.{ActorCommand, ActorProtocol}
27 import org.make.core.RequestContext
28 import org.make.core.history.HistoryActions.{VoteAndQualifications, VoteTrust}
29 import org.make.core.proposal._
30 import org.make.core.question.Question
31 import org.make.core.reference.Language
32 import org.make.core.tag.TagId
33 import org.make.core.technical.Multilingual
34 import org.make.core.user.{User, UserId}
35
36 sealed trait ProposalActorProtocol extends ActorProtocol
37
38 sealed trait ProposalCommand extends ActorCommand[ProposalId] with ProposalActorProtocol {
39 def id: ProposalId = proposalId
40 def proposalId: ProposalId
41 def requestContext: RequestContext
42 }
43
44 final case class ProposeCommand(
45 proposalId: ProposalId,
46 requestContext: RequestContext,
47 user: User,
48 createdAt: ZonedDateTime,
49 content: String,
50 submittedAsLanguage: Language,
51 isAnonymous: Boolean,
52 question: Question,
53 initialProposal: Boolean,
54 proposalType: ProposalType,
55 replyTo: ActorRef[Envelope[ProposalId]]
56 ) extends ProposalCommand
57
58 final case class UpdateProposalCommand(
59 moderator: UserId,
60 proposalId: ProposalId,
61 requestContext: RequestContext,
62 updatedAt: ZonedDateTime,
63 newContent: Option[String],
64 newTranslations: Option[Multilingual[String]],
65 tags: Seq[TagId],
66 question: Question,
67 replyTo: ActorRef[ProposalActorResponse[ModificationError, Proposal]]
68 ) extends ProposalCommand
69
70 final case class UpdateProposalVotesCommand(
71 moderator: UserId,
72 proposalId: ProposalId,
73 requestContext: RequestContext,
74 updatedAt: ZonedDateTime,
75 votes: Seq[UpdateVoteRequest],
76 replyTo: ActorRef[ProposalActorResponse[ModificationError, Proposal]]
77 ) extends ProposalCommand
78
79 final case class ViewProposalCommand(
80 proposalId: ProposalId,
81 requestContext: RequestContext,
82 replyTo: ActorRef[ProposalActorResponse[ProposalNotFound, Proposal]]
83 ) extends ProposalCommand
84
85 final case class GetProposal(
86 proposalId: ProposalId,
87 requestContext: RequestContext,
88 replyTo: ActorRef[ProposalActorResponse[ProposalNotFound, Proposal]]
89 ) extends ProposalCommand
90
91 final case class Stop(proposalId: ProposalId, requestContext: RequestContext, replyTo: ActorRef[Envelope[Unit]])
92 extends ProposalCommand
93
94 final case class AcceptProposalCommand(
95 moderator: UserId,
96 proposalId: ProposalId,
97 requestContext: RequestContext,
98 sendNotificationEmail: Boolean,
99 newContent: Option[String],
100 newTranslations: Option[Multilingual[String]],
101 question: Question,
102 tags: Seq[TagId],
103 replyTo: ActorRef[ProposalActorResponse[ModificationError, Proposal]]
104 ) extends ProposalCommand
105
106 final case class RefuseProposalCommand(
107 moderator: UserId,
108 proposalId: ProposalId,
109 requestContext: RequestContext,
110 sendNotificationEmail: Boolean,
111 refusalReason: Option[String],
112 replyTo: ActorRef[ProposalActorResponse[ModificationError, Proposal]]
113 ) extends ProposalCommand
114
115 final case class PostponeProposalCommand(
116 moderator: UserId,
117 proposalId: ProposalId,
118 requestContext: RequestContext,
119 replyTo: ActorRef[ProposalActorResponse[ModificationError, Proposal]]
120 ) extends ProposalCommand
121
122 final case class VoteProposalCommand(
123 proposalId: ProposalId,
124 maybeUserId: Option[UserId],
125 requestContext: RequestContext,
126 voteKey: VoteKey,
127 maybeOrganisationId: Option[UserId],
128 vote: Option[VoteAndQualifications],
129 voteTrust: VoteTrust,
130 newProposalsVoteThreshold: Int,
131 replyTo: ActorRef[ProposalActorResponse[VoteError, VotesActorResponse]]
132 ) extends ProposalCommand
133
134 final case class UnvoteProposalCommand(
135 proposalId: ProposalId,
136 maybeUserId: Option[UserId],
137 requestContext: RequestContext,
138 voteKey: VoteKey,
139 maybeOrganisationId: Option[UserId],
140 vote: Option[VoteAndQualifications],
141 voteTrust: VoteTrust,
142 newProposalsVoteThreshold: Int,
143 replyTo: ActorRef[ProposalActorResponse[VoteError, VotesActorResponse]]
144 ) extends ProposalCommand
145
146 final case class QualifyVoteCommand(
147 proposalId: ProposalId,
148 maybeUserId: Option[UserId],
149 requestContext: RequestContext,
150 voteKey: VoteKey,
151 qualificationKey: QualificationKey,
152 vote: Option[VoteAndQualifications],
153 voteTrust: VoteTrust,
154 replyTo: ActorRef[ProposalActorResponse[QualificationError, Qualification]]
155 ) extends ProposalCommand
156
157 final case class UnqualifyVoteCommand(
158 proposalId: ProposalId,
159 maybeUserId: Option[UserId],
160 requestContext: RequestContext,
161 voteKey: VoteKey,
162 qualificationKey: QualificationKey,
163 vote: Option[VoteAndQualifications],
164 voteTrust: VoteTrust,
165 replyTo: ActorRef[ProposalActorResponse[QualificationError, Qualification]]
166 ) extends ProposalCommand
167
168 final case class LockProposalCommand(
169 proposalId: ProposalId,
170 moderatorId: UserId,
171 moderatorName: Option[String],
172 requestContext: RequestContext,
173 replyTo: ActorRef[ProposalActorResponse[LockError, UserId]]
174 ) extends ProposalCommand
175
176 final case class PatchProposalCommand(
177 proposalId: ProposalId,
178 userId: UserId,
179 changes: PatchProposalRequest,
180 requestContext: RequestContext,
181 replyTo: ActorRef[ProposalActorResponse[ProposalNotFound, Proposal]]
182 ) extends ProposalCommand
183
184 final case class SetKeywordsCommand(
185 proposalId: ProposalId,
186 keywords: Seq[ProposalKeyword],
187 requestContext: RequestContext,
188 replyTo: ActorRef[ProposalActorResponse[ProposalNotFound, Proposal]]
189 ) extends ProposalCommand
190
191 // Responses
192
193 sealed trait ProposalActorResponse[+E, +T] extends ProposalActorProtocol {
194 def flatMap[F >: E, U](f: T => ProposalActorResponse[F, U]): ProposalActorResponse[F, U]
195 }
196
197 object ProposalActorResponse {
198
199 final case class Envelope[T](value: T) extends ProposalActorResponse[Nothing, T] {
200 override def flatMap[E, U](f: T => ProposalActorResponse[E, U]): ProposalActorResponse[E, U] = f(value)
201 }
202
203 final case class VotesActorResponse(agree: VoteActorResponse, disagree: VoteActorResponse, neutral: VoteActorResponse)
204
205 final case class VoteActorResponse(key: VoteKey, count: Int, score: Double, qualifications: Seq[Qualification])
206 case object VoteActorResponse {
207 def apply(wrapper: VotingOptionWrapper, score: Double): VoteActorResponse =
208 VoteActorResponse(
209 key = wrapper.vote.key,
210 count = wrapper.vote.count,
211 score = score,
212 qualifications = wrapper.deprecatedQualificationsSeq
213 )
214 }
215
216 sealed trait Error[E <: Error[E]] extends ProposalActorResponse[E, Nothing] { self: E =>
217 override def flatMap[F >: E, U](f: Nothing => ProposalActorResponse[F, U]): ProposalActorResponse[E, Nothing] = self
218 }
219
220 object Error {
221
222 sealed trait LockError
223 sealed trait ModificationError
224 sealed trait VoteError extends QualificationError
225 sealed trait QualificationError
226
227 sealed trait ProposalNotFound extends Error[ProposalNotFound] with LockError with ModificationError with VoteError
228 case object ProposalNotFound extends ProposalNotFound
229
230 sealed trait VoteNotFound extends Error[VoteNotFound] with VoteError
231 case object VoteNotFound extends VoteNotFound
232
233 sealed trait QualificationNotFound extends Error[QualificationNotFound] with QualificationError
234 case object QualificationNotFound extends QualificationNotFound
235
236 final case class AlreadyLockedBy(moderatorName: String) extends Error[AlreadyLockedBy] with LockError
237
238 final case class InvalidStateError(message: String) extends Error[InvalidStateError] with ModificationError
239
240 final case class HistoryError(error: Throwable) extends Error[HistoryError] with VoteError
241
242 }
243 }