Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • arsnova/arsnova-backend
  • pcvl72/arsnova-backend
  • tksl38/arsnova-backend
3 results
Show changes
Showing
with 2963 additions and 1775 deletions
/* /*
* Copyright (C) 2012 THM webMedia * This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
* *
* This file is part of ARSnova. * ARSnova Backend is free software: you can redistribute it and/or modify
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* ARSnova is distributed in the hope that it will be useful, * ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
...@@ -16,24 +15,89 @@ ...@@ -16,24 +15,89 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package de.thm.arsnova.controller; package de.thm.arsnova.controller;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import de.thm.arsnova.management.VersionInfoContributor;
import de.thm.arsnova.web.exceptions.BadRequestException;
import de.thm.arsnova.web.exceptions.NoContentException;
/**
* Default controller that handles requests which have not set a path.
*/
@Controller @Controller
public class WelcomeController extends AbstractController { public class WelcomeController extends AbstractController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public final ModelAndView home(final HttpServletRequest request) { @Value("${mobile.path}")
String referer = request.getHeader("referer"); private String mobileContextPath;
String target = "index.html";
if (referer != null && referer.endsWith("dojo-index.html")) { @Autowired
target = "dojo-index.html"; private VersionInfoContributor versionInfoContributor;
@GetMapping("/")
public View home() {
return new RedirectView(mobileContextPath + "/", false);
}
@GetMapping(value = "/", produces = "application/json")
@ResponseBody
public Map<String, Object> jsonHome() {
return versionInfoContributor.getInfoDetails();
}
@PostMapping("/checkframeoptionsheader")
@ResponseStatus(HttpStatus.OK)
public void checkFrameOptionsHeader(
@RequestParam final String url,
final HttpServletRequest request) {
/* Block requests from the server itself to prevent DoS attacks caused by request loops */
if ("127.0.0.1".equals(request.getRemoteAddr()) || "::1".equals(request.getRemoteAddr())) {
throw new BadRequestException("Access to localhost not allowed.");
}
/* Block requests to servers in private networks */
try {
final InetAddress addr = InetAddress.getByName(new URL(url).getHost());
if (addr.isSiteLocalAddress()) {
throw new BadRequestException("Access to site-local addresses not allowed.");
}
} catch (final UnknownHostException | MalformedURLException e) {
throw new BadRequestException();
}
final RestTemplate restTemplate = new RestTemplate();
final SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
rf.setConnectTimeout(2000);
rf.setReadTimeout(2000);
try {
final HttpHeaders headers = restTemplate.headForHeaders(url);
if (headers.isEmpty() || headers.containsKey("x-frame-options")) {
throw new NoContentException();
}
} catch (final RestClientException e) {
throw new NoContentException();
} }
return new ModelAndView("redirect:/" + target);
} }
} }
/**
* Controllers and helper classes which define the public API.
*/
package de.thm.arsnova.controller; package de.thm.arsnova.controller;
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.exception.HttpAction;
import org.pac4j.oauth.client.FacebookClient;
import org.pac4j.oauth.client.TwitterClient;
import org.pac4j.oidc.client.GoogleOidcClient;
import org.pac4j.oidc.client.OidcClient;
import org.pac4j.saml.client.SAML2Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import de.thm.arsnova.config.SecurityConfig;
import de.thm.arsnova.config.properties.AuthenticationProviderProperties;
import de.thm.arsnova.config.properties.SystemProperties;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.ServiceDescription;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.model.migration.v2.ClientAuthentication;
import de.thm.arsnova.security.LoginAuthenticationFailureHandler;
import de.thm.arsnova.security.LoginAuthenticationSucessHandler;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.exceptions.UnauthorizedException;
/**
* Handles authentication specific requests.
*/
@Controller("v2AuthenticationController")
@RequestMapping("/v2/auth")
public class AuthenticationController extends AbstractController {
private String apiPath;
@Value("${customization.path}") private String customizationPath;
private AuthenticationProviderProperties.Guest guestProperties;
private AuthenticationProviderProperties.Registered registeredProperties;
private List<AuthenticationProviderProperties.Ldap> ldapProperties;
private List<AuthenticationProviderProperties.Oidc> oidcProperties;
private AuthenticationProviderProperties.Saml samlProperties;
private AuthenticationProviderProperties.Cas casProperties;
private Map<String, AuthenticationProviderProperties.Oauth> oauthProperties;
@Autowired
private ServletContext servletContext;
@Autowired
private AuthenticationProviderProperties providerProperties;
@Autowired(required = false)
private OidcClient oidcClient;
@Autowired(required = false)
private TwitterClient twitterClient;
@Autowired(required = false)
private GoogleOidcClient googleOidcClient;
@Autowired(required = false)
private FacebookClient facebookClient;
@Autowired(required = false)
private SAML2Client saml2Client;
@Autowired(required = false)
private CasAuthenticationEntryPoint casEntryPoint;
@Autowired
private UserService userService;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationController.class);
public AuthenticationController(final SystemProperties systemProperties,
final AuthenticationProviderProperties authenticationProviderProperties) {
apiPath = systemProperties.getApi().getProxyPath();
guestProperties = authenticationProviderProperties.getGuest();
registeredProperties = authenticationProviderProperties.getRegistered();
ldapProperties = authenticationProviderProperties.getLdap();
oidcProperties = authenticationProviderProperties.getOidc();
samlProperties = authenticationProviderProperties.getSaml();
casProperties = authenticationProviderProperties.getCas();
oauthProperties = authenticationProviderProperties.getOauth();
}
@PostConstruct
private void init() {
if (apiPath == null || "".equals(apiPath)) {
apiPath = servletContext.getContextPath();
}
}
@PostMapping({ "/login", "/doLogin" })
public void doLogin(
@RequestParam("type") final String type,
@RequestParam(value = "user", required = false) final String username,
@RequestParam(required = false) final String password,
final HttpServletRequest request,
final HttpServletResponse response
) throws IOException {
final String address = request.getRemoteAddr();
if (userService.isBannedFromLogin(address)) {
response.sendError(429, "Too Many Requests");
return;
}
final UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
if (registeredProperties.isEnabled() && "arsnova".equals(type)) {
try {
userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA, address);
} catch (final AuthenticationException e) {
logger.info("Database authentication failed.", e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
} else if (ldapProperties.stream().anyMatch(p -> p.isEnabled()) && "ldap".equals(type)) {
try {
userService.authenticate(authRequest, UserProfile.AuthProvider.LDAP, address);
} catch (final AuthenticationException e) {
logger.info("LDAP authentication failed.", e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
} else if (guestProperties.isEnabled() && "guest".equals(type)) {
try {
userService.authenticate(authRequest, UserProfile.AuthProvider.ARSNOVA_GUEST, address);
} catch (final AuthenticationException e) {
logger.debug("Guest authentication failed.", e);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
} else {
response.setStatus(HttpStatus.BAD_REQUEST.value());
}
}
@GetMapping("/dialog")
@ResponseBody
public View dialog(
@RequestParam("type") final String type,
@RequestParam(value = "successurl", defaultValue = "/") String successUrl,
@RequestParam(value = "failureurl", defaultValue = "/") String failureUrl,
final HttpServletRequest request,
final HttpServletResponse response
) throws HttpAction, IOException, ServletException {
View result = null;
/* Use URLs from a request parameters for redirection as long as the
* URL is not absolute (to prevent abuse of the redirection). */
if (UrlUtils.isAbsoluteUrl(successUrl)) {
successUrl = "/";
}
if (UrlUtils.isAbsoluteUrl(failureUrl)) {
failureUrl = "/";
}
final String host = request.getServerName();
final int port = request.getServerPort();
final String scheme = request.getScheme();
String serverUrl = scheme + "://" + host;
if ("https".equals(scheme)) {
if (443 != port) {
serverUrl = serverUrl + ":" + String.valueOf(port);
}
} else {
if (80 != port) {
serverUrl = serverUrl + ":" + String.valueOf(port);
}
}
request.getSession().setAttribute(LoginAuthenticationSucessHandler.URL_ATTRIBUTE, serverUrl + successUrl);
request.getSession().setAttribute(LoginAuthenticationFailureHandler.URL_ATTRIBUTE, serverUrl + failureUrl);
if (casProperties.isEnabled() && "cas".equals(type)) {
casEntryPoint.commence(request, response, null);
} else if (saml2Client != null && "saml".equals(type)) {
result = new RedirectView(
saml2Client.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (oidcProperties.stream().anyMatch(p -> p.isEnabled()) && "oidc".equals(type)) {
result = new RedirectView(
oidcClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (twitterClient != null && "twitter".equals(type)) {
result = new RedirectView(
twitterClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (facebookClient != null && "facebook".equals(type)) {
facebookClient.setFields("id");
facebookClient.setScope("");
result = new RedirectView(
facebookClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else if (googleOidcClient != null && "google".equals(type)) {
result = new RedirectView(
googleOidcClient.getRedirectAction(new J2EContext(request, response)).getLocation());
} else {
response.setStatus(HttpStatus.BAD_REQUEST.value());
}
return result;
}
@GetMapping({ "/", "/whoami" })
@ResponseBody
public ClientAuthentication whoami(@AuthenticationPrincipal final User user) {
if (user == null) {
throw new UnauthorizedException();
}
return new ClientAuthentication(user);
}
@PostMapping("/logout")
public String doLogout(final HttpServletRequest request) {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
userService.removeUserIdFromMaps(userService.getCurrentUser().getId());
request.getSession().invalidate();
SecurityContextHolder.clearContext();
if (auth instanceof CasAuthenticationToken) {
return "redirect:" + apiPath + SecurityConfig.CAS_LOGOUT_PATH;
}
return "redirect:" + (request.getHeader("referer") != null ? request.getHeader("referer") : "/");
}
@GetMapping("/services")
@ResponseBody
public List<ServiceDescription> getServices(final HttpServletRequest request) {
final List<ServiceDescription> services = new ArrayList<>();
/* The first parameter is replaced by the backend, the second one by the frondend */
final String dialogUrl = apiPath + "/v2/auth/dialog?type={0}&successurl='{0}'";
if (guestProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
"guest",
"Guest",
null,
guestProperties.getAllowedRoles()
);
sdesc.setOrder(guestProperties.getOrder());
services.add(sdesc);
}
if (registeredProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
"arsnova",
registeredProperties.getTitle(),
customizationPath + "/login?provider=arsnova&redirect={0}",
registeredProperties.getAllowedRoles()
);
sdesc.setOrder(registeredProperties.getOrder());
services.add(sdesc);
}
if (ldapProperties.get(0).isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.LDAP_PROVIDER_ID,
ldapProperties.get(0).getTitle(),
customizationPath + "/login?provider=" + SecurityConfig.LDAP_PROVIDER_ID + "&redirect={0}",
ldapProperties.get(0).getAllowedRoles()
);
sdesc.setOrder(ldapProperties.get(0).getOrder());
services.add(sdesc);
}
if (samlProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.SAML_PROVIDER_ID,
samlProperties.getTitle(),
MessageFormat.format(dialogUrl, SecurityConfig.SAML_PROVIDER_ID),
samlProperties.getAllowedRoles()
);
sdesc.setOrder(samlProperties.getOrder());
services.add(sdesc);
}
if (casProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.CAS_PROVIDER_ID,
casProperties.getTitle(),
MessageFormat.format(dialogUrl, SecurityConfig.CAS_PROVIDER_ID),
casProperties.getAllowedRoles()
);
sdesc.setOrder(casProperties.getOrder());
services.add(sdesc);
}
if (oidcProperties.get(0).isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.OIDC_PROVIDER_ID,
oidcProperties.get(0).getTitle(),
MessageFormat.format(dialogUrl, SecurityConfig.OIDC_PROVIDER_ID),
oidcProperties.get(0).getAllowedRoles()
);
sdesc.setOrder(oidcProperties.get(0).getOrder());
services.add(sdesc);
}
final AuthenticationProviderProperties.Oauth facebookProperties =
oauthProperties.get(SecurityConfig.FACEBOOK_PROVIDER_ID);
if (facebookProperties != null && facebookProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.FACEBOOK_PROVIDER_ID,
"Facebook",
MessageFormat.format(dialogUrl, SecurityConfig.FACEBOOK_PROVIDER_ID),
facebookProperties.getAllowedRoles()
);
sdesc.setOrder(facebookProperties.getOrder());
services.add(sdesc);
}
final AuthenticationProviderProperties.Oauth googleProperties =
oauthProperties.get(SecurityConfig.GOOGLE_PROVIDER_ID);
if (googleProperties != null && googleProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.GOOGLE_PROVIDER_ID,
"Google",
MessageFormat.format(dialogUrl, SecurityConfig.GOOGLE_PROVIDER_ID),
googleProperties.getAllowedRoles()
);
sdesc.setOrder(googleProperties.getOrder());
services.add(sdesc);
}
final AuthenticationProviderProperties.Oauth twitterProperties =
oauthProperties.get(SecurityConfig.TWITTER_PROVIDER_ID);
if (twitterProperties != null && twitterProperties.isEnabled()) {
final ServiceDescription sdesc = new ServiceDescription(
SecurityConfig.TWITTER_PROVIDER_ID,
"Twitter",
MessageFormat.format(dialogUrl, SecurityConfig.TWITTER_PROVIDER_ID),
twitterProperties.getAllowedRoles()
);
sdesc.setOrder(twitterProperties.getOrder());
services.add(sdesc);
}
return services;
}
private Collection<GrantedAuthority> getAuthorities(final boolean admin) {
final List<GrantedAuthority> authList = new ArrayList<>();
authList.add(new SimpleGrantedAuthority("ROLE_USER"));
if (admin) {
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
return authList;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.PaginationController;
import de.thm.arsnova.model.Room;
import de.thm.arsnova.model.migration.FromV2Migrator;
import de.thm.arsnova.model.migration.ToV2Migrator;
import de.thm.arsnova.model.migration.v2.Comment;
import de.thm.arsnova.model.migration.v2.CommentReadingCount;
import de.thm.arsnova.service.CommentService;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.DeprecatedApi;
import de.thm.arsnova.web.Pagination;
/**
* Handles requests related to comments.
*/
@RestController("v2CommentController")
@RequestMapping("/v2/audiencequestion")
@Api(value = "/audiencequestion", description = "Comment (Interposed/Audience Question) API")
public class CommentController extends PaginationController {
@Autowired
private CommentService commentService;
@Autowired
private RoomService roomService;
@Autowired
private UserService userService;
@Autowired
private ToV2Migrator toV2Migrator;
@Autowired
private FromV2Migrator fromV2Migrator;
@ApiOperation(value = "Count all the comments in current room",
nickname = "getCommentCount")
@GetMapping(value = "/count", produces = MediaType.TEXT_PLAIN_VALUE)
@DeprecatedApi
@Deprecated
public String getCommentCount(
@ApiParam(value = "Room-Key from current room", required = true)
@RequestParam("sessionkey")
final String roomShortId) {
return String.valueOf(commentService.count(roomService.getIdByShortId(roomShortId)));
}
@ApiOperation(value = "count all unread comments",
nickname = "getUnreadCommentCount")
@GetMapping("/readcount")
@DeprecatedApi
@Deprecated
public CommentReadingCount getUnreadCommentCount(
@ApiParam(value = "Room-Key from current room", required = true)
@RequestParam("sessionkey") final String roomShortId, final String user) {
return commentService.countRead(roomService.getIdByShortId(roomShortId), user);
}
@ApiOperation(value = "Retrieves all Comments for a Room",
nickname = "getComments")
@GetMapping("/")
@Pagination
public List<Comment> getComments(
@ApiParam(value = "Room-Key from current room", required = true)
@RequestParam("sessionkey")
final String roomShortId) {
return commentService.getByRoomId(roomService.getIdByShortId(roomShortId), offset, limit).stream()
.map(toV2Migrator::migrate).collect(Collectors.toList());
}
@ApiOperation(value = "Retrieves an Comment",
nickname = "getComment")
@GetMapping("/{commentId}")
public Comment getComment(
@ApiParam(value = "ID of the Comment that needs to be deleted", required = true)
@PathVariable
final String commentId)
throws IOException {
return toV2Migrator.migrate(commentService.getAndMarkRead(commentId));
}
@ApiOperation(value = "Creates a new Comment for a Room and returns the Comment's data",
nickname = "postComment")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
})
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public void postComment(
@ApiParam(value = "Room-Key from current room", required = true)
@RequestParam("sessionkey")
final String roomShortId,
@ApiParam(value = "the body from the new comment", required = true)
@RequestBody
final Comment comment) {
final de.thm.arsnova.model.Comment commentV3 = fromV2Migrator.migrate(comment);
final Room roomV3 = roomService.getByShortId(roomShortId);
commentV3.setRoomId(roomV3.getId());
commentService.create(commentV3);
}
@ApiOperation(value = "Deletes a Comment",
nickname = "deleteComment")
@DeleteMapping("/{commentId}")
public void deleteComment(
@ApiParam(value = "ID of the comment that needs to be deleted", required = true)
@PathVariable
final String commentId) {
commentService.delete(commentService.get(commentId));
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import de.thm.arsnova.config.properties.SystemProperties;
import de.thm.arsnova.controller.AbstractController;
/**
* The ConfigurationController provides frontend clients with information necessary to correctly interact with the
* backend and other frontends as well as settings for ARSnova. The the alternative /arsnova-config route is necessary
* in case the backend application is deployed as root context.
*/
@Controller("v2ConfigurationController")
@RequestMapping({"/v2/configuration", "/v2/arsnova-config"})
public class ConfigurationController extends AbstractController {
private static final Logger logger = LoggerFactory
.getLogger(ConfigurationController.class);
private ServletContext servletContext;
private String apiPath;
private String socketioPath;
@Value("${customization.path}")
private String customizationPath;
@Value("${mobile.path}")
private String mobilePath;
@Value("${presenter.path}")
private String presenterPath;
@Value("${ui.links.overlay.url}")
private String overlayUrl;
@Value("${ui.links.organization.url}")
private String organizationUrl;
@Value("${ui.links.imprint.url}")
private String imprintUrl;
@Value("${ui.links.blog.url:}")
private String blogUrl;
@Value("${ui.links.privacy-policy.url}")
private String privacyPolicyUrl;
@Value("${ui.links.documentation.url}")
private String documentationUrl;
@Value("${ui.links.presenter-documentation.url}")
private String presenterDocumentationUrl;
@Value("${ui.feedback.warning:5}")
private String feedbackWarningOffset;
@Value("${ui.mathjax.enabled:true}")
private String mathJaxEnabled;
@Value("${ui.mathjax.src:}")
private String mathJaxSrc;
@Value("${features.contents.formats.freetext.imageanswer.enabled:false}")
private String imageAnswerEnabled;
@Value("${features.contents.formats.grid-square.enabled:false}")
private String gridSquareEnabled;
private String roomImportExportEnabled = "true";
@Value("${features.content-pool.enabled:false}")
private String publicPoolEnabled;
@Value("${features.contents.answer-option-limit:8}")
private String answerOptionLimit;
@Value("${system.uploads.max-filesize:}")
private String maxUploadFilesize;
@Value("${ui.parse-answer-option-formatting:false}")
private String parseAnswerOptionFormatting;
@Value("${features.content-pool.subjects}")
private String ppSubjects;
@Value("${features.content-pool.licenses}")
private String ppLicenses;
@Value("${features.content-pool.logo-max-filesize}")
private String ppLogoMaxFilesize;
@Value("${system.uploads.max-filesize}")
private String gridImageMaxFileSize;
@Value("${ui.tracking.provider}")
private String trackingProvider;
@Value("${ui.tracking.tracker-url}")
private String trackingTrackerUrl;
@Value("${ui.tracking.site-id}")
private String trackingSiteId;
@Value("${ui.demo-room-id:}")
private String demoRoomShortId;
@Value("${ui.slogan:}")
private String arsnovaSlogan;
@Value("${ui.splashscreen.logo-path:}")
private String splashscreenLogo;
@Value("${ui.splashscreen.slogan:}")
private String splashscreenSlogan;
@Value("${ui.splashscreen.slogan-color:}")
private String splashscreenSloganColor;
@Value("${ui.splashscreen.background-color:}")
private String splashscreenBgColor;
@Value("${ui.splashscreen.loading-ind-color:}")
private String splashscreenLoadingIndColor;
@Value("${ui.splashscreen.min-delay:}")
private String splashscreenDelay;
@Value("${features.content-pool.session-levels.de}")
private String ppLevelsDe;
@Value("${features.content-pool.session-levels.en}")
private String ppLevelsEn;
public ConfigurationController(final SystemProperties systemProperties, final ServletContext servletContext) {
apiPath = systemProperties.getApi().getProxyPath();
socketioPath = systemProperties.getSocketio().getProxyPath();
this.servletContext = servletContext;
}
@PostConstruct
private void init() {
if (apiPath == null || "".equals(apiPath)) {
apiPath = servletContext.getContextPath();
}
}
@GetMapping
@ResponseBody
public Map<String, Object> getConfiguration(final HttpServletRequest request) {
final Map<String, Object> config = new HashMap<>();
final Map<String, Boolean> features = new HashMap<>();
final Map<String, String> publicPool = new HashMap<>();
final Map<String, Object> splashscreen = new HashMap<>();
config.put("apiPath", apiPath + "/v2");
if (!"".equals(socketioPath)) {
config.put("socketioPath", socketioPath);
}
if (!"".equals(customizationPath)) {
config.put("customizationPath", customizationPath);
}
if (!"".equals(mobilePath)) {
config.put("mobilePath", mobilePath);
}
if (!"".equals(presenterPath)) {
config.put("presenterPath", presenterPath);
}
if (!"".equals(documentationUrl)) {
config.put("documentationUrl", documentationUrl);
}
if (!"".equals(blogUrl)) {
config.put("blogUrl", blogUrl);
}
if (!"".equals(presenterDocumentationUrl)) {
config.put("presenterDocumentationUrl", presenterDocumentationUrl);
}
if (!"".equals(overlayUrl)) {
config.put("overlayUrl", overlayUrl);
}
if (!"".equals(organizationUrl)) {
config.put("organizationUrl", organizationUrl);
}
if (!"".equals(imprintUrl)) {
config.put("imprintUrl", imprintUrl);
}
if (!"".equals(privacyPolicyUrl)) {
config.put("privacyPolicyUrl", privacyPolicyUrl);
}
if (!"".equals(demoRoomShortId)) {
config.put("demoRoomShortId", demoRoomShortId);
}
if (!"".equals(arsnovaSlogan)) {
config.put("arsnovaSlogan", arsnovaSlogan);
}
if (!"".equals(maxUploadFilesize)) {
config.put("maxUploadFilesize", maxUploadFilesize);
}
if (!"".equals(mathJaxSrc) && "true".equals(mathJaxEnabled)) {
config.put("mathJaxSrc", mathJaxSrc);
}
config.put("answerOptionLimit", Integer.valueOf(answerOptionLimit));
config.put("feedbackWarningOffset", Integer.valueOf(feedbackWarningOffset));
config.put("parseAnswerOptionFormatting", Boolean.valueOf(parseAnswerOptionFormatting));
config.put("features", features);
features.put("mathJax", "true".equals(mathJaxEnabled));
/* Keep the markdown property for now since the frontend still depends on it */
features.put("markdown", true);
features.put("imageAnswer", "true".equals(imageAnswerEnabled));
features.put("gridSquare", "true".equals(gridSquareEnabled));
features.put("sessionImportExport", "true".equals(roomImportExportEnabled));
features.put("publicPool", "true".equals(publicPoolEnabled));
features.put("exportToClick", false);
// add public pool configuration on demand
if (features.get("publicPool")) {
config.put("publicPool", publicPool);
publicPool.put("subjects", ppSubjects);
publicPool.put("licenses", ppLicenses);
publicPool.put("logoMaxFilesize", ppLogoMaxFilesize);
publicPool.put("levelsDe", ppLevelsDe);
publicPool.put("levelsEn", ppLevelsEn);
}
config.put("splashscreen", splashscreen);
if (!"".equals(splashscreenLogo)) {
splashscreen.put("logo", splashscreenLogo);
}
if (!"".equals(splashscreenSlogan)) {
splashscreen.put("slogan", splashscreenSlogan);
}
if (!"".equals(splashscreenSloganColor)) {
splashscreen.put("sloganColor", splashscreenSloganColor);
}
if (!"".equals(splashscreenBgColor)) {
splashscreen.put("bgcolor", splashscreenBgColor);
}
if (!"".equals(splashscreenLoadingIndColor)) {
splashscreen.put("loadIndColor", splashscreenLoadingIndColor);
}
if (!"".equals(splashscreenDelay)) {
splashscreen.put("minDelay", Integer.valueOf(splashscreenDelay));
}
if (!"".equals(trackingTrackerUrl)) {
final Map<String, String> tracking = new HashMap<>();
config.put("tracking", tracking);
tracking.put("provider", trackingProvider);
tracking.put("trackerUrl", trackingTrackerUrl);
tracking.put("siteId", trackingSiteId);
}
config.put("grid", gridImageMaxFileSize);
return config;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.naming.OperationNotSupportedException;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.PaginationController;
import de.thm.arsnova.model.ChoiceAnswer;
import de.thm.arsnova.model.ChoiceQuestionContent;
import de.thm.arsnova.model.ContentGroup;
import de.thm.arsnova.model.GridImageContent;
import de.thm.arsnova.model.TextAnswer;
import de.thm.arsnova.model.migration.FromV2Migrator;
import de.thm.arsnova.model.migration.ToV2Migrator;
import de.thm.arsnova.model.migration.v2.Answer;
import de.thm.arsnova.model.migration.v2.Content;
import de.thm.arsnova.service.AnswerService;
import de.thm.arsnova.service.ContentGroupService;
import de.thm.arsnova.service.ContentService;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.TimerService;
import de.thm.arsnova.util.PaginationListDecorator;
import de.thm.arsnova.web.DeprecatedApi;
import de.thm.arsnova.web.Pagination;
import de.thm.arsnova.web.exceptions.BadRequestException;
import de.thm.arsnova.web.exceptions.ForbiddenException;
import de.thm.arsnova.web.exceptions.NoContentException;
import de.thm.arsnova.web.exceptions.NotFoundException;
import de.thm.arsnova.web.exceptions.NotImplementedException;
/**
* Handles requests related to contents.
*/
@RestController("v2ContentController")
@RequestMapping("/v2/lecturerquestion")
@Api(value = "/lecturerquestion", description = "Content (Skill/Lecturer Question) API")
public class ContentController extends PaginationController {
@Autowired
private ContentService contentService;
@Autowired
private ContentGroupService contentGroupService;
@Autowired
private AnswerService answerService;
@Autowired
private RoomService roomService;
@Autowired
private TimerService timerService;
@Autowired
private ToV2Migrator toV2Migrator;
@Autowired
private FromV2Migrator fromV2Migrator;
@ApiOperation(value = "Get content with provided content Id",
nickname = "getContent")
@ApiResponses(value = {
@ApiResponse(code = 404, message = HTML_STATUS_404)
})
@GetMapping("/{contentId}")
public Content getContent(@PathVariable final String contentId) {
final de.thm.arsnova.model.Content content = contentService.get(contentId);
if (content != null) {
final Optional<ContentGroup> contentGroup = contentGroupService.getByRoomId(content.getRoomId()).stream()
.filter(cg -> cg.getContentIds().contains(contentId)).findFirst();
final Content contentV2 = toV2Migrator.migrate(content);
contentGroup.ifPresent(cg -> contentV2.setQuestionVariant(cg.getName()));
return contentV2;
}
throw new NotFoundException();
}
@ApiOperation(value = "Post provided content",
nickname = "postContent")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
})
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public Content postContent(@RequestBody final Content content) {
final de.thm.arsnova.model.Content contentV3 = fromV2Migrator.migrate(content);
final String roomId = roomService.getIdByShortId(content.getSessionKeyword());
contentV3.setRoomId(roomId);
contentService.create(contentV3);
contentGroupService.addContentToGroup(roomId, content.getQuestionVariant(), contentV3.getId());
return toV2Migrator.migrate(contentV3);
}
@ApiOperation(value = "Post provided contents", nickname = "bulkPostContents")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
})
@PostMapping("/bulk")
@ResponseStatus(HttpStatus.CREATED)
public List<Content> bulkPostContents(@RequestBody final List<Content> contents) {
final List<de.thm.arsnova.model.Content> contentsV3 =
contents.stream().map(c -> contentService.create(fromV2Migrator.migrate(c))).collect(Collectors.toList());
return contentsV3.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
@ApiOperation(value = "Update the content, identified by provided id, with the provided content in the Request Body",
nickname = "updateContent")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
})
@PutMapping("/{contentId}")
public Content updateContent(
@PathVariable final String contentId,
@RequestBody final Content content) {
return toV2Migrator.migrate(contentService.update(fromV2Migrator.migrate(content)));
}
@ApiOperation(value = "Start new Pi Round on content, identified by provided id, with an optional time",
nickname = "startPiRound")
@GetMapping("/{contentId}/questionimage")
public String getContentImage(
@PathVariable final String contentId,
@RequestParam(value = "fcImage", defaultValue = "false", required = false) final boolean fcImage) {
throw new NotImplementedException();
}
@PostMapping("/{contentId}/startnewpiround")
public void startPiRound(
@PathVariable final String contentId,
@RequestParam(value = "time", defaultValue = "0", required = false) final int time) {
if (time == 0) {
timerService.startNewRound(contentId);
} else {
timerService.startNewRoundDelayed(contentId, time);
}
}
@PostMapping("/{contentId}/canceldelayedpiround")
@ApiOperation(value = "Cancel Pi Round on content, identified by provided id",
nickname = "cancelPiRound")
public void cancelPiRound(
@PathVariable final String contentId) {
timerService.cancelRoundChange(contentId);
}
@PostMapping("/{contentId}/resetpiroundstate")
@ApiOperation(value = "Reset Pi Round on content, identified by provided id",
nickname = "resetPiContent")
public void resetPiContent(
@PathVariable final String contentId) {
timerService.resetRoundState(contentId);
}
@ApiOperation(value = "Set voting admission on content, identified by provided id",
nickname = "setVotingAdmission")
@PostMapping("/{contentId}/disablevote")
public void setVotingAdmission(
@PathVariable final String contentId,
@RequestParam(value = "disable", defaultValue = "false", required = false) final Boolean disableVote) {
boolean disable = false;
if (disableVote != null) {
disable = disableVote;
}
contentService.setVotingAdmission(contentId, disable);
}
@ApiOperation(value = "Set voting admission for all contents",
nickname = "setVotingAdmissionForAllContents")
@PostMapping("/disablevote")
public void setVotingAdmissionForAllContents(
@RequestParam(value = "sessionkey")
final String roomShortId,
@RequestParam(value = "disable", defaultValue = "false", required = false)
final Boolean disableVote,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false", required = false) final
boolean lectureContentsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false", required = false) final
boolean preparationContentsOnly) {
final String roomId = roomService.getIdByShortId(roomShortId);
boolean disable = false;
final Iterable<de.thm.arsnova.model.Content> contents;
if (disableVote != null) {
disable = disableVote;
}
if (lectureContentsOnly) {
contents = contentService.getByRoomIdAndGroup(roomId, "lecture");
contentService.setVotingAdmissions(roomId, disable, contents);
} else if (preparationContentsOnly) {
contents = contentService.getByRoomIdAndGroup(roomId, "preparation");
contentService.setVotingAdmissions(roomId, disable, contents);
} else {
contents = contentService.getByRoomId(roomId);
contentService.setVotingAdmissions(roomId, disable, contents);
}
}
@ApiOperation(value = "Publish a content, identified by provided id and content in Request Body.",
nickname = "publishContent")
@PostMapping("/{contentId}/publish")
public void publishContent(
@PathVariable final String contentId,
@RequestParam(defaultValue = "true", required = false) final boolean publish,
@RequestBody final Content content
) throws IOException {
final de.thm.arsnova.model.Content contentV3 = fromV2Migrator.migrate(content);
boolean p = publish;
if (content != null) {
p = contentV3.getState().isVisible();
}
contentService.patch(contentV3, Collections.singletonMap("visible", p), de.thm.arsnova.model.Content::getState);
}
@ApiOperation(value = "Publish all contents",
nickname = "publishAllContents")
@PostMapping("/publish")
public void publishAllContents(
@RequestParam(value = "sessionkey")
final String roomShortId,
@RequestParam(required = false)
final Boolean publish,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false", required = false) final
boolean lectureContentsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false", required = false) final
boolean preparationContentsOnly
) throws IOException {
final String roomId = roomService.getIdByShortId(roomShortId);
final boolean p = publish == null || publish;
final Iterable<de.thm.arsnova.model.Content> contents;
if (lectureContentsOnly) {
contents = contentService.getByRoomIdAndGroup(roomId, "lecture");
contentService.publishContents(roomId, p, contents);
} else if (preparationContentsOnly) {
contents = contentService.getByRoomIdAndGroup(roomId, "preparation");
contentService.publishContents(roomId, p, contents);
} else {
contentService.publishAll(roomId, p);
}
}
@ApiOperation(value = "Publish statistics from content with provided id",
nickname = "publishStatistics")
@PostMapping("/{contentId}/publishstatistics")
public void publishStatistics(
@PathVariable final String contentId,
@RequestParam(defaultValue = "true", required = false) final Boolean showStatistics,
@RequestBody final Content content
) throws IOException {
final de.thm.arsnova.model.Content contentV3 = fromV2Migrator.migrate(content);
boolean p = showStatistics;
if (content != null) {
p = contentV3.getState().isResponsesVisible();
}
contentService.patch(contentV3, Collections.singletonMap("responsesVisible", p),
de.thm.arsnova.model.Content::getState);
}
@ApiOperation(value = "Publish correct answer from content with provided id",
nickname = "publishCorrectAnswer")
@PostMapping("/{contentId}/publishcorrectanswer")
public void publishCorrectAnswer(
@PathVariable final String contentId,
@RequestParam(defaultValue = "true", required = false) final boolean showCorrectAnswer,
@RequestBody final Content content
) throws IOException {
final de.thm.arsnova.model.Content contentV3 = fromV2Migrator.migrate(content);
boolean p = showCorrectAnswer;
if (content != null) {
p = contentV3.getState().isAdditionalTextVisible();
}
contentService.patch(contentV3, Collections.singletonMap("solutionVisible", p),
de.thm.arsnova.model.Content::getState);
}
@ApiOperation(value = "Get contents",
nickname = "getContents")
@GetMapping("/")
@Pagination
public List<Content> getContents(
@RequestParam(value = "sessionkey") final String roomShortId,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureContentsOnly,
@RequestParam(value = "flashcardsonly", defaultValue = "false") final boolean flashcardsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationContentsOnly,
@RequestParam(value = "requestImageData", defaultValue = "false") final boolean requestImageData,
final HttpServletResponse response) {
final String roomId = roomService.getIdByShortId(roomShortId);
final Iterable<de.thm.arsnova.model.Content> contents;
if (lectureContentsOnly) {
contents = contentService.getByRoomIdAndGroup(roomId, "lecture");
} else if (flashcardsOnly) {
contents = contentService.getByRoomIdAndGroup(roomId, "flashcard");
} else if (preparationContentsOnly) {
contents = contentService.getByRoomIdAndGroup(roomId, "preparation");
} else {
contents = contentService.getByRoomId(roomId);
}
if (contents == null || !contents.iterator().hasNext()) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return null;
}
return new PaginationListDecorator<>(StreamSupport.stream(contents.spliterator(), false)
.map(toV2Migrator::migrate).collect(Collectors.toList()), offset, limit);
}
@ApiOperation(value = "Delete contents",
nickname = "deleteContents")
@DeleteMapping("/")
public void deleteContents(
@RequestParam(value = "sessionkey")
final String roomShortId,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false")
final boolean lectureContentsOnly,
@RequestParam(value = "flashcardsonly", defaultValue = "false")
final boolean flashcardsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false")
final boolean preparationContentsOnly,
final HttpServletResponse response) {
final String roomId = roomService.getIdByShortId(roomShortId);
if (lectureContentsOnly) {
contentService.deleteLectureContents(roomId);
} else if (preparationContentsOnly) {
contentService.deletePreparationContents(roomId);
} else if (flashcardsOnly) {
contentService.deleteFlashcards(roomId);
} else {
contentService.deleteAllContents(roomId);
}
}
@ApiOperation(value = "Get the amount of contents by the room-key",
nickname = "getContentCount")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/count", produces = MediaType.TEXT_PLAIN_VALUE)
public String getContentCount(
@RequestParam(value = "sessionkey") final String roomShortId,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false") final boolean lectureContentsOnly,
@RequestParam(value = "flashcardsonly", defaultValue = "false") final boolean flashcardsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false") final boolean preparationContentsOnly) {
final String roomId = roomService.getIdByShortId(roomShortId);
final int count;
if (lectureContentsOnly) {
count = contentService.countByRoomIdAndGroup(roomId, "lecture");
} else if (preparationContentsOnly) {
count = contentService.countByRoomIdAndGroup(roomId, "preparation");
} else if (flashcardsOnly) {
count = contentService.countByRoomIdAndGroup(roomId, "flashcard");
} else {
count = contentService.countByRoomId(roomId);
}
return String.valueOf(count);
}
@ApiOperation(value = "Delete answers and content",
nickname = "deleteAnswersAndContent")
@DeleteMapping("/{contentId}")
public void deleteAnswersAndContent(
@PathVariable final String contentId) {
contentService.delete(contentId);
}
@ApiOperation(value = "Get unanswered content IDs by provided room short ID",
nickname = "getUnAnsweredContentIds")
@DeprecatedApi
@Deprecated
@GetMapping("/unanswered")
public List<String> getUnAnsweredContentIds(
@RequestParam(value = "sessionkey")
final String roomShortId,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false")
final boolean lectureContentsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false")
final boolean preparationContentsOnly) {
final String roomId = roomService.getIdByShortId(roomShortId);
final List<String> answers;
if (lectureContentsOnly) {
answers = contentService.getUnAnsweredLectureContentIds(roomId);
} else if (preparationContentsOnly) {
answers = contentService.getUnAnsweredPreparationContentIds(roomId);
} else {
answers = contentService.getUnAnsweredContentIds(roomId);
}
if (answers == null || answers.isEmpty()) {
throw new NoContentException();
}
return answers;
}
/**
* Returns a JSON document which represents the given answer of a content.
*
* @param contentId
* CouchDB Content ID for which the given answer should be
* retrieved
* @return JSON Document of {@link Answer} or {@link NotFoundException}
* @throws NotFoundException
* if wrong room, wrong content or no answer was given by
* the current user
* @throws ForbiddenException
* if not logged in
*/
@ApiOperation(value = "Get my answer for a content, identified by provided content ID",
nickname = "getMyAnswer")
@DeprecatedApi
@Deprecated
@GetMapping("/{contentId}/myanswer")
public Answer getMyAnswer(
@PathVariable final String contentId,
final HttpServletResponse response) {
final de.thm.arsnova.model.Content content = contentService.get(contentId);
final de.thm.arsnova.model.Answer answer = answerService.getMyAnswer(contentId);
if (answer == null) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return null;
}
if (content.getFormat().equals(de.thm.arsnova.model.Content.Format.TEXT)) {
return toV2Migrator.migrate((TextAnswer) answer);
} else {
return toV2Migrator.migrate((ChoiceAnswer) answer, content);
}
}
/**
* Returns a list of {@link Answer}s encoded as a JSON document for a given
* content id. In this case only {@link Answer} <tt>contentId</tt>,
* <tt>answerText</tt>, <tt>answerSubject</tt> and <tt>answerCount</tt>
* properties are set.
*
* @param contentId
* CouchDB Content ID for which the given answers should be
* retrieved
* @throws NotFoundException
* if wrong room, wrong content or no answers was given
* @throws ForbiddenException
* if not logged in
*/
@ApiOperation(value = "Get answers for a content, identified by provided content ID",
nickname = "getAnswers")
@GetMapping("/{contentId}/answer/")
public List<Answer> getAnswers(
@PathVariable final String contentId,
@RequestParam(value = "piround", required = false) final Integer piRound,
@RequestParam(value = "all", required = false, defaultValue = "false") final Boolean allAnswers,
final HttpServletResponse response) {
final de.thm.arsnova.model.Content content = contentService.get(contentId);
if (content instanceof ChoiceQuestionContent || content instanceof GridImageContent) {
return toV2Migrator.migrate(answerService.getAllStatistics(contentId),
content, content.getState().getRound());
} else {
final List<de.thm.arsnova.model.TextAnswer> answers;
if (allAnswers) {
answers = answerService.getAllTextAnswers(contentId, -1, -1);
} else if (null == piRound) {
answers = answerService.getTextAnswers(contentId, offset, limit);
} else {
if (piRound < 1 || piRound > 2) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
return null;
}
answers = answerService.getTextAnswers(contentId, piRound, offset, limit);
}
if (answers == null) {
return new ArrayList<>();
}
return answers.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
}
@ApiOperation(value = "Save answer, provided in the Request Body, for a content, identified by provided content ID",
nickname = "saveAnswer")
@PostMapping("/{contentId}/answer/")
public Answer saveAnswer(
@PathVariable final String contentId,
@RequestBody final Answer answer,
final HttpServletResponse response) {
final de.thm.arsnova.model.Content content = contentService.get(contentId);
final de.thm.arsnova.model.Answer answerV3 = fromV2Migrator.migrate(answer, content);
if (answerV3.getContentId() == null) {
answerV3.setContentId(contentId);
}
if (!contentId.equals(answerV3.getContentId())) {
throw new BadRequestException("Mismatching content IDs.");
}
if (answerV3 instanceof TextAnswer) {
return toV2Migrator.migrate((TextAnswer) answerService.create(answerV3));
} else {
return toV2Migrator.migrate((ChoiceAnswer) answerService.create(answerV3), content);
}
}
@ApiOperation(value = "Update answer, provided in Request Body, identified by content ID and answer ID",
nickname = "updateAnswer")
@PutMapping("/{contentId}/answer/{answerId}")
public Answer updateAnswer(
@PathVariable final String contentId,
@PathVariable final String answerId,
@RequestBody final Answer answer,
final HttpServletResponse response) {
final de.thm.arsnova.model.Content content = contentService.get(contentId);
final de.thm.arsnova.model.Answer answerV3 = fromV2Migrator.migrate(answer, content);
if (answerV3 instanceof TextAnswer) {
return toV2Migrator.migrate((TextAnswer) answerService.update(answerV3));
} else {
return toV2Migrator.migrate((ChoiceAnswer) answerService.update(answerV3), content);
}
}
@ApiOperation(value = "Get Image, identified by content ID and answer ID",
nickname = "getImage")
@GetMapping("/{contentId}/answer/{answerId}/image")
public String getImage(
@PathVariable final String contentId,
@PathVariable final String answerId,
final HttpServletResponse response) {
throw new NotImplementedException();
}
@ApiOperation(value = "Delete answer, identified by content ID and answer ID",
nickname = "deleteAnswer")
@DeleteMapping("/{contentId}/answer/{answerId}")
public void deleteAnswer(
@PathVariable final String contentId,
@PathVariable final String answerId,
final HttpServletResponse response) {
answerService.delete(answerService.get(answerId));
}
@ApiOperation(value = "Delete answers from a content, identified by content ID",
nickname = "deleteAnswers")
@DeleteMapping("/{contentId}/answer/")
public void deleteAnswers(
@PathVariable final String contentId,
final HttpServletResponse response) {
answerService.deleteAnswers(contentId);
}
@ApiOperation(value = "Delete all answers and contents from a room, identified by room short ID",
nickname = "deleteAllContentsAnswers")
@DeleteMapping("/answers")
public void deleteAllContentsAnswers(
@RequestParam(value = "sessionkey")
final String roomShortId,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false")
final boolean lectureContentsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false")
final boolean preparationContentsOnly,
final HttpServletResponse response) {
final String roomId = roomService.getIdByShortId(roomShortId);
if (lectureContentsOnly) {
contentService.deleteAllLectureAnswers(roomId);
} else if (preparationContentsOnly) {
contentService.deleteAllPreparationAnswers(roomId);
} else {
contentService.deleteAllContentsAnswers(roomId);
}
}
/**
* Returns the count of answers for given content ID.
*
* @param contentId
* Content ID for which the given answers should be
* retrieved
* @throws NotFoundException
* if wrong room or wrong content
* @throws ForbiddenException
* if not logged in
*/
@ApiOperation(value = "Get the amount of answers for a content, identified by content ID",
nickname = "getAnswerCount")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/{contentId}/answercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String getAnswerCount(@PathVariable final String contentId) {
return String.valueOf(answerService.countAnswersByContentIdAndRound(contentId));
}
@ApiOperation(value = "Get the amount of answers for a content, identified by the content ID",
nickname = "getAllAnswerCount")
@GetMapping("/{contentId}/allroundanswercount")
public List<Integer> getAllAnswerCount(@PathVariable final String contentId) {
return Arrays.asList(
answerService.countAnswersByContentIdAndRound(contentId, 1),
answerService.countAnswersByContentIdAndRound(contentId, 2)
);
}
@ApiOperation(value = "Get the total amount of answers by a content, identified by the content ID",
nickname = "getTotalAnswerCountByContent")
@GetMapping(value = "/{contentId}/totalanswercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String getTotalAnswerCountByContent(@PathVariable final String contentId) {
return String.valueOf(answerService.countTotalAnswersByContentId(contentId));
}
@ApiOperation(value = "Get the amount of answers and abstention answers by a content, identified by the content ID",
nickname = "getAnswerAndAbstentionCount")
@GetMapping("/{contentId}/answerandabstentioncount")
public List<Integer> getAnswerAndAbstentionCount(@PathVariable final String contentId) {
return Arrays.asList(
answerService.countAnswersByContentIdAndRound(contentId),
answerService.countTotalAbstentionsByContentId(contentId)
);
}
@ApiOperation(value = "Get all Freetext answers by a content, identified by the content ID",
nickname = "getFreetextAnswers")
@GetMapping("/{contentId}/freetextanswer/")
@Pagination
public List<Answer> getFreetextAnswers(@PathVariable final String contentId) {
return answerService.getTextAnswersByContentId(contentId, offset, limit).stream()
.map(toV2Migrator::migrate).collect(Collectors.toList());
}
@ApiOperation(value = "Get my answers of an room, identified by the room short ID",
nickname = "getMyAnswers")
@DeprecatedApi
@Deprecated
@GetMapping("/myanswers")
public List<Answer> getMyAnswers(@RequestParam(value = "sessionkey") final String roomShortId)
throws OperationNotSupportedException {
return answerService.getMyAnswersByRoomId(roomService.getIdByShortId(roomShortId)).stream()
.map(a -> {
if (a instanceof ChoiceAnswer) {
return toV2Migrator.migrate(
(ChoiceAnswer) a, contentService.get(a.getContentId()));
} else {
return toV2Migrator.migrate((TextAnswer) a);
}
}).collect(Collectors.toList());
}
@ApiOperation(value = "Get the total amount of answers of a room, identified by the room short ID",
nickname = "getTotalAnswerCount")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/answercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String getTotalAnswerCount(
@RequestParam(value = "sessionkey")
final String roomShortId,
@RequestParam(value = "lecturequestionsonly", defaultValue = "false")
final boolean lectureContentsOnly,
@RequestParam(value = "preparationquestionsonly", defaultValue = "false")
final boolean preparationContentsOnly) {
final String roomId = roomService.getIdByShortId(roomShortId);
int count = 0;
if (lectureContentsOnly) {
count = answerService.countLectureContentAnswers(roomId);
} else if (preparationContentsOnly) {
count = answerService.countPreparationContentAnswers(roomId);
} else {
count = answerService.countTotalAnswersByRoomId(roomId);
}
return String.valueOf(count);
}
}
/* /*
* Copyright (C) 2012 THM webMedia * This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
* *
* This file is part of ARSnova. * ARSnova Backend is free software: you can redistribute it and/or modify
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* ARSnova is distributed in the hope that it will be useful, * ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
...@@ -16,78 +15,71 @@ ...@@ -16,78 +15,71 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package de.thm.arsnova.controller;
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.ApiParam;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.connector.client.ConnectorClient; import de.thm.arsnova.connector.client.ConnectorClient;
import de.thm.arsnova.connector.model.Course; import de.thm.arsnova.connector.model.Course;
import de.thm.arsnova.connector.model.UserRole; import de.thm.arsnova.connector.model.UserRole;
import de.thm.arsnova.exceptions.NotFoundException; import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.exceptions.UnauthorizedException; import de.thm.arsnova.security.User;
import de.thm.arsnova.services.ISessionService; import de.thm.arsnova.service.UserService;
import de.thm.arsnova.services.IUserService; import de.thm.arsnova.web.exceptions.NotImplementedException;
import de.thm.arsnova.web.exceptions.UnauthorizedException;
@Controller /**
* Provides access to a user's courses in an LMS such as Moodle.
*/
@RestController("v2CourseController")
public class CourseController extends AbstractController { public class CourseController extends AbstractController {
public static final Logger LOGGER = LoggerFactory
.getLogger(CourseController.class);
@Autowired(required = false) @Autowired(required = false)
private ConnectorClient connectorClient; private ConnectorClient connectorClient;
@Autowired @Autowired
private IUserService userService; private UserService userService;
@Autowired @GetMapping("/v2/mycourses")
private ISessionService sessionService; public List<Course> myCourses(
@ApiParam(value = "sort my courses by name", required = true)
@RequestParam(value = "sortby", defaultValue = "name") final String sortby) {
@RequestMapping(value = "/mycourses", method = RequestMethod.GET) final User currentUser = userService.getCurrentUser();
@ResponseBody
public final List<Course> myCourses(
@RequestParam(value="sortby", defaultValue="name") final String sortby
) {
String username = userService.getCurrentUser().getUsername();
if (username == null) { if (currentUser == null || currentUser.getUsername() == null) {
throw new UnauthorizedException(); throw new UnauthorizedException();
} }
if (connectorClient == null) { if (connectorClient == null) {
throw new NotFoundException(); throw new NotImplementedException();
} }
List<Course> result = new ArrayList<Course>(); final List<Course> result = new ArrayList<>();
for (Course course : connectorClient.getCourses(username).getCourse()) { for (final Course course : connectorClient.getCourses(currentUser.getUsername()).getCourse()) {
if ( if (
course.getMembership().isMember() course.getMembership().isMember()
&& course.getMembership().getUserrole().equals(UserRole.TEACHER) && course.getMembership().getUserrole().equals(UserRole.TEACHER)) {
) {
result.add(course); result.add(course);
} }
} }
if (sortby != null && sortby.equals("shortname")) { if ("shortname".equals(sortby)) {
Collections.sort(result, new CourseShortNameComperator()); Collections.sort(result, new CourseShortNameComperator());
} else { } else {
Collections.sort(result, new CourseNameComperator()); Collections.sort(result, new CourseNameComperator());
} }
return result; return result;
} }
...@@ -95,16 +87,16 @@ public class CourseController extends AbstractController { ...@@ -95,16 +87,16 @@ public class CourseController extends AbstractController {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override
public int compare(Course course1, Course course2) { public int compare(final Course course1, final Course course2) {
return course1.getFullname().compareToIgnoreCase(course2.getFullname()); return course1.getFullname().compareToIgnoreCase(course2.getFullname());
} }
} }
private static class CourseShortNameComperator implements Comparator<Course>, Serializable { private static class CourseShortNameComperator implements Comparator<Course>, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override
public int compare(Course course1, Course course2) { public int compare(final Course course1, final Course course2) {
return course1.getShortname().compareToIgnoreCase(course2.getShortname()); return course1.getShortname().compareToIgnoreCase(course2.getShortname());
} }
} }
......
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.Feedback;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.FeedbackService;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.DeprecatedApi;
import de.thm.arsnova.web.exceptions.NotFoundException;
import de.thm.arsnova.websocket.ArsnovaSocketioServerImpl;
/**
* Handles requests concerning the user's feedback, i.e., "too fast" or "faster, please". This HTTP API is
* deprecated in favor of the socket implementation.
*
* @see ArsnovaSocketioServerImpl
*/
@RestController("v2FeedbackController")
@RequestMapping("/v2/session/{shortId}")
public class FeedbackController extends AbstractController {
@Autowired
private FeedbackService feedbackService;
@Autowired
private RoomService roomService;
@Autowired
private UserService userService;
@DeprecatedApi
@Deprecated
@GetMapping("/feedback")
public Feedback getFeedback(@PathVariable final String shortId) {
return feedbackService.getByRoomId(roomService.getIdByShortId(shortId));
}
@DeprecatedApi
@Deprecated
@GetMapping(value = "/myfeedback", produces = MediaType.TEXT_PLAIN_VALUE)
public String getMyFeedback(@PathVariable final String shortId) {
final String roomId = roomService.getIdByShortId(shortId);
final Integer value = feedbackService.getByRoomIdAndUserId(roomId, userService.getCurrentUser().getId());
if (value != null && value >= Feedback.MIN_FEEDBACK_TYPE && value <= Feedback.MAX_FEEDBACK_TYPE) {
return value.toString();
}
throw new NotFoundException();
}
@DeprecatedApi
@Deprecated
@GetMapping(value = "/feedbackcount", produces = MediaType.TEXT_PLAIN_VALUE)
public String getFeedbackCount(@PathVariable final String shortId) {
return String.valueOf(feedbackService.countFeedbackByRoomId(roomService.getIdByShortId(shortId)));
}
@DeprecatedApi
@Deprecated
@GetMapping(value = "/roundedaveragefeedback", produces = MediaType.TEXT_PLAIN_VALUE)
public String getAverageFeedbackRounded(@PathVariable final String shortId) {
return String.valueOf(feedbackService.calculateRoundedAverageFeedback(roomService.getIdByShortId(shortId)));
}
@DeprecatedApi
@Deprecated
@GetMapping(value = "/averagefeedback", produces = MediaType.TEXT_PLAIN_VALUE)
public String getAverageFeedback(@PathVariable final String shortId) {
return String.valueOf(feedbackService.calculateAverageFeedback(roomService.getIdByShortId(shortId)));
}
@DeprecatedApi
@Deprecated
@PostMapping("/feedback")
@ResponseStatus(HttpStatus.CREATED)
public Feedback postFeedback(
@PathVariable final String shortId,
@RequestBody final int value) {
final String roomId = roomService.getIdByShortId(shortId);
final User user = userService.getCurrentUser();
feedbackService.save(roomId, value, user.getId());
final Feedback feedback = feedbackService.getByRoomId(roomId);
return feedback;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.service.CommentService;
import de.thm.arsnova.service.ContentService;
import de.thm.arsnova.web.DeprecatedApi;
/**
* This controller forwards requests from deprecated URLs to their new controller, where the requests are handled.
*/
@Controller("v2LegacyController")
@RequestMapping("/v2")
public class LegacyController extends AbstractController {
@Autowired
private ContentService contentService;
@Autowired
private CommentService commentService;
/* specific routes */
@DeprecatedApi
@GetMapping("/session/mysessions")
public String redirectSessionMy() {
return "forward:/v2/session/?ownedonly=true";
}
@DeprecatedApi
@GetMapping("/session/visitedsessions")
public String redirectSessionVisited() {
return "forward:/v2/session/?visitedonly=true";
}
@DeprecatedApi
@RequestMapping(value = "/session/{shortId}/question")
public String redirectQuestionByLecturer(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/skillquestions")
public String redirectQuestionByLecturerList(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/skillquestioncount")
public String redirectQuestionByLecturerCount(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/count?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/answercount")
public String redirectQuestionByLecturerAnswerCount(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/answercount?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/unanswered")
public String redirectQuestionByLecturerUnnsweredCount(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/answercount?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/myanswers")
public String redirectQuestionByLecturerMyAnswers(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/myanswers?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{shortId}/interposed")
public String redirectQuestionByAudience(@PathVariable final String shortId) {
return String.format("forward:/v2/audiencequestion/?sessionkey=%s", shortId);
}
@DeprecatedApi
@DeleteMapping("/session/{shortId}/interposed")
@ResponseBody
public void deleteAllInterposedQuestions(@PathVariable final String shortId) {
commentService.deleteByRoomId(shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/interposedcount")
public String redirectQuestionByAudienceCount(@PathVariable final String shortId) {
return String.format("forward:/v2/audiencequestion/count?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/interposedreadingcount")
public String redirectQuestionByAudienceReadCount(@PathVariable final String shortId) {
return String.format("forward:/v2/audiencequestion/readcount?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping(value = { "/whoami", "/whoami.json" })
public String redirectWhoami() {
return "forward:/v2/auth/whoami";
}
@DeprecatedApi
@PostMapping(value = "/doLogin")
public String redirectLogin() {
return "forward:/v2/auth/login";
}
/* generalized routes */
@DeprecatedApi
@RequestMapping(value = { "/session/{shortId}/question/{arg1}", "/session/{shortId}/questions/{arg1}" })
public String redirectQuestionByLecturerWithOneArgument(
@PathVariable final String shortId,
@PathVariable final String arg1) {
return String.format("forward:/v2/lecturerquestion/%s/?sessionkey=%s", arg1, shortId);
}
@DeprecatedApi
@RequestMapping(
value = { "/session/{shortId}/question/{arg1}/{arg2}", "/session/{shortId}/questions/{arg1}/{arg2}" }
)
public String redirectQuestionByLecturerWithTwoArguments(
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2) {
return String.format("forward:/v2/lecturerquestion/%s/%s/?sessionkey=%s", arg1, arg2, shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{shortId}/interposed/{arg1}")
public String redirectQuestionByAudienceWithOneArgument(
@PathVariable final String shortId,
@PathVariable final String arg1) {
return String.format("forward:/v2/audiencequestion/%s/?sessionkey=%s", arg1, shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{shortId}/interposed/{arg1}/{arg2}")
public String redirectQuestionByAudienceWithTwoArguments(
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2) {
return String.format("forward:/v2/audiencequestion/%s/%s/?sessionkey=%s", arg1, arg2, shortId);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.model.migration.FromV2Migrator;
import de.thm.arsnova.model.migration.ToV2Migrator;
import de.thm.arsnova.model.migration.v2.Motd;
import de.thm.arsnova.model.migration.v2.MotdList;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.MotdService;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.exceptions.ForbiddenException;
@RestController("v2MotdController")
@RequestMapping("/v2/motd")
@Api(value = "/motd", description = "Message of the Day API")
public class MotdController extends AbstractController {
@Autowired
private MotdService motdService;
@Autowired
private RoomService roomService;
@Autowired
private UserService userService;
@Autowired
private ToV2Migrator toV2Migrator;
@Autowired
private FromV2Migrator fromV2Migrator;
@ApiOperation(value = "get messages. if adminview=false,"
+ " only messages with startdate<clientdate<enddate are returned")
@GetMapping("/")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204),
@ApiResponse(code = 501, message = HTML_STATUS_501)
})
public List<Motd> getMotd(
@ApiParam(value = "clientdate", required = false)
@RequestParam(value = "clientdate", defaultValue = "")
final String clientdate,
@ApiParam(value = "adminview", required = false)
@RequestParam(value = "adminview", defaultValue = "false")
final Boolean adminview,
@ApiParam(value = "audience", required = false)
@RequestParam(value = "audience", defaultValue = "all")
final String audience,
@ApiParam(value = "sessionkey", required = false)
@RequestParam(value = "sessionkey", required = false)
final String roomShortId) {
final List<de.thm.arsnova.model.Motd> motds;
final Date date = new Date(System.currentTimeMillis());
if (!clientdate.isEmpty()) {
date.setTime(Long.parseLong(clientdate));
}
String roomId = "";
if (roomShortId != null) {
roomId = roomService.getIdByShortId(roomShortId);
}
if (adminview) {
motds = roomShortId != null
? motdService.getAllRoomMotds(roomId)
: motdService.getAdminMotds();
} else {
motds = roomShortId != null
? motdService.getCurrentRoomMotds(date, roomId)
: motdService.getCurrentMotds(date, audience);
}
return motds.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
@ApiOperation(value = "create a new message of the day", nickname = "createMotd")
@ApiResponses(value = {
@ApiResponse(code = 201, message = HTML_STATUS_201),
@ApiResponse(code = 503, message = HTML_STATUS_503)
})
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public Motd postNewMotd(
@ApiParam(value = "current motd", required = true) @RequestBody final Motd motd,
final HttpServletResponse response) {
final de.thm.arsnova.model.Motd motdV3 = fromV2Migrator.migrate(motd);
final String roomId = roomService.getIdByShortId(motd.getSessionkey());
if (de.thm.arsnova.model.Motd.Audience.ROOM == motdV3.getAudience() && roomId != null) {
motdService.save(roomId, motdV3);
} else {
motdService.save(motdV3);
}
return toV2Migrator.migrate(motdV3);
}
@ApiOperation(value = "update a message of the day", nickname = "updateMotd")
@PutMapping("/{motdId}")
public Motd updateMotd(
@ApiParam(value = "motdkey from current motd", required = true) @PathVariable final String motdId,
@ApiParam(value = "current motd", required = true) @RequestBody final Motd motd) {
final de.thm.arsnova.model.Motd motdV3 = fromV2Migrator.migrate(motd);
final String roomId = roomService.getIdByShortId(motd.getSessionkey());
if (motdV3.getAudience() == de.thm.arsnova.model.Motd.Audience.ROOM && roomId != null) {
motdService.update(roomId, motdV3);
} else {
motdService.update(motdV3);
}
return toV2Migrator.migrate(motdV3);
}
@ApiOperation(value = "deletes a message of the day", nickname = "deleteMotd")
@DeleteMapping("/{motdId}")
public void deleteMotd(
@ApiParam(value = "Motd-key from the message that shall be deleted", required = true)
@PathVariable
final String motdId) {
final de.thm.arsnova.model.Motd motd = motdService.get(motdId);
motdService.delete(motd);
}
@GetMapping("/userlist")
public MotdList getAcknowledgedIds(@AuthenticationPrincipal final User user, @RequestParam final String username) {
if (user == null || !user.getUsername().equals(username)) {
throw new ForbiddenException();
}
final UserProfile profile = userService.get(user.getId());
return toV2Migrator.migrateMotdList(profile);
}
@PutMapping("/userlist")
public void putAcknowledgedIds(@AuthenticationPrincipal final User user, @RequestBody final MotdList motdList) {
if (user == null || !user.getUsername().equals(motdList.getUsername())) {
throw new ForbiddenException();
}
final UserProfile profile = userService.get(user.getId());
profile.setAcknowledgedMotds(fromV2Migrator.migrate(motdList));
userService.update(profile);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.PaginationController;
import de.thm.arsnova.model.migration.FromV2Migrator;
import de.thm.arsnova.model.migration.ToV2Migrator;
import de.thm.arsnova.model.migration.v2.Room;
import de.thm.arsnova.model.migration.v2.RoomFeature;
import de.thm.arsnova.model.migration.v2.RoomInfo;
import de.thm.arsnova.model.transport.ImportExportContainer;
import de.thm.arsnova.model.transport.ScoreStatistics;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.RoomServiceImpl;
import de.thm.arsnova.service.RoomServiceImpl.RoomNameComparator;
import de.thm.arsnova.service.RoomServiceImpl.RoomShortNameComparator;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.DeprecatedApi;
import de.thm.arsnova.web.Pagination;
import de.thm.arsnova.web.exceptions.NotImplementedException;
import de.thm.arsnova.web.exceptions.UnauthorizedException;
/**
* Handles requests related to ARSnova Rooms.
*/
@RestController("v2RoomController")
@RequestMapping("/v2/session")
@Api(value = "/session", description = "Room (Session) API")
public class RoomController extends PaginationController {
@Autowired
private RoomService roomService;
@Autowired
private UserService userService;
@Autowired
private ToV2Migrator toV2Migrator;
@Autowired
private FromV2Migrator fromV2Migrator;
@ApiOperation(value = "join a Room",
nickname = "joinRoom")
@DeprecatedApi
@Deprecated
@GetMapping("/{shortId}")
public Room joinRoom(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable final String shortId,
@ApiParam(value = "Adminflag", required = false)
@RequestParam(value = "admin", defaultValue = "false")
final boolean admin) {
if (admin) {
return toV2Migrator.migrate(roomService.getForAdmin(shortId));
} else {
return toV2Migrator.migrate(roomService.getByShortId(shortId));
}
}
@ApiOperation(value = "deletes a Room",
nickname = "deleteRoom")
@DeleteMapping("/{shortId}")
public void deleteRoom(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable
final String shortId) {
final de.thm.arsnova.model.Room room = roomService.getByShortId(shortId);
roomService.delete(room);
}
@ApiOperation(value = "count active users",
nickname = "countActiveUsers")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/{shortId}/activeusercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countActiveUsers(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId) {
return String.valueOf(roomService.activeUsers(roomService.getIdByShortId(shortId)));
}
@ApiOperation(value = "Creates a new Room and returns the Room's data",
nickname = "postNewRoom")
@ApiResponses(value = {
@ApiResponse(code = 201, message = HTML_STATUS_201),
@ApiResponse(code = 503, message = HTML_STATUS_503)
})
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public Room postNewRoom(
@ApiParam(value = "current Room", required = true)
@RequestBody
final Room room,
final HttpServletResponse response) {
/* FIXME: migrate LMS course support
if (room != null && room.isCourseSession()) {
final List<Course> courses = new ArrayList<>();
final Course course = new Course();
course.setId(room.getCourseId());
courses.add(course);
final int sessionCount = roomService.countSessionsByCourses(courses);
if (sessionCount > 0) {
final String appendix = " (" + (sessionCount + 1) + ")";
room.setName(room.getName() + appendix);
room.setAbbreviation(room.getAbbreviation() + appendix);
}
}
*/
return toV2Migrator.migrate(roomService.create(fromV2Migrator.migrate(room)));
}
@ApiOperation(value = "updates a Room",
nickname = "postNewRoom")
@PutMapping("/{shortId}")
public Room updateRoom(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "current room", required = true) @RequestBody final Room room) {
return toV2Migrator.migrate(roomService.update(fromV2Migrator.migrate(room)));
}
@ApiOperation(value = "change the Room creator (owner)", nickname = "changeRoomCreator")
@RequestMapping(value = "/{shortId}/changecreator", method = RequestMethod.PUT)
public Room changeRoomCreator(
@ApiParam(value = "Room-key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "new Room creator", required = true) @RequestBody final String newCreator) {
return toV2Migrator.migrate(roomService.updateCreator(roomService.getIdByShortId(shortId), newCreator));
}
@ApiOperation(value = "Retrieves a list of Rooms",
nickname = "getRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204),
@ApiResponse(code = 501, message = HTML_STATUS_501)
})
@GetMapping("/")
@Pagination
public List<Room> getRooms(
@ApiParam(value = "ownedOnly", required = true)
@RequestParam(value = "ownedonly", defaultValue = "false")
final boolean ownedOnly,
@ApiParam(value = "visitedOnly", required = true)
@RequestParam(value = "visitedonly", defaultValue = "false")
final boolean visitedOnly,
@ApiParam(value = "sortby", required = true)
@RequestParam(value = "sortby", defaultValue = "name")
final String sortby,
@ApiParam(value = "for a given username. admin rights needed", required = false)
@RequestParam(value = "username", defaultValue = "")
final String username,
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms;
if (!"".equals(username)) {
final String userId = userService.getByUsername(username).getId();
try {
if (ownedOnly && !visitedOnly) {
rooms = roomService.getUserRooms(userId);
} else if (visitedOnly && !ownedOnly) {
rooms = roomService.getUserRoomHistory(username);
} else {
response.setStatus(HttpStatus.NOT_IMPLEMENTED.value());
return null;
}
} catch (final AccessDeniedException e) {
throw new UnauthorizedException();
}
} else {
/* TODO implement all parameter combinations, implement use of user parameter */
try {
if (ownedOnly && !visitedOnly) {
rooms = roomService.getMyRooms(offset, limit);
} else if (visitedOnly && !ownedOnly) {
rooms = roomService.getMyRoomHistory(offset, limit);
} else {
response.setStatus(HttpStatus.NOT_IMPLEMENTED.value());
return null;
}
} catch (final AccessDeniedException e) {
throw new UnauthorizedException();
}
}
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
if ("shortname".equals(sortby)) {
Collections.sort(rooms, new RoomShortNameComparator());
} else {
Collections.sort(rooms, new RoomServiceImpl.RoomNameComparator());
}
return rooms.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
/**
* Returns a list of my own Rooms with only the necessary information like name, keyword, or counters.
*/
@ApiOperation(value = "Retrieves a Room",
nickname = "getMyRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204)
})
@GetMapping(value = "/", params = "statusonly=true")
@Pagination
public List<RoomInfo> getMyRooms(
@ApiParam(value = "visitedOnly", required = true)
@RequestParam(value = "visitedonly", defaultValue = "false")
final boolean visitedOnly,
@ApiParam(value = "sort by", required = false)
@RequestParam(value = "sortby", defaultValue = "name")
final String sortby,
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms;
if (!visitedOnly) {
rooms = roomService.getMyRoomsInfo(offset, limit);
} else {
rooms = roomService.getMyRoomHistoryInfo(offset, limit);
}
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
if ("shortname".equals(sortby)) {
Collections.sort(rooms, new RoomShortNameComparator());
} else {
Collections.sort(rooms, new RoomNameComparator());
}
return rooms.stream().map(toV2Migrator::migrateStats).collect(Collectors.toList());
}
@ApiOperation(value = "Retrieves all public pool Rooms for the current user",
nickname = "getMyPublicPoolRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204)
})
@GetMapping(value = "/publicpool", params = "statusonly=true")
public List<RoomInfo> getMyPublicPoolRooms(
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms = roomService.getMyPublicPoolRoomsInfo();
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
return rooms.stream().map(toV2Migrator::migrateStats).collect(Collectors.toList());
}
@ApiOperation(value = "Retrieves all public pool Rooms",
nickname = "getMyPublicPoolRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204)
})
@GetMapping("/publicpool")
public List<Room> getPublicPoolRooms(
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms = roomService.getPublicPoolRoomsInfo();
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
return rooms.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
@ApiOperation(value = "imports a Room",
nickname = "importRoom")
@PostMapping("/import")
public Room importRoom(
@ApiParam(value = "current Room", required = true) @RequestBody final ImportExportContainer room,
final HttpServletResponse response) {
return toV2Migrator.migrate(roomService.importRooms(room));
}
@ApiOperation(value = "export Rooms", nickname = "exportRoom")
@GetMapping("/export")
public List<ImportExportContainer> getExport(
@ApiParam(value = "Room-Key", required = true)
@RequestParam(value = "sessionkey", defaultValue = "")
final List<String> shortIds,
@ApiParam(value = "wether statistics shall be exported", required = true)
@RequestParam(value = "withAnswerStatistics", defaultValue = "false")
final Boolean withAnswerStatistics,
@ApiParam(value = "wether comments shall be exported", required = true)
@RequestParam(value = "withFeedbackQuestions", defaultValue = "false")
final Boolean withFeedbackQuestions,
final HttpServletResponse response) throws IOException {
final List<ImportExportContainer> rooms = new ArrayList<>();
ImportExportContainer temp;
for (final String shortId : shortIds) {
final String id = roomService.getIdByShortId(shortId);
roomService.setActive(id, false);
temp = roomService.exportRoom(id, withAnswerStatistics, withFeedbackQuestions);
if (temp != null) {
rooms.add(temp);
}
roomService.setActive(id, true);
}
return rooms;
}
@ApiOperation(value = "copy a Rooms to the public pool if enabled")
@PostMapping("/{shortId}/copytopublicpool")
public Room copyToPublicPool(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable
final String shortId,
@ApiParam(value = "public pool attributes for Room", required = true)
@RequestBody
final ImportExportContainer.PublicPool publicPool)
throws IOException {
final String id = roomService.getIdByShortId(shortId);
roomService.setActive(id, false);
final de.thm.arsnova.model.Room roomInfo = roomService.copyRoomToPublicPool(shortId, publicPool);
roomService.setActive(id, true);
return toV2Migrator.migrate(roomInfo);
}
@ApiOperation(value = "copy a Room from the public pool if enabled")
@PostMapping("/{shortId}/copyfrompublicpool")
public Room copyFromPublicPool(
@ApiParam(value = "Short ID of the Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "custom attributes for Room", required = true) @RequestBody final Room sessionAttributes) {
throw new NotImplementedException();
}
@ApiOperation(value = "Locks or unlocks a Room",
nickname = "lockRoom")
@ApiResponses(value = {
@ApiResponse(code = 404, message = HTML_STATUS_404)
})
@PostMapping("/{shortId}/lock")
public Room lockRoom(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "lock", required = true) @RequestParam(required = false) final Boolean lock,
final HttpServletResponse response) throws IOException {
if (lock != null) {
return toV2Migrator.migrate(roomService.setActive(roomService.getIdByShortId(shortId), lock));
}
response.setStatus(HttpStatus.NOT_FOUND.value());
return null;
}
@ApiOperation(value = "retrieves a value for the score",
nickname = "getLearningProgress")
@GetMapping("/{shortId}/learningprogress")
public ScoreStatistics getLearningProgress(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable
final String shortId,
@ApiParam(value = "type", required = false)
@RequestParam(value = "type", defaultValue = "questions")
final String type,
@ApiParam(value = "question variant", required = false)
@RequestParam(value = "questionVariant", required = false)
final String questionVariant,
final HttpServletResponse response) {
return roomService.getLearningProgress(roomService.getIdByShortId(shortId), type, questionVariant);
}
@ApiOperation(value = "retrieves a value for the learning progress for the current user",
nickname = "getMyLearningProgress")
@GetMapping("/{shortId}/mylearningprogress")
public ScoreStatistics getMyLearningProgress(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@RequestParam(value = "type", defaultValue = "questions") final String type,
@RequestParam(value = "questionVariant", required = false) final String questionVariant,
final HttpServletResponse response) {
return roomService.getMyLearningProgress(roomService.getIdByShortId(shortId), type, questionVariant);
}
@ApiOperation(value = "retrieves all Room features",
nickname = "getRoomFeatures")
@GetMapping("/{shortId}/features")
public RoomFeature getRoomFeatures(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
final HttpServletResponse response) {
final de.thm.arsnova.model.Room room = roomService.getByShortId(shortId);
return toV2Migrator.migrate(room.getSettings());
}
@PutMapping("/{shortId}/features")
@ApiOperation(value = "change all Room features",
nickname = "changeRoomFeatures")
public RoomFeature changeRoomFeatures(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "Room feature", required = true) @RequestBody final RoomFeature features,
final HttpServletResponse response) {
final de.thm.arsnova.model.Room room = roomService.getByShortId(shortId);
room.setSettings(fromV2Migrator.migrate(features));
roomService.update(room);
return toV2Migrator.migrate(room.getSettings());
}
@PostMapping(value = "/{shortId}/lockfeedbackinput", produces = MediaType.TEXT_PLAIN_VALUE)
@ApiOperation(value = "locks input of user live feedback",
nickname = "lockFeedbackInput")
public String lockFeedbackInput(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "lock", required = true) @RequestParam(required = true) final Boolean lock,
final HttpServletResponse response) throws IOException {
return String.valueOf(roomService.lockFeedbackInput(roomService.getIdByShortId(shortId), lock));
}
@PostMapping(value = "/{shortId}/flipflashcards", produces = MediaType.TEXT_PLAIN_VALUE)
@ApiOperation(value = "flip all flashcards in Room",
nickname = "lockFeedbackInput")
public String flipFlashcards(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "flip", required = true) @RequestParam(required = true) final Boolean flip,
final HttpServletResponse response) {
return String.valueOf(roomService.flipFlashcards(roomService.getIdByShortId(shortId), flip));
}
/* internal redirections */
@RequestMapping(value = "/{shortId}/lecturerquestion")
public String redirectLecturerQuestion(
@PathVariable final String shortId,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/?sessionkey=%s", shortId);
}
@RequestMapping(value = "/{shortId}/lecturerquestion/{arg1}")
public String redirectLecturerQuestionWithOneArgument(
@PathVariable final String shortId,
@PathVariable final String arg1,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/?sessionkey=%s", arg1, shortId);
}
@RequestMapping(value = "/{shortId}/lecturerquestion/{arg1}/{arg2}")
public String redirectLecturerQuestionWithTwoArguments(
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/%s/?sessionkey=%s", arg1, arg2, shortId);
}
@RequestMapping(value = "/{shortId}/lecturerquestion/{arg1}/{arg2}/{arg3}")
public String redirectLecturerQuestionWithThreeArguments(
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2,
@PathVariable final String arg3,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/%s/%s/?sessionkey=%s", arg1, arg2, arg3, shortId);
}
@RequestMapping(value = "/{shortId}/audiencequestion")
public String redirectAudienceQuestion(
@PathVariable final String shortId,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/?sessionkey=%s", shortId);
}
@RequestMapping(value = "/{shortId}/audiencequestion/{arg1}")
public String redirectAudienceQuestionWithOneArgument(
@PathVariable final String shortId,
@PathVariable final String arg1,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/%s/?sessionkey=%s", arg1, shortId);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.websocket.ArsnovaSocketioServer;
/**
* Initiates the socket communication.
*/
@RestController("v2SocketController")
@RequestMapping("/v2/socket")
@Api(value = "/socket", description = "WebSocket Initialization API")
public class SocketController extends AbstractController {
@Autowired
private UserService userService;
@Autowired
private ArsnovaSocketioServer server;
private static final Logger logger = LoggerFactory.getLogger(SocketController.class);
@ApiOperation(value = "requested to assign Websocket session",
nickname = "authorize")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204),
@ApiResponse(code = 400, message = HTML_STATUS_400),
@ApiResponse(code = 403, message = HTML_STATUS_403)
})
@PostMapping("/assign")
public void authorize(
@ApiParam(value = "sessionMap", required = true) @RequestBody final Map<String, String> sessionMap,
@ApiParam(value = "response", required = true) final HttpServletResponse response) {
final String socketid = sessionMap.get("session");
if (null == socketid) {
logger.debug("Expected property 'session' missing.");
response.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}
final User user = userService.getCurrentUser();
if (null == user) {
logger.debug("Client {} requested to assign Websocket session but has not authenticated.", socketid);
response.setStatus(HttpStatus.FORBIDDEN.value());
return;
}
userService.putUserIdToSocketId(UUID.fromString(socketid), user.getId());
response.setStatus(HttpStatus.NO_CONTENT.value());
}
@ApiOperation(value = "retrieves a socket url",
nickname = "getSocketUrl")
@GetMapping(value = "/url", produces = MediaType.TEXT_PLAIN_VALUE)
public String getSocketUrl(final HttpServletRequest request) {
return (server.isUseSsl() ? "https://" : "http://") + request.getServerName() + ":" + server.getPortNumber();
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.Statistics;
import de.thm.arsnova.service.StatisticsService;
import de.thm.arsnova.web.CacheControl;
import de.thm.arsnova.web.DeprecatedApi;
/**
* Allows retrieval of several statistics such as the number of active users.
*/
@RestController("v2StatisticsController")
@Api(value = "/statistics", description = "Statistics API")
@RequestMapping("/v2/statistics")
public class StatisticsController extends AbstractController {
@Autowired
private StatisticsService statisticsService;
@ApiOperation(value = "Retrieves global statistics",
nickname = "getStatistics")
@GetMapping("/")
@CacheControl(maxAge = 60, policy = CacheControl.Policy.PUBLIC)
public Statistics getStatistics() {
return statisticsService.getStatistics();
}
@ApiOperation(value = "Retrieves the amount of all active users",
nickname = "countActiveUsers")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/activeusercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countActiveUsers() {
return String.valueOf(statisticsService.getStatistics().getActiveUsers());
}
@ApiOperation(value = "Retrieves the amount of all currently logged in users",
nickname = "countLoggedInUsers")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/loggedinusercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countLoggedInUsers() {
return String.valueOf(statisticsService.getStatistics().getLoggedinUsers());
}
@ApiOperation(value = "Retrieves the total amount of all sessions",
nickname = "countSessions")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/sessioncount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countSessions() {
return String.valueOf(statisticsService.getStatistics().getOpenSessions()
+ statisticsService.getStatistics().getClosedSessions());
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.controller.v2;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.service.UserService;
/**
* Handles requests related to ARSnova's own user registration and login process.
*/
@Controller("v2UserController")
@RequestMapping("/v2/user")
public class UserController extends AbstractController {
@Autowired
private DaoAuthenticationProvider daoProvider;
@Autowired
private UserService userService;
@PostMapping(value = "/register")
public void register(@RequestParam final String username,
@RequestParam final String password,
final HttpServletRequest request, final HttpServletResponse response) {
if (null != userService.create(username, password)) {
return;
}
/* TODO: Improve error handling: send reason to client */
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
@PostMapping(value = "/{username}/activate")
public void activate(
@PathVariable final String username,
@RequestParam final String key,
final HttpServletRequest request,
final HttpServletResponse response) {
final UserProfile userProfile = userService.getByUsername(username);
if (userProfile == null || !userService.activateAccount(userProfile.getId(), key, request.getRemoteAddr())) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}
@DeleteMapping(value = "/{username}/")
public void activate(
@PathVariable final String username,
final HttpServletRequest request,
final HttpServletResponse response) {
if (null == userService.deleteByUsername(username)) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
@PostMapping(value = "/{username}/resetpassword")
public void resetPassword(
@PathVariable final String username,
@RequestParam(required = false) final String key,
@RequestParam(required = false) final String password,
final HttpServletRequest request,
final HttpServletResponse response) {
final UserProfile userProfile = userService.getByUsername(username);
if (null == userProfile) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (null != key) {
if (!userService.resetPassword(userProfile, key, password)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
} else {
userService.initiatePasswordReset(username);
}
}
}
package de.thm.arsnova.controller.v2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller("v2WelcomeController")
@RequestMapping("/v2")
public class WelcomeController {
@GetMapping(value = "/")
public String forwardHome() {
return "forward:/";
}
}
/*
* Copyright (C) 2012 THM webMedia
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.dao;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.ezmorph.Morpher;
import net.sf.ezmorph.MorpherRegistry;
import net.sf.ezmorph.bean.BeanMorpher;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import com.fourspaces.couchdb.Database;
import com.fourspaces.couchdb.Document;
import com.fourspaces.couchdb.View;
import com.fourspaces.couchdb.ViewResults;
import de.thm.arsnova.connector.model.Course;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.Feedback;
import de.thm.arsnova.entities.FoodVote;
import de.thm.arsnova.entities.InterposedQuestion;
import de.thm.arsnova.entities.InterposedReadingCount;
import de.thm.arsnova.entities.LoggedIn;
import de.thm.arsnova.entities.PossibleAnswer;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.entities.VisitedSession;
import de.thm.arsnova.exceptions.ForbiddenException;
import de.thm.arsnova.exceptions.NotFoundException;
import de.thm.arsnova.exceptions.UnauthorizedException;
import de.thm.arsnova.services.IFeedbackService;
import de.thm.arsnova.services.ISessionService;
import de.thm.arsnova.services.IUserService;
@Component
public class CouchDBDao implements IDatabaseDao {
@Autowired
private IUserService userService;
@Autowired
private IFeedbackService feedbackService;
@Autowired
private ISessionService sessionService;
private String databaseHost;
private int databasePort;
private String databaseName;
private Database database;
public static final Logger LOGGER = LoggerFactory.getLogger(CouchDBDao.class);
@Value("${couchdb.host}")
public final void setDatabaseHost(final String newDatabaseHost) {
this.databaseHost = newDatabaseHost;
}
@Value("${couchdb.port}")
public final void setDatabasePort(final String newDatabasePort) {
this.databasePort = Integer.parseInt(newDatabasePort);
}
@Value("${couchdb.name}")
public final void setDatabaseName(final String newDatabaseName) {
this.databaseName = newDatabaseName;
}
public final void setSessionService(final ISessionService service) {
this.sessionService = service;
}
public final void setUserService(final IUserService service) {
this.userService = service;
}
/**
* This method cleans up old feedback votes at the scheduled interval.
*/
@Override
public final void cleanFeedbackVotes(final int cleanupFeedbackDelay) {
final long timelimitInMillis = 60000 * (long) cleanupFeedbackDelay;
final long maxAllowedTimeInMillis = System.currentTimeMillis() - timelimitInMillis;
Map<String, Set<String>> affectedUsers = new HashMap<String, Set<String>>();
Set<String> allAffectedSessions = new HashSet<String>();
List<Document> results = findFeedbackForDeletion(maxAllowedTimeInMillis);
for (Document d : results) {
try {
// Read the required document data
Document feedback = this.getDatabase().getDocument(d.getId());
String arsInternalSessionId = feedback.getString("sessionId");
String user = feedback.getString("user");
// Store user and session data for later. We need this to
// communicate the changes back to the users.
Set<String> affectedArsSessions = affectedUsers.get(user);
if (affectedArsSessions == null) {
affectedArsSessions = new HashSet<String>();
}
affectedArsSessions.add(getSessionKeyword(arsInternalSessionId));
affectedUsers.put(user, affectedArsSessions);
allAffectedSessions.addAll(affectedArsSessions);
this.database.deleteDocument(feedback);
LOGGER.debug("Cleaning up Feedback document " + d.getId());
} catch (IOException e) {
LOGGER.error("Could not delete Feedback document " + d.getId());
} catch (JSONException e) {
LOGGER.error(
"Could not delete Feedback document {}, error is: {} ",
new Object[] {d.getId(), e}
);
}
}
if (!results.isEmpty()) {
feedbackService.broadcastFeedbackChanges(affectedUsers, allAffectedSessions);
}
}
private List<Document> findFeedbackForDeletion(final long maxAllowedTimeInMillis) {
View cleanupFeedbackView = new View("understanding/cleanup");
cleanupFeedbackView.setStartKey("null");
cleanupFeedbackView.setEndKey(String.valueOf(maxAllowedTimeInMillis));
ViewResults feedbackForCleanup = this.getDatabase().view(cleanupFeedbackView);
return feedbackForCleanup.getResults();
}
@Override
public final Session getSession(final String keyword) {
Session result = this.getSessionFromKeyword(keyword);
if (result == null) {
throw new NotFoundException();
}
if (result.isActive() || result.getCreator().equals(userService.getCurrentUser().getUsername())) {
return result;
}
throw new ForbiddenException();
}
@Override
public final List<Session> getMySessions(final User user) {
try {
View view = new View("session/by_creator");
view.setStartKey("[" + URLEncoder.encode("\"" + user.getUsername() + "\"", "UTF-8") + "]");
view.setEndKey("[" + URLEncoder.encode("\"" + user.getUsername() + "\",{}", "UTF-8") + "]");
ViewResults sessions = this.getDatabase().view(view);
List<Session> result = new ArrayList<Session>();
for (Document d : sessions.getResults()) {
Session session = (Session) JSONObject.toBean(
d.getJSONObject().getJSONObject("value"),
Session.class
);
session.setCreator(d.getJSONObject().getJSONArray("key").getString(0));
session.setName(d.getJSONObject().getJSONArray("key").getString(1));
session.set_id(d.getId());
result.add(session);
}
return result;
} catch (UnsupportedEncodingException e) {
return null;
}
}
@Override
public final List<Question> getSkillQuestions(final String sessionKeyword) {
Session session = this.getSessionFromKeyword(sessionKeyword);
if (session == null) {
throw new NotFoundException();
}
User user = this.userService.getCurrentUser();
View view = null;
try {
if (session.getCreator().equals(user.getUsername())) {
view = new View("skill_question/by_session_sorted_by_subject_and_text");
} else {
if (user.getType().equals(User.THM)) {
view = new View("skill_question/by_session_for_thm_full");
} else {
view = new View("skill_question/by_session_for_all_full");
}
}
view.setStartKey("[" + URLEncoder.encode("\"" + session.get_id() + "\"", "UTF-8") + "]");
view.setEndKey("[" + URLEncoder.encode("\"" + session.get_id() + "\",{}", "UTF-8") + "]");
ViewResults questions = this.getDatabase().view(view);
if (questions == null || questions.isEmpty()) {
return null;
}
List<Question> result = new ArrayList<Question>();
MorpherRegistry morpherRegistry = JSONUtils.getMorpherRegistry();
Morpher dynaMorpher = new BeanMorpher(PossibleAnswer.class, morpherRegistry);
morpherRegistry.registerMorpher(dynaMorpher);
for (Document document : questions.getResults()) {
Question question = (Question) JSONObject.toBean(
document.getJSONObject().getJSONObject("value"),
Question.class
);
Collection<PossibleAnswer> answers = JSONArray.toCollection(
document.getJSONObject().getJSONObject("value")
.getJSONArray("possibleAnswers"),
PossibleAnswer.class
);
question.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers));
question.setSessionKeyword(this.getSessionKeyword(question.getSessionId()));
result.add(question);
}
return result;
} catch (UnsupportedEncodingException e) {
return null;
} catch (IOException e) {
return null;
}
}
@Override
public final int getSkillQuestionCount(final Session session) {
try {
View view = new View("skill_question/count_by_session");
view.setKey(URLEncoder.encode("\"" + session.get_id() + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
} catch (UnsupportedEncodingException e) {
return 0;
}
}
@Override
public final Session getSessionFromKeyword(final String keyword) {
try {
View view = new View("session/by_keyword");
view.setKey(URLEncoder.encode("\"" + keyword + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
return (Session) JSONObject.toBean(
results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
Session.class
);
} catch (UnsupportedEncodingException e) {
return null;
}
}
@Override
public final Session getSessionFromId(final String sessionId) {
try {
View view = new View("session/by_id");
view.setKey(URLEncoder.encode("\"" + sessionId + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
return (Session) JSONObject.toBean(
results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
Session.class
);
} catch (UnsupportedEncodingException e) {
return null;
}
}
@Override
public final Session saveSession(final Session session) {
Document sessionDocument = new Document();
sessionDocument.put("type", "session");
sessionDocument.put("name", session.getName());
sessionDocument.put("shortName", session.getShortName());
sessionDocument.put("keyword", sessionService.generateKeyword());
sessionDocument.put("creator", this.actualUserName());
sessionDocument.put("active", true);
sessionDocument.put("courseType", session.getCourseType());
sessionDocument.put("courseId", session.getCourseId());
try {
database.saveDocument(sessionDocument);
} catch (IOException e) {
return null;
}
return this.getSession(sessionDocument.getString("keyword"));
}
@Override
public final Feedback getFeedback(final String keyword) {
String sessionId = this.getSessionId(keyword);
if (sessionId == null) {
throw new NotFoundException();
}
View view = new View("understanding/by_session");
view.setGroup(true);
view.setStartKey(URLEncoder.encode("[\"" + sessionId + "\"]"));
view.setEndKey(URLEncoder.encode("[\"" + sessionId + "\",{}]"));
ViewResults results = this.getDatabase().view(view);
LOGGER.info("Feedback: {}", results.getJSONArray("rows"));
return this.createFeedbackObject(results);
}
private Feedback createFeedbackObject(final ViewResults results) {
int[] values = {0, 0, 0, 0};
JSONArray rows = results.getJSONArray("rows");
try {
for (int i = Feedback.MIN_FEEDBACK_TYPE; i <= Feedback.MAX_FEEDBACK_TYPE; i++) {
String key = rows.optJSONObject(i).optJSONArray("key").getString(1);
JSONObject feedback = rows.optJSONObject(i);
if (key.equals("Bitte schneller")) {
values[Feedback.FEEDBACK_FASTER] = feedback.getInt("value");
}
if (key.equals("Kann folgen")) {
values[Feedback.FEEDBACK_OK] = feedback.getInt("value");
}
if (key.equals("Zu schnell")) {
values[Feedback.FEEDBACK_SLOWER] = feedback.getInt("value");
}
if (key.equals("Nicht mehr dabei")) {
values[Feedback.FEEDBACK_AWAY] = feedback.getInt("value");
}
}
} catch (Exception e) {
return new Feedback(
values[Feedback.FEEDBACK_FASTER],
values[Feedback.FEEDBACK_OK],
values[Feedback.FEEDBACK_SLOWER],
values[Feedback.FEEDBACK_AWAY]
);
}
return new Feedback(
values[Feedback.FEEDBACK_FASTER],
values[Feedback.FEEDBACK_OK],
values[Feedback.FEEDBACK_SLOWER],
values[Feedback.FEEDBACK_AWAY]
);
}
@Override
public final boolean saveFeedback(
final String keyword,
final int value,
final de.thm.arsnova.entities.User user
) {
String sessionId = this.getSessionId(keyword);
if (sessionId == null) {
return false;
}
if (!(value >= Feedback.MIN_FEEDBACK_TYPE && value <= Feedback.MAX_FEEDBACK_TYPE)) {
return false;
}
Document feedback = new Document();
List<Document> postedFeedback = findPreviousFeedback(sessionId, user);
// Feedback can only be posted once. If there already is some feedback,
// we need to update it.
if (!postedFeedback.isEmpty()) {
for (Document f : postedFeedback) {
// Use the first found feedback and update value and timestamp
try {
feedback = this.getDatabase().getDocument(f.getId());
feedback.put("value", feedbackValueToString(value));
feedback.put("timestamp", System.currentTimeMillis());
} catch (IOException e) {
return false;
}
break;
}
} else {
feedback.put("type", "understanding");
feedback.put("user", user.getUsername());
feedback.put("sessionId", sessionId);
feedback.put("timestamp", System.currentTimeMillis());
feedback.put("value", feedbackValueToString(value));
}
try {
this.getDatabase().saveDocument(feedback);
} catch (IOException e) {
return false;
}
return true;
}
private List<Document> findPreviousFeedback(final String sessionId, final de.thm.arsnova.entities.User user) {
View view = new View("understanding/by_user");
try {
view.setKey(
URLEncoder.encode(
"[\"" + sessionId + "\",\"" + user.getUsername() + "\"]",
"UTF-8"
)
);
} catch (UnsupportedEncodingException e) {
return Collections.<Document> emptyList();
}
ViewResults results = this.getDatabase().view(view);
return results.getResults();
}
private String feedbackValueToString(final int value) {
switch (value) {
case Feedback.FEEDBACK_FASTER:
return "Bitte schneller";
case Feedback.FEEDBACK_OK:
return "Kann folgen";
case Feedback.FEEDBACK_SLOWER:
return "Zu schnell";
case Feedback.FEEDBACK_AWAY:
return "Nicht mehr dabei";
default:
return null;
}
}
private int feedbackValueFromString(final String value) {
if (value.equals("Bitte schneller")) {
return Feedback.FEEDBACK_FASTER;
}
if (value.equals("Kann folgen")) {
return Feedback.FEEDBACK_OK;
}
if (value.equals("Zu schnell")) {
return Feedback.FEEDBACK_AWAY;
}
if (value.equals("Nicht mehr dabei")) {
return Feedback.FEEDBACK_AWAY;
}
return Integer.MIN_VALUE;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public final boolean sessionKeyAvailable(final String keyword) {
View view = new View("session/by_keyword");
ViewResults results = this.getDatabase().view(view);
return !results.containsKey(keyword);
}
private String getSessionId(final String keyword) {
View view = new View("session/by_keyword");
view.setKey(URLEncoder.encode("\"" + keyword + "\""));
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
return results.getJSONArray("rows").optJSONObject(0).optJSONObject("value").getString("_id");
}
private String getSessionKeyword(final String internalSessionId) throws IOException {
Document document = this.getDatabase().getDocument(internalSessionId);
if (document.has("keyword")) {
return (String) document.get("keyword");
}
LOGGER.error("No session found for internal id: {}", internalSessionId);
return null;
}
private String actualUserName() {
User user = userService.getCurrentUser();
if (user == null) {
return null;
}
return user.getUsername();
}
private Database getDatabase() {
if (database == null) {
try {
com.fourspaces.couchdb.Session session = new com.fourspaces.couchdb.Session(
databaseHost,
databasePort
);
database = session.getDatabase(databaseName);
} catch (Exception e) {
LOGGER.error(
"Cannot connect to CouchDB database '" + databaseName
+ "' on host '" + databaseHost
+ "' using port " + databasePort
);
}
}
return database;
}
@Override
public final Question saveQuestion(final Session session, final Question question) {
Document q = new Document();
q.put("type", "skill_question");
q.put("questionType", question.getQuestionType());
q.put("sessionId", session.get_id());
q.put("subject", question.getSubject());
q.put("text", question.getText());
q.put("active", question.isActive());
q.put("number", 0); // TODO This number has to get incremented
// automatically
q.put("releasedFor", question.getReleasedFor());
q.put("possibleAnswers", question.getPossibleAnswers());
q.put("noCorrect", question.isNoCorrect());
q.put("showStatistic", question.isShowStatistic());
q.put("showAnswer", question.isShowAnswer());
try {
database.saveDocument(q);
question.set_id(q.getId());
question.set_rev(q.getRev());
return question;
} catch (IOException e) {
LOGGER.error("Could not save question {}", question);
}
return null;
}
@Override
public final void updateQuestion(final Question question) {
try {
Document q = this.database.getDocument(question.get_id());
q.put("subject", question.getSubject());
q.put("text", question.getText());
q.put("active", question.isActive());
q.put("releasedFor", question.getReleasedFor());
q.put("possibleAnswers", question.getPossibleAnswers());
q.put("noCorrect", question.isNoCorrect());
q.put("showStatistic", question.isShowStatistic());
q.put("showAnswer", question.isShowAnswer());
this.database.saveDocument(q);
question.set_rev(q.getRev());
} catch (IOException e) {
LOGGER.error("Could not update question {}", question);
}
}
@Override
public final boolean saveQuestion(final Session session, final InterposedQuestion question) {
Document q = new Document();
q.put("type", "interposed_question");
q.put("sessionId", session.get_id());
q.put("subject", question.getSubject());
q.put("text", question.getText());
q.put("timestamp", System.currentTimeMillis());
q.put("read", false);
try {
database.saveDocument(q);
return true;
} catch (IOException e) {
LOGGER.error("Could not save interposed question {}", question);
}
return false;
}
@Override
public final Question getQuestion(final String id) {
try {
View view = new View("skill_question/by_id");
view.setKey(URLEncoder.encode("\"" + id + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
Question q = (Question) JSONObject.toBean(
results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
Question.class
);
JSONArray possibleAnswers = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value")
.getJSONArray("possibleAnswers");
Collection<PossibleAnswer> answers = JSONArray.toCollection(
possibleAnswers,
PossibleAnswer.class
);
q.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers));
q.setSessionKeyword(this.getSessionKeyword(q.getSessionId()));
return q;
} catch (IOException e) {
LOGGER.error("Could not get question with id {}", id);
}
return null;
}
@Override
public final LoggedIn registerAsOnlineUser(final User user, final Session session) {
try {
View view = new View("logged_in/all");
view.setKey(URLEncoder.encode("\"" + user.getUsername() + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
LoggedIn loggedIn = new LoggedIn();
if (results.getJSONArray("rows").optJSONObject(0) != null) {
JSONObject json = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value");
loggedIn = (LoggedIn) JSONObject.toBean(json, LoggedIn.class);
JSONArray vs = json.optJSONArray("visitedSessions");
if (vs != null) {
Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class);
loggedIn.setVisitedSessions(new ArrayList<VisitedSession>(visitedSessions));
}
}
loggedIn.setUser(user.getUsername());
loggedIn.setSessionId(session.get_id());
loggedIn.addVisitedSession(session);
loggedIn.updateTimestamp();
JSONObject json = JSONObject.fromObject(loggedIn);
Document doc = new Document(json);
if (doc.getId().isEmpty()) {
// If this is a new user without a logged_in document, we have
// to remove the following
// pre-filled fields. Otherwise, CouchDB will take these empty
// fields as genuine
// identifiers, and will throw errors afterwards.
doc.remove("_id");
doc.remove("_rev");
}
this.getDatabase().saveDocument(doc);
LoggedIn l = (LoggedIn) JSONObject.toBean(doc.getJSONObject(), LoggedIn.class);
JSONArray vs = doc.getJSONObject().optJSONArray("visitedSessions");
if (vs != null) {
Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class);
l.setVisitedSessions(new ArrayList<VisitedSession>(visitedSessions));
}
return l;
} catch (UnsupportedEncodingException e) {
return null;
} catch (IOException e) {
return null;
}
}
@Override
public final void updateSessionOwnerActivity(final Session session) {
try {
session.setLastOwnerActivity(System.currentTimeMillis());
JSONObject json = JSONObject.fromObject(session);
this.getDatabase().saveDocument(new Document(json));
} catch (IOException e) {
LOGGER.error("Failed to update lastOwnerActivity for Session {}", session);
return;
}
}
@Override
public final Integer getMyFeedback(final String keyword, final User user) {
try {
String sessionId = this.getSessionId(keyword);
if (sessionId == null) {
throw new NotFoundException();
}
View view = new View("understanding/by_user");
view.setKey(
URLEncoder.encode(
"[\"" + sessionId + "\", \"" + user.getUsername() + "\"]",
"UTF-8"
)
);
ViewResults results = this.getDatabase().view(view);
JSONArray rows = results.getJSONArray("rows");
if (rows.size() == 0) {
return null;
}
JSONObject json = rows.optJSONObject(0).optJSONObject("value");
return this.feedbackValueFromString(json.getString("value"));
} catch (UnsupportedEncodingException e) {
return null;
}
}
@Override
public final List<String> getQuestionIds(final Session session, final User user) {
View view;
if (user.getType().equals("thm")) {
view = new View("skill_question/by_session_only_id_for_thm");
} else {
view = new View("skill_question/by_session_only_id_for_all");
}
try {
view.setKey(URLEncoder.encode("\"" + session.get_id() + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (results.getResults().size() == 0) {
return new ArrayList<String>();
}
List<String> ids = new ArrayList<String>();
for (Document d : results.getResults()) {
ids.add(d.getId());
}
return ids;
} catch (IOException e) {
LOGGER.error("Could not get list of question ids of session {}", session.getKeyword());
}
return new ArrayList<String>();
}
@Override
public final void deleteQuestion(final Question question) {
try {
this.deleteAnswers(question);
this.deleteDocument(question.get_id());
} catch (IOException e) {
LOGGER.error("IOException: Could not delete question {}", question.get_id());
}
}
private void deleteDocument(final String documentId) throws IOException {
Document d = this.getDatabase().getDocument(documentId);
this.getDatabase().deleteDocument(d);
}
@Override
public final void deleteAnswers(final Question question) {
try {
View view = new View("answer/cleanup");
view.setKey(URLEncoder.encode("\"" + question.get_id() + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
for (Document d : results.getResults()) {
this.deleteDocument(d.getId());
}
} catch (IOException e) {
LOGGER.error("IOException: Could not delete answers for question {}", question.get_id());
}
}
@Override
public final List<String> getUnAnsweredQuestions(final Session session, final User user) {
try {
View view = new View("answer/by_user");
view.setKey(
"[" + URLEncoder.encode(
"\"" + user.getUsername() + "\",\"" + session.get_id() + "\"",
"UTF-8"
)
+ "]"
);
ViewResults anseweredQuestions = this.getDatabase().view(view);
List<String> answered = new ArrayList<String>();
for (Document d : anseweredQuestions.getResults()) {
answered.add(d.getString("value"));
}
List<String> questions = this.getQuestionIds(session, user);
List<String> unanswered = new ArrayList<String>();
for (String questionId : questions) {
if (!answered.contains(questionId)) {
unanswered.add(questionId);
}
}
return unanswered;
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving unansweredquestions", e);
}
return null;
}
@Override
public final Answer getMyAnswer(final String questionId) {
User user = userService.getCurrentUser();
if (user == null) {
throw new UnauthorizedException();
}
try {
View view = new View("answer/by_question_and_user");
view.setKey(
"[" + URLEncoder.encode(
"\"" + questionId + "\",\"" + user.getUsername() + "\"",
"UTF-8"
)
+ "]"
);
ViewResults results = this.getDatabase().view(view);
if (results.getResults().isEmpty()) {
return null;
}
return (Answer) JSONObject.toBean(
results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
Answer.class
);
} catch (UnsupportedEncodingException e) {
LOGGER.error(
"Error while retrieving answer for user {} and question {}, {}",
new Object[] {user, questionId, e }
);
}
return null;
}
@Override
public final List<Answer> getAnswers(final String questionId) {
try {
View view = new View("skill_question/count_answers");
view.setStartKey("[" + URLEncoder.encode("\"" + questionId + "\"", "UTF-8") + "]");
view.setEndKey("[" + URLEncoder.encode("\"" + questionId + "\",{}", "UTF-8") + "]");
view.setGroup(true);
ViewResults results = this.getDatabase().view(view);
List<Answer> answers = new ArrayList<Answer>();
for (Document d : results.getResults()) {
Answer a = new Answer();
a.setAnswerCount(d.getInt("value"));
a.setQuestionId(d.getJSONObject().getJSONArray("key").getString(0));
a.setAnswerText(d.getJSONObject().getJSONArray("key").getString(1));
a.setAnswerSubject(d.getJSONObject().getJSONArray("key").getString(2));
answers.add(a);
}
return answers;
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving answers", e);
}
return null;
}
@Override
public final int getAnswerCount(final String questionId) {
try {
View view = new View("skill_question/count_answers_by_question");
view.setKey(URLEncoder.encode("\"" + questionId + "\"", "UTF-8"));
view.setGroup(true);
ViewResults results = this.getDatabase().view(view);
if (results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving answer count", e);
}
return 0;
}
@Override
public final int countActiveUsers(final long since) {
try {
View view = new View("statistic/count_active_users");
view.setStartKey(String.valueOf(since));
ViewResults results = this.getDatabase().view(view);
LOGGER.info("getActiveUsers() {}", results);
if (isEmptyResults(results)) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).getInt("value");
} catch (Exception e) {
LOGGER.error("Error while retrieving active users count", e);
}
return 0;
}
@Override
public final int countActiveUsers(Session session, long since) {
if (session == null) throw new NotFoundException();
try {
View view = new View("logged_in/count");
view.setStartKey(
URLEncoder.encode("[\"" + session.get_id() + "\", " + String.valueOf(since) + "]", "UTF-8")
);
view.setEndKey(URLEncoder.encode("[\"" + session.get_id() + "\", {}]", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (isEmptyResults(results)) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).getInt("value");
} catch (UnsupportedEncodingException e) {
return 0;
}
}
private boolean isEmptyResults(ViewResults results) {
return results == null || results.getResults().isEmpty() || results.getJSONArray("rows").size() == 0;
}
@Override
public List<Answer> getFreetextAnswers(String questionId) {
try {
View view = new View("skill_question/freetext_answers");
view.setKey(URLEncoder.encode("\"" + questionId + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (results.getResults().isEmpty()) {
throw new NotFoundException();
}
List<Answer> answers = new ArrayList<Answer>();
for (Document d : results.getResults()) {
Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class);
a.setAnswerSubject(d.getJSONObject().getJSONObject("value").getString("subject"));
a.setAnswerText(d.getJSONObject().getJSONObject("value").getString("text"));
a.setQuestionId(questionId);
answers.add(a);
}
return answers;
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving freetext answers", e);
}
return null;
}
@Override
public List<Answer> getMyAnswers(String sessionKey) {
Session s = this.getSessionFromKeyword(sessionKey);
if (s == null) {
throw new NotFoundException();
}
User user = userService.getCurrentUser();
if (user == null) {
throw new UnauthorizedException();
}
try {
View view = new View("answer/by_user_and_session");
view.setKey(
"[" + URLEncoder.encode("\"" + user.getUsername() + "\",\"" + s.get_id() + "\"", "UTF-8") + "]"
);
ViewResults results = this.getDatabase().view(view);
List<Answer> answers = new ArrayList<Answer>();
if (results.getResults().isEmpty()) {
return answers;
}
for (Document d : results.getResults()) {
Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class);
a.set_id(d.getId());
a.set_rev(d.getRev());
a.setUser(user.getUsername());
a.setSessionId(s.get_id());
answers.add(a);
}
return answers;
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving user answers", e);
}
return null;
}
@Override
public int getTotalAnswerCount(String sessionKey) {
Session s = this.getSessionFromKeyword(sessionKey);
if (s == null) {
throw new NotFoundException();
}
try {
View view = new View("skill_question/count_answers_by_session");
view.setKey(URLEncoder.encode("\"" + s.get_id() + "\"", "UTF-8"));
ViewResults results = this.getDatabase().view(view);
if (results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving total answer count", e);
}
return 0;
}
@Override
public int getInterposedCount(String sessionKey) {
Session s = this.getSessionFromKeyword(sessionKey);
if (s == null) {
throw new NotFoundException();
}
try {
View view = new View("interposed_question/count_by_session");
view.setKey(URLEncoder.encode("\"" + s.get_id() + "\"", "UTF-8"));
view.setGroup(true);
ViewResults results = this.getDatabase().view(view);
if (results.size() == 0 || results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving interposed question count", e);
}
return 0;
}
@Override
public InterposedReadingCount getInterposedReadingCount(Session session) {
try {
View view = new View("interposed_question/count_by_session_reading");
view.setStartKey(URLEncoder.encode("[\"" + session.get_id() + "\"]", "UTF-8"));
view.setEndKey(URLEncoder.encode("[\"" + session.get_id() + "\", {}]", "UTF-8"));
view.setGroup(true);
ViewResults results = this.getDatabase().view(view);
if (results.size() == 0 || results.getResults().size() == 0) {
return new InterposedReadingCount();
}
int read = results.getJSONArray("rows").optJSONObject(0).optInt("value");
int unread = 0;
if (results.getJSONArray("rows").optJSONObject(1) != null) {
unread = results.getJSONArray("rows").optJSONObject(1).optInt("value");
}
return new InterposedReadingCount(read, unread);
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving interposed question count", e);
}
return new InterposedReadingCount();
}
@Override
public List<InterposedQuestion> getInterposedQuestions(String sessionKey) {
Session s = this.getSessionFromKeyword(sessionKey);
if (s == null) {
throw new NotFoundException();
}
try {
View view = new View("interposed_question/by_session");
view.setKey(URLEncoder.encode("\"" + s.get_id() + "\"", "UTF-8"));
ViewResults questions = this.getDatabase().view(view);
if (questions == null || questions.isEmpty()) {
return null;
}
List<InterposedQuestion> result = new ArrayList<InterposedQuestion>();
LOGGER.debug("{}", questions.getResults());
for (Document document : questions.getResults()) {
InterposedQuestion question = (InterposedQuestion) JSONObject.toBean(
document.getJSONObject().getJSONObject("value"),
InterposedQuestion.class
);
question.setSessionId(sessionKey);
question.set_id(document.getId());
result.add(question);
}
return result;
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving interposed questions", e);
}
return null;
}
public Question getInterposedQuestion(String sessionKey, String documentId) {
try {
Document document = this.getDatabase().getDocument(documentId);
if (document == null) {
LOGGER.error("Document is NULL");
return null;
}
Question question = (Question) JSONObject.toBean(document.getJSONObject(), Question.class);
question.setQuestionType("interposed_question");
return question;
} catch (IOException e) {
LOGGER.error("Error while retrieving interposed question", e);
}
return null;
}
@Override
public void vote(String menu) {
User u = this.userService.getCurrentUser();
if (u == null) {
throw new UnauthorizedException();
}
String date = new SimpleDateFormat("dd-mm-yyyyy").format(new Date());
try {
View view = new View("food_vote/get_user_vote");
view.setKey("[" + URLEncoder.encode("\"" + date + "\",\"" + u.getUsername() + "\"", "UTF-8") + "]");
ViewResults results = this.getDatabase().view(view);
if (results.getResults().isEmpty()) {
Document vote = new Document();
vote.put("type", "food_vote");
vote.put("name", menu);
vote.put("user", u.getUsername());
vote.put("day", date);
this.database.saveDocument(vote);
} else {
Document vote = results.getResults().get(0);
vote.put("name", menu);
this.database.saveDocument(vote);
}
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving user food vote", e);
} catch (IOException e) {
LOGGER.error("Error while saving user food vote", e);
}
}
@Override
public List<FoodVote> getFoodVote() {
List<FoodVote> foodVotes = new ArrayList<FoodVote>();
String date = new SimpleDateFormat("dd-mm-yyyyy").format(new Date());
try {
View view = new View("food_vote/count_by_day");
view.setStartKey("[" + URLEncoder.encode("\"" + date + "\"", "UTF-8") + "]");
view.setEndKey("[" + URLEncoder.encode("\"" + date + "\",{}", "UTF-8") + "]");
view.setGroup(true);
ViewResults results = this.getDatabase().view(view);
for (Document d : results.getResults()) {
FoodVote vote = new FoodVote();
vote.setCount(d.getJSONObject().optInt("value"));
vote.setDay(date);
vote.setName(d.getJSONObject().getJSONArray("key").getString(1));
foodVotes.add(vote);
}
return foodVotes;
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving food vote count", e);
}
return foodVotes;
}
@Override
public int getFoodVoteCount() {
String date = new SimpleDateFormat("dd-mm-yyyyy").format(new Date());
try {
View view = new View("food_vote/count_by_day");
view.setStartKey("[" + URLEncoder.encode("\"" + date + "\"", "UTF-8") + "]");
view.setEndKey("[" + URLEncoder.encode("\"" + date + "\",{}", "UTF-8") + "]");
view.setGroup(false);
ViewResults results = this.getDatabase().view(view);
if (results.size() == 0 || results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
} catch (UnsupportedEncodingException e) {
LOGGER.error("Error while retrieving food vote count", e);
}
return 0;
}
@Override
public int countSessions() {
return sessionsCountValue("openSessions")
+ sessionsCountValue("closedSessions");
}
@Override
public int countClosedSessions() {
return sessionsCountValue("closedSessions");
}
@Override
public int countOpenSessions() {
return sessionsCountValue("openSessions");
}
@Override
public int countAnswers() {
return sessionsCountValue("answers");
}
@Override
public int countQuestions() {
return sessionsCountValue("questions");
}
private int sessionsCountValue(String key) {
try {
View view = new View("statistic/count_sessions");
view.setGroup(true);
ViewResults results = this.getDatabase().view(view);
if (isEmptyResults(results)) {
return 0;
}
int result = 0;
JSONArray rows = results.getJSONArray("rows");
for (int i = 0; i < rows.size(); i++) {
JSONObject row = rows.getJSONObject(i);
if (
row.getString("key").equals(key)
) {
result += row.getInt("value");
}
}
return result;
} catch (Exception e) {
LOGGER.error("Error while retrieving session count", e);
}
return 0;
}
@Override
public InterposedQuestion getInterposedQuestion(String questionId) {
try {
Document document = this.getDatabase().getDocument(questionId);
InterposedQuestion question = (InterposedQuestion) JSONObject.toBean(document.getJSONObject(),
InterposedQuestion.class);
question.setSessionId(getSessionKeyword(question.getSessionId()));
return question;
} catch (IOException e) {
LOGGER.error("Could not load interposed question {}", questionId);
}
return null;
}
@Override
public void markInterposedQuestionAsRead(InterposedQuestion question) {
try {
question.setRead(true);
Document document = this.getDatabase().getDocument(question.get_id());
document.put("read", question.isRead());
this.getDatabase().saveDocument(document);
} catch (IOException e) {
LOGGER.error("Coulg not mark interposed question as read {}", question.get_id());
}
}
@Override
public List<Session> getMyVisitedSessions(User user) {
try {
View view = new View("logged_in/visited_sessions_by_user");
view.setKey(URLEncoder.encode("\"" + user.getUsername() + "\"", "UTF-8"));
ViewResults sessions = this.getDatabase().view(view);
List<Session> allSessions = new ArrayList<Session>();
for (Document d : sessions.getResults()) {
// Not all users have visited sessions
if (d.getJSONObject().optJSONArray("value") != null) {
@SuppressWarnings("unchecked")
Collection<Session> visitedSessions = JSONArray.toCollection(
d.getJSONObject().getJSONArray("value"),
Session.class
);
allSessions.addAll(visitedSessions);
}
}
// Do these sessions still exist?
List<Session> result = new ArrayList<Session>();
for (Session s : allSessions) {
Session session = this.getSessionFromKeyword(s.getKeyword());
if (session != null) {
result.add(session);
}
}
return result;
} catch (UnsupportedEncodingException e) {
return null;
}
}
@Override
public Answer saveAnswer(Answer answer, User user) {
try {
Document a = new Document();
a.put("type", "skill_question_answer");
a.put("sessionId", answer.getSessionId());
a.put("questionId", answer.getQuestionId());
a.put("answerSubject", answer.getAnswerSubject());
a.put("answerText", answer.getAnswerText());
a.put("timestamp", answer.getTimestamp());
a.put("user", user.getUsername());
this.database.saveDocument(a);
answer.set_id(a.getId());
answer.set_rev(a.getRev());
return answer;
} catch (IOException e) {
LOGGER.error("Could not save answer {}", answer);
}
return null;
}
@Override
public Answer updateAnswer(Answer answer) {
try {
Document a = this.database.getDocument(answer.get_id());
a.put("answerSubject", answer.getAnswerSubject());
a.put("answerText", answer.getAnswerText());
a.put("timestamp", answer.getTimestamp());
this.database.saveDocument(a);
answer.set_rev(a.getRev());
return answer;
} catch (IOException e) {
LOGGER.error("Could not save answer {}", answer);
}
return null;
}
@Override
public void deleteAnswer(String answerId) {
try {
this.database.deleteDocument(this.database.getDocument(answerId));
} catch (IOException e) {
LOGGER.error("Could not delete answer {} because of {}", answerId, e.getMessage());
}
}
@Override
public void deleteInterposedQuestion(InterposedQuestion question) {
try {
this.deleteDocument(question.get_id());
} catch (IOException e) {
LOGGER.error("Could not delete interposed question {} because of {}", question.get_id(), e.getMessage());
}
}
@Override
public List<Session> getCourseSessions(List<Course> courses) {
ExtendedView view = new ExtendedView("logged_in/available_moodlesessions");
view.setCourseIdKeys(courses);
ViewResults sessions = this.getDatabase().view(view);
List<Session> result = new ArrayList<Session>();
for (Document d : sessions.getResults()) {
Session session = (Session) JSONObject.toBean(
d.getJSONObject().getJSONObject("value"),
Session.class
);
result.add(session);
}
return result;
}
private class ExtendedView extends View {
private String keys;
public ExtendedView(String fullname) {
super(fullname);
}
public void setKeys(String newKeys) {
this.keys = newKeys;
}
public void setCourseIdKeys(List<Course> courses) {
if (courses.isEmpty()) {
this.keys = "[]";
return;
}
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < courses.size() - 1; i++) {
sb.append("\"" + courses.get(i).getId() + "\",");
}
sb.append("\"" + courses.get(courses.size() - 1).getId() + "\"");
sb.append("]");
try {
this.setKeys(URLEncoder.encode(sb.toString(), "UTF-8"));
}
catch (UnsupportedEncodingException e) {
LOGGER.error("Error while encoding course ID keys", e);
}
}
public String getQueryString() {
StringBuilder query = new StringBuilder();
if (super.getQueryString() != null) {
query.append(super.getQueryString());
}
if (this.keys != null) {
if (query.toString().isEmpty()) {
query.append("&");
}
query.append("keys=" + this.keys);
}
if (query.toString().isEmpty()) return null;
return query.toString();
}
}
@Override
public Session lockSession(Session session, Boolean lock) {
try {
Document s = this.database.getDocument(session.get_id());
s.put("active", lock);
this.database.saveDocument(s);
session.set_rev(s.getRev());
return session;
} catch (IOException e) {
LOGGER.error("Could not lock session {}", session);
}
return null;
}
}
/*
* Copyright (C) 2012 THM webMedia
*
* This file is part of ARSnova.
*
* ARSnova is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.thm.arsnova.dao;
import java.io.IOException;
import java.util.List;
import de.thm.arsnova.connector.model.Course;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.Feedback;
import de.thm.arsnova.entities.FoodVote;
import de.thm.arsnova.entities.InterposedQuestion;
import de.thm.arsnova.entities.InterposedReadingCount;
import de.thm.arsnova.entities.LoggedIn;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.User;
public interface IDatabaseDao {
void cleanFeedbackVotes(int cleanupFeedbackDelay);
Session getSessionFromKeyword(String keyword);
Session getSession(String keyword);
List<Session> getMySessions(User user);
Session saveSession(Session session);
Feedback getFeedback(String keyword);
boolean saveFeedback(String keyword, int value, User user);
boolean sessionKeyAvailable(String keyword);
Question saveQuestion(Session session, Question question);
boolean saveQuestion(Session session, InterposedQuestion question);
Question getQuestion(String id);
List<Question> getSkillQuestions(String session);
int getSkillQuestionCount(Session session);
LoggedIn registerAsOnlineUser(User u, Session s);
void updateSessionOwnerActivity(Session session);
Integer getMyFeedback(String keyword, User user);
List<String> getQuestionIds(Session session, User user);
void deleteQuestion(Question question);
List<String> getUnAnsweredQuestions(Session session, User user);
Answer getMyAnswer(String questionId);
List<Answer> getAnswers(String questionId);
int getAnswerCount(String questionId);
List<Answer> getFreetextAnswers(String questionId);
int countActiveUsers(long since);
int countActiveUsers(Session session, long since);
List<Answer> getMyAnswers(String sessionKey);
int getTotalAnswerCount(String sessionKey);
int getInterposedCount(String sessionKey);
InterposedReadingCount getInterposedReadingCount(Session session);
List<InterposedQuestion> getInterposedQuestions(String sessionKey);
void vote(String menu);
int getFoodVoteCount();
List<FoodVote> getFoodVote();
int countSessions();
int countOpenSessions();
int countClosedSessions();
int countAnswers();
int countQuestions();
InterposedQuestion getInterposedQuestion(String questionId);
void markInterposedQuestionAsRead(InterposedQuestion question);
List<Session> getMyVisitedSessions(User user);
void updateQuestion(Question question);
void deleteAnswers(Question question);
Answer saveAnswer(Answer answer, User user);
Answer updateAnswer(Answer answer);
Session getSessionFromId(String sessionId);
void deleteAnswer(String answerId);
void deleteInterposedQuestion(InterposedQuestion question);
List<Session> getCourseSessions(List<Course> courses);
Session lockSession(Session session, Boolean lock);
}
package de.thm.arsnova.dao;
package de.thm.arsnova.entities;
public class Answer {
private String _id;
private String _rev;
private String type;
private String sessionId;
private String questionId;
private String answerText;
private String answerSubject;
private String user;
private long timestamp;
private int answerCount;
public Answer() {
this.type = "skill_question_answer";
}
public final String get_id() {
return _id;
}
public final void set_id(String _id) {
this._id = _id;
}
public final String get_rev() {
return _rev;
}
public final void set_rev(final String _rev) {
this._rev = _rev;
}
public final String getType() {
return type;
}
public final void setType(final String type) {
this.type = type;
}
public final String getSessionId() {
return sessionId;
}
public final void setSessionId(final String sessionId) {
this.sessionId = sessionId;
}
public final String getQuestionId() {
return questionId;
}
public final void setQuestionId(final String questionId) {
this.questionId = questionId;
}
public final String getAnswerText() {
return answerText;
}
public final void setAnswerText(final String answerText) {
this.answerText = answerText;
}
public final String getAnswerSubject() {
return answerSubject;
}
public final void setAnswerSubject(final String answerSubject) {
this.answerSubject = answerSubject;
}
public final String getUser() {
return user;
}
public final void setUser(final String user) {
this.user = user;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public final int getAnswerCount() {
return answerCount;
}
public final void setAnswerCount(final int answerCount) {
this.answerCount = answerCount;
}
@Override
public final String toString() {
return "Answer type:'" + type + "'"
+ ", session: " + sessionId
+ ", question: " + questionId
+ ", subject: " + answerSubject
+ ", answerCount: " + answerCount
+ ", answer: " + answerText
+ ", user: " + user;
}
}
package de.thm.arsnova.entities;
public class Authorize {
private String user;
private String socketid;
public final String getUser() {
return user;
}
public final void setUser(final String user) {
this.user = user;
}
public final String getSocketid() {
return socketid;
}
public final void setSocketid(final String socketid) {
this.socketid = socketid;
}
@Override
public final String toString() {
return "user: " + user + ", socketid: " + socketid;
}
}