working on time tracking implementation
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -3,19 +3,23 @@ package de.srsoftware.umbrella.core.model;
|
|||||||
|
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.Util.*;
|
import static de.srsoftware.umbrella.core.Util.*;
|
||||||
|
import static java.time.ZoneOffset.UTC;
|
||||||
|
|
||||||
import de.srsoftware.tools.Mappable;
|
import de.srsoftware.tools.Mappable;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class Time implements Mappable{
|
public class Time implements Mappable{
|
||||||
|
|
||||||
private final Collection<Long> taskIds;
|
private final Collection<Long> taskIds;
|
||||||
private final LocalDateTime end, start;
|
private LocalDateTime end;
|
||||||
private final long id, userId;
|
private final LocalDateTime start;
|
||||||
|
private long id;
|
||||||
|
private final long userId;
|
||||||
private final String description, subject;
|
private final String description, subject;
|
||||||
private final State state;
|
private final State state;
|
||||||
|
|
||||||
@@ -46,8 +50,9 @@ public class Time implements Mappable{
|
|||||||
default -> throw new IllegalArgumentException();
|
default -> throw new IllegalArgumentException();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
public Time(long id, long userId, String subject, String description, LocalDateTime start, LocalDateTime end, State state, Collection<Long> taskIds){
|
public Time(long id, long userId, String subject, String description, LocalDateTime start, LocalDateTime end, State state, Collection<Long> taskIds){
|
||||||
this.id=id;
|
this.id=id;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
@@ -58,6 +63,17 @@ public class Time implements Mappable{
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
this.taskIds = taskIds;
|
this.taskIds = taskIds;
|
||||||
}
|
}
|
||||||
|
public String description(){
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime end(){
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long endSecond(){
|
||||||
|
return end == null ? null : end.toEpochSecond(UTC);
|
||||||
|
}
|
||||||
|
|
||||||
public long id(){
|
public long id(){
|
||||||
return id;
|
return id;
|
||||||
@@ -88,14 +104,31 @@ public class Time implements Mappable{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setId(long newValue) {
|
||||||
|
id = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDateTime start(){
|
public LocalDateTime start(){
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long startSecond(){
|
||||||
|
return start == null ? null : start.toEpochSecond(UTC);
|
||||||
|
}
|
||||||
|
|
||||||
public State state(){
|
public State state(){
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Time stop(LocalDateTime now) {
|
||||||
|
end = now;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String subject(){
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<Long> taskIds(){
|
public Collection<Long> taskIds(){
|
||||||
return taskIds;
|
return taskIds;
|
||||||
}
|
}
|
||||||
@@ -114,4 +147,8 @@ public class Time implements Mappable{
|
|||||||
map.put(TASK_IDS,taskIds);
|
map.put(TASK_IDS,taskIds);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long userId(){
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
let { task_id } = $props();
|
let { task_id } = $props();
|
||||||
|
|
||||||
async function addTask(){
|
async function addTask(){
|
||||||
const url = api(`time/add_task/${task_id}`);
|
const url = api(`time/track_task/${task_id}`);
|
||||||
const resp = await fetch(url,{credentials:'include'}); // create new time or return time with assigned tasks
|
const resp = await fetch(url,{credentials:'include'}); // create new time or return time with assigned tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public class Constants {
|
|||||||
private Constants(){}
|
private Constants(){}
|
||||||
|
|
||||||
|
|
||||||
public static final String ADD_TASK = "add_task";
|
public static final String CHILDREN = "children";
|
||||||
public static final String CONFIG_DATABASE = "umbrella.modules.time.database";
|
public static final String CONFIG_DATABASE = "umbrella.modules.time.database";
|
||||||
|
|
||||||
public static final String TABLE_TASK_TIMES = "task_times";
|
public static final String TABLE_TASK_TIMES = "task_times";
|
||||||
@@ -14,6 +14,6 @@ public class Constants {
|
|||||||
public static final String TASKS = "tasks";
|
public static final String TASKS = "tasks";
|
||||||
public static final String TIME_ID = "time_id";
|
public static final String TIME_ID = "time_id";
|
||||||
public static final String TIMES = "times";
|
public static final String TIMES = "times";
|
||||||
public static final String CHILDREN = "children";
|
public static final String TRACK_TASK = "track_task";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.srsoftware.umbrella.time;
|
|||||||
|
|
||||||
import static de.srsoftware.tools.jdbc.Condition.*;
|
import static de.srsoftware.tools.jdbc.Condition.*;
|
||||||
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
|
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
|
||||||
|
import static de.srsoftware.tools.jdbc.Query.insertInto;
|
||||||
import static de.srsoftware.tools.jdbc.Query.select;
|
import static de.srsoftware.tools.jdbc.Query.select;
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.model.Status.OPEN;
|
import static de.srsoftware.umbrella.core.model.Status.OPEN;
|
||||||
@@ -10,12 +11,14 @@ import static de.srsoftware.umbrella.core.model.Time.State.Complete;
|
|||||||
import static de.srsoftware.umbrella.time.Constants.*;
|
import static de.srsoftware.umbrella.time.Constants.*;
|
||||||
import static java.lang.System.Logger.Level.ERROR;
|
import static java.lang.System.Logger.Level.ERROR;
|
||||||
import static java.text.MessageFormat.format;
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.ZoneOffset.UTC;
|
||||||
|
|
||||||
import de.srsoftware.umbrella.core.BaseDb;
|
import de.srsoftware.umbrella.core.BaseDb;
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.Time;
|
import de.srsoftware.umbrella.core.model.Time;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -27,11 +30,6 @@ public class SqliteDb extends BaseDb implements TimeDb {
|
|||||||
super(connection);
|
super(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Time createNew(long userId, String subject, String description) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int createTables() {
|
protected int createTables() {
|
||||||
int currentVersion = createSettingsTable();
|
int currentVersion = createSettingsTable();
|
||||||
@@ -134,4 +132,19 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
throw new UmbrellaException("Failed to load times for task list");
|
throw new UmbrellaException("Failed to load times for task list");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Time save(Time track) throws SQLException {
|
||||||
|
if (track.id() == 0){ // create new
|
||||||
|
var rs = insertInto(TABLE_TASK_TIMES,USER_ID,SUBJECT,DESCRIPTION,START_TIME,END_TIME,STATE)
|
||||||
|
.values(track.userId(),track.subject(),track.description(),track.startSecond(),track.endSecond(),track.state().code())
|
||||||
|
.execute(db)
|
||||||
|
.getGeneratedKeys();
|
||||||
|
if (rs.next()) track.setId(rs.getLong(1));
|
||||||
|
rs.close();
|
||||||
|
return track;
|
||||||
|
} else { // update
|
||||||
|
// TODO: update
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ package de.srsoftware.umbrella.time;
|
|||||||
|
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.Time;
|
import de.srsoftware.umbrella.core.model.Time;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public interface TimeDb {
|
public interface TimeDb {
|
||||||
Time createNew(long userId, String subject, String description);
|
|
||||||
|
|
||||||
HashMap<Long,Time> listTimes(Collection<Long> taskIds, boolean showClosed) throws UmbrellaException;
|
HashMap<Long,Time> listTimes(Collection<Long> taskIds, boolean showClosed) throws UmbrellaException;
|
||||||
|
|
||||||
HashMap<Long,Time> listUserTimes(long userId, boolean showClosed);
|
HashMap<Long,Time> listUserTimes(long userId, boolean showClosed);
|
||||||
|
|
||||||
|
Time save(Time track) throws SQLException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
|||||||
import de.srsoftware.umbrella.core.model.*;
|
import de.srsoftware.umbrella.core.model.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class TimeModule extends BaseHandler implements TimeService {
|
public class TimeModule extends BaseHandler implements TimeService {
|
||||||
@@ -57,7 +57,7 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
if (user.isEmpty()) return unauthorized(ex);
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case ADD_TASK -> getAddTask(user.get(),path,ex);
|
case TRACK_TASK -> trackTask(user.get(),path,ex);
|
||||||
case null -> getUserTimes(user.get(),ex);
|
case null -> getUserTimes(user.get(),ex);
|
||||||
default -> super.doGet(path,ex);
|
default -> super.doGet(path,ex);
|
||||||
};
|
};
|
||||||
@@ -83,28 +83,27 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getAddTask(UmbrellaUser user, Path path, HttpExchange ex) throws IOException {
|
private boolean trackTask(UmbrellaUser user, Path path, HttpExchange ex) throws IOException {
|
||||||
if (path.empty()) throw missingFieldException(TASK_ID);
|
if (path.empty()) throw missingFieldException(TASK_ID);
|
||||||
long taskId;
|
Task task;
|
||||||
try {
|
try {
|
||||||
taskId = Long.parseLong(path.pop());
|
var taskId = Long.parseLong(path.pop());
|
||||||
|
task = taskService().load(List.of(taskId)).get(taskId);
|
||||||
|
if (task == null) throw UmbrellaException.notFound("Failed to load task with id = {0}",taskId);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw invalidFieldException(TASK_ID,"long value");
|
throw invalidFieldException(TASK_ID,"long value");
|
||||||
}
|
}
|
||||||
var taskIds = new HashSet<Long>();
|
var now = LocalDateTime.now();
|
||||||
var times = timeDb.listUserTimes(user.id(), false).values().stream()
|
timeDb.listUserTimes(user.id(),false).values()
|
||||||
.filter(time -> time.state() == Started)
|
.stream().filter(time -> time.state() == Started)
|
||||||
.peek(time -> taskIds.addAll(time.taskIds()))
|
.sorted(Comparator.comparing(Time::start))
|
||||||
.collect(Collectors.toMap(Time::id, t -> t));
|
.findFirst()
|
||||||
taskIds.add(taskId);
|
.map(running -> running.stop(now))
|
||||||
var tasks = taskService().load(taskIds);
|
.ifPresent(timeDb::save);
|
||||||
if (times.isEmpty()){
|
var track = new Time(0,user.id(),task.name(),task.description(),now,null,Started,List.of(task.id()));
|
||||||
var task = tasks.get(taskId);
|
timeDb.save(track);
|
||||||
if (task == null) throw UmbrellaException.notFound("Failed to find task with id = {0}",taskId);
|
|
||||||
var time = timeDb.createNew(user.id(),task.name(),task.description());
|
return sendContent(ex,track);
|
||||||
times.put(time.id(),time);
|
|
||||||
}
|
|
||||||
return sendContent(ex,Map.of(TIMES,times,TASKS,tasks));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getUserTimes(UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean getUserTimes(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
|
|||||||
Reference in New Issue
Block a user