11package com .objectcomputing .checkins .services .pulseresponse ;
22
33import com .objectcomputing .checkins .exceptions .NotFoundException ;
4+ import com .objectcomputing .checkins .util .form .FormUrlEncodedDecoder ;
5+ import com .objectcomputing .checkins .services .memberprofile .MemberProfileServices ;
6+
7+ import io .micronaut .http .MediaType ;
48import io .micronaut .core .annotation .Nullable ;
59import io .micronaut .core .convert .format .Format ;
10+ import io .micronaut .http .HttpStatus ;
611import io .micronaut .http .HttpRequest ;
712import io .micronaut .http .HttpResponse ;
13+ import io .micronaut .http .annotation .Header ;
814import io .micronaut .http .annotation .Body ;
915import io .micronaut .http .annotation .Controller ;
1016import io .micronaut .http .annotation .Get ;
1420import io .micronaut .scheduling .annotation .ExecuteOn ;
1521import io .micronaut .security .annotation .Secured ;
1622import io .micronaut .security .rules .SecurityRule ;
23+
1724import io .swagger .v3 .oas .annotations .tags .Tag ;
1825import jakarta .validation .Valid ;
1926import jakarta .validation .constraints .NotNull ;
2229import java .time .LocalDate ;
2330import java .util .Set ;
2431import java .util .UUID ;
32+ import java .util .Map ;
33+ import java .nio .charset .StandardCharsets ;
2534
2635@ Controller ("/services/pulse-responses" )
2736@ ExecuteOn (TaskExecutors .BLOCKING )
28- @ Secured (SecurityRule .IS_AUTHENTICATED )
2937@ Tag (name = "pulse-responses" )
3038public class PulseResponseController {
31-
3239 private final PulseResponseService pulseResponseServices ;
40+ private final MemberProfileServices memberProfileServices ;
41+ private final SlackSignatureVerifier slackSignatureVerifier ;
42+ private final PulseSlackCommand pulseSlackCommand ;
43+ private final SlackPulseResponseConverter slackPulseResponseConverter ;
3344
34- public PulseResponseController (PulseResponseService pulseResponseServices ) {
45+ public PulseResponseController (PulseResponseService pulseResponseServices ,
46+ MemberProfileServices memberProfileServices ,
47+ SlackSignatureVerifier slackSignatureVerifier ,
48+ PulseSlackCommand pulseSlackCommand ,
49+ SlackPulseResponseConverter slackPulseResponseConverter ) {
3550 this .pulseResponseServices = pulseResponseServices ;
51+ this .memberProfileServices = memberProfileServices ;
52+ this .slackSignatureVerifier = slackSignatureVerifier ;
53+ this .pulseSlackCommand = pulseSlackCommand ;
54+ this .slackPulseResponseConverter = slackPulseResponseConverter ;
3655 }
3756
3857 /**
@@ -43,6 +62,7 @@ public PulseResponseController(PulseResponseService pulseResponseServices) {
4362 * @param dateTo
4463 * @return
4564 */
65+ @ Secured (SecurityRule .IS_AUTHENTICATED )
4666 @ Get ("/{?teamMemberId,dateFrom,dateTo}" )
4767 public Set <PulseResponse > findPulseResponses (@ Nullable @ Format ("yyyy-MM-dd" ) LocalDate dateFrom ,
4868 @ Nullable @ Format ("yyyy-MM-dd" ) LocalDate dateTo ,
@@ -56,6 +76,7 @@ public Set<PulseResponse> findPulseResponses(@Nullable @Format("yyyy-MM-dd") Loc
5676 * @param pulseResponse, {@link PulseResponseCreateDTO}
5777 * @return {@link HttpResponse<PulseResponse>}
5878 */
79+ @ Secured (SecurityRule .IS_AUTHENTICATED )
5980 @ Post
6081 public HttpResponse <PulseResponse > createPulseResponse (@ Body @ Valid PulseResponseCreateDTO pulseResponse ,
6182 HttpRequest <?> request ) {
@@ -70,6 +91,7 @@ public HttpResponse<PulseResponse> createPulseResponse(@Body @Valid PulseRespons
7091 * @param pulseResponse, {@link PulseResponse}
7192 * @return {@link HttpResponse<PulseResponse>}
7293 */
94+ @ Secured (SecurityRule .IS_AUTHENTICATED )
7395 @ Put
7496 public HttpResponse <PulseResponse > update (@ Body @ Valid @ NotNull PulseResponse pulseResponse ,
7597 HttpRequest <?> request ) {
@@ -82,6 +104,7 @@ public HttpResponse<PulseResponse> update(@Body @Valid @NotNull PulseResponse pu
82104 * @param id
83105 * @return
84106 */
107+ @ Secured (SecurityRule .IS_AUTHENTICATED )
85108 @ Get ("/{id}" )
86109 public PulseResponse readPulse (@ NotNull UUID id ) {
87110 PulseResponse result = pulseResponseServices .read (id );
@@ -90,4 +113,88 @@ public PulseResponse readPulse(@NotNull UUID id) {
90113 }
91114 return result ;
92115 }
116+
117+ @ Secured (SecurityRule .IS_ANONYMOUS )
118+ @ Post (uri = "/command" , consumes = MediaType .APPLICATION_FORM_URLENCODED )
119+ public HttpResponse commandPulseResponse (
120+ @ Header ("X-Slack-Signature" ) String signature ,
121+ @ Header ("X-Slack-Request-Timestamp" ) String timestamp ,
122+ @ Body String requestBody ) {
123+ // Validate the request
124+ if (slackSignatureVerifier .verifyRequest (signature ,
125+ timestamp , requestBody )) {
126+ // Convert the request body to a map of values.
127+ FormUrlEncodedDecoder formUrlEncodedDecoder = new FormUrlEncodedDecoder ();
128+ Map <String , Object > body =
129+ formUrlEncodedDecoder .decode (requestBody ,
130+ StandardCharsets .UTF_8 );
131+
132+ // Respond to the slack command.
133+ String triggerId = (String )body .get ("trigger_id" );
134+ if (pulseSlackCommand .send (triggerId )) {
135+ return HttpResponse .ok ();
136+ } else {
137+ return HttpResponse .status (HttpStatus .INTERNAL_SERVER_ERROR );
138+ }
139+ } else {
140+ return HttpResponse .unauthorized ();
141+ }
142+ }
143+
144+ @ Secured (SecurityRule .IS_ANONYMOUS )
145+ @ Post (uri = "/external" , consumes = MediaType .APPLICATION_FORM_URLENCODED )
146+ public HttpResponse externalPulseResponse (
147+ @ Header ("X-Slack-Signature" ) String signature ,
148+ @ Header ("X-Slack-Request-Timestamp" ) String timestamp ,
149+ @ Body String requestBody ,
150+ HttpRequest <?> request ) {
151+ // Validate the request
152+ if (slackSignatureVerifier .verifyRequest (signature ,
153+ timestamp , requestBody )) {
154+ // Convert the request body to a map of values.
155+ FormUrlEncodedDecoder formUrlEncodedDecoder =
156+ new FormUrlEncodedDecoder ();
157+ Map <String , Object > body =
158+ formUrlEncodedDecoder .decode (requestBody ,
159+ StandardCharsets .UTF_8 );
160+
161+ final String key = "payload" ;
162+ if (body .containsKey (key )) {
163+ PulseResponseCreateDTO pulseResponseDTO =
164+ slackPulseResponseConverter .get (memberProfileServices ,
165+ (String )body .get (key ));
166+
167+ // If we receive a null DTO, that means that this is not the
168+ // actual submission of the form. We can just return 200 so
169+ // that Slack knows to continue without error.
170+ if (pulseResponseDTO == null ) {
171+ return HttpResponse .ok ();
172+ }
173+
174+ // Create the pulse response
175+ PulseResponse pulseResponse =
176+ pulseResponseServices .unsecureSave (
177+ new PulseResponse (
178+ pulseResponseDTO .getInternalScore (),
179+ pulseResponseDTO .getExternalScore (),
180+ pulseResponseDTO .getSubmissionDate (),
181+ pulseResponseDTO .getTeamMemberId (),
182+ pulseResponseDTO .getInternalFeelings (),
183+ pulseResponseDTO .getExternalFeelings ()
184+ )
185+ );
186+
187+ if (pulseResponse == null ) {
188+ return HttpResponse .status (HttpStatus .CONFLICT ,
189+ "Already submitted today" );
190+ } else {
191+ return HttpResponse .ok ();
192+ }
193+ } else {
194+ return HttpResponse .unprocessableEntity ();
195+ }
196+ } else {
197+ return HttpResponse .unauthorized ();
198+ }
199+ }
93200}
0 commit comments