Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: find project by project done. #33

Merged
merged 4 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.seyed.ali.timeentryservice.client;

import com.seyed.ali.timeentryservice.keycloak.util.KeycloakSecurityUtil;
import com.seyed.ali.timeentryservice.model.payload.ProjectDTO;
import com.seyed.ali.timeentryservice.model.payload.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
Expand Down Expand Up @@ -37,4 +38,10 @@ public boolean isProjectValid(String projectId) {
return (boolean) booleanResult.getData();
}

public ProjectDTO getProjectByNameOrId(String projectInfo) {
String url = this.projectServiceBaseURL + "/projects?identifier=" + projectInfo;
return this.sendRequest(url, HttpMethod.GET, new ParameterizedTypeReference<>() {
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -117,4 +118,22 @@ public ResponseEntity<Result> deleteTimeEntry(@PathVariable String timeEntryId)
));
}

// ###################################################################################
@GetMapping("/project/{projectCriteria}")
@Operation(summary = "Get all time entries by project(ID or Name)", description = "Fetches all time entries from the database based on name or ID", responses = {
@ApiResponse(
responseCode = "200",
description = "Successful operation",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = TimeEntry.class)))
)
})
public ResponseEntity<Result> getTimeEntriesByProject(@PathVariable String projectCriteria) {
return ResponseEntity.ok(new Result(
true,
OK,
"TimeEntries - Project",
this.timeEntryService.getTimeEntriesByProjectCriteria(projectCriteria)
));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.seyed.ali.timeentryservice.exceptions.OperationNotSupportedException;
import com.seyed.ali.timeentryservice.exceptions.ResourceNotFoundException;
import com.seyed.ali.timeentryservice.model.payload.response.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
Expand All @@ -14,6 +15,7 @@
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.net.ConnectException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -53,6 +55,16 @@ public ResponseEntity<Result> handleOperationNotSupportedException(OperationNotS
));
}

@ExceptionHandler({ConnectException.class})
public ResponseEntity<Result> handleConnectException(ConnectException e) {
return ResponseEntity.status(SERVICE_UNAVAILABLE).body(new Result(
false,
SERVICE_UNAVAILABLE,
"The service is not available 👎🏻",
"ServerMessage 🚫 - " + e.getMessage()
));
}

@ExceptionHandler({MethodArgumentNotValidException.class})
public ResponseEntity<Result> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.seyed.ali.timeentryservice.model.domain;

