Add staff application support with database integration and submission flow

This commit is contained in:
2025-09-24 23:12:09 +02:00
parent f886609a0e
commit 643b15f2e0
5 changed files with 224 additions and 3 deletions
@@ -1,15 +1,92 @@
package com.alttd.altitudeweb.controllers.forms;
import com.alttd.altitudeweb.api.ApplicationsApi;
import com.alttd.altitudeweb.controllers.data_from_auth.AuthenticatedUuid;
import com.alttd.altitudeweb.database.Databases;
import com.alttd.altitudeweb.database.web_db.forms.StaffApplication;
import com.alttd.altitudeweb.database.web_db.forms.StaffApplicationMapper;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerification;
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
import com.alttd.altitudeweb.mappers.StaffApplicationDataMapper;
import com.alttd.altitudeweb.model.FormResponseDto;
import com.alttd.altitudeweb.model.StaffApplicationDto;
import org.springframework.http.HttpStatusCode;
import com.alttd.altitudeweb.services.limits.RateLimit;
import com.alttd.altitudeweb.setup.Connection;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
@AllArgsConstructor
@RateLimit(limit = 30, timeValue = 1, timeUnit = TimeUnit.HOURS)
public class ApplicationController implements ApplicationsApi {
private final StaffApplicationDataMapper staffApplicationDataMapper;
@Override
public ResponseEntity<FormResponseDto> submitStaffApplication(StaffApplicationDto staffApplicationDto) {
throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Staff applications are not yet supported");
UUID userUuid = AuthenticatedUuid.getAuthenticatedUserUuid();
StaffApplication application = staffApplicationDataMapper.map(userUuid, staffApplicationDto);
saveApplication(application);
Optional<EmailVerification> optionalEmail = fetchEmailVerification(userUuid, application.email());
boolean verified = optionalEmail.map(EmailVerification::verified).orElse(false);
if (verified) {
markAsSent(application.id());
}
FormResponseDto response = buildResponse(application, verified);
return ResponseEntity.status(201).body(response);
}
private void saveApplication(StaffApplication application) {
CompletableFuture<Void> saveFuture = new CompletableFuture<>();
Connection.getConnection(Databases.DEFAULT)
.runQuery(sqlSession -> {
try {
sqlSession.getMapper(StaffApplicationMapper.class).insert(application);
saveFuture.complete(null);
} catch (Exception e) {
log.error("Failed to insert staff application", e);
saveFuture.completeExceptionally(e);
}
});
saveFuture.join();
}
private Optional<EmailVerification> fetchEmailVerification(UUID userUuid, String email) {
CompletableFuture<Optional<EmailVerification>> emailVerificationFuture = new CompletableFuture<>();
Connection.getConnection(Databases.DEFAULT)
.runQuery(sqlSession -> {
EmailVerification verifiedMail = sqlSession.getMapper(EmailVerificationMapper.class)
.findByUserAndEmail(userUuid, email);
emailVerificationFuture.complete(Optional.ofNullable(verifiedMail));
});
return emailVerificationFuture.join();
}
private void markAsSent(UUID applicationId) {
Connection.getConnection(Databases.DEFAULT)
.runQuery(sqlSession -> sqlSession.getMapper(StaffApplicationMapper.class).markAsSent(applicationId));
}
private FormResponseDto buildResponse(StaffApplication application, boolean verified) {
String message = verified
? "Your staff application has been submitted. You will be notified when it has been reviewed."
: "Application created. Please verify your email to complete submission.";
return new FormResponseDto(
application.id().toString(),
message,
true
);
}
}
@@ -0,0 +1,53 @@
package com.alttd.altitudeweb.mappers;
import com.alttd.altitudeweb.database.web_db.forms.StaffApplication;
import com.alttd.altitudeweb.model.StaffApplicationDto;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class StaffApplicationDataMapper {
/**
* Maps the incoming DTO and the authenticated user's UUID to a StaffApplication entity.
* Normalizes and prepares fields as needed (lowercase email, join availableDays, timestamps, ids).
*/
public StaffApplication map(UUID userUuid, StaffApplicationDto dto) {
String email = dto.getEmail() == null ? null : dto.getEmail().toLowerCase();
String availableDaysJoined = joinList(dto.getAvailableDays());
return new StaffApplication(
UUID.randomUUID(),
userUuid,
email,
dto.getAge(),
dto.getDiscordUsername(),
Boolean.TRUE.equals(dto.getMeetsRequirements()),
dto.getPronouns(),
dto.getJoinDate(),
dto.getWeeklyPlaytime(),
availableDaysJoined,
dto.getAvailableTimes(),
dto.getPreviousExperience(),
dto.getPluginExperience(),
dto.getModeratorExpectations(),
dto.getAdditionalInfo(),
Instant.now(),
null,
null
);
}
private String joinList(List<String> list) {
if (list == null) return null;
// Avoid NPEs and trim entries
return list.stream()
.filter(s -> s != null && !s.isBlank())
.map(String::trim)
.collect(Collectors.joining(","));
}
}