implemented function to return estimated times of company.

Therefore task and project module had to be created and partially implemented
This commit is contained in:
2025-07-13 23:45:18 +02:00
parent ea888c2be4
commit bac04ac047
39 changed files with 642 additions and 61 deletions

6
task/build.gradle.kts Normal file
View File

@@ -0,0 +1,6 @@
description = "Umbrella : Tasks"
dependencies{
implementation(project(":core"))
implementation(project(":project"))
}

View File

@@ -0,0 +1,19 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.task;
public class Constants {
private Constants(){}
public static final String CONFIG_DATABASE = "umbrella.modules.task.database";
public static final String CHILDREN = "children";
public static final String DUE_DATE = "due_date";
public static final String ESTIMATED_TIMES = "estimated_times";
public static final String ESTIMATED_TIME = "estimated_time";
public static final String EST_TIME = "est_time";
public static final String NO_INDEX = "no_index";
public static final String PARENT_TASK_ID = "parent_task_id";
public static final String PROJECT_ID = "project_id";
public static final String START_DATE = "start_date";
public static final String TABLE_TASKS = "tasks";
public static final String FIELD_TASKS = "tasks";
}

View File

@@ -0,0 +1,38 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.task;
import static de.srsoftware.tools.jdbc.Condition.in;
import static de.srsoftware.tools.jdbc.Query.select;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
import static de.srsoftware.umbrella.task.Constants.PROJECT_ID;
import static de.srsoftware.umbrella.task.Constants.TABLE_TASKS;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
public class SqliteDb implements TaskDb {
private final Connection db;
public SqliteDb(Connection connection) {
db = connection;
}
public Collection<Task> listTasks(List<Long> projectIds) throws UmbrellaException {
try {
var rs = select("*").from(TABLE_TASKS).where(PROJECT_ID, in(projectIds.toArray())).exec(db);
var list = new HashSet<Task>();
while (rs.next()) list.add(Task.of(rs));
rs.close();
return list;
} catch (SQLException e) {
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project ids");
}
}
}

View File

@@ -0,0 +1,54 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.task;
import static de.srsoftware.tools.Optionals.nullIfEmpty;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Constants.SHOW_CLOSED;
import static de.srsoftware.umbrella.core.Constants.STATUS;
import static de.srsoftware.umbrella.task.Constants.*;
import de.srsoftware.tools.Mappable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
public record Task(long id, long projectId, Long parentTaskId, String name, String description, int status, Double estimatedTime, LocalDate start, LocalDate dueDate,boolean showClosed, boolean noIndex) implements Mappable {
public static Task of(ResultSet rs) throws SQLException {
var estTime = rs.getDouble(EST_TIME);
var parentTaskId = rs.getLong(PARENT_TASK_ID);
var startDate = nullIfEmpty(rs.getString(START_DATE));
var dueDate = nullIfEmpty(rs.getString(DUE_DATE));
return new Task(
rs.getLong(ID),
rs.getLong(PROJECT_ID),
parentTaskId == 0d ? null : parentTaskId,
rs.getString(NAME),
rs.getString(DESCRIPTION),
rs.getInt(STATUS),
estTime == 0d ? null : estTime,
startDate != null ? LocalDate.parse(startDate) : null,
dueDate != null ? LocalDate.parse(dueDate) : null,
rs.getBoolean(SHOW_CLOSED),
rs.getBoolean(NO_INDEX)
);
}
@Override
public Map<String, Object> toMap() {
var map = new HashMap<String,Object>();
map.put(ID, id);
map.put(PROJECT_ID, projectId);
map.put(PARENT_TASK_ID, parentTaskId);
map.put(NAME, name);
map.put(DESCRIPTION, description);
map.put(STATUS, status);
map.put(ESTIMATED_TIME, estimatedTime);
map.put(START_DATE,start);
map.put(DUE_DATE,dueDate);
map.put(SHOW_CLOSED,showClosed);
map.put(NO_INDEX,noIndex);
return map;
}
}

View File

@@ -0,0 +1,8 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.task;
public interface TaskDb {
}

View File

@@ -0,0 +1,94 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.task;
import static de.srsoftware.tools.Optionals.is0;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.Constants.COMPANY_ID;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
import static de.srsoftware.umbrella.task.Constants.*;
import static java.util.Objects.isNull;
import static java.util.stream.Collectors.toMap;
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.api.CompanyService;
import de.srsoftware.umbrella.core.api.ProjectService;
import de.srsoftware.umbrella.core.api.UserService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Project;
import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class TaskModule extends BaseHandler {
private final SqliteDb taskDb;
private final ProjectService projects;
private final UserService users;
private final CompanyService companies;
public TaskModule(Configuration config, ProjectService projectService) throws UmbrellaException {
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
taskDb = new SqliteDb(connect(dbFile));
projects = projectService;
companies = projectService.companyService();
users = companies.userService();
}
@Override
public boolean doPost(Path path, HttpExchange ex) throws IOException {
addCors(ex);
try {
Optional<Token> token = SessionToken.from(ex).map(Token::of);
var user = users.loadUser(token);
if (user.isEmpty()) return unauthorized(ex);
var head = path.pop();
return switch (head) {
case ESTIMATED_TIMES -> estimatedTimes(user.get(),ex);
default -> super.doGet(path,ex);
};
} catch (UmbrellaException e){
return send(ex,e);
}
}
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);
var companyId = cid.longValue();
var company = companies.get(companyId);
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
var projects = this.projects.listProjects(companyId,false);
var taskList = taskDb.listTasks(projects.stream().map(Project::id).toList());
var map = taskList.stream().collect(toMap(Task::id, t -> t));
var tree = new HashMap<Long,Map<String,Object>>();
taskList.stream().filter(task -> !is0(task.estimatedTime())).forEach(task -> placeInTree(task,tree,map));
var result = new ArrayList<Map<String,Object>>();
projects.forEach(project -> {
var projectMap = new HashMap<>(project.toMap());
var children = tree.values().stream().filter(root -> project.id() == (Long)root.get(PROJECT_ID)).toList();
projectMap.put(FIELD_TASKS,children);
result.add(projectMap);
});
return sendContent(ex,result);
}
private Map<String,Object> placeInTree(Task task, HashMap<Long, Map<String,Object>> tree, Map<Long, Task> map) {
var taskMap = task.toMap();
if (task.parentTaskId() != null){
Task parent = map.get(task.parentTaskId());
var trunk = placeInTree(parent,tree,map);
ArrayList<Object> children = (ArrayList<Object>) trunk.computeIfAbsent(CHILDREN, k -> new ArrayList<Object>());
children.add(taskMap);
return taskMap;
}
tree.put(task.id(),taskMap);
return taskMap;
}
}