Introduces /api/feedback endpoint (to replace Meteor /feedback)

This commit is contained in:
Gustavo Trott 2024-07-03 10:12:28 -03:00
parent 759bf882c7
commit a21dcb5818
6 changed files with 137 additions and 3 deletions

View File

@ -0,0 +1,44 @@
package org.bigbluebutton.api.model.request;
import org.bigbluebutton.api.model.constraint.UserSessionConstraint;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
public class Feedback extends RequestWithSession<Feedback.Params> {
public enum Params implements RequestParameters {
SESSION_TOKEN("sessionToken");
private final String value;
Params(String value) { this.value = value; }
public String getValue() { return value; }
}
@UserSessionConstraint
private String sessionToken;
public Feedback(HttpServletRequest servletRequest) {
super(servletRequest);
}
public String getSessionToken() {
return sessionToken;
}
public void setSessionToken(String sessionToken) {
this.sessionToken = sessionToken;
}
@Override
public void populateFromParamsMap(Map<String, String[]> params) {
if(params.containsKey(Feedback.Params.SESSION_TOKEN.getValue())) setSessionToken(params.get(Feedback.Params.SESSION_TOKEN.getValue())[0]);
}
@Override
public void convertParamsFromString() {
}
}

View File

@ -44,6 +44,7 @@ public class ValidationService {
SIGN_OUT("signOut", RequestType.GET), SIGN_OUT("signOut", RequestType.GET),
LEARNING_DASHBOARD("learningDashboard", RequestType.GET), LEARNING_DASHBOARD("learningDashboard", RequestType.GET),
GET_JOIN_URL("getJoinUrl", RequestType.GET), GET_JOIN_URL("getJoinUrl", RequestType.GET),
FEEDBACK("feedback", RequestType.GET),
INSERT_DOCUMENT("insertDocument", RequestType.GET), INSERT_DOCUMENT("insertDocument", RequestType.GET),
SEND_CHAT_MESSAGE("sendChatMessage", RequestType.GET); SEND_CHAT_MESSAGE("sendChatMessage", RequestType.GET);
@ -130,6 +131,7 @@ public class ValidationService {
case SIGN_OUT -> new SignOut(servletRequest); case SIGN_OUT -> new SignOut(servletRequest);
case LEARNING_DASHBOARD -> new LearningDashboard(servletRequest); case LEARNING_DASHBOARD -> new LearningDashboard(servletRequest);
case GET_JOIN_URL -> new GetJoinUrl(servletRequest); case GET_JOIN_URL -> new GetJoinUrl(servletRequest);
case FEEDBACK -> new Feedback(servletRequest);
}; };
} }

View File

