Merge branch 'module/projects'
This commit is contained in:
@@ -4,12 +4,23 @@ package de.srsoftware.umbrella.core.api;
|
|||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public interface TagService {
|
public interface TagService {
|
||||||
void deleteEntity(String task, long taskId);
|
void deleteEntity(String task, long taskId);
|
||||||
|
|
||||||
Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException;
|
Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the tags for all the entities denominated by the collection <em>entityIds</em>.
|
||||||
|
* @param module the realm the entities belong to
|
||||||
|
* @param entityIds the set of entities of the respective realm
|
||||||
|
* @param user the user, for whom the tags shall be loaded
|
||||||
|
* @return a map from entity ids to assigned tags
|
||||||
|
* @throws UmbrellaException
|
||||||
|
*/
|
||||||
|
Map<Long, ? extends Collection<String>> getTags(String module, Collection<Long> entityIds, UmbrellaUser user) throws UmbrellaException;
|
||||||
|
|
||||||
void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags);
|
void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags);
|
||||||
|
|
||||||
String save(String module, long entityId, Collection<Long> userIds, String tag);
|
String save(String module, long entityId, Collection<Long> userIds, String tag);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
let highlight = $state({});
|
let highlight = $state({});
|
||||||
let filter = $derived(filter_input.toLowerCase());
|
let filter = $derived(filter_input.toLowerCase());
|
||||||
let project = $state(null);
|
let project = $state(null);
|
||||||
let ready = $state(false);
|
|
||||||
let tasks = $state({});
|
let tasks = $state({});
|
||||||
let users = {};
|
let users = {};
|
||||||
let columns = $derived(project.allowed_states?Object.keys(project.allowed_states).length+1:1);
|
let columns = $derived(project.allowed_states?Object.keys(project.allowed_states).length+1:1);
|
||||||
@@ -96,8 +95,6 @@
|
|||||||
try {
|
try {
|
||||||
await loadProject();
|
await loadProject();
|
||||||
await loadTasks({project_id:+id,parent_task_id:0});
|
await loadTasks({project_id:+id,parent_task_id:0});
|
||||||
ready = true;
|
|
||||||
loadTags();
|
|
||||||
} catch (ignored) {}
|
} catch (ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,31 +117,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadTag(task){
|
|
||||||
try {
|
|
||||||
const url = api(`tags/task/${task.id}`);
|
|
||||||
const resp = await fetch(url,{
|
|
||||||
credentials:'include',
|
|
||||||
signal: signal
|
|
||||||
});
|
|
||||||
if (resp.ok) {
|
|
||||||
const tags = await resp.json();
|
|
||||||
if (tags.length) task.tags = tags.sort();
|
|
||||||
}
|
|
||||||
} catch (ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadTags(){
|
|
||||||
for (let uid of Object.keys(tasks)){
|
|
||||||
for (let state of Object.keys(tasks[uid])){
|
|
||||||
for (let tid of Object.keys(tasks[uid][state])){
|
|
||||||
const task = tasks[uid][state][tid];
|
|
||||||
loadTag(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTasks(selector){
|
async function loadTasks(selector){
|
||||||
const url = api('task/list');
|
const url = api('task/list');
|
||||||
selector.show_closed = true;
|
selector.show_closed = true;
|
||||||
@@ -205,7 +177,7 @@
|
|||||||
<span class="error">{error}</span>
|
<span class="error">{error}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if ready}
|
{#if project}
|
||||||
<div class="kanban" style="display: grid; grid-template-columns: {`repeat(${columns}, auto)`}">
|
<div class="kanban" style="display: grid; grid-template-columns: {`repeat(${columns}, auto)`}">
|
||||||
<span class="filter">
|
<span class="filter">
|
||||||
<input type="text" bind:value={filter_input} autofocus />
|
<input type="text" bind:value={filter_input} autofocus />
|
||||||
|
|||||||
@@ -16,13 +16,12 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
|||||||
import de.srsoftware.umbrella.core.model.Permission;
|
import de.srsoftware.umbrella.core.model.Permission;
|
||||||
import de.srsoftware.umbrella.core.model.Project;
|
import de.srsoftware.umbrella.core.model.Project;
|
||||||
import de.srsoftware.umbrella.core.model.Status;
|
import de.srsoftware.umbrella.core.model.Status;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class SqliteDb extends BaseDb implements ProjectDb {
|
public class SqliteDb extends BaseDb implements ProjectDb {
|
||||||
private static final System.Logger LOG = System.getLogger("ProjectDb");
|
private static final System.Logger LOG = System.getLogger("ProjectDb");
|
||||||
|
|||||||
@@ -253,8 +253,13 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
public Set<String> list(long userId, String module, long entityId) {
|
public Set<String> list(long userId, String module, long entityId) {
|
||||||
try {
|
try {
|
||||||
var tags = new HashSet<String>();
|
var tags = new HashSet<String>();
|
||||||
|
|
||||||
|
// load tags assigned to user
|
||||||
var rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,equal(userId)).exec(db);
|
var rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,equal(userId)).exec(db);
|
||||||
while (rs.next()) tags.add(rs.getString(1));
|
while (rs.next()) tags.add(rs.getString(1));
|
||||||
|
rs.close();
|
||||||
|
|
||||||
|
// load tags assigned to no user
|
||||||
rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,isNull()).exec(db);
|
rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,isNull()).exec(db);
|
||||||
while (rs.next()) tags.add(rs.getString(1));
|
while (rs.next()) tags.add(rs.getString(1));
|
||||||
rs.close();
|
rs.close();
|
||||||
@@ -264,6 +269,26 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Long, ? extends Collection<String>> list(long userId, String module, Collection<Long> entityIds) {
|
||||||
|
try {
|
||||||
|
var tags = new HashMap<Long,HashSet<String>>();
|
||||||
|
|
||||||
|
// load tags assigned to user
|
||||||
|
var rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,equal(userId)).exec(db);
|
||||||
|
while (rs.next()) tags.computeIfAbsent(rs.getLong(ENTITY_ID), k -> new HashSet<>()).add(rs.getString(TAG));
|
||||||
|
rs.close();
|
||||||
|
|
||||||
|
// load tags assigned to no user
|
||||||
|
rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,isNull()).exec(db);
|
||||||
|
while (rs.next()) tags.computeIfAbsent(rs.getLong(ENTITY_ID), k -> new HashSet<>()).add(rs.getString(TAG));
|
||||||
|
rs.close();
|
||||||
|
return tags;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new UmbrellaException("Failed to load tags");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(Collection<Long> userIds, String module, long entityId, Collection<String> tags) {
|
public void save(Collection<Long> userIds, String module, long entityId, Collection<String> tags) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* © SRSoftware 2025 */
|
/* © SRSoftware 2025 */
|
||||||
package de.srsoftware.umbrella.tags;
|
package de.srsoftware.umbrella.tags;
|
||||||
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -15,8 +16,16 @@ public interface TagDB {
|
|||||||
|
|
||||||
Set<String> list(long userId, String module, long entityId);
|
Set<String> list(long userId, String module, long entityId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the tags for all the entities denominated by the collection <em>entityIds</em>.
|
||||||
|
* @param module the realm the entities belong to
|
||||||
|
* @param entityIds the set of entities of the respective realm
|
||||||
|
* @param userId the id of the user, for whom the tags shall be loaded
|
||||||
|
* @return a map from entity ids to assigned tags
|
||||||
|
*/
|
||||||
|
Map<Long, ? extends Collection<String>> list(long userId, String module, Collection<Long> entityIds);
|
||||||
|
|
||||||
void save(Collection<Long> userIds, String module, long entityId, Collection<String> tags);
|
void save(Collection<Long> userIds, String module, long entityId, Collection<String> tags);
|
||||||
|
|
||||||
void updateId(String module, Object oldId, Object newId);
|
void updateId(String module, Object oldId, Object newId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,11 @@ public class TagModule extends BaseHandler implements TagService {
|
|||||||
return tagDb.list(user.id(),module,entityId);
|
return tagDb.list(user.id(),module,entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Long, ? extends Collection<String>> getTags(String module, Collection<Long> entityIds, UmbrellaUser user) throws UmbrellaException {
|
||||||
|
return tagDb.list(user.id(),module,entityIds);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags) {
|
public void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags) {
|
||||||
tagDb.save(userIds,module,entityId,tags);
|
tagDb.save(userIds,module,entityId,tags);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
|||||||
import static de.srsoftware.umbrella.core.model.Permission.*;
|
import static de.srsoftware.umbrella.core.model.Permission.*;
|
||||||
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
||||||
import static de.srsoftware.umbrella.task.Constants.*;
|
import static de.srsoftware.umbrella.task.Constants.*;
|
||||||
|
import static java.lang.System.Logger.Level.DEBUG;
|
||||||
import static java.lang.System.Logger.Level.WARNING;
|
import static java.lang.System.Logger.Level.WARNING;
|
||||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.configuration.Configuration;
|
import de.srsoftware.configuration.Configuration;
|
||||||
@@ -385,14 +385,20 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
private boolean postTaskList(UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean postTaskList(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
var json = json(ex);
|
var json = json(ex);
|
||||||
LOG.log(WARNING, "Missing permission check in {0}.postTaskList!", getClass().getSimpleName());
|
LOG.log(WARNING, "Missing permission check in {0}.postTaskList!", getClass().getSimpleName());
|
||||||
var showClosed = json.has(SHOW_CLOSED) && json.get(SHOW_CLOSED) instanceof Boolean bool ? bool : false;
|
var showClosed = json.has(SHOW_CLOSED) && json.get(SHOW_CLOSED) instanceof Boolean bool ? bool : false;
|
||||||
var noIndex = json.has(NO_INDEX) && json.get(NO_INDEX) instanceof Boolean bool ? bool : false;
|
var noIndex = json.has(NO_INDEX) && json.get(NO_INDEX) instanceof Boolean bool ? bool : false;
|
||||||
var projectId = json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number number ? number.longValue() : null;
|
var projectId = json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number number ? number.longValue() : null;
|
||||||
var parentTaskId = json.has(PARENT_TASK_ID) && json.get(PARENT_TASK_ID) instanceof Number number ? number.longValue() : null;
|
var parentTaskId = json.has(PARENT_TASK_ID) && json.get(PARENT_TASK_ID) instanceof Number number ? number.longValue() : null;
|
||||||
if (isSet(projectId)) {
|
if (isSet(projectId)) {
|
||||||
if (parentTaskId == null) return sendContent(ex, mapValues(taskDb.listRootTasks(projectId, user, showClosed)));
|
if (parentTaskId == null) {
|
||||||
|
var list = taskDb.listRootTasks(projectId, user, showClosed);
|
||||||
|
return sendContent(ex, mapValues(list));
|
||||||
|
}
|
||||||
var projectTasks = taskDb.listProjectTasks(projectId, parentTaskId, noIndex);
|
var projectTasks = taskDb.listProjectTasks(projectId, parentTaskId, noIndex);
|
||||||
loadMembers(projectTasks.values());
|
loadMembers(projectTasks.values());
|
||||||
|
var tags = tagService().getTags(TASK,projectTasks.keySet(),user);
|
||||||
|
|
||||||
|
projectTasks = addTags(projectTasks, tags);
|
||||||
return sendContent(ex, mapValues(projectTasks));
|
return sendContent(ex, mapValues(projectTasks));
|
||||||
}
|
}
|
||||||
if (isSet(parentTaskId)) return sendContent(ex, mapValues(taskDb.listChildrenOf(parentTaskId, user, showClosed)));
|
if (isSet(parentTaskId)) return sendContent(ex, mapValues(taskDb.listChildrenOf(parentTaskId, user, showClosed)));
|
||||||
@@ -400,4 +406,25 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
if (isSet(taskIds)) return sendContent(ex, mapValues(taskDb.load(taskIds)));
|
if (isSet(taskIds)) return sendContent(ex, mapValues(taskDb.load(taskIds)));
|
||||||
return sendEmptyResponse(HTTP_NOT_IMPLEMENTED, ex);
|
return sendEmptyResponse(HTTP_NOT_IMPLEMENTED, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TaggedTask extends Task{
|
||||||
|
|
||||||
|
private final Collection<String> tags;
|
||||||
|
|
||||||
|
public TaggedTask(Task task, Collection<String> tags) {
|
||||||
|
super(task.id(), task.projectId(), task.parentTaskId(), task.name(), task.description(), task.status(), task.estimatedTime(), task.start(), task.dueDate(), task.showClosed(), task.noIndex(), task.members(), task.priority());
|
||||||
|
this.tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> toMap() {
|
||||||
|
var map = super.toMap();
|
||||||
|
map.put(TAGS,tags);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, Task> addTags(Map<Long, Task> taskList, Map<Long, ? extends Collection<String>> tags) {
|
||||||
|
return taskList.values().stream().map(task -> new TaggedTask(task, tags.get(task.id()))).collect(Collectors.toMap(Task::id, t -> t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user