import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
Expand Down Expand Up @@ -30,6 +31,7 @@ public class TimeEntry implements Serializable {
@OneToMany(mappedBy = "timeEntry", cascade = CascadeType.ALL)
@ToString.Exclude
@Builder.Default
@JsonManagedReference // this is the forward part of the relationship – the one that gets serialized normally
private List<TimeSegment> timeSegmentList = new ArrayList<>();

private String userId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.seyed.ali.timeentryservice.model.domain;

import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
Expand All @@ -26,6 +27,7 @@ public class TimeSegment implements Serializable {
private Duration duration;

@ManyToOne(cascade = CascadeType.ALL)
@JsonBackReference // this is the back part of the relationship – it will be omitted from serialization to avoid the infinite loop
private TimeEntry timeEntry;

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

Expand All @@ -13,6 +14,7 @@
* DTO for {@link com.seyed.ali.projectservice.model.domain.Project}
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProjectDTO implements Serializable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@
@AllArgsConstructor
public class TimeEntryDTO {

// #######
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for the time entry", example = "12345")
@Size(max = 36, message = "timeEntryId must be maximum 36 characters")
private String timeEntryId;

@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Unique identifier for the associated time entry", example = "12345")
@NotBlank(message = "projectId is mandatory and cannot be blank")
@NotNull(message = "projectId is mandatory and cannot be null")
private String projectId;

@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for the task to assign the time entry with", example = "12345")
private String taskId;

// #######
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Start time of the time entry in the format yyyy-MM-dd HH:mm", example = "2024-05-12 08:00:00")
@NotBlank(message = "startTime is mandatory") @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$", message = "startTime must be in the format yyyy-MM-dd HH:mm:ss")
private String startTime;
Expand All @@ -45,12 +55,4 @@ public class TimeEntryDTO {
@Pattern(regexp = "^\\d{2}:\\d{2}:\\d{2}$", message = "duration must be in the format HH:mm:ss")
private String duration;

@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Unique identifier for the associated time entry", example = "12345")
@NotBlank(message = "projectId is mandatory and cannot be blank")
@NotNull(message = "projectId is mandatory and cannot be null")
private String projectId;

@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for the task to assign the time entry with", example = "12345")
private String taskId;

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@
@NoArgsConstructor
public class TimeEntryResponse {

// #######
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for the time entry", example = "12345")
private String timeEntryId;

@ArraySchema(schema = @Schema(implementation = TimeSegmentDTO.class))
private List<TimeSegmentDTO> timeSegmentDTOList;
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for associated project with the time entry", example = "12345")
private String projectId;

@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for the task to associate the task with time entry", example = "12345")
private String taskId;

// #######
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "A flag determining this time entry is billable", example = "true")
private boolean billable;

Expand All @@ -31,10 +36,8 @@ public class TimeEntryResponse {
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Total time recorded", example = "00:00:18")
private String totalDuration;

@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for associated project with the time entry", example = "12345")
private String projectId;

@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Unique identifier for the task to associate the task with time entry", example = "12345")
private String taskId;
// #######
@ArraySchema(schema = @Schema(implementation = TimeSegmentDTO.class))
private List<TimeSegmentDTO> timeSegmentDTOList;

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.seyed.ali.timeentryservice.service;

import com.seyed.ali.timeentryservice.client.ProjectServiceClient;
import com.seyed.ali.timeentryservice.exceptions.ResourceNotFoundException;
import com.seyed.ali.timeentryservice.model.domain.TimeEntry;
import com.seyed.ali.timeentryservice.model.domain.TimeSegment;
import com.seyed.ali.timeentryservice.model.payload.ProjectDTO;
import com.seyed.ali.timeentryservice.model.payload.TimeEntryDTO;
import com.seyed.ali.timeentryservice.repository.TimeEntryRepository;
import com.seyed.ali.timeentryservice.service.cache.TimeEntryCacheManager;
Expand All @@ -27,6 +29,7 @@ public class TimeEntryServiceImpl implements TimeEntryService {
private final TimeParser timeParser;
private final TimeEntryUtility timeEntryUtility;
private final TimeEntryCacheManager timeEntryCacheManager;
private final ProjectServiceClient projectServiceClient;

/**
* {@inheritDoc}
Expand Down Expand Up @@ -128,4 +131,14 @@ public void deleteTimeEntry(TimeEntry timeEntry) {
this.timeEntryRepository.delete(foundTimeEntry);
}

/**
* {@inheritDoc}
*/
@Override
@Transactional
public List<TimeEntry> getTimeEntriesByProjectCriteria(String projectCriteria) throws ResourceNotFoundException {
ProjectDTO projectDTO = this.projectServiceClient.getProjectByNameOrId(projectCriteria);
return this.timeEntryRepository.findByProjectId(projectDTO.getProjectId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,16 @@ public TimeEntryDTO stopTrackingTimeEntry(String timeEntryId) {
String endTimeStr = this.timeParser.parseLocalDateTimeToString(endTime);
String durationStr = this.timeParser.parseDurationToString(totalDuration);

return new TimeEntryDTO(null, startTimeStr, endTimeStr, timeEntry.isBillable(), timeEntry.getHourlyRate().toString(), durationStr, timeEntry.getProjectId(), timeEntry.getTaskId());
return TimeEntryDTO.builder()
.timeEntryId(null)
.projectId(timeEntry.getProjectId())
.taskId(timeEntry.getTaskId())
.startTime(startTimeStr)
.endTime(endTimeStr)
.duration(durationStr)
.billable(timeEntry.isBillable())
.hourlyRate(timeEntry.getHourlyRate().toString())
.build();
}

/**
Expand All @@ -98,7 +107,16 @@ public TimeEntryDTO continueTrackingTimeEntry(String timeEntryId) {

String hourlyRate = timeEntry.getHourlyRate() != null ? timeEntry.getHourlyRate().toString() : null;
String startTimeStr = this.timeParser.parseLocalDateTimeToString(timeEntry.getTimeSegmentList().getLast().getStartTime());
return new TimeEntryDTO(timeEntryId, startTimeStr, null, timeEntry.isBillable(), hourlyRate, null, timeEntry.getProjectId(), timeEntry.getTaskId());
return TimeEntryDTO.builder()
.timeEntryId(timeEntryId)
.projectId(timeEntry.getProjectId())
.taskId(timeEntry.getTaskId())
.startTime(startTimeStr)
.endTime(null)
.duration(null)
.billable(timeEntry.isBillable())
.hourlyRate(hourlyRate)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,13 @@ public interface TimeEntryService {
*/
void deleteTimeEntry(TimeEntry timeEntry);

/**
* Fetches the time-entries by project(either it's ID or Name).
*
* @param projectCriteria either the ID or the Name of the project.
* @return List of Found TimeEntries.
* @throws ResourceNotFoundException If the project is not found.
*/
List<TimeEntry> getTimeEntriesByProjectCriteria(String projectCriteria) throws ResourceNotFoundException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,15 @@ public TimeEntryResponse convertToTimeEntryResponse(TimeEntry timeEntry) {
}

String totalDurationStr = this.timeParser.parseDurationToString(totalDuration);
return new TimeEntryResponse(timeEntry.getTimeEntryId(), timeSegmentDTOList, timeEntry.isBillable(), hourlyRate, totalDurationStr, timeEntry.getProjectId(), timeEntry.getTaskId());
return TimeEntryResponse.builder()
.timeEntryId(timeEntry.getTimeEntryId())
.projectId(timeEntry.getProjectId())
.taskId(timeEntry.getTaskId())
.totalDuration(totalDurationStr)
.billable(timeEntry.isBillable())
.hourlyRate(hourlyRate)
.timeSegmentDTOList(timeSegmentDTOList)
.build();
}

/**
Expand All @@ -90,7 +98,16 @@ public TimeEntryDTO createTimeEntryDTO(TimeEntry timeEntry, TimeSegment lastTime
: null;
String endTimeStr = this.timeParser.parseLocalDateTimeToString(lastTimeSegment.getEndTime());
String durationStr = this.timeParser.parseDurationToString(lastTimeSegment.getDuration());
return new TimeEntryDTO(timeEntry.getTimeEntryId(), startTimeString, endTimeStr, timeEntry.isBillable(), hourlyRate, durationStr, timeEntry.getProjectId(), timeEntry.getTaskId());
return TimeEntryDTO.builder()
.timeEntryId(timeEntry.getTimeEntryId())
.projectId(timeEntry.getProjectId())
.taskId(timeEntry.getTaskId())
.startTime(startTimeString)
.endTime(endTimeStr)
.billable(timeEntry.isBillable())
.hourlyRate(hourlyRate)
.duration(durationStr)
.build();
}

/**
Expand Down
Loading