@ -227,7 +227,16 @@ const MeetingEnded: React.FC<MeetingEndedProps> = ({
comment, comment,
isModerator, isModerator,
}; };
const url = './feedback';
const pathMatch = window.location.pathname.match('^(.*)/html5client/join$');
if (pathMatch == null) {
throw new Error('Failed to match BBB client URI');
}
const serverPathPrefix = pathMatch[1];
const sessionToken = sessionStorage.getItem('sessionToken');
const url = `https://${window.location.hostname}${serverPathPrefix}/bigbluebutton/api/feedback?sessionToken=${sessionToken}`;
const options = { const options = {
method: 'POST', method: 'POST',
body: JSON.stringify(message), body: JSON.stringify(message),
@ -351,7 +360,7 @@ const MeetingEnded: React.FC<MeetingEndedProps> = ({
) : null} ) : null}
</> </>
); );
}, []); }, [askForFeedbackOnLogout, dispatched, selectedStars]);
useEffect(() => { useEffect(() => {
// Sets Loading to falsed and removes loading splash screen // Sets Loading to falsed and removes loading splash screen

View File

@ -56,7 +56,7 @@ public:
customStyleUrl: null customStyleUrl: null
darkTheme: darkTheme:
enabled: true enabled: true
askForFeedbackOnLogout: false askForFeedbackOnLogout: true
# the default logoutUrl matches window.location.origin i.e. bigbluebutton.org for demo.bigbluebutton.org # the default logoutUrl matches window.location.origin i.e. bigbluebutton.org for demo.bigbluebutton.org
# in some cases we want only custom logoutUrl to be used when provided on meeting create. Default value: true # in some cases we want only custom logoutUrl to be used when provided on meeting create. Default value: true
askForConfirmationOnLeave: true askForConfirmationOnLeave: true

View File

@ -111,6 +111,10 @@ class UrlMappings {
action = [GET: 'getJoinUrl', POST: 'getJoinUrl'] action = [GET: 'getJoinUrl', POST: 'getJoinUrl']
} }
"/bigbluebutton/api/feedback"(controller: "api") {
action = [POST: 'feedback']
}
"/bigbluebutton/api/learningDashboard"(controller: "api") { "/bigbluebutton/api/learningDashboard"(controller: "api") {
action = [GET: 'learningDashboard', POST: 'learningDashboard'] action = [GET: 'learningDashboard', POST: 'learningDashboard']
} }

View File

@ -19,6 +19,7 @@
package org.bigbluebutton.web.controllers package org.bigbluebutton.web.controllers
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject
import grails.web.context.ServletContextHolder import grails.web.context.ServletContextHolder
import groovy.json.JsonBuilder import groovy.json.JsonBuilder
import groovy.xml.MarkupBuilder import groovy.xml.MarkupBuilder
@ -31,6 +32,7 @@ import org.bigbluebutton.api.*
import org.bigbluebutton.api.domain.GuestPolicy import org.bigbluebutton.api.domain.GuestPolicy
import org.bigbluebutton.api.domain.Meeting import org.bigbluebutton.api.domain.Meeting
import org.bigbluebutton.api.domain.UserSession import org.bigbluebutton.api.domain.UserSession
import org.bigbluebutton.api.domain.UserSessionBasicData
import org.bigbluebutton.api.service.ValidationService import org.bigbluebutton.api.service.ValidationService
import org.bigbluebutton.api.service.ServiceUtils import org.bigbluebutton.api.service.ServiceUtils
import org.bigbluebutton.api.util.ParamsUtil import org.bigbluebutton.api.util.ParamsUtil
@ -1217,6 +1219,79 @@ class ApiController {
} }
} }
def feedback = {
String API_CALL = 'feedback'
log.debug CONTROLLER_NAME + "#${API_CALL}"
if (!params.sessionToken) {
invalid("missingSession", "Invalid session token")
}
String requestBody = request.inputStream == null ? null : request.inputStream.text
Gson gson = new Gson()
JsonObject body = gson.fromJson(requestBody, JsonObject.class)
if (!body
|| !body.has("userName")
|| !body.has("authToken")
|| !body.has("comment")
|| !body.has("rating")) {
invalid("missingParameters", "One or more required parameters are missing")
return
}
String userName = "[unconfirmed] " + body.get("userName").getAsString()
String meetingId = ""
String userId = ""
String authToken = body.get("authToken").getAsString()
String comment = body.get("comment").getAsString()
int rating = body.get("rating").getAsInt()
String sessionToken = sanitizeSessionToken(params.sessionToken)
UserSession userSession = meetingService.getUserSessionWithSessionToken(sessionToken)
if(userSession) {
userName = userSession.fullname
userId = userSession.internalUserId
meetingId = userSession.meetingID
} else {
//Usually the session was already removed when the user send the feedback
UserSessionBasicData removedUserSession = meetingService.getRemovedUserSessionWithSessionToken(sessionToken)
if(removedUserSession) {
userId = removedUserSession.userId
meetingId = removedUserSession.meetingId
}
}
if(userId == "") {
invalid("invalidSession", "Invalid Session")
}
response.contentType = 'application/json'
response.setStatus(200)
withFormat {
json {
def builder = new JsonBuilder()
builder {
"status" "ok"
}
render(contentType: "application/json", text: builder.toPrettyString())
}
}
def feedback = [
meetingId: meetingId,
userId: userId,
authToken: authToken,
userName: userName,
comment: comment,
rating: rating
]
log.info("FEEDBACK LOG: ${feedback}")
}
/*********************************************** /***********************************************
* LEARNING DASHBOARD DATA * LEARNING DASHBOARD DATA
***********************************************/ ***********************************************/