Skip to content
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
Expand Up @@ -9,8 +9,7 @@
* Configuration properties for mentorship-related settings. This class binds to properties under
* the prefix "mentorship" in the application configuration file.
*
* <p>Properties: - daysCycleOpen: Number of days a mentorship cycle remains open for registration.
* Default is 10. - validation.enabled: Flag to enable or disable mentorship cycle validation during
* <p>Properties: - validation.enabled: Flag to enable or disable mentorship cycle validation during
* mentee registration. Default is true. Set to false for testing and debugging purposes.
*/
@Getter
Expand All @@ -19,12 +18,6 @@
@ConfigurationProperties(prefix = "mentorship")
public class MentorshipConfig {

/** the default n. days for the mentorship cycle remains open from the beginning of the month. */
private static final int DEFAULT_DAYS = 10;

/** Number of days a mentorship cycle remains open for registration. */
private int daysCycleOpen = DEFAULT_DAYS;

/** Validation-specific configuration. */
private Validation validation = new Validation();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wcc.platform.configuration;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
Expand Down Expand Up @@ -41,6 +42,7 @@ public ObjectMapper objectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();

objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class MemberDto {
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id;

private String fullName;
private String position;
private String email;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wcc.platform.domain.platform.mentorship;

import com.wcc.platform.domain.cms.pages.mentorship.MentorsPage;
import java.time.LocalDate;
import java.time.Month;
import java.time.Year;
Expand Down Expand Up @@ -52,11 +53,20 @@ public boolean isActive() {
}

/**
* Convert to MentorshipCycle value object for backward compatibility.
* Convert to MentorsPage.OpenCycle for backward compatibility.
*
* @return MentorsPage.OpenCycle value object
*/
public MentorsPage.OpenCycle toOpenCycleValue() {
return new MentorshipCycle(mentorshipType, cycleMonth).toOpenCycle();
}

/**
* Convert to MentorshipCycle value object.
*
* @return MentorshipCycle value object
*/
public MentorshipCycle toMentorshipCycle() {
return new MentorshipCycle(mentorshipType, cycleMonth != null ? cycleMonth : null);
return new MentorshipCycle(mentorshipType, cycleMonth);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public class PostgresMenteeApplicationRepository implements MenteeApplicationRep

private static final String INSERT_APPLICATION =
"INSERT INTO mentee_applications "
+ "(mentee_id, mentor_id, cycle_id, priority_order, application_status, application_message, why_mentor) "
+ "(mentee_id, mentor_id, cycle_id, priority_order, "
+ "application_status, application_message, why_mentor) "
+ "VALUES (?, ?, ?, ?, ?::application_status, ?, ?) "
+ "RETURNING application_id";

Expand Down Expand Up @@ -163,8 +164,7 @@ public List<MenteeApplication> getAll() {

@Override
public Long countMenteeApplications(final Long menteeId, final Long cycleId) {
final Long count = jdbc.queryForObject(COUNT_MENTEE_APPS, Long.class, menteeId, cycleId);
return count != null ? count : 0L;
return jdbc.queryForObject(COUNT_MENTEE_APPS, Long.class, menteeId, cycleId);
}

private MenteeApplication mapRow(final ResultSet rs) throws SQLException {
Expand Down
63 changes: 12 additions & 51 deletions src/main/java/com/wcc/platform/service/MenteeService.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
package com.wcc.platform.service;

import com.wcc.platform.configuration.MentorshipConfig;
import com.wcc.platform.domain.exceptions.*;
import com.wcc.platform.domain.platform.member.Member;
import com.wcc.platform.domain.platform.mentorship.CycleStatus;
import com.wcc.platform.domain.platform.mentorship.Mentee;
import com.wcc.platform.domain.platform.mentorship.MenteeApplication;
import com.wcc.platform.domain.platform.mentorship.MenteeRegistration;
import com.wcc.platform.domain.platform.mentorship.MentorshipCycle;
import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity;
import com.wcc.platform.domain.platform.mentorship.MentorshipType;
import com.wcc.platform.domain.platform.type.MemberType;
import com.wcc.platform.domain.platform.type.RoleType;
import com.wcc.platform.repository.MemberRepository;
import com.wcc.platform.repository.MenteeApplicationRepository;
import com.wcc.platform.repository.MenteeRepository;
import com.wcc.platform.repository.MentorRepository;
import com.wcc.platform.repository.MentorshipCycleRepository;
import java.time.Year;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -28,11 +23,7 @@
@Service
@AllArgsConstructor
public class MenteeService {

private static final int MAX_MENTORS = 5;

private final MentorshipService mentorshipService;
private final MentorshipConfig mentorshipConfig;
private final MentorshipCycleRepository cycleRepository;
private final MenteeApplicationRepository registrationsRepo;
private final MenteeRepository menteeRepository;
Expand Down Expand Up @@ -62,8 +53,14 @@ public List<Mentee> getAllMentees() {
public Mentee saveRegistration(final MenteeRegistration request) {
validateMentors(request);

final Year cycleYear = request.cycleYear() != null ? request.cycleYear() : Year.now();
final var cycle = getMentorshipCycle(request.mentorshipType(), cycleYear);
final var cycle = getCurrentCycle();

if (!request.mentorshipType().equals(cycle.getMentorshipType())) {
throw new InvalidMentorshipTypeException(
String.format(
"Mentee mentorship type '%s' does not match current cycle type '%s'",
request.mentorshipType(), cycle.getMentorshipType()));
}

final var menteeId = ensureMenteeId(request.mentee());
if (menteeId != null) {
Expand Down Expand Up @@ -255,49 +252,13 @@ private void validateMentors(final MenteeRegistration registrationRequest) {
* Retrieves the MentorshipCycleEntity for the given mentorship type and cycle year. Validates
* that the cycle is open and the mentorship type matches the current cycle.
*
* @param mentorshipType The type of mentorship for which the cycle is being retrieved.
* @param cycleYear The year of the mentorship cycle.
* @return The MentorshipCycleEntity corresponding to the specified type and year.
* @throws MentorshipCycleClosedException If the mentorship cycle is closed.
* @throws InvalidMentorshipTypeException If the mentorship type does not match the current cycle
* type.
*/
private MentorshipCycleEntity getMentorshipCycle(
final MentorshipType mentorshipType, final Year cycleYear) {
final var openCycle = cycleRepository.findByYearAndType(cycleYear, mentorshipType);

if (openCycle.isPresent()) {
final MentorshipCycleEntity cycle = openCycle.get();

if (mentorshipConfig.getValidation().isEnabled() && cycle.getStatus() != CycleStatus.OPEN) {
throw new MentorshipCycleClosedException(
String.format(
"Mentorship cycle for %s in %d is %s. Registration is not available.",
mentorshipType, cycleYear.getValue(), cycle.getStatus()));
}

return cycle;
}

// Fallback to old mentorship service validation for backward compatibility
final MentorshipCycle currentCycle = mentorshipService.getCurrentCycle();

if (currentCycle == MentorshipService.CYCLE_CLOSED) {
throw new MentorshipCycleClosedException(
"Mentorship cycle is currently closed. Registration is not available.");
}

if (mentorshipType != currentCycle.cycle()) {
throw new InvalidMentorshipTypeException(
String.format(
"Mentee mentorship type '%s' does not match current cycle type '%s'.",
mentorshipType, currentCycle.cycle()));
}

return MentorshipCycleEntity.builder()
.cycleYear(cycleYear)
.mentorshipType(mentorshipType)
.build();
private MentorshipCycleEntity getCurrentCycle() {
return cycleRepository
.findOpenCycle()
.orElseThrow(() -> new MentorshipCycleClosedException("Mentorship cycle is closed"));
}

/**
Expand Down
86 changes: 26 additions & 60 deletions src/main/java/com/wcc/platform/service/MentorshipService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,45 @@
import com.wcc.platform.domain.exceptions.MemberNotFoundException;
import com.wcc.platform.domain.exceptions.MentorStatusException;
import com.wcc.platform.domain.platform.member.ProfileStatus;
import com.wcc.platform.domain.platform.mentorship.CycleStatus;
import com.wcc.platform.domain.platform.mentorship.Mentor;
import com.wcc.platform.domain.platform.mentorship.MentorDto;
import com.wcc.platform.domain.platform.mentorship.MentorshipCycle;
import com.wcc.platform.domain.platform.mentorship.MentorshipType;
import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity;
import com.wcc.platform.domain.platform.type.RoleType;
import com.wcc.platform.domain.resource.MemberProfilePicture;
import com.wcc.platform.domain.resource.Resource;
import com.wcc.platform.repository.MemberProfilePictureRepository;
import com.wcc.platform.repository.MemberRepository;
import com.wcc.platform.repository.MentorRepository;
import com.wcc.platform.repository.MentorshipCycleRepository;
import com.wcc.platform.utils.FiltersUtil;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/** Platform Service. */
@Slf4j
@Service
@AllArgsConstructor
@SuppressWarnings({
"PMD.ExcessiveImports",
"PMD.TooManyMethods"
}) // TODO: https://github.com/Women-Coding-Community/wcc-backend/issues/520
public class MentorshipService {

/* package */ static final MentorshipCycle CYCLE_CLOSED = new MentorshipCycle(null, null);
public static final MentorshipCycleEntity CLOSED_CYCLE =
MentorshipCycleEntity.builder().status(CycleStatus.CLOSED).build();

private static final String EUROPE_LONDON = "Europe/London";
private static final MentorshipCycle ACTIVE_LONG_TERM =
new MentorshipCycle(MentorshipType.LONG_TERM, Month.MARCH);
private static final int MINIMUM_HOURS = 2;

private final MentorRepository mentorRepository;
private final MemberRepository memberRepository;
private final MentorshipCycleRepository cycleRepository;
private final UserProvisionService userProvisionService;
private final MemberProfilePictureRepository profilePicRepo;
private final int daysCycleOpen;
private final MentorshipNotificationService notificationService;

@Autowired
public MentorshipService(
final MentorRepository mentorRepository,
final MemberRepository memberRepository,
final UserProvisionService userProvisionService,
final MemberProfilePictureRepository profilePicRepo,
final @Value("${mentorship.daysCycleOpen}") int daysCycleOpen,
final MentorshipNotificationService notificationService) {
this.mentorRepository = mentorRepository;
this.memberRepository = memberRepository;
this.userProvisionService = userProvisionService;
this.profilePicRepo = profilePicRepo;
this.daysCycleOpen = daysCycleOpen;
this.notificationService = notificationService;
}

/**
* Create a mentor record.
*
Expand Down Expand Up @@ -136,16 +114,16 @@ public MentorsPage getMentorsPage(
final var mentors = FiltersUtil.applyFilters(getAllActiveMentors(currentCycle), filters);

return mentorsPage.updateUpdate(
currentCycle.toOpenCycle(), FiltersUtil.mentorshipAllFilters(), mentors);
currentCycle.toOpenCycleValue(), FiltersUtil.mentorshipAllFilters(), mentors);
}

public MentorsPage getMentorsPage(final MentorsPage mentorsPage) {
return getMentorsPage(mentorsPage, null);
}

/**
* Return all mentors ignoring their status and the current cycle.
* Intended for privileged (admin/leader) use only.
* Return all mentors ignoring their status and the current cycle. Intended for privileged
* (admin/leader) use only.
*
* @return list of all mentor DTOs regardless of {@link ProfileStatus}.
*/
Expand All @@ -155,6 +133,18 @@ public List<MentorDto> getAllMentors() {
.toList();
}

/**
* Retrieves the mentorship cycle for the given mentorship type and cycle year. If no open cycle
* is found, returns a default closed cycle.
*
* @return The MentorshipCycleEntity
*/
public MentorshipCycleEntity getCurrentCycle() {
final var openCycle = cycleRepository.findOpenCycle();

return openCycle.orElse(CLOSED_CYCLE);
}

/**
* Return all ACTIVE mentors in the current cycle. Mentors with PENDING or REJECTED status are
* always excluded regardless of the cycle state.
Expand All @@ -165,44 +155,20 @@ public List<MentorDto> getAllActiveMentors() {
return getAllActiveMentors(getCurrentCycle());
}

private List<MentorDto> getAllActiveMentors(final MentorshipCycle currentCycle) {
private List<MentorDto> getAllActiveMentors(final MentorshipCycleEntity currentCycle) {
final var allActiveMentors =
mentorRepository.getAll().stream()
.filter(m -> m.getProfileStatus() == ProfileStatus.ACTIVE);

if (currentCycle == CYCLE_CLOSED) {
if (currentCycle.getStatus() == CycleStatus.CLOSED) {
return allActiveMentors.map(mentor -> enrichWithProfilePicture(mentor.toDto())).toList();
}

return allActiveMentors
.map(mentor -> enrichWithProfilePicture(mentor.toDto(currentCycle)))
.map(mentor -> enrichWithProfilePicture(mentor.toDto(currentCycle.toMentorshipCycle())))
.toList();
}

/* package */ MentorshipCycle getCurrentCycle() {
final ZonedDateTime londonTime = nowLondon();
final LocalDate currentDate = londonTime.toLocalDate();

final var currentMonth = currentDate.getMonth();
final int dayOfMonth = currentDate.getDayOfMonth();

if (currentMonth == Month.MARCH && dayOfMonth <= daysCycleOpen) {
return ACTIVE_LONG_TERM;
}

if (currentMonth.getValue() >= Month.MAY.getValue()
&& currentMonth.getValue() <= Month.NOVEMBER.getValue()
&& dayOfMonth <= daysCycleOpen) {
return new MentorshipCycle(MentorshipType.AD_HOC, currentMonth);
}

return CYCLE_CLOSED;
}

/* package */ ZonedDateTime nowLondon() {
return ZonedDateTime.now(ZoneId.of(EUROPE_LONDON));
}

private MentorDto enrichWithProfilePicture(final MentorDto dto) {
final Optional<Image> profilePicture = fetchProfilePicture(dto.getId());

Expand Down
1 change: 0 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ backend:
url: ${SPRING_APP_BASE_URL:http://localhost:8080}

mentorship:
daysCycleOpen: 10
validation:
enabled: false

Expand Down
Loading
Loading