Merge branch 'feature/translation' into module/messagebus

This commit is contained in:
2026-01-16 21:45:49 +01:00
109 changed files with 2311 additions and 1275 deletions

View File

@@ -5,17 +5,23 @@ package de.srsoftware.umbrella.task;
import static de.srsoftware.tools.jdbc.Condition.*;
import static de.srsoftware.tools.jdbc.Query.*;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Errors.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.notFound;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Field.TASK;
import static de.srsoftware.umbrella.core.constants.Field.TYPE;
import static de.srsoftware.umbrella.core.constants.Text.*;
import static de.srsoftware.umbrella.core.constants.Text.TASKS;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.failedToCreateTable;
import static de.srsoftware.umbrella.core.model.Status.*;
import static de.srsoftware.umbrella.core.model.Translatable.t;
import static de.srsoftware.umbrella.project.Constants.*;
import static de.srsoftware.umbrella.task.Constants.*;
import static java.text.MessageFormat.format;
import de.srsoftware.tools.jdbc.Query;
import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.core.constants.Text;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import java.sql.Connection;
@@ -55,16 +61,16 @@ public class SqliteDb extends BaseDb implements TaskDb {
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw databaseException(FAILED_TO_CREATE_TABLE,TABLE_TASK_DEPENDENCIES).causedBy(e);
throw failedToCreateTable(TABLE_TASK_DEPENDENCIES).causedBy(e);
}
}
private void createPriorityColumn() {
var sql = format("ALTER TABLE {0} ADD {1} INT NOT NULL DEFAULT 0",TABLE_TASKS,PRIORITY);
var sql = format("ALTER TABLE {0} ADD {1} INT NOT NULL DEFAULT 0",TABLE_TASKS, PRIORITY);
try {
db.prepareStatement(sql).execute();
} catch (SQLException e) {
throw databaseException(FAILED_TO_ADD_COLUMN,PRIORITY,TABLE_TASKS).causedBy(e);
throw databaseException(FAILED_TO_ADD_COLUMN, NAME, PRIORITY, TABLE,TABLE_TASKS).causedBy(e);
}
}
@@ -88,7 +94,7 @@ public class SqliteDb extends BaseDb implements TaskDb {
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw databaseException(FAILED_TO_CREATE_TABLE,TABLE_PROJECTS).causedBy(e);
throw failedToCreateTable(TABLE_PROJECTS).causedBy(e);
}
}
@@ -105,7 +111,7 @@ CREATE TABLE IF NOT EXISTS {0} (
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw databaseException(FAILED_TO_CREATE_TABLE,TABLE_PROJECT_USERS).causedBy(e);
throw failedToCreateTable(TABLE_PROJECT_USERS).causedBy(e);
}
}
@@ -116,23 +122,23 @@ CREATE TABLE IF NOT EXISTS {0} (
long count = 0;
if (rs.next()) count = rs.getLong(1);
rs.close();
if (count>0) throw new UmbrellaException("Task \"{0}\" has child tasks and thus cannot be deleted!",task.name());
if (count>0) throw unprocessable("Task \"{task}\" has child tasks and thus cannot be deleted!", TASK,task.name());
Query.delete().from(TABLE_TASKS).where(ID,equal(task.id())).execute(db);
Query.delete().from(TABLE_TASKS_USERS).where(TASK_ID,equal(task.id())).execute(db);
} catch (SQLException e) {
throw databaseException(FAILED_TO_DROP_ENTITY,"task").causedBy(e);
throw failedToDropObject(task).causedBy(e);
}
}
@Override
public void dropMember(long projectId, long userId) {
public void dropMember(long taskId, long userId) {
try {
Query.delete().from(TABLE_TASKS_USERS)
.where(TASK_ID,equal(projectId))
.where(TASK_ID,equal(taskId))
.where(USER_ID,equal(userId))
.execute(db);
} catch (SQLException e) {
throw databaseException(FAILED_TO_DROP_ENTITY_OF_ENTITY,USER_ID,userId,PROJECT,projectId).causedBy(e);
throw failedToDropObjectFromObject(USER_ID,userId,t(Text.TASK),taskId).causedBy(e);
}
}
@@ -143,7 +149,7 @@ CREATE TABLE IF NOT EXISTS {0} (
var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID)
.where(USER_ID,equal(userId));
if (fulltext) {
for (var key : keys) query.where(format("CONCAT({0},\" \",{1})",NAME,DESCRIPTION),like("%"+key+"%"));
for (var key : keys) query.where(format("CONCAT({0},\" \",{1})", NAME, DESCRIPTION),like("%"+key+"%"));
} else {
for (var key : keys) query.where(NAME,like("%"+key+"%"));
}
@@ -155,7 +161,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return tasks;
} catch (SQLException e){
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER,TASKS,USER).causedBy(e);
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER, TYPE,t(TASKS), OWNER,t(Text.USER)).causedBy(e);
}
}
@@ -168,7 +174,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return result;
} catch (SQLException e){
throw databaseException(FAILED_TO_LIST_ENTITIES,"task members").causedBy(e);
throw failedToLoadObject("task members").causedBy(e);
}
}
@@ -183,7 +189,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return loadDependencies(tasks);
} catch (SQLException e) {
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER,TASKS,"project_ids").causedBy(e);
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER, TYPE,t(TASKS), OWNER,"project_ids").causedBy(e);
}
}
@@ -203,7 +209,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return loadDependencies(tasks);
} catch (SQLException e){
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER,TASKS,"project "+projectId).causedBy(e);
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER, TYPE,t(TASKS), OWNER, t(PROJECT_WITH_ID, ID,projectId)).causedBy(e);
}
}
@@ -222,7 +228,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return loadDependencies(tasks);
} catch (SQLException e){
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER,"child tasks",parentTaskId).causedBy(e);
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER, TYPE,"child tasks", OWNER,parentTaskId).causedBy(e);
}
}
@@ -241,7 +247,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return loadDependencies(tasks);
} catch (SQLException e){
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER,"project "+projectId).causedBy(e);
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER, TYPE,t(TASKS), OWNER, t(PROJECT_WITH_ID, ID,projectId)).causedBy(e);
}
}
@@ -256,7 +262,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return map;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER,"user "+userId).causedBy(e);
throw databaseException(FAILED_TO_LOAD_ENTITIES_OF_OWNER, TYPE,t(TASKS), OWNER, t(USER_WITH_ID, ID,userId)).causedBy(e);
}
}
@@ -264,7 +270,7 @@ CREATE TABLE IF NOT EXISTS {0} (
public Task load(long taskId) throws UmbrellaException {
var map = load(List.of(taskId));
var task = map.get(taskId);
if (task == null) throw notFound(FAILED_TO_LOAD_ENTITY_BY_ID,TASK,taskId);
if (task == null) throw notFound(FAILED_TO_LOAD_OBJECT_BY_ID, OBJECT,t(Text.TASK), ID,taskId);
return task;
}
@@ -280,7 +286,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return loadDependencies(map);
} catch (SQLException e) {
throw databaseException(FAILED_TO_LOAD_ENTITY,TASK).causedBy(e);
throw failedToLoadObject(t(TASKS)).causedBy(e);
}
}
@@ -295,7 +301,7 @@ CREATE TABLE IF NOT EXISTS {0} (
rs.close();
return tasks;
} catch (SQLException e) {
throw databaseException(FAILED_TO_LOAD_ENTITY,"task dependencies").causedBy(e);
throw failedToLoadObject("task dependencies").causedBy(e);
}
}
@@ -303,18 +309,18 @@ CREATE TABLE IF NOT EXISTS {0} (
public Task save(Task task) {
try {
if (task.id() == 0){ // new task
var rs = insertInto(TABLE_TASKS,PROJECT_ID,PARENT_TASK_ID,NAME,DESCRIPTION,STATUS,EST_TIME,START_DATE,DUE_DATE,SHOW_CLOSED,NO_INDEX,PRIORITY)
var rs = insertInto(TABLE_TASKS, PROJECT_ID, PARENT_TASK_ID, NAME, DESCRIPTION, STATUS, EST_TIME, START_DATE, DUE_DATE, SHOW_CLOSED, NO_INDEX, PRIORITY)
.values(task.projectId(),task.parentTaskId(),task.name(),task.description(),task.status(),task.estimatedTime(),task.start(),task.dueDate(),task.showClosed(),task.noIndex(),task.priority())
.execute(db)
.getGeneratedKeys();
Long taskId = null;
if (rs.next()) taskId=rs.getLong(1);
rs.close();
if (taskId == null) throw new UmbrellaException("Failed to save task {0}",task.name());
if (taskId == null) throw failedToStoreObject(task.name());
return new Task(taskId,task.projectId(),task.parentTaskId(),task.name(),task.description(),task.status(),task.estimatedTime(),task.start(),task.dueDate(),task.showClosed(),task.noIndex(),task.members(), task.priority());
}
if (task.isDirty(MEMBERS)){
var query = replaceInto(TABLE_TASKS_USERS,TASK_ID,USER_ID,PERMISSIONS);
var query = replaceInto(TABLE_TASKS_USERS,TASK_ID, USER_ID,PERMISSIONS);
for (var member : task.members().entrySet()) query.values(task.id(),member.getKey(),member.getValue().permission().code());
query.execute(db).close();
task.clean(MEMBERS);
@@ -336,7 +342,7 @@ CREATE TABLE IF NOT EXISTS {0} (
task.clean(REQUIRED_TASKS_IDS);
}
if (task.isDirty()) {
update(TABLE_TASKS).set(PROJECT_ID,PARENT_TASK_ID,NAME,DESCRIPTION,STATUS,EST_TIME,START_DATE,DUE_DATE,SHOW_CLOSED,NO_INDEX,PRIORITY)
update(TABLE_TASKS).set(PROJECT_ID, PARENT_TASK_ID, NAME, DESCRIPTION, STATUS, EST_TIME, START_DATE, DUE_DATE, SHOW_CLOSED, NO_INDEX, PRIORITY)
.where(ID,equal(task.id())).prepare(db)
.apply(task.projectId(),task.parentTaskId(),task.name(),task.description(),task.status(),task.estimatedTime(),task.start(),task.dueDate(),task.showClosed(),task.noIndex(),task.priority())
.close();
@@ -344,16 +350,16 @@ CREATE TABLE IF NOT EXISTS {0} (
}
return task;
} catch (SQLException e){
throw databaseException(FAILED_TO_STORE_ENTITY,task.name()).causedBy(e);
throw failedToStoreObject(task.name()).causedBy(e);
}
}
@Override
public void setMember(long taskId, long userId, Permission permission) {
try {
replaceInto(TABLE_TASKS_USERS,TASK_ID,USER_ID,PERMISSIONS).values(taskId,userId,permission.code()).execute(db).close();
replaceInto(TABLE_TASKS_USERS,TASK_ID, USER_ID,PERMISSIONS).values(taskId,userId,permission.code()).execute(db).close();
} catch (SQLException e) {
throw databaseException(FAILED_TO_STORE_ENTITY,PERMISSIONS).causedBy(e);
throw failedToStoreObject(PERMISSIONS).causedBy(e);
}
}
}

View File

@@ -3,14 +3,19 @@ package de.srsoftware.umbrella.task;
import static de.srsoftware.tools.Optionals.*;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.ModuleRegistry.*;
import static de.srsoftware.umbrella.core.Paths.*;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_NOT_IMPLEMENTED;
import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.constants.Field.*;
import static de.srsoftware.umbrella.core.constants.Field.TAGS;
import static de.srsoftware.umbrella.core.constants.Field.TASKS;
import static de.srsoftware.umbrella.core.constants.Module.TASK;
import static de.srsoftware.umbrella.core.constants.Path.*;
import static de.srsoftware.umbrella.core.constants.Text.LONG;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Permission.*;
import static de.srsoftware.umbrella.core.model.Permission.OWNER;
import static de.srsoftware.umbrella.core.model.Translatable.t;
import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus;
import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE;
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
@@ -26,6 +31,7 @@ import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.ModuleRegistry;
import de.srsoftware.umbrella.core.api.*;
import de.srsoftware.umbrella.core.constants.Text;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import de.srsoftware.umbrella.core.model.Task;
@@ -45,7 +51,7 @@ public class TaskModule extends BaseHandler implements TaskService {
public TaskModule(Configuration config) throws UmbrellaException {
super();
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingField(CONFIG_DATABASE));
taskDb = new SqliteDb(connect(dbFile));
ModuleRegistry.add(this);
}
@@ -60,7 +66,7 @@ public class TaskModule extends BaseHandler implements TaskService {
private boolean deleteTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
var task = loadMembers(taskDb.load(taskId));
var member = task.members().get(user.id());
if (member == null || !member.mayWrite()) throw forbidden("You are not allowed to delete {0}", task.name());
if (member == null || !member.mayWrite()) throw forbidden("You are not allowed to delete {object}", OBJECT, task.name());
taskDb.delete(task);
noteService().deleteEntity(TASK, "" + taskId);
tagService().deleteEntity(TASK, taskId);
@@ -153,17 +159,17 @@ public class TaskModule extends BaseHandler implements TaskService {
}
private void dropMember(Task task, long userId) {
if (task.members().get(userId).permission() == OWNER) throw forbidden("You may not remove the owner of the task");
if (task.members().get(userId).permission() == OWNER) throw forbidden("You may not remove the owner of {object}", OBJECT,task.name());
taskDb.dropMember(task.id(), userId);
task.members().remove(userId);
}
private boolean estimatedTimes(UmbrellaUser user, HttpExchange ex) throws IOException, UmbrellaException {
var json = json(ex);
if (!(json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number cid)) throw missingFieldException(COMPANY_ID);
if (!(json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number cid)) throw missingField(COMPANY_ID);
var companyId = cid.longValue();
var company = companyService().get(companyId);
if (!companyService().membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
if (!companyService().membership(companyId,user.id())) throw notAmember(company.name());
var projectMap = projectService().listCompanyProjects(companyId, false);
var taskMap = taskDb.listTasks(projectMap.keySet());
var taskTree = new HashMap<Long, Map<String, Object>>();
@@ -201,7 +207,7 @@ public class TaskModule extends BaseHandler implements TaskService {
private boolean getTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
var task = loadMembers(taskDb.load(taskId));
if (!task.hasMember(user)) throw forbidden("You are not a member of {0}",task.name());
if (!task.hasMember(user)) throw notAmember(task.name());
return sendContent(ex, task);
}
@@ -212,12 +218,12 @@ public class TaskModule extends BaseHandler implements TaskService {
if (params.get(OFFSET) instanceof String o) try {
offset = Long.parseLong(o);
} catch (NumberFormatException e) {
throw invalidFieldException(OFFSET, "number");
throw invalidField(OFFSET, t(Text.NUMBER));
}
if (params.get(LIMIT) instanceof String l) try {
limit = Long.parseLong(l);
} catch (NumberFormatException e) {
throw invalidFieldException(LIMIT, "number");
throw invalidField(LIMIT, t(Text.NUMBER));
}
var tasks = taskDb.listUserTasks(user.id(), limit, offset, false);
var mapped = loadMembers(tasks).stream().map(Task::toMap);
@@ -294,12 +300,12 @@ public class TaskModule extends BaseHandler implements TaskService {
try {
userId = Long.parseLong(key);
} catch (NumberFormatException e) {
throw invalidFieldException(USER_ID, "long");
throw invalidField(USER_ID, t(LONG));
}
var permission = switch (json.get(key)) {
case Number code -> Permission.of(code.intValue());
case String name -> Permission.valueOf(name);
default -> throw invalidFieldException(PERMISSION, "int / String");
default -> throw invalidField(PERMISSION, "int / String");
};
if (permission == OWNER) { // if a new person is about to become the task owner
for (var member : members.values()) { // alter the previous owners to editors
@@ -320,7 +326,7 @@ public class TaskModule extends BaseHandler implements TaskService {
var task = loadMembers(taskDb.load(taskId));
var old = task.toMap();
var member = task.members().get(user.id());
if (member == null || member.permission() == READ_ONLY) throw forbidden("You are not a allowed to edit {0}!", task.name());
if (member == null || member.permission() == READ_ONLY) throw forbidden("You are not a allowed to edit {object}!", OBJECT, task.name());
var json = json(ex);
if (json.has(DROP_MEMBER) && json.get(DROP_MEMBER) instanceof Number id) dropMember(task, id.longValue());
if (json.has(MEMBERS) && json.get(MEMBERS) instanceof JSONObject memberJson) patchMembers(task, memberJson);
@@ -333,7 +339,7 @@ public class TaskModule extends BaseHandler implements TaskService {
private boolean postNewTask(UmbrellaUser user, HttpExchange ex) throws IOException {
var json = json(ex);
if (!(json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number pid)) throw missingFieldException(PROJECT_ID);
if (!(json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number pid)) throw missingField(PROJECT_ID);
long projectId = pid.longValue();
var project = projectService().load(projectId);
@@ -347,7 +353,7 @@ public class TaskModule extends BaseHandler implements TaskService {
taskService().loadMembers(parentTask);
members = parentTask.members();
member = members.get(user.id());
if (member == null || member.permission() == READ_ONLY) throw forbidden("You are not allowed to add sub-stasks to {0}", parentTask.name());
if (member == null || member.permission() == READ_ONLY) throw forbidden("You are not allowed to add sub-stasks to {object}", OBJECT, parentTask.name());
}
var newMembers = new HashMap<Long, Permission>();
@@ -395,7 +401,7 @@ public class TaskModule extends BaseHandler implements TaskService {
private boolean postSearch(UmbrellaUser user, HttpExchange ex) throws IOException {
var json = json(ex);
if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingFieldException(KEY);
if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingField(KEY);
var projectId = json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number pid ? pid.longValue() : null;
var keys = Arrays.asList(key.split(" "));
var fulltext = json.has(FULLTEXT) && json.get(FULLTEXT) instanceof Boolean val && val;