Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
17a03aa
create session
MarineGiroux Jul 28, 2025
0f5090f
create speaker
MarineGiroux Jul 29, 2025
05c5e05
WIP : display of sessions seen from the speaker side
MarineGiroux Jul 30, 2025
b0d27bc
when a user clicks on an event he can see his sessions
MarineGiroux Jul 31, 2025
3e485e2
when a user clicks on an event he can see his profil
MarineGiroux Jul 31, 2025
b4723f0
refacto code for create reusable components
MarineGiroux Aug 1, 2025
2f42728
WIP : refacto create session and speaker
MarineGiroux Aug 4, 2025
a171b13
refacto create session and speaker
MarineGiroux Aug 5, 2025
b4a2557
rename package speaker-session to speaker-section
MarineGiroux Aug 7, 2025
7d8ffae
Display of session schedules and track. Addition of the image card fo…
MarineGiroux Aug 7, 2025
9c90fae
Display of session schedules and track. Addition of the image card fo…
MarineGiroux Aug 7, 2025
13bf964
backtrack for adding image
MarineGiroux Aug 8, 2025
52a5220
firestore: backtracking: putting speakers back into sessions
MarineGiroux Aug 11, 2025
1650418
WIP: link speakers to users
MarineGiroux Aug 11, 2025
9af2326
link speakers to users :
MarineGiroux Aug 12, 2025
5bb2b1b
display of events, sessions and speaker profile / refresh user profil…
MarineGiroux Aug 13, 2025
e25aceb
improved display of event dates / if an event or a team is deleted, i…
MarineGiroux Aug 14, 2025
3c10e48
creating a speaker in a empty session collection
MarineGiroux Aug 20, 2025
c21698c
update team url
MarineGiroux Aug 20, 2025
e567b2c
update event for archived event
MarineGiroux Aug 20, 2025
9488e84
refacto code for create mapper
MarineGiroux Aug 21, 2025
09d2e19
go back : mapper
MarineGiroux Aug 21, 2025
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
36 changes: 36 additions & 0 deletions back/src/main/java/com/speakerspace/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.speakerspace.config;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("SpeakerLink-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}

121 changes: 54 additions & 67 deletions back/src/main/java/com/speakerspace/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,29 @@
import com.speakerspace.config.FirebaseTokenRequest;
import com.speakerspace.dto.UserDTO;
import com.speakerspace.exception.*;
import com.speakerspace.mapper.UserMapper;
import com.speakerspace.service.UserService;
import com.speakerspace.service.UserSpeakerLinkService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.nio.file.AccessDeniedException;

@RestController
@Slf4j
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private static final Logger logger = LoggerFactory.getLogger(AuthController.class);

private final UserService userService;
private final UserSpeakerLinkService userSpeakerLinkService;
private final CookieService cookieService;
private final FirebaseAuth firebaseAuth;
private final UserMapper userMapper;

@PostMapping("/login")
public ResponseEntity<UserDTO> login(@RequestBody FirebaseTokenRequest request, HttpServletResponse response) {
Expand All @@ -36,15 +39,25 @@ public ResponseEntity<UserDTO> login(@RequestBody FirebaseTokenRequest request,

FirebaseToken decodedToken = verifyFirebaseToken(request.getIdToken());
String uid = decodedToken.getUid();
String email = decodedToken.getEmail();

cookieService.setAuthCookie(response, request.getIdToken());

UserDTO existingUser = userService.getUserByUid(uid);

if (existingUser == null) {
existingUser = createNewUser(decodedToken);

if (email != null) {
linkSpeakersToUser(uid, email);
}
} else {
existingUser = updateExistingUserIfNeeded(existingUser, decodedToken);

if (existingUser.email() != null &&
(email == null || !email.equalsIgnoreCase(existingUser.email()))) {
linkSpeakersToUser(uid, existingUser.email());
}
}

return ResponseEntity.ok(existingUser);
Expand All @@ -58,7 +71,7 @@ public ResponseEntity<Void> logout(HttpServletResponse response) {

@PostMapping
public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDTO) {
logger.info("Creating/updating user: {}", userDTO.uid());
log.info("Creating/updating user: {}", userDTO.uid());
UserDTO savedUser = userService.saveUser(userDTO);
return ResponseEntity.ok(savedUser);
}
Expand Down Expand Up @@ -97,22 +110,40 @@ public ResponseEntity<UserDTO> updateUserProfile(@RequestBody UserDTO userDTO, H
return ResponseEntity.ok(updatedUser);
}

private String authenticateAndAuthorize(HttpServletRequest request, String targetUid) {
String token = cookieService.getAuthTokenFromCookies(request);
if (token == null) {
throw new UnauthorizedException("Authentication required");
}

try {
FirebaseToken decodedToken = firebaseAuth.verifyIdToken(token);
String tokenUid = decodedToken.getUid();

if (!tokenUid.equals(targetUid)) {
throw new AccessDeniedException("Not authorized to access this profile");
}

return tokenUid;
} catch (FirebaseAuthException | AccessDeniedException e) {
if (e.getMessage().contains("expired")) {
throw new TokenExpiredException("Token expired, please refresh");
}
throw new FirebaseAuthenticationException("Token verification failed", e);
}
}

private FirebaseToken verifyFirebaseToken(String idToken) {
try {
return firebaseAuth.verifyIdToken(idToken);
} catch (FirebaseAuthException e) {
logger.error("Firebase token verification failed: {}", e.getMessage());
log.error("Firebase token verification failed: {}", e.getMessage());
throw new FirebaseAuthenticationException("Invalid token");
}
}

private UserDTO createNewUser(FirebaseToken decodedToken) {
UserDTO userDTO = UserDTO.builder()
.uid(decodedToken.getUid())
.email(decodedToken.getEmail())
.displayName(decodedToken.getName())
.photoURL(decodedToken.getPicture())
.build();
UserDTO userDTO = userMapper.createFromFirebaseToken(decodedToken);

UserDTO createdUser = userService.saveUser(userDTO);
if (createdUser == null) {
Expand All @@ -122,67 +153,23 @@ private UserDTO createNewUser(FirebaseToken decodedToken) {
}

private UserDTO updateExistingUserIfNeeded(UserDTO existingUser, FirebaseToken decodedToken) {
boolean needsUpdate = false;
UserDTO.UserDTOBuilder builder = UserDTO.builder()
.uid(existingUser.uid())
.email(existingUser.email())
.displayName(existingUser.displayName())
.photoURL(existingUser.photoURL())
.company(existingUser.company())
.city(existingUser.city())
.phoneNumber(existingUser.phoneNumber())
.githubLink(existingUser.githubLink())
.twitterLink(existingUser.twitterLink())
.blueSkyLink(existingUser.blueSkyLink())
.linkedInLink(existingUser.linkedInLink())
.biography(existingUser.biography())
.otherLink(existingUser.otherLink());

if (existingUser.email() == null && decodedToken.getEmail() != null) {
builder.email(decodedToken.getEmail());
needsUpdate = true;
}

if ((existingUser.displayName() == null || existingUser.displayName().isEmpty())
&& decodedToken.getName() != null) {
builder.displayName(decodedToken.getName());
needsUpdate = true;
}
UserMapper.UpdateResult updateResult = userMapper.updateFromFirebaseTokenIfNeeded(existingUser, decodedToken);

if ((existingUser.photoURL() == null || existingUser.photoURL().isEmpty())
&& decodedToken.getPicture() != null) {
builder.photoURL(decodedToken.getPicture());
needsUpdate = true;
}

if (needsUpdate) {
UserDTO updatedUserDTO = builder.build();
return userService.saveUser(updatedUserDTO);
if (updateResult.wasUpdated()) {
return userService.saveUser(updateResult.getUserDTO());
}

return existingUser;
}

private String authenticateAndAuthorize(HttpServletRequest request, String targetUid) {
String token = cookieService.getAuthTokenFromCookies(request);
if (token == null) {
throw new UnauthorizedException("Authentication required");
}

try {
FirebaseToken decodedToken = firebaseAuth.verifyIdToken(token);
String tokenUid = decodedToken.getUid();

if (!tokenUid.equals(targetUid)) {
throw new AccessDeniedException("Not authorized to access this profile");
}

return tokenUid;
} catch (FirebaseAuthException | AccessDeniedException e) {
if (e.getMessage().contains("expired")) {
throw new TokenExpiredException("Token expired, please refresh");
}
throw new FirebaseAuthenticationException("Token verification failed", e);
}
private void linkSpeakersToUser(String uid, String email) {
userSpeakerLinkService.linkExistingSpeakersToNewUser(uid, email)
.whenComplete((result, throwable) -> {
if (throwable != null) {
log.error("Failed to link speakers for user {}: {}", uid, throwable.getMessage());
} else {
log.info("Successfully completed speaker linking for user {}", uid);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.speakerspace.dto.EventDTO;
import com.speakerspace.exception.EntityNotFoundException;
import com.speakerspace.exception.UnauthorizedException;
import com.speakerspace.mapper.EventMapper;
import com.speakerspace.security.AuthenticationHelper;
import com.speakerspace.service.EventService;
import com.speakerspace.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
Expand All @@ -21,6 +23,8 @@ public class EventController {

private final EventService eventService;
private final AuthenticationHelper authHelper;
private final UserService userService;
private final EventMapper eventMapper;

@PostMapping("/create")
public ResponseEntity<EventDTO> createEvent(@RequestBody EventDTO eventDTO, Authentication authentication) {
Expand Down Expand Up @@ -80,6 +84,28 @@ public ResponseEntity<List<EventDTO>> getMyEvents() {
return ResponseEntity.ok(eventService.getEventsForCurrentUser());
}

@GetMapping("/{id}/for-current-user")
public ResponseEntity<Map<String, Object>> getEventForCurrentUser(@PathVariable String id) {
EventDTO event = (EventDTO) eventService.getEventByIdForCurrentUser(id);
if (event == null) {
throw new EntityNotFoundException("Event not found with id: " + id);
}

String currentUserId = userService.getCurrentUserId();
boolean isAdmin = currentUserId.equals(event.userCreateId());
boolean isSpeaker = eventService.isUserSpeakerOfEvent(id);

String userRole = isAdmin ? "admin" : (isSpeaker ? "speaker" : "none");

Map<String, Object> response = Map.of(
"event", event,
"userRole", userRole,
"hasAccess", isAdmin || isSpeaker
);

return ResponseEntity.ok(response);
}

@PutMapping("/{id}")
public ResponseEntity<EventDTO> updateEvent(
@PathVariable String id,
Expand Down Expand Up @@ -174,4 +200,26 @@ public ResponseEntity<Map<String, Boolean>> isUserSpeakerOfEvent(
boolean isSpeaker = eventService.isUserSpeakerOfEvent(eventId);
return ResponseEntity.ok(Map.of("isSpeaker", isSpeaker));
}
}

@PatchMapping("/{eventId}/archive")
public ResponseEntity<EventDTO> archiveEvent(
@PathVariable String eventId,
Authentication authentication) throws AccessDeniedException {

if (authentication == null) {
throw new UnauthorizedException("Authentication required");
}

EventDTO existingEvent = eventService.getEventById(eventId);
if (existingEvent == null) {
throw new EntityNotFoundException("Event not found with id: " + eventId);
}

if (!authHelper.isUserAuthorized(authentication, existingEvent.userCreateId())) {
throw new AccessDeniedException("User not authorized to archive this event");
}

EventDTO archivedEvent = eventService.archiveEvent(eventId);
return ResponseEntity.ok(archivedEvent);
}
}
Loading