diff --git a/.gitignore b/.gitignore index 631c05b6..1184cc0a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ logs/ config.xml /db/ +application.properties diff --git a/pom.xml b/pom.xml index 3499e9ba..306736b3 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,17 @@ + + org.mapstruct + mapstruct + 1.5.5.Final + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + provided + org.openjfx javafx-controls @@ -85,6 +96,27 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-web + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-security + 3.1.3 + + + org.glassfish.jaxb jaxb-runtime @@ -226,10 +258,24 @@ coverage + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + + + org.mapstruct + mapstruct-processor + 1.5.5.Final + + + + org.jacoco jacoco-maven-plugin - 0.8.7 + 0.8.10 prepare-agent diff --git a/src/main/java/de/doubleslash/keeptime/App.java b/src/main/java/de/doubleslash/keeptime/App.java index bc742dd9..ce056575 100644 --- a/src/main/java/de/doubleslash/keeptime/App.java +++ b/src/main/java/de/doubleslash/keeptime/App.java @@ -305,7 +305,7 @@ private void registerMaximizeEventlistener(final Scene mainScene, final Stage pr @Override public void stop() throws Exception { - springContext.stop(); + springContext.close(); } } diff --git a/src/main/java/de/doubleslash/keeptime/controller/Controller.java b/src/main/java/de/doubleslash/keeptime/controller/Controller.java index b42d4f14..6caa7fd6 100644 --- a/src/main/java/de/doubleslash/keeptime/controller/Controller.java +++ b/src/main/java/de/doubleslash/keeptime/controller/Controller.java @@ -34,7 +34,6 @@ import de.doubleslash.keeptime.model.Settings; import de.doubleslash.keeptime.model.Work; import jakarta.annotation.PreDestroy; -import javafx.collections.ObservableList; @Service public class Controller { @@ -84,9 +83,7 @@ public void changeProject(final Project newProject, final long minusSeconds) { final Work newWork = new Work(workEnd, workEnd.plusSeconds(minusSeconds), newProject, ""); model.getPastWorkItems().add(newWork); - model.activeWorkItem.set(newWork); - } public Work saveCurrentWork(final LocalDateTime workEnd) { @@ -98,19 +95,19 @@ public Work saveCurrentWork(final LocalDateTime workEnd) { currentWork.setEndTime(workEnd); - final String time = DateFormatter - .secondsToHHMMSS(Duration.between(currentWork.getStartTime(), currentWork.getEndTime()).getSeconds()); + final String time = DateFormatter.secondsToHHMMSS( + Duration.between(currentWork.getStartTime(), currentWork.getEndTime()).getSeconds()); LOG.info("Saving Work from '{}' to '{}' ({}) on project '{}' with notes '{}'", currentWork.getStartTime(), currentWork.getEndTime(), time, currentWork.getProject().getName(), currentWork.getNotes()); // Save in db return model.getWorkRepository().save(currentWork); - } public void addNewProject(final Project project) { LOG.info("Creating new project '{}'.", project); + model.getAllProjects().add(project); model.getAvailableProjects().add(project); @@ -190,7 +187,7 @@ public void deleteProject(final Project p) { final int indexToRemove = p.getIndex(); p.setEnabled(false); // we don't delete it because of the referenced work - // items + // items p.setIndex(-1); model.getAvailableProjects().remove(p); @@ -253,7 +250,7 @@ public void deleteWork(final Work workToBeDeleted) { /** * Changes the indexes of the originalList parameter to have a consistent order. - * + * * @param originalList * list of all projects to adapt the indexes for * @param changedProject @@ -296,7 +293,7 @@ List resortProjectIndexes(final List originalList, final Proje /** * Decreases all indexes by one, after the removed index - * + * * @param originalList * list of all projects to adapt the indexes for * @param removedIndex @@ -361,5 +358,4 @@ public long calcSeconds(final List workItems) { return seconds; } - } diff --git a/src/main/java/de/doubleslash/keeptime/model/Model.java b/src/main/java/de/doubleslash/keeptime/model/Model.java index 5d9ce501..3159f4e3 100644 --- a/src/main/java/de/doubleslash/keeptime/model/Model.java +++ b/src/main/java/de/doubleslash/keeptime/model/Model.java @@ -25,6 +25,7 @@ import javafx.collections.ObservableList; import javafx.collections.transformation.SortedList; import javafx.scene.paint.Color; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; @@ -35,7 +36,7 @@ public class Model { private ProjectRepository projectRepository; private WorkRepository workRepository; private SettingsRepository settingsRepository; - + @Autowired public Model(final ProjectRepository projectRepository, final WorkRepository workRepository, final SettingsRepository settingsRepository) { super(); diff --git a/src/main/java/de/doubleslash/keeptime/model/Project.java b/src/main/java/de/doubleslash/keeptime/model/Project.java index 5e4db8a1..9a8593ab 100644 --- a/src/main/java/de/doubleslash/keeptime/model/Project.java +++ b/src/main/java/de/doubleslash/keeptime/model/Project.java @@ -29,7 +29,6 @@ public class Project { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", updatable = false, nullable = false) private long id; - private String name; @Lob @@ -39,9 +38,7 @@ public class Project { private Color color; private boolean isWork; - private boolean isDefault; - private boolean isEnabled; private int index; @@ -50,9 +47,7 @@ public Project() { // Needed for jpa } - public Project(final String name, final String description, final Color color, final boolean isWork, final int index, - final boolean isDefault) { - super(); + public Project(String name, String description, Color color, boolean isWork, int index, boolean isDefault) { this.name = name; this.description = description; this.color = color; @@ -62,8 +57,7 @@ public Project(final String name, final String description, final Color color, f this.index = index; } - public Project(final String name, final String description, final Color color, final boolean isWork, - final int index) { + public Project(String name, String description, Color color, boolean isWork, int index) { this(name, description, color, isWork, index, false); } @@ -71,7 +65,7 @@ public String getName() { return name; } - public void setName(final String name) { + public void setName(String name) { this.name = name; } @@ -79,7 +73,7 @@ public Color getColor() { return color; } - public void setColor(final Color color) { + public void setColor(Color color) { this.color = color; } @@ -87,7 +81,7 @@ public boolean isWork() { return isWork; } - public void setWork(final boolean isWork) { + public void setWork(boolean isWork) { this.isWork = isWork; } @@ -95,7 +89,7 @@ public boolean isDefault() { return isDefault; } - public void setDefault(final boolean isDefault) { + public void setDefault(boolean isDefault) { this.isDefault = isDefault; } @@ -103,7 +97,7 @@ public boolean isEnabled() { return isEnabled; } - public void setEnabled(final boolean isEnabled) { + public void setEnabled(boolean isEnabled) { this.isEnabled = isEnabled; } @@ -115,7 +109,7 @@ public int getIndex() { return index; } - public void setIndex(final int index) { + public void setIndex(int index) { this.index = index; } @@ -123,7 +117,7 @@ public String getDescription() { return description; } - public void setDescription(final String description) { + public void setDescription(String description) { this.description = description; } @@ -132,5 +126,4 @@ public String toString() { return "Project [id=" + id + ", name=" + name + ", description=" + description + ", color=" + color + ", isWork=" + isWork + ", isDefault=" + isDefault + ", isEnabled=" + isEnabled + ", index=" + index + "]"; } - -} +} \ No newline at end of file diff --git a/src/main/java/de/doubleslash/keeptime/model/ScreenSettings.java b/src/main/java/de/doubleslash/keeptime/model/ScreenSettings.java index da43dbec..0dc8e19e 100644 --- a/src/main/java/de/doubleslash/keeptime/model/ScreenSettings.java +++ b/src/main/java/de/doubleslash/keeptime/model/ScreenSettings.java @@ -1,3 +1,19 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + package de.doubleslash.keeptime.model; import javafx.beans.property.ObjectProperty; diff --git a/src/main/java/de/doubleslash/keeptime/model/Settings.java b/src/main/java/de/doubleslash/keeptime/model/Settings.java index 8efb9587..1a7b613b 100644 --- a/src/main/java/de/doubleslash/keeptime/model/Settings.java +++ b/src/main/java/de/doubleslash/keeptime/model/Settings.java @@ -29,7 +29,7 @@ /** * Object holding settings - * + * * @author nmutter */ @Entity @@ -73,7 +73,8 @@ public class Settings { private boolean confirmClose; - public Settings() {} + public Settings() { + } public Settings(final Color hoverBackgroundColor, final Color hoverFontColor, final Color defaultBackgroundColor, final Color defaultFontColor, final Color taskBarColor, final boolean useHotkey, @@ -96,7 +97,6 @@ public Settings(final Color hoverBackgroundColor, final Color hoverFontColor, fi this.remindIfNotesAreEmpty = remindIfNotesAreEmpty; this.remindIfNotesAreEmptyOnlyForWorkEntry = remindIfNotesAreEmptyOnlyForWorkEntry; this.confirmClose = confirmClose; - } public boolean isRemindIfNotesAreEmptyOnlyForWorkEntry() { @@ -222,5 +222,4 @@ public boolean isRemindIfNotesAreEmpty() { public void setRemindIfNotesAreEmpty(final boolean emptyNoteReminder) { this.remindIfNotesAreEmpty = emptyNoteReminder; } - } diff --git a/src/main/java/de/doubleslash/keeptime/model/Work.java b/src/main/java/de/doubleslash/keeptime/model/Work.java index 97ae32c8..27ce51c4 100644 --- a/src/main/java/de/doubleslash/keeptime/model/Work.java +++ b/src/main/java/de/doubleslash/keeptime/model/Work.java @@ -18,7 +18,14 @@ import java.time.LocalDateTime; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; @Entity @Table(name = "Work") @@ -30,14 +37,12 @@ public class Work { private LocalDateTime startTime; private LocalDateTime endTime; - @ManyToOne private Project project; @Lob private String notes; - public Work() { - } + public Work() {} public Work(final LocalDateTime startTime, final LocalDateTime endTime, final Project project, final String notes) { super(); @@ -88,5 +93,4 @@ public String toString() { return "Work [id=" + id + ", startTime=" + startTime + ", endTime=" + endTime + ", projectName=" + project.getName() + ", notes=" + notes + "]"; } - } diff --git a/src/main/java/de/doubleslash/keeptime/model/persistenceconverter/ColorConverter.java b/src/main/java/de/doubleslash/keeptime/model/persistenceconverter/ColorConverter.java index abc3c405..2e40fefa 100644 --- a/src/main/java/de/doubleslash/keeptime/model/persistenceconverter/ColorConverter.java +++ b/src/main/java/de/doubleslash/keeptime/model/persistenceconverter/ColorConverter.java @@ -20,7 +20,6 @@ import javafx.scene.paint.Color; public class ColorConverter implements AttributeConverter { - @Override public Color convertToEntityAttribute(final String arg0) { try { @@ -34,5 +33,4 @@ public Color convertToEntityAttribute(final String arg0) { public String convertToDatabaseColumn(final Color arg0) { return arg0.toString(); } - } diff --git a/src/main/java/de/doubleslash/keeptime/model/repos/ProjectRepository.java b/src/main/java/de/doubleslash/keeptime/model/repos/ProjectRepository.java index 5af3e8cc..e1c7248d 100644 --- a/src/main/java/de/doubleslash/keeptime/model/repos/ProjectRepository.java +++ b/src/main/java/de/doubleslash/keeptime/model/repos/ProjectRepository.java @@ -14,14 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . + package de.doubleslash.keeptime.model.repos; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; - import de.doubleslash.keeptime.model.Project; +import java.util.List; + @Repository public interface ProjectRepository extends JpaRepository { - + List findByName(String name); } diff --git a/src/main/java/de/doubleslash/keeptime/model/repos/WorkRepository.java b/src/main/java/de/doubleslash/keeptime/model/repos/WorkRepository.java index 0064e193..b6ac0532 100644 --- a/src/main/java/de/doubleslash/keeptime/model/repos/WorkRepository.java +++ b/src/main/java/de/doubleslash/keeptime/model/repos/WorkRepository.java @@ -16,18 +16,24 @@ package de.doubleslash.keeptime.model.repos; -import java.time.LocalDate; -import java.util.List; - +import de.doubleslash.keeptime.model.Work; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import de.doubleslash.keeptime.model.Work; +import java.time.LocalDate; +import java.util.List; @Repository public interface WorkRepository extends JpaRepository { @Query(value = "SELECT w FROM Work w WHERE CAST(startTime AS DATE) = ?1 ORDER BY startTime ASC") List findByStartDateOrderByStartTimeAsc(LocalDate creationDate); + + @Query("SELECT w FROM Work w WHERE " + "(:projectId IS NULL OR w.project.id = :projectId) " + + "AND (:minStartTime IS NULL OR CAST(w.startTime AS DATE) >= :minStartTime) " + + "AND (:maxStartTime IS NULL OR CAST(w.startTime AS DATE) <= :maxStartTime)") + List findWorkItems(@Param("projectId") Long projectId, @Param("minStartTime") LocalDate minStartTime, + @Param("maxStartTime") LocalDate maxStartTime); } diff --git a/src/main/java/de/doubleslash/keeptime/rest/DTO/ProjectDTO.java b/src/main/java/de/doubleslash/keeptime/rest/DTO/ProjectDTO.java new file mode 100644 index 00000000..b309f3e2 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/DTO/ProjectDTO.java @@ -0,0 +1,102 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.DTO; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.PositiveOrZero; + +public class ProjectDTO { + private long id; + @NotEmpty(message = "Name must not be null or empty") + private String name; + private String description; + /** + * Color in format of 0xRRGGBBAA (R=Red, G=Green, B=Blue, A=Alpha). E.g. 0xff0000ff is fully opaque red. + */ + private String color; + private boolean isWork; + @PositiveOrZero(message = "Index must not be negative") + private int index; + private boolean isEnabled; + + public ProjectDTO(long id, String name, String description, String color, boolean isWork, int index, + boolean isEnabled) { + this.id = id; + this.name = name; + this.description = description; + this.color = color; + this.isWork = isWork; + this.index = index; + this.isEnabled = isEnabled; + } + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public boolean isWork() { + return isWork; + } + + public void setWork(boolean isWork) { + this.isWork = isWork; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public boolean isEnabled() { + return isEnabled; + } + + public void setEnabled(final boolean enabled) { + isEnabled = enabled; + } +} diff --git a/src/main/java/de/doubleslash/keeptime/rest/DTO/ProjectIdentificationDTO.java b/src/main/java/de/doubleslash/keeptime/rest/DTO/ProjectIdentificationDTO.java new file mode 100644 index 00000000..46a63cd4 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/DTO/ProjectIdentificationDTO.java @@ -0,0 +1,35 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.DTO; + +public class ProjectIdentificationDTO { + private long id; + + public ProjectIdentificationDTO() {} + + public ProjectIdentificationDTO(final long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } +} \ No newline at end of file diff --git a/src/main/java/de/doubleslash/keeptime/rest/DTO/WorkDTO.java b/src/main/java/de/doubleslash/keeptime/rest/DTO/WorkDTO.java new file mode 100644 index 00000000..0bb05e5d --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/DTO/WorkDTO.java @@ -0,0 +1,85 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.DTO; + +import java.time.LocalDateTime; + +import jakarta.validation.constraints.NotNull; + +public class WorkDTO { + private long id; + + @NotNull + private LocalDateTime startTime; + + @NotNull + private LocalDateTime endTime; + + @NotNull + private ProjectIdentificationDTO project; + + private String notes; + + public WorkDTO(long id, LocalDateTime startTime, LocalDateTime endTime, ProjectIdentificationDTO project, + String notes) { + this.id = id; + this.startTime = startTime; + this.endTime = endTime; + this.project = project; + this.notes = notes; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public ProjectIdentificationDTO getProject() { + return project; + } + + public void setProject(ProjectIdentificationDTO project) { + this.project = project; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } +} diff --git a/src/main/java/de/doubleslash/keeptime/rest/SecurityConfiguration.java b/src/main/java/de/doubleslash/keeptime/rest/SecurityConfiguration.java new file mode 100644 index 00000000..b40a5ae2 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/SecurityConfiguration.java @@ -0,0 +1,42 @@ +// Copyright 2023 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest; + +import static org.springframework.security.config.Customizer.withDefaults; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + @Bean + public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { + http.csrf(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) + .httpBasic(withDefaults()); + + return http.build(); + } +} diff --git a/src/main/java/de/doubleslash/keeptime/rest/controller/FXUtils.java b/src/main/java/de/doubleslash/keeptime/rest/controller/FXUtils.java new file mode 100644 index 00000000..c0daa4f9 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/controller/FXUtils.java @@ -0,0 +1,27 @@ +package de.doubleslash.keeptime.rest.controller; + +import javafx.application.Platform; + +import java.util.concurrent.CompletableFuture; + +public class FXUtils { + public static void runInFxThreadAndWait(Runnable runnable) { + CompletableFuture future = new CompletableFuture<>(); + + Platform.runLater(() -> { + try { + runnable.run(); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + // Wait for the result (blocking for simplicity; adjust as needed for async handling) + try { + future.get(); // This blocks until the CompletableFuture is completed + } catch (Exception e) { + throw new RuntimeException("Error processing request", e); + } + } +} diff --git a/src/main/java/de/doubleslash/keeptime/rest/controller/ProjectController.java b/src/main/java/de/doubleslash/keeptime/rest/controller/ProjectController.java new file mode 100644 index 00000000..c684ff00 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/controller/ProjectController.java @@ -0,0 +1,204 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.controller; + +import de.doubleslash.keeptime.controller.Controller; +import de.doubleslash.keeptime.model.Model; +import de.doubleslash.keeptime.model.Project; +import de.doubleslash.keeptime.model.Work; +import de.doubleslash.keeptime.model.repos.ProjectRepository; +import de.doubleslash.keeptime.model.repos.WorkRepository; +import de.doubleslash.keeptime.rest.DTO.ProjectDTO; +import de.doubleslash.keeptime.rest.DTO.ProjectIdentificationDTO; +import de.doubleslash.keeptime.rest.DTO.WorkDTO; +import de.doubleslash.keeptime.rest.mapper.ProjectMapper; +import de.doubleslash.keeptime.rest.mapper.WorkMapper; +import jakarta.validation.Valid; +import org.springframework.dao.DataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/projects") +public class ProjectController { + private final ProjectRepository projectRepository; + private final WorkRepository workRepository; + private final Controller controller; + private final Model model; + private final WorkMapper workMapper; + private final ProjectMapper projectMapper; + + public ProjectController(final ProjectRepository projectRepository, final WorkRepository workRepository, + final Controller controller, Model model, WorkMapper workMapper, ProjectMapper projectMapper) { + this.projectRepository = projectRepository; + this.workRepository = workRepository; + this.controller = controller; + this.model = model; + this.workMapper = workMapper; + this.projectMapper = projectMapper; + } + + @GetMapping + public ResponseEntity> getProjectColorDTOsByName( + @RequestParam(name = "name", required = false) final String name) { + List projects; + + if (name != null) { + projects = projectRepository.findByName(name); + } else { + projects = projectRepository.findAll(); + } + List projectDTOS = projects.stream().map(projectMapper::projectToProjectDTO).toList(); + return ResponseEntity.ok(projectDTOS); + } + + @GetMapping("/{id}") + public @Valid ProjectDTO getProjectById(@PathVariable final long id) { + final Optional project = projectRepository.findById(id); + + if (project.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Project with id '" + id + "' not found"); + } + return projectMapper.projectToProjectDTO(project.get()); + } + + @GetMapping("/{id}/works") + public List getWorksFromProject(@PathVariable final long id) { + return workRepository.findWorkItems(id, null, null).stream().map(workMapper::workToWorkDTO).toList(); + } + + @PostMapping + public ResponseEntity createProject(@Valid @RequestBody final ProjectDTO newProjectDTO) { + try { + Project newProject = projectMapper.projectDTOToProject(newProjectDTO); + + FXUtils.runInFxThreadAndWait(() -> controller.addNewProject(newProject)); + + ProjectDTO projectDTO = projectMapper.projectToProjectDTO(newProject); + return ResponseEntity.status(HttpStatus.CREATED).body(projectDTO); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @PutMapping("/{id}") + public ResponseEntity updateProject(@PathVariable final long id, + @Valid @RequestBody final ProjectDTO newValuedProjectDTO) { + + if (id != newValuedProjectDTO.getId()) { + return ResponseEntity.badRequest().build(); + } + Optional optionalProject = projectRepository.findById(id); + + if (optionalProject.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + Project existingProject = optionalProject.get(); + + try { + Project newValuedProject = projectMapper.projectDTOToProject(newValuedProjectDTO); + + FXUtils.runInFxThreadAndWait(() -> controller.editProject(existingProject, newValuedProject)); + + ProjectDTO updatedProjectDTO = projectMapper.projectToProjectDTO(existingProject); + + return ResponseEntity.ok(updatedProjectDTO); + } catch (DataAccessException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + @PostMapping("/{id}/works") + public ResponseEntity createWorkInProject(@PathVariable final long id, + @Valid @RequestBody final WorkDTO workDTO) { + + if (id != workDTO.getProject().getId()) { + return ResponseEntity.badRequest().build(); + } + + Optional projectOptional = projectRepository.findById(id); + + if (projectOptional.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + final Work newWork = workMapper.workDTOToWork(workDTO); + Project project = projectOptional.get(); + newWork.setProject(project); + + workRepository.save(newWork); + + WorkDTO createdWorkDTO = workMapper.workToWorkDTO(newWork); + + return ResponseEntity.status(HttpStatus.CREATED).body(createdWorkDTO); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteProject(@PathVariable final long id) { + Optional projectOptional = projectRepository.findById(id); + + if (projectOptional.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + Project project = projectOptional.get(); + + if (project.isDefault()) { + return new ResponseEntity<>("Project cannot be deleted as it is the default", HttpStatus.BAD_REQUEST); + } + FXUtils.runInFxThreadAndWait(() -> controller.deleteProject(project)); + + return new ResponseEntity<>("Project successfully deleted", HttpStatus.OK); + } + + @GetMapping("/current") + public ProjectDTO getWorkProjects() { + Project project = model.activeWorkItem.get().getProject(); + return projectMapper.projectToProjectDTO(project); + } + + @PutMapping("/current") + public ResponseEntity changeProject( + @Valid @RequestBody ProjectIdentificationDTO newProject) { + Optional projectOptional = projectRepository.findById(newProject.getId()); + + if (projectOptional.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + try { + FXUtils.runInFxThreadAndWait(() -> controller.changeProject(projectOptional.get())); + + return ResponseEntity.ok(newProject); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null); + } + } + + @ResponseStatus(value = HttpStatus.NOT_FOUND) + public static class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/doubleslash/keeptime/rest/controller/WorksController.java b/src/main/java/de/doubleslash/keeptime/rest/controller/WorksController.java new file mode 100644 index 00000000..fbea5800 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/controller/WorksController.java @@ -0,0 +1,109 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.controller; + +import de.doubleslash.keeptime.model.Model; +import de.doubleslash.keeptime.model.Project; +import de.doubleslash.keeptime.model.Work; +import de.doubleslash.keeptime.model.repos.ProjectRepository; +import de.doubleslash.keeptime.model.repos.WorkRepository; +import de.doubleslash.keeptime.rest.DTO.WorkDTO; +import de.doubleslash.keeptime.rest.mapper.WorkMapper; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/api/works") +public class WorksController { + + private final WorkRepository workRepository; + private final ProjectRepository projectRepository; + private final Model model; + private final WorkMapper workMapper; + + public WorksController(final WorkRepository workRepository, final ProjectRepository projectRepository, Model model, + WorkMapper workMapper) { + this.workRepository = workRepository; + this.projectRepository = projectRepository; + this.model = model; + this.workMapper = workMapper; + } + + @GetMapping + public List getWorks(@RequestParam(name = "id", required = false) final Long projectId, + @RequestParam(name = "fromDate", required = false) final LocalDate fromDate, + @RequestParam(name = "toDate", required = false) final LocalDate toDate) { + List works = workRepository.findWorkItems(projectId, fromDate, toDate); + return works.stream().map(workMapper::workToWorkDTO).toList(); + } + + @PutMapping("/{id}") + public ResponseEntity editWork(@PathVariable("id") Long workId, @RequestBody WorkDTO newValuedWorkDTO) { + + if (workId != newValuedWorkDTO.getId()) { + return ResponseEntity.badRequest().build(); + } + + Work newValuedWork = workMapper.workDTOToWork(newValuedWorkDTO); + Optional optionalWork = workRepository.findById(workId); + Optional optionalProject = projectRepository.findById(newValuedWorkDTO.getProject().getId()); + + if (optionalWork.isEmpty() || optionalProject.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + Work workToBeEdited = optionalWork.get(); + + workToBeEdited.setStartTime(newValuedWork.getStartTime()); + workToBeEdited.setEndTime(newValuedWork.getEndTime()); + workToBeEdited.setNotes(newValuedWork.getNotes()); + workToBeEdited.setProject(optionalProject.get()); + + Work editedWork = workRepository.save(workToBeEdited); + + return ResponseEntity.ok(workMapper.workToWorkDTO(editedWork)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteWork(@PathVariable final long id) { + Optional optionalWork = workRepository.findById(id); + + if (optionalWork.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + Work workToBeDeleted = optionalWork.get(); + workRepository.delete(workToBeDeleted); + return new ResponseEntity<>("Work successfully deleted", HttpStatus.OK); + } + + @GetMapping("/current") + public ResponseEntity getCurrentWork() { + Work workProjects = model.activeWorkItem.get(); + + if (workProjects != null) { + return ResponseEntity.ok(workMapper.workToWorkDTO(workProjects)); + } else { + return ResponseEntity.notFound().build(); + } + } +} diff --git a/src/main/java/de/doubleslash/keeptime/rest/mapper/ColorMapper.java b/src/main/java/de/doubleslash/keeptime/rest/mapper/ColorMapper.java new file mode 100644 index 00000000..8854b1f2 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/mapper/ColorMapper.java @@ -0,0 +1,42 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.mapper; + +import de.doubleslash.keeptime.model.persistenceconverter.ColorConverter; +import javafx.scene.paint.Color; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface ColorMapper { + + ColorConverter colorConverter = new ColorConverter(); + + default String colorToColorDTO(Color color) { + if (color == null) { + return null; + } + return colorConverter.convertToDatabaseColumn(color); + } + + default Color colorDTOToColor(String colorDTO) { + if (colorDTO == null) { + return null; + } + + return colorConverter.convertToEntityAttribute(colorDTO); + } +} diff --git a/src/main/java/de/doubleslash/keeptime/rest/mapper/ProjectMapper.java b/src/main/java/de/doubleslash/keeptime/rest/mapper/ProjectMapper.java new file mode 100644 index 00000000..922983b9 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/mapper/ProjectMapper.java @@ -0,0 +1,29 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.mapper; + +import de.doubleslash.keeptime.rest.DTO.ProjectDTO; +import de.doubleslash.keeptime.model.Project; +import org.mapstruct.Mapper; + +@Mapper(uses = ColorMapper.class, componentModel = "spring") +public interface ProjectMapper { + + ProjectDTO projectToProjectDTO(Project project); + + Project projectDTOToProject(ProjectDTO projectDTO); +} diff --git a/src/main/java/de/doubleslash/keeptime/rest/mapper/WorkMapper.java b/src/main/java/de/doubleslash/keeptime/rest/mapper/WorkMapper.java new file mode 100644 index 00000000..4b57f1e3 --- /dev/null +++ b/src/main/java/de/doubleslash/keeptime/rest/mapper/WorkMapper.java @@ -0,0 +1,30 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package de.doubleslash.keeptime.rest.mapper; + +import org.mapstruct.Mapper; + +import de.doubleslash.keeptime.model.Work; +import de.doubleslash.keeptime.rest.DTO.WorkDTO; + +@Mapper(componentModel = "spring") +public interface WorkMapper { + + WorkDTO workToWorkDTO(Work work); + + Work workDTOToWork(WorkDTO workDTO); +} diff --git a/src/main/java/de/doubleslash/keeptime/view/SettingsController.java b/src/main/java/de/doubleslash/keeptime/view/SettingsController.java index 6e1c3e4e..f91cf56e 100644 --- a/src/main/java/de/doubleslash/keeptime/view/SettingsController.java +++ b/src/main/java/de/doubleslash/keeptime/view/SettingsController.java @@ -17,9 +17,16 @@ package de.doubleslash.keeptime.view; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.file.Paths; import java.sql.SQLException; import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; import org.h2.tools.RunScript; import org.h2.tools.Script; @@ -39,7 +46,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.cell.PropertyValueFactory; @@ -54,14 +60,15 @@ @Component public class SettingsController { - @FXML private ColorPicker hoverBackgroundColor; + @FXML private ColorPicker hoverFontColor; @FXML private ColorPicker defaultBackgroundColor; + @FXML private ColorPicker defaultFontColor; @@ -70,21 +77,28 @@ public class SettingsController { @FXML private Button resetHoverBackgroundButton; + @FXML private Button resetHoverFontButton; + @FXML private Button resetDefaultBackgroundButton; + @FXML private Button resetDefaultFontButton; + @FXML private Button resetTaskBarFontButton; @FXML private CheckBox useHotkeyCheckBox; + @FXML private CheckBox displayProjectsRightCheckBox; + @FXML private CheckBox hideProjectsOnMouseExitCheckBox; + @FXML private CheckBox saveWindowPositionCheckBox; @@ -152,8 +166,26 @@ public class SettingsController { @FXML private Region licensesIcon; + @FXML + private TextField authName; + + @FXML + private PasswordField authPassword; + + @FXML + private CheckBox activateRestApiCheckBox; + + @FXML + private Hyperlink swaggerHyperLink; + + @FXML + private TextField authPort; + + private final String propertiesFilePath = "application.properties"; + private static final String GITHUB_PAGE = "https://www.github.com/doubleSlashde/KeepTime"; private static final String GITHUB_ISSUE_PAGE = GITHUB_PAGE + "/issues"; + private static final Color HYPERLINK_COLOR = Color.rgb(0, 115, 170); private final ApplicationProperties applicationProperties; @@ -172,6 +204,7 @@ public SettingsController(final Model model, final Controller controller, this.model = model; this.controller = controller; this.applicationProperties = applicationProperties; + } @FXML @@ -203,10 +236,45 @@ private void initialize() { initExportButton(); initImportButton(); + try (FileInputStream input = new FileInputStream(propertiesFilePath)) { + Properties properties = new Properties(); + properties.load(input); + String apiStatus = properties.getProperty("api"); + if (apiStatus != null) { + if (apiStatus.equals("ON")) { + activateRestApiCheckBox.setSelected(true); + String port = properties.getProperty("server.port"); + String userName = properties.getProperty("spring.security.user.name"); + String userPassword = properties.getProperty("spring.security.user.password"); + + if (port != null) { + authPort.setText(port); + } + if (userName!= null) { + authName.setText(userName); + authPassword.setText(userPassword); + } + } else if (apiStatus.equals("OFF")) { + activateRestApiCheckBox.setSelected(false); + } + } + } catch (IOException e) { + LOG.debug( + "There is currently no additional '{}' file available. This is fine as it should only be present when rest-api is used.", + propertiesFilePath, e); + } + LOG.debug("saveButton.setOnAction"); + saveButton.setOnAction(ae -> { LOG.info("Save clicked"); + if (activateRestApiCheckBox.isSelected()) { + handleApiOn(); + } else { + handleApiOff(); + } + if (!OS.isWindows()) { if (hoverBackgroundColor.getValue().getOpacity() < 0.5) { hoverBackgroundColor.setValue(Color.rgb((int) (hoverBackgroundColor.getValue().getRed() * 255), @@ -256,9 +324,7 @@ private void initialize() { }); LOG.debug("cancelButton.setOnAction"); - cancelButton.setOnAction(ae -> - - { + cancelButton.setOnAction(ae -> { LOG.info("Cancel clicked"); thisStage.close(); }); @@ -274,10 +340,10 @@ private void initialize() { LOG.debug("aboutButton.setOnAction"); initializeAbout(); + } private static void setRegionSvg(Region region, double requiredWidth, double requiredHeight, RESOURCE resource) { - region.setShape(SvgNodeProvider.getSvgNodeWithScale(resource, 1.0, 1.0)); region.setMinSize(requiredWidth, requiredHeight); region.setPrefSize(requiredWidth, requiredHeight); @@ -339,17 +405,16 @@ protected void updateItem(final String item, final boolean empty) { licenseTableView.getColumns().add(nameColumn); licenseTableView.getColumns().add(licenseColumn); - LOG.debug("hyperlink setonaction"); gitHubHyperlink.setOnAction(ae -> { - LOG.debug("hyperlink clicked"); BrowserHelper.openURL(GITHUB_PAGE); }); - - LOG.debug("roportbugbutton setonaction"); reportBugButton.setOnAction(ae -> { - LOG.info("Clicked reportBugButton"); BrowserHelper.openURL(GITHUB_ISSUE_PAGE); }); + swaggerHyperLink.setOnAction(ae -> { + String port = authPort.getText().isEmpty() ? "8080" : authPort.getText(); + BrowserHelper.openURL("http://localhost:" + port + "/api/swagger"); + }); } private void initImportButton() { @@ -363,15 +428,13 @@ private void initImportButton() { confirmationAlert.setTitle("Import"); confirmationAlert.setHeaderText("Do you want to Override current Data ?"); - confirmationAlert.getDialogPane() - .setContent(new Label( - """ - Import previously exported .sql file. This will overwrite the currently used database contents - all current data will be lost! - - If you do not have a .sql file yet you need to open the previous version of KeepTime and in the settings dialog press "Export". - - You will need to restart the application after this action. If you proceed you need to select the previous exported .sql file.\ - """)); + confirmationAlert.getDialogPane().setContent(new Label(""" + Import previously exported .sql file. This will overwrite the currently used database contents - all current data will be lost! + + If you do not have a .sql file yet you need to open the previous version of KeepTime and in the settings dialog press "Export". + + You will need to restart the application after this action. If you proceed you need to select the previous exported .sql file.\ + """)); confirmationAlert.showAndWait(); if (confirmationAlert.getResult() == ButtonType.NO) { @@ -393,11 +456,12 @@ private void initImportButton() { final String password = applicationProperties.getSpringDataSourcePassword(); if (file.getName().contains("H2-version-1")) { - new RunScript().runTool("-url", url, "-user", username, "-password", password, "-script", file.toString(), - "-options", "FROM_1X"); + new RunScript().runTool("-url", url, "-user", username, "-password", password, "-script", + file.toString(), "-options", "FROM_1X"); LOG.info("FROM_1X feature is used"); - }else { - new RunScript().runTool("-url", url, "-user", username, "-password", password, "-script", file.toString()); + } else { + new RunScript().runTool("-url", url, "-user", username, "-password", password, "-script", + file.toString()); } Alert informationDialog = new Alert(AlertType.INFORMATION); @@ -407,11 +471,10 @@ private void initImportButton() { informationDialog.setTitle("Import done"); informationDialog.setHeaderText("The data was imported."); - informationDialog.getDialogPane() - .setContent(new Label(""" - KeepTime will now be CLOSED! - You have to RESTART it again to see the changes\ - """)); + informationDialog.getDialogPane().setContent(new Label(""" + KeepTime will now be CLOSED! + You have to RESTART it again to see the changes\ + """)); informationDialog.showAndWait(); Platform.exit(); @@ -425,9 +488,7 @@ private void initImportButton() { errorDialog.showAndWait(); } - }); - } private void initExportButton() { @@ -503,10 +564,6 @@ public void setStage(final Stage thisStage) { this.thisStage = thisStage; } - private FXMLLoader createFXMLLoader(final RESOURCE fxmlLayout) { - return new FXMLLoader(Resources.getResource(fxmlLayout)); - } - public ObservableList loadLicenseRows() { final ObservableList licenseRows = FXCollections.observableArrayList(); licenseRows.add(new LicenseTableRow("Open Sans", Licenses.APACHEV2)); @@ -518,7 +575,11 @@ public ObservableList loadLicenseRows() { licenseRows.add(new LicenseTableRow("mockito-core", Licenses.MIT)); licenseRows.add(new LicenseTableRow("h2", Licenses.EPLV1)); licenseRows.add(new LicenseTableRow("Font Awesome Icons", Licenses.CC_4_0)); - + licenseRows.add(new LicenseTableRow("mapstruct", Licenses.APACHEV2)); + licenseRows.add(new LicenseTableRow("mapstruct-processor", Licenses.APACHEV2)); + licenseRows.add(new LicenseTableRow("spring-boot-starter-web", Licenses.APACHEV2)); + licenseRows.add(new LicenseTableRow("spring-boot-starter-validation", Licenses.APACHEV2)); + licenseRows.add(new LicenseTableRow("spring-boot-starter-security", Licenses.APACHEV2)); licenseRows.sort(Comparator.comparing(LicenseTableRow::getName)); return licenseRows; @@ -538,4 +599,45 @@ private void showLicense(final Licenses license) { alert.show(); } } + + private void handleApiOff() { + Map propertiesToUpdate = new HashMap<>(); + propertiesToUpdate.put("spring.main.web-application-type", "none"); + propertiesToUpdate.put("api", "OFF"); + propertyWrite(propertiesToUpdate); + } + + private void handleApiOn() { + String username = authName.getText(); + String password = authPassword.getText(); + + Map propertiesToUpdate = new HashMap<>(); + propertiesToUpdate.put("spring.main.web-application-type", ""); + propertiesToUpdate.put("server.port", authPort.getText()); + propertiesToUpdate.put("api", "ON"); + propertiesToUpdate.put("spring.security.user.name", username); + propertiesToUpdate.put("spring.security.user.password", password); + + propertyWrite(propertiesToUpdate); + } + + private void propertyWrite(Map propertiesToUpdate) { + Properties properties = new Properties(); + + try (InputStream inputStream = new FileInputStream(propertiesFilePath)){ + properties.load(inputStream); + } catch (IOException e) { + LOG.debug("Could not open '{}' file. This is most likely fine as it was just not needed before and will be created next.", propertiesFilePath, e); + } + + try(FileOutputStream outputStream = new FileOutputStream(propertiesFilePath)) { + for (Map.Entry entry : propertiesToUpdate.entrySet()) { + properties.setProperty(entry.getKey(), entry.getValue()); + } + properties.store(outputStream, "REST-API settings"); + } catch (IOException e) { + LOG.error("Error while persisting properties: '{}'.", propertiesToUpdate, e); + } + } + } diff --git a/src/main/java/de/doubleslash/keeptime/view/ViewController.java b/src/main/java/de/doubleslash/keeptime/view/ViewController.java index 1088de66..48a03959 100644 --- a/src/main/java/de/doubleslash/keeptime/view/ViewController.java +++ b/src/main/java/de/doubleslash/keeptime/view/ViewController.java @@ -255,9 +255,11 @@ private void initialize() { textAreaColorRunnable.run(); model.activeWorkItem.addListener((a, b, c) -> { - updateProjectView(); - textArea.setText(""); - textArea.requestFocus(); + Platform.runLater(() -> { + updateProjectView(); + textArea.setText(""); + textArea.requestFocus(); + }); }); model.defaultBackgroundColor.addListener((a, b, c) -> updateMainBackgroundColor.run()); @@ -297,7 +299,6 @@ private void initialize() { .bind(Bindings.createStringBinding( () -> DateFormatter.secondsToHHMMSS(activeWorkSecondsProperty.get()), activeWorkSecondsProperty)); - // update ui each second new Interval(1).registerCallBack(() -> { final LocalDateTime now = LocalDateTime.now(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fbda1171..ed26591e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -22,4 +22,10 @@ spring.datasource.driver-class-name=org.h2.Driver spring.flyway.baselineOnMigrate=true spring.flyway.baselineVersion=0.0.0 -spring.jpa.hibernate.ddl-auto=validate \ No newline at end of file +spring.jpa.hibernate.ddl-auto=validate + +# REST API +server.port=8080 +spring.main.web-application-type=none +springdoc.api-docs.path=/api/openapi +springdoc.swagger-ui.path=/api/swagger diff --git a/src/main/resources/layouts/settings.fxml b/src/main/resources/layouts/settings.fxml index 2af1ce80..7a145cac 100644 --- a/src/main/resources/layouts/settings.fxml +++ b/src/main/resources/layouts/settings.fxml @@ -24,9 +24,11 @@ + + @@ -37,281 +39,387 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - + - - - - - - - + - - - - - - - - - - - + - + - + - - - - - + - + - - - - - - - - + @@ -320,110 +428,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -435,136 +449,213 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/de/doubleslash/keeptime/rest/controller/ProjectMapperTest.java b/src/test/java/de/doubleslash/keeptime/rest/controller/ProjectMapperTest.java new file mode 100644 index 00000000..cfe2653c --- /dev/null +++ b/src/test/java/de/doubleslash/keeptime/rest/controller/ProjectMapperTest.java @@ -0,0 +1,70 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +package de.doubleslash.keeptime.rest.controller; + +import de.doubleslash.keeptime.rest.DTO.ProjectDTO; +import de.doubleslash.keeptime.rest.mapper.ProjectMapper; +import de.doubleslash.keeptime.model.Project; +import javafx.scene.paint.Color; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + + +@SpringBootTest +class ProjectMapperTest { + + @Autowired + ProjectMapper projectMapper; + + @Test + void projectToProjectDTO() { + //ARRANGE + Project project = new Project(); + project.setName("ProjectName"); + project.setDescription("ProjectDescription"); + project.setColor(Color.BLUE); + project.setIndex(0); + project.setDefault(true); + project.setEnabled(true); + project.setWork(false); + + //ACT + final ProjectDTO projectDTO = projectMapper.projectToProjectDTO(project); + //Assert + assertEquals("0x0000ffff", projectDTO.getColor()); + + } + + @Test + void projectDTOToProject() { + // ARRANGE + ProjectDTO project = new ProjectDTO(1, "ProjectName", "ProjectDescription", "0xff0000ff", false, 0, true); + + // ACT + final Project project1 = projectMapper.projectDTOToProject(project); + + // ASSERT + assertEquals(Color.RED, project1.getColor()); + assertEquals("ProjectName", project1.getName()); + assertEquals("ProjectDescription", project1.getDescription()); + assertTrue(project1.isEnabled()); + } +} diff --git a/src/test/java/de/doubleslash/keeptime/rest/controller/WorkMapperTest.java b/src/test/java/de/doubleslash/keeptime/rest/controller/WorkMapperTest.java new file mode 100644 index 00000000..e4fbceef --- /dev/null +++ b/src/test/java/de/doubleslash/keeptime/rest/controller/WorkMapperTest.java @@ -0,0 +1,82 @@ +// Copyright 2024 doubleSlash Net Business GmbH +// +// This file is part of KeepTime. +// KeepTime is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +package de.doubleslash.keeptime.rest.controller; + +import de.doubleslash.keeptime.rest.DTO.ProjectIdentificationDTO; +import de.doubleslash.keeptime.rest.DTO.WorkDTO; +import de.doubleslash.keeptime.rest.mapper.WorkMapper; +import de.doubleslash.keeptime.rest.mapper.WorkMapperImpl; +import de.doubleslash.keeptime.model.Project; +import de.doubleslash.keeptime.model.Work; +import javafx.scene.paint.Color; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + + +class WorkMapperTest { + + WorkMapper workMapper = new WorkMapperImpl(); + + @Test + void workToWorkDTO() { + // ARRANGE + final Project project = new Project(); + project.setName("ProjectName"); + project.setColor(Color.BLUE); + project.setDescription("ProjectDescription"); + + final LocalDateTime from = LocalDateTime.now(); + final LocalDateTime to = LocalDateTime.now(); + final Work work = new Work(from, to, project, "Did something"); + + // ACT + final WorkDTO workDTO = workMapper.workToWorkDTO(work); + + // ASSERT + assertEquals(work.getId() ,workDTO.getId()); + assertEquals(from ,workDTO.getStartTime()); + assertEquals(to ,workDTO.getEndTime()); + assertEquals("Did something", workDTO.getNotes()); + + assertEquals(project.getId(), workDTO.getProject().getId()); + } + + @Test + public void workDTOToWork() { + // Arrange + LocalDateTime startTime = LocalDateTime.of(2024, 4, 22, 9, 0); + LocalDateTime endTime = LocalDateTime.of(2024, 4, 22, 17, 0); + ProjectIdentificationDTO projectIdentificationDTO = new ProjectIdentificationDTO(0); + String notes = "Test Notizen"; + WorkDTO workDTO = new WorkDTO(1,startTime, endTime, projectIdentificationDTO, notes); + + // Act + Work work = workMapper.workDTOToWork(workDTO); + + // Assert + assertNotNull(work); + assertEquals(startTime, work.getStartTime()); + assertEquals(endTime, work.getEndTime()); + assertNotNull(work.getProject()); + assertEquals(projectIdentificationDTO.getId(), work.getProject().getId()); + assertEquals(notes, work.getNotes()); + } +} \ No newline at end of file