implemented creating new poll and managing poll options
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -9,13 +9,11 @@ import de.srsoftware.umbrella.core.constants.Field;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.srsoftware.tools.Optionals.is0;
|
||||
import static de.srsoftware.umbrella.core.constants.Field.*;
|
||||
import static java.text.MessageFormat.format;
|
||||
|
||||
public record Poll(String id, Owner owner, String name, String description, boolean isPrivate, List<Option> options, Map<Integer,String> weights, Map<UmbrellaUser,Long> shares) implements Mappable {
|
||||
public class Poll implements Mappable {
|
||||
public static class Option implements Mappable{
|
||||
private int id;
|
||||
Integer status;
|
||||
@@ -106,6 +104,47 @@ public record Poll(String id, Owner owner, String name, String description, bool
|
||||
}
|
||||
}
|
||||
|
||||
private Owner owner;
|
||||
private String id, name, description;
|
||||
private boolean isPrivate;
|
||||
private List<Option> options;
|
||||
private Map<Integer,String> weights;
|
||||
private Map<UmbrellaUser,Long> shares;
|
||||
private Set<String> dirtyFields = new HashSet<>();
|
||||
|
||||
public Poll(String id, Owner owner, String name, String description, boolean isPrivate, List<Option> options, Map<Integer,String> weights, Map<UmbrellaUser,Long> shares){
|
||||
this.id = id;
|
||||
this.owner = owner;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.isPrivate = isPrivate;
|
||||
this.options = new ArrayList<>();
|
||||
this.weights = new HashMap<>();
|
||||
this.shares = new HashMap<>();
|
||||
}
|
||||
|
||||
public String description(){
|
||||
return description;
|
||||
}
|
||||
|
||||
public Poll description(String newVal){
|
||||
description = newVal;
|
||||
dirtyFields.add(DESCRIPTION);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String id(){
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isDirty(){
|
||||
return !dirtyFields.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isPrivate(){
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
private Map<Long, Map<String, Object>> mapShares() {
|
||||
var result = new HashMap<Long,Map<String,Object>>();
|
||||
for (var entry : shares.entrySet()){
|
||||
@@ -117,6 +156,16 @@ public record Poll(String id, Owner owner, String name, String description, bool
|
||||
return result;
|
||||
}
|
||||
|
||||
public String name(){
|
||||
return name;
|
||||
}
|
||||
|
||||
public Poll name(String newVal){
|
||||
name = newVal;
|
||||
dirtyFields.add(NAME);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Poll of(ResultSet rs) throws SQLException {
|
||||
var id = rs.getString(ID);
|
||||
var userId = rs.getLong(Field.USER_ID);
|
||||
@@ -128,6 +177,16 @@ public record Poll(String id, Owner owner, String name, String description, bool
|
||||
return new Poll(id,owner,name,description,isPrivate,new ArrayList<>(),new HashMap<>(),new HashMap<>());
|
||||
}
|
||||
|
||||
public List<Option> options(){
|
||||
return options;
|
||||
}
|
||||
public Owner owner(){
|
||||
return owner;
|
||||
}
|
||||
|
||||
public Map<UmbrellaUser, Long> shares(){
|
||||
return shares;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
@@ -145,4 +204,8 @@ public record Poll(String id, Owner owner, String name, String description, bool
|
||||
Field.WEIGHTS, weights
|
||||
);
|
||||
}
|
||||
|
||||
public Map<Integer, String> weights(){
|
||||
return weights;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
</style>
|
||||
|
||||
{#if editable && editing}
|
||||
<input bind:value={editValue} onkeyup={typed} autofocus />
|
||||
<input bind:value={editValue} onkeyup={typed} {title} autofocus />
|
||||
{:else}
|
||||
<svelte:element this={type} href={href} onclick={ignore} {onmousedown} {onmouseup} {ontouchstart} {ontouchend} {oncontextmenu} class={{editable}} {title} >{value}</svelte:element>
|
||||
{/if}
|
||||
|
||||
@@ -129,8 +129,8 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="markdown {editing?'editing':''}">
|
||||
{#if editing}
|
||||
<div class="markdown {editing || simple ?'editing':''}">
|
||||
{#if editing || simple}
|
||||
<span class="hint">{@html t('markdown_supported')}</span>
|
||||
{#if stored_source}
|
||||
<span id="restore_markdown" onclick={restore} class="hint">{t('unsaved_content')}</span>
|
||||
|
||||
@@ -23,7 +23,24 @@
|
||||
} else error(res);
|
||||
}
|
||||
|
||||
async function patchOption(option, field, newVal){
|
||||
async function patch_poll(field, newVal){
|
||||
let url = api(`poll/${id}`);
|
||||
let data = {}
|
||||
data[field] = newVal;
|
||||
let res = await patch(url,data);
|
||||
if (res.ok) {
|
||||
yikes();
|
||||
const json = await res.json();
|
||||
poll = { ...poll, ...json };
|
||||
return true;
|
||||
|
||||
}
|
||||
error(res);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
async function patch_option(option, field, newVal){
|
||||
let url = api(`poll/${id}/option/${option.id}`);
|
||||
let data = {}
|
||||
data[field] = newVal;
|
||||
@@ -31,11 +48,15 @@
|
||||
if (res.ok) {
|
||||
yikes();
|
||||
const json = await res.json();
|
||||
if (field == 'name' && newVal == ''){
|
||||
poll.options = poll.options.filter(o => o.id !== option.id);
|
||||
} else poll.options = json.options;
|
||||
} else error(res);
|
||||
}
|
||||
|
||||
|
||||
async function save_new_option(){
|
||||
if (!new_option.name) return;
|
||||
let url = api('poll/'+id+'/option');
|
||||
let res = await post(url,new_option);
|
||||
if (res.ok){
|
||||
@@ -54,11 +75,11 @@
|
||||
{#if poll && poll.name}
|
||||
<fieldset>
|
||||
<legend>{t('name')}</legend>
|
||||
<LineEditor bind:value={poll.name} editable={true} />
|
||||
<LineEditor bind:value={poll.name} editable={true} onSet={name => patch_poll('name',name)} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{t('description')}</legend>
|
||||
<MarkdownEditor bind:value={poll.description} />
|
||||
<MarkdownEditor bind:value={poll.description} onSet={desc => patch_poll('description',desc)} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{t('options')}</legend>
|
||||
@@ -70,13 +91,13 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each poll.options as option}
|
||||
{#each poll.options as option (option.id)}
|
||||
<tr>
|
||||
<td>
|
||||
<LineEditor editable={true} value={option.name} onSet={name => patchOption(option,'name',name)} />
|
||||
<LineEditor editable={true} value={option.name} onSet={name => patch_option(option,'name',name)} title={t('clear to remove')} />
|
||||
</td>
|
||||
<td>
|
||||
<MarkdownEditor bind:value={option.description} />
|
||||
<MarkdownEditor bind:value={option.description} onSet={desc => patch_option(option,'description',desc)} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
import { api, get } from '../../urls.svelte';
|
||||
import { api, get, post } from '../../urls.svelte';
|
||||
import { error, yikes } from '../../warn.svelte';
|
||||
import { t } from '../../translations.svelte';
|
||||
import { user } from '../../user.svelte.js';
|
||||
|
||||
let polls = [];
|
||||
let polls = $state([]);
|
||||
let router = useTinyRouter();
|
||||
|
||||
async function create_poll(name){
|
||||
const url = api('poll');
|
||||
const res = await post(url,{name});
|
||||
if (res.ok) {
|
||||
yikes();
|
||||
const json = await res.json();
|
||||
polls.push(json);
|
||||
}
|
||||
error(res);
|
||||
}
|
||||
|
||||
function edit(poll){
|
||||
router.navigate(`/poll/${poll.id}/edit`);
|
||||
}
|
||||
@@ -61,6 +75,12 @@
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
<tr>
|
||||
<td>
|
||||
<LineEditor simple={true} onSet={create_poll}/>
|
||||
</td>
|
||||
<td colspan="3">{t('Enter name to create new')}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
@@ -27,6 +27,8 @@ import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PollModule extends BaseHandler implements PollService {
|
||||
@@ -84,7 +86,7 @@ public class PollModule extends BaseHandler implements PollService {
|
||||
if (user.isEmpty()) return unauthorized(ex);
|
||||
var head = path.pop();
|
||||
return switch (head) {
|
||||
case null -> super.doPost(path,ex);
|
||||
case null -> postNewPoll(ex,user.get());
|
||||
default -> postToPoll(ex,user.get(),head, path);
|
||||
};
|
||||
} catch (UmbrellaException e){
|
||||
@@ -113,11 +115,27 @@ public class PollModule extends BaseHandler implements PollService {
|
||||
}
|
||||
var head = path.pop();
|
||||
return switch (head){
|
||||
case null -> patchPoll(ex, poll);
|
||||
case OPTION -> patchPollOptions(ex, path, 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 null, default:
|
||||
throw UmbrellaException.badRequest(Text.UNKNOWN_FIELD,ID,key);
|
||||
}
|
||||
}
|
||||
return sendContent(ex,pollDb.save(poll));
|
||||
}
|
||||
|
||||
private boolean patchPollOptions(HttpExchange ex, Path path, Poll poll) throws IOException {
|
||||
try {
|
||||
if (path.empty()) throw missingField(ID);
|
||||
@@ -144,6 +162,14 @@ public class PollModule extends BaseHandler implements PollService {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean postNewPoll(HttpExchange ex, UmbrellaUser user) throws IOException {
|
||||
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(),Map.of());
|
||||
return sendContent(ex,pollDb.save(poll));
|
||||
}
|
||||
|
||||
private boolean postToPoll(HttpExchange ex, UmbrellaUser user, String id, Path path) throws IOException {
|
||||
var poll = pollDb.loadPoll(id);
|
||||
var permitted = user.equals(poll.owner());
|
||||
|
||||
@@ -22,9 +22,7 @@ import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.*;
|
||||
|
||||
public class SqliteDb extends BaseDb implements PollDb {
|
||||
|
||||
@@ -70,9 +68,21 @@ public class SqliteDb extends BaseDb implements PollDb {
|
||||
}
|
||||
|
||||
private void createSelectionsTable() {
|
||||
var sql = "CREATE TABLE IF NOT EXISTS {0} ({1} INT NOT NULL REFERENCES {2}({3}), {4} VARCHAR(255) NOT NULL REFERENCES {5}({3}), {6} VARCHAR(255) NOT NULL, {7} INT NOT NULL REFERENCES {8}({7}), PRIMARY KEY ({1}, {4}, {6}))";
|
||||
var sql = """
|
||||
CREATE TABLE IF NOT EXISTS {0} (
|
||||
{1} VARCHAR(255) NOT NULL,
|
||||
{2} INT NOT NULL,
|
||||
{3} INT NOT NULL,
|
||||
{4} INT NOT NULL,
|
||||
FOREIGN KEY ({1}) REFERENCES {5}({7}),
|
||||
FOREIGN KEY ({1},{2}) REFERENCES {6}({1}, {7}),
|
||||
FOREIGN KEY ({1}, {4}) REFERENCES {8}({1}, {4}),
|
||||
PRIMARY KEY ({1}, {2}, {3}))""";
|
||||
try {
|
||||
var stmt = db.prepareStatement(format(sql,TABLE_SELECTIONS,Field.OPTION_ID,TABLE_OPTIONS,ID, POLL_ID, TABLE_POLLS, USER, Field.WEIGHT, TABLE_WEIGHTS));
|
||||
var stmt = db.prepareStatement(format(sql,
|
||||
TABLE_SELECTIONS,
|
||||
POLL_ID, OPTION_ID, USER_ID, Field.WEIGHT,
|
||||
TABLE_POLLS, TABLE_OPTIONS, ID, TABLE_WEIGHTS));
|
||||
stmt.execute();
|
||||
stmt.close();
|
||||
} catch (SQLException e) {
|
||||
@@ -92,7 +102,7 @@ public class SqliteDb extends BaseDb implements PollDb {
|
||||
}
|
||||
|
||||
private void createWeightsTable(){
|
||||
var sql = "CREATE TABLE {0} ( {1} INT NOT NULL, {2} VARCHAR(255) NOT NULL REFERENCES {3}({4}), {5} TEXT, PRIMARY KEY ({2}, {1}))";
|
||||
var sql = "CREATE TABLE IF NOT EXISTS {0} ( {1} INT NOT NULL, {2} VARCHAR(255) NOT NULL REFERENCES {3}({4}), {5} TEXT, PRIMARY KEY ({2}, {1}))";
|
||||
try {
|
||||
var stmt = db.prepareStatement(format(sql,TABLE_WEIGHTS, WEIGHT,POLL_ID,TABLE_POLLS, ID, DESCRIPTION));
|
||||
stmt.execute();
|
||||
@@ -157,7 +167,7 @@ public class SqliteDb extends BaseDb implements PollDb {
|
||||
|
||||
@Override
|
||||
public Poll save(Poll poll) {
|
||||
return is0(poll.id()) ? saveNew(poll) : update(poll);
|
||||
return is0(poll.id()) ? saveNew(poll) : update(poll);
|
||||
}
|
||||
|
||||
public Poll.Option saveOption(String pollId, Poll.Option option) {
|
||||
@@ -171,7 +181,15 @@ public class SqliteDb extends BaseDb implements PollDb {
|
||||
}
|
||||
|
||||
private Poll saveNew(Poll poll) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
var uuid = UUID.randomUUID().toString();
|
||||
try {
|
||||
insertInto(TABLE_POLLS,Field.ID, USER_ID, NAME, DESCRIPTION, PRIVATE)
|
||||
.values(uuid,poll.owner().id(),poll.name(),poll.description(),poll.isPrivate())
|
||||
.execute(db);
|
||||
} catch (SQLException e) {
|
||||
throw failedToStoreObject(poll);
|
||||
}
|
||||
return new Poll(uuid,poll.owner(),poll.name(),poll.description(),poll.isPrivate(), List.of(), Map.of(), Map.of());
|
||||
}
|
||||
|
||||
private Poll.Option saveNew(String pollId, Poll.Option option) throws SQLException {
|
||||
@@ -182,12 +200,22 @@ public class SqliteDb extends BaseDb implements PollDb {
|
||||
}
|
||||
|
||||
private Poll update(Poll poll) {
|
||||
LOG.log(WARNING,"Updating poll not fully implemented");
|
||||
if (poll.isDirty()) try {
|
||||
LOG.log(WARNING,"Updating poll not fully implemented");
|
||||
|
||||
replaceInto(TABLE_POLLS,ID,Field.USER_ID,NAME,DESCRIPTION).values(poll.id(),poll.owner().id(),poll.name(),poll.description()).execute(db);
|
||||
} catch (SQLException e){
|
||||
throw failedToStoreObject(poll);
|
||||
}
|
||||
for (var option : poll.options()) saveOption(poll.id(),option);
|
||||
return poll;
|
||||
}
|
||||
|
||||
private Poll.Option update(String pollId, Poll.Option option) throws SQLException {
|
||||
if (option.name().isBlank()){
|
||||
delete().from(TABLE_OPTIONS).where(POLL_ID,equal(pollId)).where(ID,equal(option.id())).execute(db);
|
||||
return null;
|
||||
}
|
||||
replaceInto(TABLE_OPTIONS,POLL_ID, ID, NAME, DESCRIPTION, STATUS)
|
||||
.values(pollId, option.id(), option.name(), option.description(), option.status()).execute(db);
|
||||
return option;
|
||||
|
||||
Reference in New Issue
Block a user