284 lines
11 KiB
Java
284 lines
11 KiB
Java
/* © SRSoftware 2025 */
|
|
package de.srsoftware.umbrella.poll;
|
|
|
|
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
|
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
|
import static de.srsoftware.umbrella.core.constants.Field.*;
|
|
import static de.srsoftware.umbrella.core.constants.Path.*;
|
|
import static de.srsoftware.umbrella.core.constants.Path.WEIGHT;
|
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
|
import static de.srsoftware.umbrella.poll.Constants.CONFIG_DATABASE;
|
|
import static java.lang.System.Logger.Level.WARNING;
|
|
import static java.net.HttpURLConnection.HTTP_OK;
|
|
import static java.text.MessageFormat.format;
|
|
import static de.srsoftware.umbrella.core.model.Permission.READ_ONLY;
|
|
import com.sun.net.httpserver.HttpExchange;
|
|
import de.srsoftware.configuration.Configuration;
|
|
import de.srsoftware.tools.Path;
|
|
import de.srsoftware.tools.SessionToken;
|
|
import de.srsoftware.umbrella.core.BaseHandler;
|
|
import de.srsoftware.umbrella.core.ModuleRegistry;
|
|
import de.srsoftware.umbrella.core.api.PollService;
|
|
import de.srsoftware.umbrella.core.constants.Field;
|
|
import de.srsoftware.umbrella.core.constants.Text;
|
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
|
import de.srsoftware.umbrella.core.model.Permission;
|
|
import de.srsoftware.umbrella.core.model.Poll;
|
|
import de.srsoftware.umbrella.core.model.Token;
|
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
|
import org.json.JSONObject;
|
|
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
|
|
public class PollModule extends BaseHandler implements PollService {
|
|
|
|
private PollDb pollDb;
|
|
|
|
public PollModule(Configuration config){
|
|
super();
|
|
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingField(CONFIG_DATABASE));
|
|
pollDb = new SqliteDb(connect(dbFile));
|
|
ModuleRegistry.add(this);
|
|
}
|
|
|
|
@Override
|
|
public boolean doGet(Path path, HttpExchange ex) throws IOException {
|
|
addCors(ex);
|
|
try {
|
|
Optional<Token> token = SessionToken.from(ex).map(Token::of);
|
|
var head = path.pop();
|
|
var user = userService().loadUser(token).orElse(null);
|
|
return switch (head) {
|
|
case EVALUATE -> getPollEvaluation(ex,user, path);
|
|
case LIST -> getPollList(ex,user);
|
|
case null -> super.doGet(path,ex);
|
|
default -> getPoll(ex,user,head);
|
|
};
|
|
} catch (UmbrellaException e){
|
|
return send(ex,e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean doPatch(Path path, HttpExchange ex) throws IOException {
|
|
addCors(ex);
|
|
try {
|
|
Optional<Token> token = SessionToken.from(ex).map(Token::of);
|
|
var user = userService().loadUser(token);
|
|
if (user.isEmpty()) return unauthorized(ex);
|
|
var head = path.pop();
|
|
return switch (head) {
|
|
case null -> super.doPatch(path,ex);
|
|
default -> patchPoll(ex,user.get(),head, path);
|
|
};
|
|
} catch (UmbrellaException e){
|
|
return send(ex,e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean doPost(Path path, HttpExchange ex) throws IOException {
|
|
addCors(ex);
|
|
try {
|
|
Optional<Token> token = SessionToken.from(ex).map(Token::of);
|
|
var user = userService().loadUser(token).orElse(null );
|
|
var head = path.pop();
|
|
return switch (head) {
|
|
case null -> postNewPoll(ex,user);
|
|
default -> postToPoll(ex, user, head, path);
|
|
};
|
|
} catch (UmbrellaException e){
|
|
return send(ex,e);
|
|
}
|
|
}
|
|
|
|
private boolean getPoll(HttpExchange ex, UmbrellaUser user, String pollId) throws IOException {
|
|
var poll = pollDb.loadPoll(pollId);
|
|
var permitted = !poll.isPrivate() || poll.owner().equals(user);
|
|
if (!permitted && poll.permissions().get(user) == null) throw forbidden(Text.NOT_ALLOWED_TO_EDIT, Field.OBJECT,Text.POLL);
|
|
var result = new HashMap<>(poll.toMap());
|
|
if (user != null) result.put(SELECTION,pollDb.loadSelections(poll, user));
|
|
|
|
return sendContent(ex,result);
|
|
}
|
|
|
|
private boolean getPollEvaluation(HttpExchange ex, UmbrellaUser user, Path path) throws IOException {
|
|
if (user == null) return unauthorized(ex);
|
|
if (path.empty()) throw missingField(ID);
|
|
var poll = pollDb.loadPoll(path.pop());
|
|
if (!poll.owner().equals(user)) {
|
|
switch (poll.permissions().get(user)) {
|
|
case Permission.EDIT:
|
|
case Permission.OWNER:
|
|
break;
|
|
case null, default:
|
|
throw forbidden(Text.NOT_ALLOWED_TO_EVALUATE, Field.OBJECT, Text.POLL);
|
|
}
|
|
}
|
|
var result = new HashMap<>(poll.toMap());
|
|
var evaluation = pollDb.loadEvaluation(poll);
|
|
result.put(Field.EVALUATION,evaluation.toMap());
|
|
return sendContent(ex,result);
|
|
}
|
|
|
|
private boolean getPollList(HttpExchange ex, UmbrellaUser user) throws IOException {
|
|
if (user == null) return unauthorized(ex);
|
|
var list = pollDb.listPolls(user).stream().map(Poll::toMap);
|
|
return sendContent(ex,list);
|
|
}
|
|
|
|
private boolean patchPoll(HttpExchange ex, UmbrellaUser user, String pollId, Path path) throws IOException {
|
|
var poll = pollDb.loadPoll(pollId);
|
|
var permitted = poll.owner().equals(user);
|
|
if (!permitted && !Set.of(Permission.EDIT, Permission.OWNER).contains(poll.permissions().get(user))) throw forbidden(Text.NOT_ALLOWED_TO_EDIT, Field.OBJECT,Text.POLL);
|
|
|
|
var head = path.pop();
|
|
return switch (head){
|
|
case null -> patchPoll(ex, poll);
|
|
case OPTION -> patchPollOptions(ex, path, poll);
|
|
case WEIGHT -> patchWeights(ex, poll);
|
|
default -> notFound(ex);
|
|
};
|
|
}
|
|
|
|
private boolean patchPoll(HttpExchange ex, Poll poll) throws IOException {
|
|
var json = json(ex);
|
|
for (var key : json.keySet()){
|
|
switch (key){
|
|
case Field.DESCRIPTION:
|
|
poll.description(json.getString(key)); break;
|
|
case Field.NAME:
|
|
poll.name(json.getString(key)); break;
|
|
case PRIVATE:
|
|
poll.setPrivate(json.getBoolean(key)); break;
|
|
case null, default:
|
|
throw UmbrellaException.badRequest(Text.UNKNOWN_FIELD,ID,key);
|
|
}
|
|
}
|
|
return sendContent(ex,pollDb.save(poll));
|
|
}
|
|
|
|
private boolean patchWeights(HttpExchange ex, Poll poll) throws IOException {
|
|
var json = json(ex);
|
|
for (var key : json.keySet()) try {
|
|
var weight = Integer.parseInt(key);
|
|
if (!(json.get(key) instanceof String description)) throw invalidField(Field.DESCRIPTION,Text.STRING);
|
|
poll.setWeight(weight,description);
|
|
} catch (NumberFormatException nfe) {
|
|
throw invalidField(Text.WEIGHT,Text.NUMBER);
|
|
}
|
|
return sendContent(ex,pollDb.save(poll));
|
|
}
|
|
|
|
private boolean patchPollOptions(HttpExchange ex, Path path, Poll poll) throws IOException {
|
|
try {
|
|
if (path.empty()) throw missingField(ID);
|
|
var optionId = Integer.parseInt(path.pop());
|
|
var option = poll.options().stream().filter(o -> o.id()==optionId).findFirst().orElse(null);
|
|
if (option == null) throw failedToLoadObject(Text.OPTION);
|
|
var json = json(ex);
|
|
for (var key : json.keySet()){
|
|
switch (key) {
|
|
case ID:
|
|
break;
|
|
case Field.NAME:
|
|
option.name(json.getString(key)); break;
|
|
case Field.DESCRIPTION:
|
|
option.description(json.getString(key)); break;
|
|
default:
|
|
throw UmbrellaException.badRequest(Text.UNKNOWN_FIELD,FIELD,key);
|
|
}
|
|
}
|
|
|
|
return sendContent(ex, pollDb.save(poll));
|
|
} catch (NumberFormatException nfe){
|
|
throw invalidField(ID,Text.NUMBER).causedBy(nfe);
|
|
}
|
|
}
|
|
|
|
private boolean postNewPoll(HttpExchange ex, UmbrellaUser user) throws IOException {
|
|
if (user == null) return unauthorized(ex);
|
|
var json = json(ex);
|
|
if (!json.has(Field.NAME)) throw missingField(Field.NAME);
|
|
var name = json.getString(Field.NAME);
|
|
var poll = new Poll(null,user,name,"",true, List.of(), Map.of());
|
|
return sendContent(ex,pollDb.save(poll));
|
|
}
|
|
|
|
private boolean postToPoll(HttpExchange ex, UmbrellaUser user, String id, Path path) throws IOException {
|
|
var head = path.pop();
|
|
var poll = pollDb.loadPoll(id);
|
|
if (SELECT.equals(head)) {
|
|
if (user == null && poll.isPrivate()) return unauthorized(ex);
|
|
return postSelection(ex, poll, user);
|
|
}
|
|
var permitted = poll.owner().equals(user);
|
|
if (!permitted && !Set.of(Permission.OWNER, Permission.EDIT).contains(poll.permissions().get(user))) throw forbidden(Text.NOT_ALLOWED_TO_EDIT, Field.OBJECT,Text.POLL);
|
|
return switch (head){
|
|
case PERMISSIONS -> postPermission(ex, poll, user);
|
|
case OPTION -> postOption(ex, poll);
|
|
case null, default -> notFound(ex);
|
|
};
|
|
}
|
|
|
|
private boolean postPermission(HttpExchange ex, Poll poll, UmbrellaUser user) throws IOException {
|
|
LOG.log(WARNING,"Permission check not implemented for postPermission");
|
|
|
|
var json = json(ex);
|
|
if (!json.has(USER_ID)) throw missingField(USER_ID);
|
|
if (!json.has(PERMISSION)) throw missingField(PERMISSION);
|
|
if (!(json.get(USER_ID) instanceof Number userId)) throw invalidField(USER_ID,NUMBER);
|
|
if (!(json.get(PERMISSION) instanceof Number perm)) throw invalidField(PERMISSION,NUMBER);
|
|
var modifiedUser = userService().loadUser(userId.longValue());
|
|
if (perm.intValue() == 0){
|
|
pollDb.setPermission(poll.id(), modifiedUser, null);
|
|
return sendEmptyResponse(HTTP_OK,ex);
|
|
}
|
|
var permission = Permission.of(perm.intValue());
|
|
if (permission == Permission.ASSIGNEE) permission = Permission.EDIT;
|
|
pollDb.setPermission(poll.id(), modifiedUser, permission);
|
|
return sendContent(ex,Map.of(
|
|
Field.USER,modifiedUser.toMap(),
|
|
PERMISSION, permission.toMap()
|
|
));
|
|
}
|
|
|
|
private boolean postSelection(HttpExchange ex, Poll poll, UmbrellaUser user) throws IOException {
|
|
var json = json(ex);
|
|
if (!json.has(Field.SELECTION)) throw missingField(Field.SELECTION);
|
|
if (!(json.get(Field.SELECTION) instanceof JSONObject job)) throw invalidField(Field.SELECTION,JSON);
|
|
var map = new HashMap<Integer,Integer>();
|
|
for (var key : job.keySet()){
|
|
var optionId = Integer.parseInt(key);
|
|
if (!(job.get(key) instanceof Integer weight)) throw invalidField(Field.WEIGHT,Text.NUMBER);
|
|
map.put(optionId,weight);
|
|
}
|
|
if (user == null) {
|
|
if (!json.has(Field.EDITOR)) throw missingField(Field.EDITOR);
|
|
if (!(json.get(Field.EDITOR) instanceof JSONObject editor)) throw invalidField(Field.EDITOR,JSON);
|
|
if (!editor.has(Field.NAME)) throw missingField(format("{0}.{1}}",Field.EDITOR,Field.NAME));
|
|
if (!(editor.get(Field.NAME) instanceof String name)) throw invalidField(format("{0}.{1}",Field.EDITOR,Field.NAME),Text.STRING);
|
|
pollDb.saveSelection(poll, map, name);
|
|
} else pollDb.saveSelection(poll, map, user);
|
|
return sendContent(ex,poll);
|
|
}
|
|
|
|
private boolean postOption(HttpExchange ex, Poll poll) throws IOException {
|
|
var json = json(ex);
|
|
if (!json.has(Field.NAME) || !(json.get(Field.NAME) instanceof String name)) throw missingField(Field.NAME);
|
|
String description = null;
|
|
if (json.has(Field.DESCRIPTION)) {
|
|
var val = json.get(Field.DESCRIPTION);
|
|
if (val instanceof JSONObject j && j.has(Field.SOURCE)) val = j.get(Field.SOURCE);
|
|
if (val instanceof String d) description = d;
|
|
}
|
|
var options = poll.options();
|
|
int newId = options.stream().map(Poll.Option::id).max(Integer::compareTo).orElse(0) + 1;
|
|
|
|
var option = new Poll.Option(0, name, description, 0).id(newId);
|
|
poll.options().add(option);
|
|
return sendContent(ex,pollDb.save(poll));
|
|
}
|
|
}
|