Commit 2c68d6e6 authored by Daniel Gerhardt's avatar Daniel Gerhardt

Merge branch 'motd' into 'master'

MotD Feature:

There are two types of MotD: 
AdminMessages
SessionMessages

Every Message has a title, text, start- and enddate.

You can declare an admin account in the arsnova.properties (security.adminaccount = <adminusername>) and with this account you can create Messages for different audiences (different types are: all (shown on startpage), loggedIn, students, tutors) which will be shown in a messageBox.

SessionMessages can be created for every session by the sessioncreator and is displayed, when you enter the session.

When a user that is not logged in via Guest, he pushes a list to the backend with all the messagekeys he read and acknowledged, so that he isn't shown the same messages over and over again. In addition to that, the keys are also stored in the localStorage (e.g. for guest accounts).

See merge request !15
parents 527a9b57 89c91079
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* 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;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
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.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.PathVariable;
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 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 de.thm.arsnova.entities.Motd;
import de.thm.arsnova.entities.MotdList;
import de.thm.arsnova.exceptions.NotFoundException;
import de.thm.arsnova.services.IMotdService;
/**
*
*/
@RestController
@RequestMapping("/motd")
@Api(value = "/motd", description = "the Motd Controller API")
public class MotdController extends AbstractController {
private static final Logger LOGGER = LoggerFactory.getLogger(MotdController.class);
@Autowired
private IMotdService motdService;
@ApiOperation(value = "get messages. if adminview=false, only messages with startdate<clientdate<enddate are returned")
@RequestMapping(value = "/", method = RequestMethod.GET)
@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", defaultValue = "null") final String sessionkey
) {
List<Motd> motds = new ArrayList<Motd>();
Date client = new Date(System.currentTimeMillis());
if (!clientdate.isEmpty()) {
client.setTime(Long.parseLong(clientdate));
}
if (adminview) {
if (sessionkey.equals("null")) {
motds = motdService.getAdminMotds();
}
else {
motds = motdService.getAllSessionMotds(sessionkey);
}
}
else {
motds = motdService.getCurrentMotds(client, audience, sessionkey);
}
return motds;
}
@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)
})
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public Motd postNewMotd(
@ApiParam(value = "current motd", required = true) @RequestBody final Motd motd,
@ApiParam(value = "http servlet response", required = true) final HttpServletResponse response
) {
if (motd != null) {
Motd newMotd = new Motd();
if (motd.getAudience().equals("session") && motd.getSessionkey() != null) {
newMotd = motdService.saveSessionMotd(motd.getSessionkey(), motd);
}
else {
newMotd = motdService.saveMotd(motd);
}
if (newMotd == null) {
response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
return null;
}
return newMotd;
}
else {
response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
return null;
}
}
@ApiOperation(value = "update a message of the day", nickname = "updateMotd")
@RequestMapping(value = "/{motdkey}", method = RequestMethod.PUT)
public Motd updateMotd(
@ApiParam(value = "motdkey from current motd", required = true) @PathVariable final String motdkey,
@ApiParam(value = "current motd", required = true) @RequestBody final Motd motd
) {
if (motd.getAudience().equals("session") && motd.getSessionkey() != null) {
return motdService.updateSessionMotd(motd.getSessionkey(), motd);
}
else {
return motdService.updateMotd(motd);
}
}
@ApiOperation(value = "deletes a message of the day", nickname = "deleteMotd")
@RequestMapping(value = "/{motdkey}", method = RequestMethod.DELETE)
public void deleteMotd(@ApiParam(value = "Motd-key from the message that shall be deleted", required = true) @PathVariable final String motdkey) {
Motd motd = motdService.getMotd(motdkey);
if (motd.getAudience().equals("session")) {
motdService.deleteSessionMotd(motd.getSessionkey(), motd);
}
else {
motdService.deleteMotd(motd);
}
}
@ApiOperation(value = "get a list of the motdkeys the current user has confirmed to be read")
@RequestMapping(value = "/userlist", method = RequestMethod.GET)
public MotdList getUserMotdList(
@ApiParam(value = "users name", required = true) @RequestParam(value = "username", defaultValue = "null", required = true) final String username) {
return motdService.getMotdListForUser(username);
}
@ApiOperation(value = "create a list of the motdkeys the current user has confirmed to be read")
@RequestMapping(value = "/userlist", method = RequestMethod.POST)
public MotdList postUserMotdList(
@ApiParam(value = "current motdlist", required = true) @RequestBody final MotdList userMotdList
) {
return motdService.saveUserMotdList(userMotdList);
}
@ApiOperation(value = "update a list of the motdkeys the current user has confirmed to be read")
@RequestMapping(value = "/userlist", method = RequestMethod.PUT)
public MotdList updateUserMotdList(
@ApiParam(value = "current motdlist", required = true) @RequestBody final MotdList userMotdList
) {
return motdService.updateUserMotdList(userMotdList);
}
}
......@@ -29,6 +29,8 @@ import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.Date;
import java.util.StringTokenizer;
import net.sf.ezmorph.Morpher;
import net.sf.ezmorph.MorpherRegistry;
......@@ -81,6 +83,8 @@ import de.thm.arsnova.entities.transport.ImportExportSession.ImportExportQuestio
import de.thm.arsnova.events.NewAnswerEvent;
import de.thm.arsnova.exceptions.NotFoundException;
import de.thm.arsnova.services.ISessionService;
import de.thm.arsnova.entities.Motd;
import de.thm.arsnova.entities.MotdList;
/**
* Database implementation based on CouchDB.
......@@ -2334,10 +2338,10 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
view.setIncludeDocs(true);
final List<Document> questiondocs = getDatabase().view(view).getResults();
if (questiondocs == null || questiondocs.isEmpty()) {
return null;
}
final List<Question> result = new ArrayList<Question>();
final MorpherRegistry morpherRegistry = JSONUtils.getMorpherRegistry();
final Morpher dynaMorpher = new BeanMorpher(PossibleAnswer.class, morpherRegistry);
morpherRegistry.registerMorpher(dynaMorpher);
......@@ -2370,4 +2374,184 @@ public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware
}
return result;
}
@Override
public List<Motd> getAdminMotds() {
NovaView view = new NovaView("motd/admin");
return getMotds(view);
}
@Override
@Cacheable(cacheNames = "motds", key = "'all'")
public List<Motd> getMotdsForAll() {
NovaView view = new NovaView("motd/for_all");
return getMotds(view);
}
@Override
@Cacheable(cacheNames = "motds", key = "'loggedIn'")
public List<Motd> getMotdsForLoggedIn() {
NovaView view = new NovaView("motd/for_loggedin");
return getMotds(view);
}
@Override
@Cacheable(cacheNames = "motds", key = "'tutors'")
public List<Motd> getMotdsForTutors() {
NovaView view = new NovaView("motd/for_tutors");
return getMotds(view);
}
@Override
@Cacheable(cacheNames = "motds", key = "'students'")
public List<Motd> getMotdsForStudents() {
NovaView view = new NovaView("motd/for_students");
return getMotds(view);
}
@Override
@Cacheable(cacheNames = "motds", key = "('session').concat(#p0)")
public List<Motd> getMotdsForSession(final String sessionkey) {
NovaView view = new NovaView("motd/by_sessionkey");
view.setKey(sessionkey);
return getMotds(view);
}
@Override
public List<Motd> getMotds(NovaView view) {
final ViewResults motddocs = this.getDatabase().view(view);
List<Motd> motdlist = new ArrayList<Motd>();
for (final Document d : motddocs.getResults()) {
Motd motd = new Motd();
motd.set_id(d.getId());
motd.set_rev(d.getJSONObject("value").getString("_rev"));
motd.setMotdkey(d.getJSONObject("value").getString("motdkey"));
Date start = new Date(Long.parseLong(d.getJSONObject("value").getString("startdate")));
motd.setStartdate(start);
Date end = new Date(Long.parseLong(d.getJSONObject("value").getString("enddate")));
motd.setEnddate(end);
motd.setTitle(d.getJSONObject("value").getString("title"));
motd.setText(d.getJSONObject("value").getString("text"));
motd.setAudience(d.getJSONObject("value").getString("audience"));
motd.setSessionkey(d.getJSONObject("value").getString("sessionkey"));
motdlist.add(motd);
}
return motdlist;
}
@Override
public Motd getMotdByKey(String key) {
NovaView view = new NovaView("motd/by_keyword");
view.setKey(key);
Motd motd = new Motd();
ViewResults results = this.getDatabase().view(view);
for (final Document d : results.getResults()) {
motd.set_id(d.getId());
motd.set_rev(d.getJSONObject("value").getString("_rev"));
motd.setMotdkey(d.getJSONObject("value").getString("motdkey"));
Date start = new Date(Long.parseLong(d.getJSONObject("value").getString("startdate")));
motd.setStartdate(start);
Date end = new Date(Long.parseLong(d.getJSONObject("value").getString("enddate")));
motd.setEnddate(end);
motd.setTitle(d.getJSONObject("value").getString("title"));
motd.setText(d.getJSONObject("value").getString("text"));
motd.setAudience(d.getJSONObject("value").getString("audience"));
motd.setSessionkey(d.getJSONObject("value").getString("sessionkey"));
}
return motd;
}
@Override
@CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)")
public Motd createOrUpdateMotd(Motd motd) {
try {
String id = motd.get_id();
String rev = motd.get_rev();
Document d = new Document();
if (null != id) {
d = database.getDocument(id, rev);
}
if (motd.getMotdkey() == null) {
motd.setMotdkey(sessionService.generateKeyword());
}
d.put("type", "motd");
d.put("motdkey", motd.getMotdkey());
d.put("startdate", String.valueOf(motd.getStartdate().getTime()));
d.put("enddate", String.valueOf(motd.getEnddate().getTime()));
d.put("title", motd.getTitle());
d.put("text", motd.getText());
d.put("audience", motd.getAudience());
d.put("sessionkey", motd.getSessionkey());
database.saveDocument(d, id);
motd.set_id(d.getId());
motd.set_rev(d.getRev());
return motd;
} catch (IOException e) {
LOGGER.error("Could not save motd {}", motd);
}
return null;
}
@Override
@CacheEvict(cacheNames = "motds", key = "#p0.audience.concat(#p0.sessionkey)")
public void deleteMotd(Motd motd) {
try {
this.deleteDocument(motd.get_id());
} catch (IOException e) {
LOGGER.error("Could not delete Motd {}", motd.get_id());
}
}
@Override
@Cacheable(cacheNames = "motdlist", key = "#p0")
public MotdList getMotdListForUser(final String username) {
NovaView view = new NovaView("motd/list_by_username");
view.setKey(username);
ViewResults results = this.getDatabase().view(view);
MotdList motdlist = new MotdList();
for (final Document d : results.getResults()) {
motdlist.set_id(d.getId());
motdlist.set_rev(d.getJSONObject("value").getString("_rev"));
motdlist.setUsername(d.getJSONObject("value").getString("username"));
motdlist.setMotdkeys(d.getJSONObject("value").getString("motdkeys"));
}
return motdlist;
}
@Override
@CachePut(cacheNames = "motdlist", key = "#p0.username")
public MotdList createOrUpdateMotdList(MotdList motdlist) {
try {
String id = motdlist.get_id();
String rev = motdlist.get_rev();
Document d = new Document();
if (null != id) {
d = database.getDocument(id, rev);
}
d.put("type","motdlist");
d.put("username", motdlist.getUsername());
d.put("motdkeys", motdlist.getMotdkeys());
database.saveDocument(d, id);
motdlist.set_id(d.getId());
motdlist.set_rev(d.getRev());
return motdlist;
} catch (IOException e) {
LOGGER.error("Could not save motdlist {}", motdlist);
}
return null;
}
}
......@@ -33,6 +33,8 @@ import de.thm.arsnova.entities.SortOrder;
import de.thm.arsnova.entities.Statistics;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.entities.transport.ImportExportSession;
import de.thm.arsnova.entities.Motd;
import de.thm.arsnova.entities.MotdList;
/**
* All methods the database must support.
......@@ -224,4 +226,28 @@ public interface IDatabaseDao {
List<Question> setVotingAdmissionForAllQuestions(Session session, boolean disableVoting);
<T> T getObjectFromId(String documentId, Class<T> klass);
List<Motd> getAdminMotds();
List<Motd> getMotdsForAll();
List<Motd> getMotdsForLoggedIn();
List<Motd> getMotdsForTutors();
List<Motd> getMotdsForStudents();
List<Motd> getMotdsForSession(final String sessionkey);
List<Motd> getMotds(NovaView view);
Motd getMotdByKey(String key);
Motd createOrUpdateMotd(Motd motd);
void deleteMotd(Motd motd);
MotdList getMotdListForUser(final String username);
MotdList createOrUpdateMotdList(MotdList motdlist);
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* 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.entities;
import java.util.Date;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* This class represents a message of the day.
*/
@ApiModel(value = "motd", description = "the message of the day entity")
public class Motd {
private String motdkey; //ID
private Date startdate;
private Date enddate;
private String title;
private String text;
private String audience;
private String sessionkey;
private String _id;
private String _rev;
@ApiModelProperty(required = true, value = "the identification string")
public String getMotdkey() {
return motdkey;
}
public void setMotdkey(final String key) {
motdkey = key;
}
@ApiModelProperty(required = true, value = "startdate for showing this message (timestamp format)")
public Date getStartdate() {
return startdate;
}
public void setStartdate(final Date timestamp) {
startdate = timestamp;
}
@ApiModelProperty(required = true, value = "enddate for showing this message (timestamp format)")
public Date getEnddate() {
return enddate;
}
public void setEnddate(final Date timestamp) {
enddate = timestamp;
}
@ApiModelProperty(required = true, value = "tite of the message")
public String getTitle() {
return title;
}
public void setTitle(final String ttitle) {
title = ttitle;
}
@ApiModelProperty(required = true, value = "text of the message")
public String getText() {
return text;
}
public void setText(final String ttext) {
text = ttext;
}
@ApiModelProperty(required = true, value = "defines the target audience for this motd (one of the following: 'student', 'tutor', 'loggedIn', 'all')")
public String getAudience() {
return audience;
}
public void setAudience(String a){
audience = a;
}
@ApiModelProperty(required = true, value = "when audience equals session, the sessionkey referes to the session the messages belong to")
public String getSessionkey() {
return sessionkey;
}
public void setSessionkey(String a){
sessionkey = a;
}
@ApiModelProperty(required = true, value = "the couchDB ID")
public String get_id() {
return _id;
}
public void set_id(final String id) {
_id = id;
}
public void set_rev(final String rev) {
_rev = rev;
}
public String get_rev() {
return _rev;
}
@Override
public int hashCode() {
// See http://stackoverflow.com/a/113600
final int prim = 37;
int result = 42;
return prim * result + this.motdkey.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !obj.getClass().equals(this.getClass())) {
return false;
}
Motd other = (Motd) obj;
return (this.getMotdkey().equals(other.getMotdkey()));
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* 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.entities;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* This class represents a list of motdkeys for a user.
*/
@ApiModel(value = "motdlist", description = "the motdlist to save the messages a user has confirmed to be read")
public class MotdList {
private String motdkeys;
private String username;
private String _id;
private String _rev;
@ApiModelProperty(required = true, value = "the motdkeylist")
public String getMotdkeys() {
return motdkeys;
}
public void setMotdkeys(String motds) {
motdkeys = motds;
}
@ApiModelProperty(required = true, value = "the username")
public String getUsername() {
return username;
}
public void setUsername(final String u) {
username = u;
}
@ApiModelProperty(required = true, value = "the couchDB ID")
public String get_id() {
return _id;
}
public void set_id(final String id) {
_id = id;
}
public void set_rev(final String rev) {
_rev = rev;
}
public String get_rev() {
return _rev;
}
}
......@@ -45,6 +45,7 @@ public class User implements Serializable {
private String username;
private String type;
private UserSessionService.Role role;
private boolean isAdmin;
public User(Google2Profile profile) {
setUsername(profile.getEmail());
......@@ -104,6 +105,14 @@ public class User implements Serializable {
return this.role == role;
}
public void setAdmin(boolean a) {
this.isAdmin = a;
}
public boolean isAdmin() {
return this.isAdmin;
}
@Override
public String toString() {
return "User [username=" + username + ", type=" + type + "]";
......
......@@ -19,9 +19,14 @@ package de.thm.arsnova.security;
import java.io.Serializable;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.scribe.up.profile.facebook.FacebookProfile;
import org.scribe.up.profile.google.Google2Profile;
import org.scribe.up.profile.twitter.TwitterProfile;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
......@@ -41,6 +46,11 @@ import de.thm.arsnova.exceptions.UnauthorizedException;
*/
public class ApplicationPermissionEvaluator implements PermissionEvaluator {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationPermissionEvaluator.class);
@Value("${security.adminaccount}")