ADD ALOT OF POLISHING AND UPDATES...
This commit is contained in:
@@ -4,12 +4,9 @@ import com.walkguide.dto.ApiResponse;
|
||||
import com.walkguide.dto.request.CallNotifyRequest;
|
||||
import com.walkguide.dto.request.CallTokenRequest;
|
||||
import com.walkguide.dto.response.AgoraTokenResponse;
|
||||
import com.walkguide.entity.User;
|
||||
import com.walkguide.exception.ResourceNotFoundException;
|
||||
import com.walkguide.repository.UserRepository;
|
||||
import com.walkguide.security.SecurityHelper;
|
||||
import com.walkguide.service.AgoraTokenService;
|
||||
import com.walkguide.service.FcmService;
|
||||
import com.walkguide.service.CallNotificationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -17,23 +14,13 @@ import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Controller untuk VoIP call via Agora RTC.
|
||||
*
|
||||
* FLOW:
|
||||
* 1. Caller → POST /shared/call/token → dapat Agora token + channelName
|
||||
* 2. Caller join Agora channel (di Flutter)
|
||||
* 3. Caller → POST /shared/call/notify → FCM "Incoming Call" dikirim ke receiver
|
||||
* 4. Receiver terima FCM → join channel yang sama
|
||||
* 5. Audio call tersambung via Agora
|
||||
*
|
||||
* Route: /api/v1/shared/call
|
||||
* Auth: Authenticated (Guardian ATAU User)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/shared/call")
|
||||
@RequiredArgsConstructor
|
||||
@@ -43,17 +30,10 @@ import java.util.Map;
|
||||
public class CallController {
|
||||
|
||||
private final AgoraTokenService agoraTokenService;
|
||||
private final FcmService fcmService;
|
||||
private final UserRepository userRepository;
|
||||
private final CallNotificationService callNotificationService;
|
||||
|
||||
/**
|
||||
* Generate Agora RTC token untuk call session.
|
||||
* Dipanggil oleh CALLER sebelum mulai call.
|
||||
*
|
||||
* POST /api/v1/shared/call/token
|
||||
*/
|
||||
@PostMapping("/token")
|
||||
@Operation(summary = "Generate Agora token", description = "Caller minta token sebelum join Agora channel")
|
||||
@Operation(summary = "Generate Agora token", description = "Caller requests a token before joining Agora")
|
||||
public ResponseEntity<ApiResponse<AgoraTokenResponse>> generateToken(
|
||||
@Valid @RequestBody CallTokenRequest req) {
|
||||
|
||||
@@ -66,84 +46,24 @@ public class CallController {
|
||||
return ResponseEntity.ok(ApiResponse.ok(response, "Token Agora berhasil digenerate"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Kirim FCM "Incoming Call" ke receiver.
|
||||
* Dipanggil oleh CALLER setelah berhasil join Agora channel.
|
||||
*
|
||||
* POST /api/v1/shared/call/notify
|
||||
*/
|
||||
@PostMapping("/notify")
|
||||
@Operation(summary = "Notify receiver of incoming call",
|
||||
description = "Kirim FCM push notification ke receiver agar join Agora channel yang sama")
|
||||
@Operation(summary = "Notify receiver of incoming call")
|
||||
public ResponseEntity<ApiResponse<Void>> notifyCall(
|
||||
@Valid @RequestBody CallNotifyRequest req) {
|
||||
|
||||
Long callerId = SecurityHelper.getCurrentUserId();
|
||||
|
||||
// Ambil info caller untuk notifikasi
|
||||
User caller = userRepository.findById(callerId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Caller not found"));
|
||||
|
||||
// Ambil FCM token receiver
|
||||
User receiver = userRepository.findById(req.getReceiverId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Receiver not found"));
|
||||
|
||||
if (receiver.getFcmToken() == null || receiver.getFcmToken().isBlank()) {
|
||||
log.warn("[CALL] Receiver {} tidak punya FCM token — call notify gagal", req.getReceiverId());
|
||||
return ResponseEntity.ok(ApiResponse.ok(null,
|
||||
"Panggilan dikirim (receiver mungkin tidak menerima push notification)"));
|
||||
}
|
||||
|
||||
// Payload FCM untuk incoming call
|
||||
Map<String, String> payload = Map.of(
|
||||
"type", "INCOMING_CALL",
|
||||
"callerId", String.valueOf(callerId),
|
||||
"callerName", caller.getDisplayName() != null ? caller.getDisplayName() : caller.getEmail(),
|
||||
"channelName", req.getChannelName(),
|
||||
"agoraToken", req.getAgoraToken() != null ? req.getAgoraToken() : "",
|
||||
"receiverUid", String.valueOf(req.getReceiverUid())
|
||||
);
|
||||
|
||||
// High priority karena incoming call harus segera muncul
|
||||
fcmService.sendHighPriority(
|
||||
receiver.getFcmToken(),
|
||||
"📞 Panggilan Masuk",
|
||||
"Panggilan dari " + (caller.getDisplayName() != null ? caller.getDisplayName() : caller.getEmail()),
|
||||
payload
|
||||
);
|
||||
|
||||
log.info("[CALL] Incoming call notification sent | caller={} receiver={} channel={}",
|
||||
callerId, req.getReceiverId(), req.getChannelName());
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.ok(null, "Notifikasi panggilan berhasil dikirim"));
|
||||
String message = callNotificationService.notifyIncomingCall(callerId, req);
|
||||
return ResponseEntity.ok(ApiResponse.ok(null, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* End call — notifikasi ke pihak lain bahwa call sudah berakhir.
|
||||
* Opsional: Flutter bisa handle end call secara lokal juga.
|
||||
*
|
||||
* POST /api/v1/shared/call/end
|
||||
*/
|
||||
@PostMapping("/end")
|
||||
@Operation(summary = "Notify end of call")
|
||||
public ResponseEntity<ApiResponse<Void>> endCall(
|
||||
@RequestBody Map<String, Long> body) {
|
||||
|
||||
Long callerId = SecurityHelper.getCurrentUserId();
|
||||
Long otherId = body.get("otherId");
|
||||
|
||||
if (otherId != null) {
|
||||
userRepository.findById(otherId).ifPresent(other -> {
|
||||
if (other.getFcmToken() != null && !other.getFcmToken().isBlank()) {
|
||||
fcmService.sendToToken(
|
||||
other.getFcmToken(),
|
||||
"Panggilan Berakhir",
|
||||
"Panggilan telah berakhir",
|
||||
Map.of("type", "CALL_ENDED", "callerId", String.valueOf(callerId))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Long callerId = SecurityHelper.getCurrentUserId();
|
||||
Long otherId = body.get("otherId");
|
||||
callNotificationService.notifyCallEnded(callerId, otherId);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.ok(null, "Call ended"));
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class GuardianController {
|
||||
Long guardianId = SecurityHelper.getCurrentUserId();
|
||||
// Perlu ambil userId dulu — delegasikan ke service
|
||||
return ResponseEntity.ok(ApiResponse.ok(
|
||||
locationService.getLocationHistory(guardianId,
|
||||
locationService.getLocationHistoryForGuardian(guardianId,
|
||||
PageRequest.of(page, size, Sort.by("createdAt").descending())),
|
||||
"Riwayat lokasi"));
|
||||
}
|
||||
|
||||
+18
-2
@@ -2,9 +2,11 @@ package com.walkguide.controller;
|
||||
|
||||
import com.walkguide.dto.ApiResponse;
|
||||
import com.walkguide.dto.request.*;
|
||||
import com.walkguide.dto.response.PairingCodeResponse;
|
||||
import com.walkguide.dto.response.PairingStatusResponse;
|
||||
import com.walkguide.security.SecurityHelper;
|
||||
import com.walkguide.service.PairingService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -16,12 +18,26 @@ public class PairingController {
|
||||
|
||||
private final PairingService pairingService;
|
||||
|
||||
@GetMapping("/code")
|
||||
public ResponseEntity<ApiResponse<PairingCodeResponse>> code() {
|
||||
return ResponseEntity.ok(ApiResponse.ok(
|
||||
pairingService.getOrCreatePairingCode(SecurityHelper.getCurrentUserId()),
|
||||
"Pairing code aktif"));
|
||||
}
|
||||
|
||||
@PostMapping("/code/regenerate")
|
||||
public ResponseEntity<ApiResponse<PairingCodeResponse>> regenerateCode() {
|
||||
return ResponseEntity.ok(ApiResponse.ok(
|
||||
pairingService.regeneratePairingCode(SecurityHelper.getCurrentUserId()),
|
||||
"Pairing code baru dibuat"));
|
||||
}
|
||||
|
||||
@PostMapping("/invite")
|
||||
public ResponseEntity<ApiResponse<PairingStatusResponse>> invite(
|
||||
@RequestBody InviteUserRequest req) {
|
||||
@Valid @RequestBody InviteUserRequest req) {
|
||||
Long guardianId = SecurityHelper.getCurrentUserId();
|
||||
return ResponseEntity.ok(ApiResponse.ok(
|
||||
pairingService.inviteUser(guardianId, req.getUniqueUserId()),
|
||||
pairingService.inviteUser(guardianId, req.resolveSubmittedCode()),
|
||||
"Undangan dikirim ke user"));
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -5,7 +5,16 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class InviteUserRequest {
|
||||
@NotBlank(message = "User ID tidak boleh kosong")
|
||||
@Size(min = 12, max = 12, message = "User ID harus tepat 12 karakter")
|
||||
private String uniqueUserId;
|
||||
|
||||
@Size(min = 8, max = 8, message = "Pairing code harus tepat 8 karakter")
|
||||
private String pairingCode;
|
||||
|
||||
public String resolveSubmittedCode() {
|
||||
if (pairingCode != null && !pairingCode.isBlank()) {
|
||||
return pairingCode.trim().toUpperCase();
|
||||
}
|
||||
return uniqueUserId == null ? null : uniqueUserId.trim().toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.walkguide.dto.response;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class PairingCodeResponse {
|
||||
private String pairingCode;
|
||||
private LocalDateTime expiresAt;
|
||||
private long expiresInSeconds;
|
||||
}
|
||||
+2
@@ -12,6 +12,8 @@ public class PairingStatusResponse {
|
||||
private String pairedWithName; // nama Guardian (untuk User) atau nama User (untuk Guardian)
|
||||
private String pairedWithEmail;
|
||||
private String uniqueUserId; // ID user yang di-pair
|
||||
private String pairingCode; // temporary code for new pairing flow
|
||||
private LocalDateTime pairingCodeExpiresAt;
|
||||
private LocalDateTime invitedAt;
|
||||
private LocalDateTime respondedAt;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@ public class User {
|
||||
@Column(name = "unique_user_id", unique = true, length = 12)
|
||||
private String uniqueUserId;
|
||||
|
||||
@Column(name = "pairing_code", unique = true, length = 8)
|
||||
private String pairingCode;
|
||||
|
||||
@Column(name = "pairing_code_expires_at")
|
||||
private LocalDateTime pairingCodeExpiresAt;
|
||||
|
||||
@Column(name = "display_name", length = 100)
|
||||
private String displayName;
|
||||
|
||||
|
||||
@@ -9,5 +9,6 @@ import java.util.Optional;
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByEmail(String email);
|
||||
Optional<User> findByUniqueUserId(String uniqueUserId);
|
||||
Optional<User> findByPairingCode(String pairingCode);
|
||||
boolean existsByEmail(String email);
|
||||
}
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.walkguide.service;
|
||||
|
||||
import com.walkguide.dto.request.CallNotifyRequest;
|
||||
import com.walkguide.entity.User;
|
||||
import com.walkguide.exception.ResourceNotFoundException;
|
||||
import com.walkguide.repository.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CallNotificationService {
|
||||
|
||||
private final FcmService fcmService;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public String notifyIncomingCall(Long callerId, CallNotifyRequest req) {
|
||||
User caller = userRepository.findById(callerId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Caller not found"));
|
||||
|
||||
User receiver = userRepository.findById(req.getReceiverId())
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Receiver not found"));
|
||||
|
||||
if (receiver.getFcmToken() == null || receiver.getFcmToken().isBlank()) {
|
||||
log.warn("[CALL] Receiver {} has no FCM token; push notification skipped", req.getReceiverId());
|
||||
return "Panggilan dikirim (receiver mungkin tidak menerima push notification)";
|
||||
}
|
||||
|
||||
String callerName = caller.getDisplayName() != null ? caller.getDisplayName() : caller.getEmail();
|
||||
Map<String, String> payload = Map.of(
|
||||
"type", "INCOMING_CALL",
|
||||
"callerId", String.valueOf(callerId),
|
||||
"callerName", callerName,
|
||||
"channelName", req.getChannelName(),
|
||||
"agoraToken", req.getAgoraToken() != null ? req.getAgoraToken() : "",
|
||||
"receiverUid", String.valueOf(req.getReceiverUid())
|
||||
);
|
||||
|
||||
fcmService.sendHighPriority(
|
||||
receiver.getFcmToken(),
|
||||
"Panggilan Masuk",
|
||||
"Panggilan dari " + callerName,
|
||||
payload
|
||||
);
|
||||
|
||||
log.info("[CALL] Incoming call notification sent | caller={} receiver={} channel={}",
|
||||
callerId, req.getReceiverId(), req.getChannelName());
|
||||
return "Notifikasi panggilan berhasil dikirim";
|
||||
}
|
||||
|
||||
public void notifyCallEnded(Long callerId, Long otherId) {
|
||||
if (otherId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
userRepository.findById(otherId).ifPresent(other -> {
|
||||
if (other.getFcmToken() == null || other.getFcmToken().isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fcmService.sendToToken(
|
||||
other.getFcmToken(),
|
||||
"Panggilan Berakhir",
|
||||
"Panggilan telah berakhir",
|
||||
Map.of("type", "CALL_ENDED", "callerId", String.valueOf(callerId))
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.walkguide.entity.LocationHistory;
|
||||
import com.walkguide.entity.User;
|
||||
import com.walkguide.enums.ActivityLogType;
|
||||
import com.walkguide.enums.PairingStatus;
|
||||
import com.walkguide.exception.PairingException;
|
||||
import com.walkguide.exception.ResourceNotFoundException;
|
||||
import com.walkguide.repository.*;
|
||||
import com.walkguide.websocket.LocationBroadcaster;
|
||||
@@ -79,6 +80,15 @@ public class LocationService {
|
||||
.map(this::toResponse);
|
||||
}
|
||||
|
||||
public Page<LocationResponse> getLocationHistoryForGuardian(Long guardianId, Pageable pageable) {
|
||||
Long pairedUserId = pairingRelationRepository
|
||||
.findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)
|
||||
.map(pairing -> pairing.getUser().getId())
|
||||
.orElseThrow(() -> new PairingException("Guardian belum terhubung dengan User aktif"));
|
||||
|
||||
return getLocationHistory(pairedUserId, pageable);
|
||||
}
|
||||
|
||||
// ========== GEOFENCE ==========
|
||||
|
||||
private void checkGeofence(Long userId, double lat, double lng) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.walkguide.service;
|
||||
|
||||
import com.walkguide.dto.response.PairingStatusResponse;
|
||||
import com.walkguide.dto.response.PairingCodeResponse;
|
||||
import com.walkguide.entity.*;
|
||||
import com.walkguide.enums.*;
|
||||
import com.walkguide.exception.PairingException;
|
||||
@@ -10,7 +11,9 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -26,8 +29,46 @@ public class PairingService {
|
||||
private final ActivityLogService activityLogService;
|
||||
private final FcmService fcmService;
|
||||
|
||||
private static final String CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
private static final int PAIRING_CODE_LENGTH = 8;
|
||||
private static final int PAIRING_CODE_TTL_MINUTES = 15;
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
@Transactional
|
||||
public PairingStatusResponse inviteUser(Long guardianId, String uniqueUserId) {
|
||||
public PairingCodeResponse getOrCreatePairingCode(Long userId) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User tidak ditemukan"));
|
||||
if (!"ROLE_USER".equals(user.getRole())) {
|
||||
throw new PairingException("Hanya akun User yang bisa membuat pairing code.");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (user.getPairingCode() == null
|
||||
|| user.getPairingCodeExpiresAt() == null
|
||||
|| !user.getPairingCodeExpiresAt().isAfter(now)) {
|
||||
assignNewPairingCode(user, now);
|
||||
userRepository.save(user);
|
||||
}
|
||||
return buildPairingCodeResponse(user, now);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PairingCodeResponse regeneratePairingCode(Long userId) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User tidak ditemukan"));
|
||||
if (!"ROLE_USER".equals(user.getRole())) {
|
||||
throw new PairingException("Hanya akun User yang bisa membuat pairing code.");
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
assignNewPairingCode(user, now);
|
||||
userRepository.save(user);
|
||||
activityLogService.createLog(user, ActivityLogType.PAIRING_INVITE_SENT,
|
||||
"User membuat pairing code baru", null);
|
||||
return buildPairingCodeResponse(user, now);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PairingStatusResponse inviteUser(Long guardianId, String submittedCode) {
|
||||
// Guardian tidak boleh punya pairing ACTIVE atau PENDING
|
||||
if (pairingRelationRepository.existsByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)) {
|
||||
throw new PairingException("Kamu sudah memiliki user yang dipair. Unpair dulu sebelum invite user baru.");
|
||||
@@ -38,8 +79,7 @@ public class PairingService {
|
||||
|
||||
User guardian = userRepository.findById(guardianId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Guardian tidak ditemukan"));
|
||||
User user = userRepository.findByUniqueUserId(uniqueUserId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User dengan ID '" + uniqueUserId + "' tidak ditemukan"));
|
||||
User user = resolveUserByPairingCode(submittedCode);
|
||||
|
||||
if (!"ROLE_USER".equals(user.getRole())) {
|
||||
throw new PairingException("ID tersebut bukan milik User. Pastikan kamu memasukkan ID yang benar.");
|
||||
@@ -55,6 +95,10 @@ public class PairingService {
|
||||
.build();
|
||||
pairing = pairingRelationRepository.save(pairing);
|
||||
|
||||
user.setPairingCode(null);
|
||||
user.setPairingCodeExpiresAt(null);
|
||||
userRepository.save(user);
|
||||
|
||||
// Kirim FCM ke user
|
||||
fcmService.sendToToken(user.getFcmToken(),
|
||||
"Pairing Request",
|
||||
@@ -199,6 +243,51 @@ public class PairingService {
|
||||
.enabled(name != null).build();
|
||||
}
|
||||
|
||||
private User resolveUserByPairingCode(String submittedCode) {
|
||||
if (submittedCode == null || submittedCode.isBlank()) {
|
||||
throw new PairingException("Pairing code tidak boleh kosong.");
|
||||
}
|
||||
String code = submittedCode.trim().toUpperCase();
|
||||
User user = userRepository.findByPairingCode(code)
|
||||
.orElseThrow(() -> new ResourceNotFoundException(
|
||||
"Pairing code '" + code + "' tidak ditemukan. Minta User generate kode baru."));
|
||||
if (user.getPairingCodeExpiresAt() == null
|
||||
|| !user.getPairingCodeExpiresAt().isAfter(LocalDateTime.now())) {
|
||||
user.setPairingCode(null);
|
||||
user.setPairingCodeExpiresAt(null);
|
||||
userRepository.save(user);
|
||||
throw new PairingException("Pairing code sudah kadaluarsa. Minta User generate kode baru.");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private void assignNewPairingCode(User user, LocalDateTime now) {
|
||||
String candidate;
|
||||
do {
|
||||
candidate = randomCode();
|
||||
} while (userRepository.findByPairingCode(candidate).isPresent());
|
||||
user.setPairingCode(candidate);
|
||||
user.setPairingCodeExpiresAt(now.plusMinutes(PAIRING_CODE_TTL_MINUTES));
|
||||
}
|
||||
|
||||
private String randomCode() {
|
||||
StringBuilder sb = new StringBuilder(PAIRING_CODE_LENGTH);
|
||||
for (int i = 0; i < PAIRING_CODE_LENGTH; i++) {
|
||||
sb.append(CODE_CHARS.charAt(RANDOM.nextInt(CODE_CHARS.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private PairingCodeResponse buildPairingCodeResponse(User user, LocalDateTime now) {
|
||||
long seconds = Math.max(0,
|
||||
ChronoUnit.SECONDS.between(now, user.getPairingCodeExpiresAt()));
|
||||
return PairingCodeResponse.builder()
|
||||
.pairingCode(user.getPairingCode())
|
||||
.expiresAt(user.getPairingCodeExpiresAt())
|
||||
.expiresInSeconds(seconds)
|
||||
.build();
|
||||
}
|
||||
|
||||
private PairingStatusResponse buildStatus(PairingRelation p, User guardian, User user, String viewerRole) {
|
||||
String pairedWithName = "GUARDIAN".equals(viewerRole)
|
||||
? user.getDisplayName() : guardian.getDisplayName();
|
||||
@@ -211,6 +300,8 @@ public class PairingService {
|
||||
.pairedWithName(pairedWithName)
|
||||
.pairedWithEmail(pairedWithEmail)
|
||||
.uniqueUserId(user.getUniqueUserId())
|
||||
.pairingCode(user.getPairingCode())
|
||||
.pairingCodeExpiresAt(user.getPairingCodeExpiresAt())
|
||||
.invitedAt(p.getInvitedAt())
|
||||
.respondedAt(p.getRespondedAt())
|
||||
.build();
|
||||
|
||||
Reference in New Issue
Block a user