diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 1f2bcc0..2ac05fc 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -19,8 +19,9 @@ dependencies{ implementation(project(":legacy")) implementation(project(":markdown")) implementation(project(":messages")) - implementation(project(":task")) implementation(project(":project")) + implementation(project(":task")) + implementation(project(":time")) implementation(project(":translations")) implementation(project(":user")) implementation(project(":web")) diff --git a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java index 525d7a5..a6bbf28 100644 --- a/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java +++ b/backend/src/main/java/de/srsoftware/umbrella/backend/Application.java @@ -19,6 +19,7 @@ import de.srsoftware.umbrella.message.MessageApi; import de.srsoftware.umbrella.message.MessageSystem; import de.srsoftware.umbrella.project.ProjectModule; import de.srsoftware.umbrella.task.TaskModule; +import de.srsoftware.umbrella.time.TimeModule; import de.srsoftware.umbrella.translations.Translations; import de.srsoftware.umbrella.user.UserModule; import de.srsoftware.umbrella.web.WebHandler; @@ -66,6 +67,7 @@ public class Application { var messageApi = new MessageApi(messageSystem); var projectModule = new ProjectModule(config,companyModule); var taskModule = new TaskModule(config,projectModule); + var timeModule = new TimeModule(config,taskModule); var webHandler = new WebHandler(); documentApi .bindPath("/api/document") .on(server); @@ -74,6 +76,7 @@ public class Application { messageApi .bindPath("/api/messages") .on(server); projectModule .bindPath("/api/project") .on(server); taskModule .bindPath("/api/task") .on(server); + timeModule .bindPath("/api/times") .on(server); translationModule.bindPath("/api/translations").on(server); userModule .bindPath("/api/user") .on(server); legacyApi .bindPath("/legacy") .on(server); diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java index b69a44a..53d73d7 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Constants.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Constants.java @@ -19,13 +19,17 @@ public class Constants { public static final String DEFAULT_THEME = "winter"; public static final String DESCRIPTION = "description"; public static final String DOMAIN = "domain"; + public static final String DUE_DATE = "due_date"; public static final String EMAIL = "email"; + public static final String END_TIME = "end_time"; public static final String ERROR_FAILED_CREATE_TABLE = "Failed to create \"{0}\" table!"; public static final String ERROR_INVALID_FIELD = "Expected {0} to be {1}!"; public static final String ERROR_MISSING_CONFIG = "Config is missing value for {0}!"; public static final String ERROR_MISSING_FIELD = "Json is missing {0} field!"; public static final String ERROR_READ_TABLE = "Failed to read {0} from {1} table"; + public static final String EST_TIME = "est_time"; + public static final String ESTIMATED_TIME = "estimated_time"; public static final String EXPIRATION = "expiration"; public static final String GET = "GET"; @@ -37,11 +41,14 @@ public class Constants { public static final String MESSAGES = "messages"; public static final String NAME = "name"; public static final String MIME = "mime"; + public static final String NO_INDEX = "no_index"; public static final String NUMBER = "number"; public static final String OPTIONAL = "optional"; + public static final String PARENT_TASK_ID = "parent_task_id"; public static final String PASS = "pass"; public static final String PASSWORD = "password"; public static final String POST = "POST"; + public static final String PROJECT_ID = "project_id"; public static final String RECEIVERS = "receivers"; public static final String REDIRECT = "redirect"; @@ -50,6 +57,8 @@ public class Constants { public static final String SETTINGS = "settings"; public static final String SHOW_CLOSED = "show_closed"; public static final String SOURCE = "source"; + public static final String START_DATE = "start_date"; + public static final String START_TIME = "start_time"; public static final String STATE = "state"; public static final String STATUS = "status"; public static final String STATUS_CODE = "code"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/Util.java b/core/src/main/java/de/srsoftware/umbrella/core/Util.java index 5bcb277..064e4b5 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/Util.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/Util.java @@ -15,6 +15,9 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Map; import java.util.regex.Pattern; import org.json.JSONObject; @@ -140,4 +143,8 @@ public class Util { LOG.log(INFO,"Using plantuml @ {0}",file.getAbsolutePath()); plantumlJar = file; } + + public static LocalDateTime dateTimeOf(long epocSecs){ + return LocalDateTime.ofInstant(Instant.ofEpochSecond(epocSecs), ZoneId.systemDefault()); + } } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java new file mode 100644 index 0000000..35b525f --- /dev/null +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/TaskService.java @@ -0,0 +1,15 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.core.api; + +import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Task; +import java.util.Collection; + +public interface TaskService { + CompanyService companyService(); + Collection listCompanyTasks(long companyId) throws UmbrellaException; + ProjectService projectService(); + + UserService userService(); + +} diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/TimeService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/TimeService.java new file mode 100644 index 0000000..a3f044b --- /dev/null +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/TimeService.java @@ -0,0 +1,5 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.core.api; + +public interface TimeService { +} diff --git a/task/src/main/java/de/srsoftware/umbrella/task/Task.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Task.java similarity index 89% rename from task/src/main/java/de/srsoftware/umbrella/task/Task.java rename to core/src/main/java/de/srsoftware/umbrella/core/model/Task.java index 3a7c2ba..e71bb7f 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/Task.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Task.java @@ -1,12 +1,9 @@ /* © SRSoftware 2025 */ -package de.srsoftware.umbrella.task; +package de.srsoftware.umbrella.core.model; 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.core.Util.markdown; -import static de.srsoftware.umbrella.task.Constants.*; import de.srsoftware.tools.Mappable; import java.sql.ResultSet; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java new file mode 100644 index 0000000..37b2442 --- /dev/null +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java @@ -0,0 +1,77 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.core.model; + +import de.srsoftware.tools.Mappable; + +import static de.srsoftware.umbrella.core.Constants.*; +import static de.srsoftware.umbrella.core.Util.dateTimeOf; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public record Time(long id, long userId, String subject, String description, LocalDateTime start, LocalDateTime end, State state, Set taskIds) implements Mappable { + public enum State{ + Started(10), + Open(20), + Pending(40), + Complete(60), + Cancelled(100); + + private int code; + + State(int code){ + this.code = code; + } + + public int code(){ + return code; + } + + public static State of(int code){ + return switch (code){ + case 10 -> Started; + case 20 -> Open; + case 40 -> Pending; + case 60 -> Complete; + case 100 -> Cancelled; + default -> throw new IllegalArgumentException(); + }; + } + } + + @Override + public Map toMap() { + var map = new HashMap(); + map.put(ID,id); + map.put(USER_ID,userId); + map.put(SUBJECT,subject); + map.put(DESCRIPTION,description); + map.put(START_TIME,start); + map.put(END_TIME,end); + map.put(STATE,Map.of(STATUS_CODE,state.code,NAME,state.name())); + return map; + } + + public static Time of(ResultSet rs) throws SQLException { + var startTimestamp = rs.getLong(START_TIME); + var start = startTimestamp == 0 ? null : dateTimeOf(startTimestamp); + var endTimestamp = rs.getLong(END_TIME); + var end = endTimestamp == 0 ? null : dateTimeOf(endTimestamp); + + return new Time( + rs.getLong(ID), + rs.getLong(USER_ID), + rs.getString(SUBJECT), + rs.getString(DESCRIPTION), + start, + end, + State.of(rs.getInt(STATE)), + new HashSet<>() + ); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b162c9e..920cc8a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,18 +1,17 @@ rootProject.name = "Umbrella25" include("backend") +include("company") +include("contact") include("core") include("documents") include("legacy") +include("items") include("messages") -include("translations") -include("user") -include("web") - -include("company") -include("contact") - include("markdown") include("project") -include("items") include("task") +include("time") +include("translations") +include("user") +include("web") diff --git a/task/src/main/java/de/srsoftware/umbrella/task/Constants.java b/task/src/main/java/de/srsoftware/umbrella/task/Constants.java index eb9487d..e23b008 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/Constants.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/Constants.java @@ -6,14 +6,7 @@ public class 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"; } diff --git a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java index be575b8..b1935ad 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java @@ -4,11 +4,12 @@ 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.Constants.PROJECT_ID; 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 de.srsoftware.umbrella.core.model.Task; import java.sql.Connection; import java.sql.SQLException; import java.util.Collection; diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java index 855a484..6e356cb 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -4,10 +4,10 @@ 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.Constants.PROJECT_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; @@ -17,16 +17,17 @@ 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.TaskService; 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.Task; 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 { +public class TaskModule extends BaseHandler implements TaskService { private final SqliteDb taskDb; private final ProjectService projects; @@ -41,7 +42,12 @@ public class TaskModule extends BaseHandler { users = companies.userService(); } - @Override + @Override + public CompanyService companyService() { + return companies; + } + + @Override public boolean doPost(Path path, HttpExchange ex) throws IOException { addCors(ex); try { @@ -81,6 +87,12 @@ public class TaskModule extends BaseHandler { return sendContent(ex,result); } + @Override + public Collection listCompanyTasks(long companyId) throws UmbrellaException { + var projectList = projects.listProjects(companyId,false); + return taskDb.listTasks(projectList.stream().map(Project::id).toList()); + } + private Map placeInTree(Task task, HashMap> tree, Map map) { var taskMap = task.toMap(); if (task.parentTaskId() != null){ @@ -93,4 +105,14 @@ public class TaskModule extends BaseHandler { tree.put(task.id(),taskMap); return taskMap; } + + @Override + public ProjectService projectService() { + return projects; + } + + @Override + public UserService userService() { + return users; + } } diff --git a/time/build.gradle.kts b/time/build.gradle.kts new file mode 100644 index 0000000..ab7c779 --- /dev/null +++ b/time/build.gradle.kts @@ -0,0 +1,5 @@ +description = "Umbrella : Timetracking" + +dependencies{ + implementation(project(":core")) +} \ No newline at end of file diff --git a/time/src/main/java/de/srsoftware/umbrella/time/Constants.java b/time/src/main/java/de/srsoftware/umbrella/time/Constants.java new file mode 100644 index 0000000..8526d5c --- /dev/null +++ b/time/src/main/java/de/srsoftware/umbrella/time/Constants.java @@ -0,0 +1,18 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.time; + +public class Constants { + private Constants(){} + + + public static final String CONFIG_DATABASE = "umbrella.modules.time.database"; + + public static final String TABLE_TASK_TIMES = "task_times"; + public static final String TABLE_TIMES = "times"; + public static final String TASK_ID = "task_id"; + public static final String TASKS = "tasks"; + public static final String TIME_ID = "time_id"; + public static final String TIMES = "times"; + public static final String CHILDREN = "children"; + +} diff --git a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java new file mode 100644 index 0000000..85b5fc3 --- /dev/null +++ b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java @@ -0,0 +1,50 @@ +/* © SRSoftware 2025 */ +package de.srsoftware.umbrella.time; + +import static de.srsoftware.tools.jdbc.Condition.in; +import static de.srsoftware.tools.jdbc.Query.select; +import static de.srsoftware.umbrella.core.Constants.ID; +import static de.srsoftware.umbrella.time.Constants.*; + +import de.srsoftware.umbrella.core.exceptions.UmbrellaException; +import de.srsoftware.umbrella.core.model.Time; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +public class SqliteDb implements TimeDb { + + private final Connection db; + + public SqliteDb(Connection connection) { + db = connection; + } + + @Override + public Collection