From 9f8fd0af1e8d1674d41ed68850446c13c1924a09 Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Wed, 20 May 2020 02:03:03 +0200 Subject: [PATCH] Implement custom record searches Create record overview projection Add today search with scoped principal Add sample data for records --- .../geotime/controllers/RecordController.java | 26 +++++++++++++ .../de/hft/geotime/entities/TimeRecord.java | 21 +++++++--- .../geotime/entities/TimetrackAccount.java | 7 ++++ .../hft/geotime/entities/TimetrackUser.java | 6 ++- .../projections/RecordOverviewProjection.java | 27 +++++++++++++ .../repositories/RecordRepository.java | 38 ++++++++++++++++++- .../security/SecurityConfiguration.java | 14 +++++++ .../SecurityEvaluationContextExtension.java | 21 ++++++++++ backend/src/main/resources/data.sql | 13 +++++-- 9 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 backend/src/main/java/de/hft/geotime/controllers/RecordController.java create mode 100644 backend/src/main/java/de/hft/geotime/entities/projections/RecordOverviewProjection.java create mode 100644 backend/src/main/java/de/hft/geotime/security/SecurityConfiguration.java create mode 100644 backend/src/main/java/de/hft/geotime/security/SecurityEvaluationContextExtension.java diff --git a/backend/src/main/java/de/hft/geotime/controllers/RecordController.java b/backend/src/main/java/de/hft/geotime/controllers/RecordController.java new file mode 100644 index 0000000..716aaba --- /dev/null +++ b/backend/src/main/java/de/hft/geotime/controllers/RecordController.java @@ -0,0 +1,26 @@ +package de.hft.geotime.controllers; + +import de.hft.geotime.repositories.RecordRepository; +import de.hft.geotime.repositories.TimetrackUserRepository; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class RecordController { + + private final RecordRepository recordRepository; + private final TimetrackUserRepository userRepository; + private final ProjectionFactory projectionFactory; + + public RecordController(RecordRepository recordRepository, TimetrackUserRepository userRepository, ProjectionFactory projectionFactory) { + this.recordRepository = recordRepository; + this.userRepository = userRepository; + this.projectionFactory = projectionFactory; + } + + @GetMapping("/track") + public void track() { + //"/track?accountid=bla" → start/stop recording for that account + } +} diff --git a/backend/src/main/java/de/hft/geotime/entities/TimeRecord.java b/backend/src/main/java/de/hft/geotime/entities/TimeRecord.java index 8a54422..b1914cd 100644 --- a/backend/src/main/java/de/hft/geotime/entities/TimeRecord.java +++ b/backend/src/main/java/de/hft/geotime/entities/TimeRecord.java @@ -5,8 +5,8 @@ import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; -import java.time.Duration; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; @Data @NoArgsConstructor @@ -17,11 +17,20 @@ public class TimeRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; - @OneToOne(fetch = FetchType.LAZY) + + @ManyToOne private TimetrackAccount account; - private Date startdate; - private Date enddate; - private Duration time; + + @Column(columnDefinition = "TIMESTAMP") + private LocalDateTime startdate; + + @Column(columnDefinition = "TIMESTAMP") + private LocalDateTime enddate; + private RecordType type; + public long getDuration() { + return startdate.until(enddate, ChronoUnit.MINUTES); + } + } diff --git a/backend/src/main/java/de/hft/geotime/entities/TimetrackAccount.java b/backend/src/main/java/de/hft/geotime/entities/TimetrackAccount.java index 9ee6191..58d7cc2 100644 --- a/backend/src/main/java/de/hft/geotime/entities/TimetrackAccount.java +++ b/backend/src/main/java/de/hft/geotime/entities/TimetrackAccount.java @@ -1,10 +1,12 @@ package de.hft.geotime.entities; +import com.fasterxml.jackson.annotation.JsonBackReference; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; +import java.util.List; @Data @NoArgsConstructor @@ -18,7 +20,12 @@ public class TimetrackAccount { private double revenue; private String name; private String description; + + @JsonBackReference @ManyToOne private TimetrackUser user; + @OneToMany(mappedBy = "account") + private List records; + } diff --git a/backend/src/main/java/de/hft/geotime/entities/TimetrackUser.java b/backend/src/main/java/de/hft/geotime/entities/TimetrackUser.java index d3b6b8c..9aa9c6e 100644 --- a/backend/src/main/java/de/hft/geotime/entities/TimetrackUser.java +++ b/backend/src/main/java/de/hft/geotime/entities/TimetrackUser.java @@ -1,6 +1,7 @@ package de.hft.geotime.entities; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonManagedReference; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -28,10 +29,11 @@ public class TimetrackUser { private String lastname; - @OneToOne(fetch = FetchType.EAGER) + @ManyToOne(fetch = FetchType.EAGER) private Role role; - @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JsonManagedReference + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private List accounts; @ManyToOne diff --git a/backend/src/main/java/de/hft/geotime/entities/projections/RecordOverviewProjection.java b/backend/src/main/java/de/hft/geotime/entities/projections/RecordOverviewProjection.java new file mode 100644 index 0000000..a92c091 --- /dev/null +++ b/backend/src/main/java/de/hft/geotime/entities/projections/RecordOverviewProjection.java @@ -0,0 +1,27 @@ +package de.hft.geotime.entities.projections; + +import de.hft.geotime.entities.TimeRecord; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.rest.core.config.Projection; + +import java.time.LocalDateTime; + +@Projection(name = "overview", types = TimeRecord.class) +public interface RecordOverviewProjection { + + LocalDateTime getStartdate(); + + LocalDateTime getEnddate(); + + long getDuration(); + + @Value("#{target.type.name()}") + String getType(); + + @Value("#{target.account.name}") + String getAccount(); + + @Value("#{target.account.user.username}") + String getUsername(); + +} diff --git a/backend/src/main/java/de/hft/geotime/repositories/RecordRepository.java b/backend/src/main/java/de/hft/geotime/repositories/RecordRepository.java index 765b6e9..2191a80 100644 --- a/backend/src/main/java/de/hft/geotime/repositories/RecordRepository.java +++ b/backend/src/main/java/de/hft/geotime/repositories/RecordRepository.java @@ -1,11 +1,15 @@ package de.hft.geotime.repositories; import de.hft.geotime.entities.TimeRecord; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.format.annotation.DateTimeFormat; -import java.util.List; +import java.time.LocalDateTime; @RepositoryRestResource( path = "records", @@ -14,4 +18,34 @@ import java.util.List; ) public interface RecordRepository extends PagingAndSortingRepository { + @RestResource(rel = "allBetween", path = "allBetween") + Page findAllByStartdateBetween( + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime start, + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime end, + Pageable pageable + ); + + @RestResource(rel = "allBetweenAndUser", path = "allBetweenAndUser") + Page findAllByStartdateBetweenAndAccount_User_Username( + LocalDateTime start, + LocalDateTime end, + String username, + Pageable pageable + ); + + @RestResource(rel = "allForUser", path = "allForUser") + Page findAllByAccount_User_Username(String username, Pageable pageable); + + @RestResource(rel = "allForUserAndAccount", path = "allForUserAndAccount") + Page findAllByAccount_User_UsernameAndAccount_Name(String username, String account, Pageable pageable); + + @RestResource(rel = "allFrom", path = "allFrom") + Page findAllByStartdateGreaterThanEqual( + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime date, + Pageable pageable + ); + + @Query("SELECT record from TimeRecord record where record.account.user.username = :#{principal} AND record.startdate > (current_date-1)") + Page today(Pageable pageable); + } diff --git a/backend/src/main/java/de/hft/geotime/security/SecurityConfiguration.java b/backend/src/main/java/de/hft/geotime/security/SecurityConfiguration.java new file mode 100644 index 0000000..4d86016 --- /dev/null +++ b/backend/src/main/java/de/hft/geotime/security/SecurityConfiguration.java @@ -0,0 +1,14 @@ +package de.hft.geotime.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.spel.spi.EvaluationContextExtension; + +@Configuration +class SecurityConfiguration { + + @Bean + EvaluationContextExtension securityExtension() { + return new SecurityEvaluationContextExtension(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/de/hft/geotime/security/SecurityEvaluationContextExtension.java b/backend/src/main/java/de/hft/geotime/security/SecurityEvaluationContextExtension.java new file mode 100644 index 0000000..5e42052 --- /dev/null +++ b/backend/src/main/java/de/hft/geotime/security/SecurityEvaluationContextExtension.java @@ -0,0 +1,21 @@ +package de.hft.geotime.security; + +import org.springframework.data.spel.spi.EvaluationContextExtension; +import org.springframework.security.access.expression.SecurityExpressionRoot; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +public class SecurityEvaluationContextExtension implements EvaluationContextExtension { + + @Override + public String getExtensionId() { + return "security"; + } + + @Override + public SecurityExpressionRoot getRootObject() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return new SecurityExpressionRoot(authentication) { + }; + } +} \ No newline at end of file diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index 3f9bdf3..84c0f16 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -20,8 +20,15 @@ INSERT INTO timetrack_user (id, firstname, lastname, password, username, role_id (3, 'Tim', 'Zieger' ,'$2y$10$pYGHZhoaelceImO7aIN4nOkWJBp.oqNGFYaRAonHkYF4u9ljqPelC', 'ziti', 1, 1), (4, 'Simon', 'Kellner' ,'$2y$10$Puzm/Nr/Dyq3nQxlkXGIfubS5JPtXJSOf2e6mrQ6HhVYQN9YiQQsC', 'kesi', 1, 1); -INSERT INTO timetrack_account (description, `name`, revenue, user_id) VALUES - ('Gleitzeit Marcel', 'Primary Marcel', 16.0, 1), - ('Festgeld Marcel', 'Secondary Marcel', 25.0, 1); +INSERT INTO timetrack_account (id, description, `name`, revenue, user_id) VALUES + (1, 'Gleitzeit Marcel', 'Primary', 16.0, 1), + (2, 'Festgeld Marcel', 'Secondary', 25.0, 1), + (3, 'Festgeld Tim', 'Primary', 25.0, 3); + +INSERT INTO time_record (id, enddate, startdate, `type`, account_id) VALUES + (1, '2020-05-10 16:00:00', '2020-05-10 12:00:00', 0, 1), + (2, '2020-05-09 16:00:00', '2020-05-09 12:00:00', 1, 1), + (3, '2020-05-20 16:00:00', '2020-05-20 00:00:00', 1, 2), + (4, '2020-05-11 16:00:00', '2020-05-11 12:00:00', 1, 3); SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file