From 0e86711c13371f6f01e559e73ca867abe8b91b75 Mon Sep 17 00:00:00 2001 From: Pasindu Owa Gamage Date: Wed, 15 Apr 2026 00:08:28 +0530 Subject: [PATCH 1/6] feat: enhance event management with CRUD operations and user association --- .../controller/EventController.java | 58 +++++++++++++++---- .../repository/EventRepository.java | 5 ++ .../repository/impl/EventRepositoryImpl.java | 50 +++++++++++++++- .../eventsphere/service/EventService.java | 4 ++ .../service/impl/EventServiceImpl.java | 35 ++++++++++- 5 files changed, 138 insertions(+), 14 deletions(-) diff --git a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java index 386ab38..4cb8457 100644 --- a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java +++ b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java @@ -3,12 +3,15 @@ import dev.pasinduog.eventsphere.dto.AiMatchResult; import dev.pasinduog.eventsphere.dto.MatchSuggestionResponse; import dev.pasinduog.eventsphere.model.Event; +import dev.pasinduog.eventsphere.model.User; import dev.pasinduog.eventsphere.service.AiMatchmakingService; import dev.pasinduog.eventsphere.service.EventService; +import dev.pasinduog.eventsphere.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.security.Principal; import java.util.List; @RestController @@ -17,6 +20,7 @@ public class EventController { private final EventService eventService; private final AiMatchmakingService aiMatchmakingService; + private final UserService userService; @GetMapping("/upcoming") List getUpcomingEvents() { @@ -25,25 +29,57 @@ List getUpcomingEvents() { @GetMapping("/{eventId}/matches") @PreAuthorize("isAuthenticated()") - List getMatchSuggestions(@PathVariable String eventId, @RequestParam String userId) { - return aiMatchmakingService.getMatchSuggestions(eventId, userId); + List getMatchSuggestions(@PathVariable String eventId, Principal principal) { + User currentUser = userService.getUserEntityByEmail(principal.getName()); + return aiMatchmakingService.getMatchSuggestions(eventId, currentUser.getId()); } - @PostMapping - @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") - boolean createEvent(@RequestBody Event event){ - return eventService.createEvent(event); - } @PostMapping("/{eventId}/register") @PreAuthorize("isAuthenticated()") - boolean registerEvent(@PathVariable String eventId, @RequestParam String userId){ - return eventService.registerUserForEvent(eventId, userId); + boolean registerForEvent(@PathVariable String eventId, Principal principal) { + User currentUser = userService.getUserEntityByEmail(principal.getName()); + return eventService.registerUserForEvent(eventId, currentUser.getId()); } @PostMapping("/{eventId}/matchmaking") @PreAuthorize("isAuthenticated()") - AiMatchResult generateNetworkingMatches(@PathVariable String eventId, @RequestParam String userId){ - return aiMatchmakingService.generateMatchesForUser(eventId, userId); + AiMatchResult generateNetworkingMatches(@PathVariable String eventId, Principal principal) { + User currentUser = userService.getUserEntityByEmail(principal.getName()); + return aiMatchmakingService.generateMatchesForUser(eventId, currentUser.getId()); + } + + @PostMapping + @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") + boolean createEvent(@RequestBody Event event, Principal principal) { + User currentUser = userService.getUserEntityByEmail(principal.getName()); + event.setOrganizerId(currentUser.getId()); + return eventService.createEvent(event); + } + + @PutMapping("/{eventId}") + @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") + boolean updateEvent(@PathVariable String eventId, @RequestBody Event event, Principal principal) { + User currentUser = userService.getUserEntityByEmail(principal.getName()); + event.setOrganizerId(currentUser.getId()); + return eventService.updateEvent(event, eventId); + } + + @PutMapping("/{eventId}/cancel") + @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") + boolean cancelEvent(@PathVariable String eventId) { + return eventService.cancelEvent(eventId); + } + + @DeleteMapping("/{eventId}/delete") + @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") + boolean softDeleteEvent(@PathVariable String eventId) { + return eventService.softDelete(eventId); + } + + @DeleteMapping("/{eventId}") + @PreAuthorize("hasAuthority('ADMIN')") + boolean deleteEvent(@PathVariable String eventId) { + return eventService.delete(eventId); } } diff --git a/src/main/java/dev/pasinduog/eventsphere/repository/EventRepository.java b/src/main/java/dev/pasinduog/eventsphere/repository/EventRepository.java index c03dd05..6bf6d6d 100644 --- a/src/main/java/dev/pasinduog/eventsphere/repository/EventRepository.java +++ b/src/main/java/dev/pasinduog/eventsphere/repository/EventRepository.java @@ -7,6 +7,11 @@ public interface EventRepository { boolean save(Event event); + boolean update(Event event); + boolean cancelEvent(String eventId); + boolean softDelete(String eventId); + boolean delete(String eventId); Optional findById(String id); + List findByOrganizerEmail(String email); List findUpcomingEvents(); } diff --git a/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java b/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java index fc5a531..e7bd4ac 100644 --- a/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java @@ -34,6 +34,7 @@ private RowMapper rowMapper() { @Override public boolean save(Event event) { try { + if (event.getOrganizerId() == null) return false; String sql = "INSERT INTO events (id, organizer_id, title, description, start_time, end_time, max_attendees, status) VALUES (?,?,?,?,?,?,?,?)"; return jdbcTemplate.update(sql, event.getId(), @@ -49,12 +50,59 @@ public boolean save(Event event) { } } + @Override + public boolean update(Event event) { + try { + if (event.getOrganizerId() == null) return false; + String sql = "UPDATE events SET organizer_id = ?, title = ?, description = ?, start_time = ?, end_time = ?, " + + "max_attendees = ?, status = ? WHERE id = ?"; + return jdbcTemplate.update(sql, + event.getOrganizerId(), + event.getTitle(), + event.getDescription(), + event.getStartTime(), + event.getEndTime(), + event.getMaxAttendees(), + event.getStatus(), + event.getId()) > 0; + } catch (DuplicateKeyException e) { + throw new EventAlreadyExistsException(event.getTitle()); + } + } + + @Override + public boolean cancelEvent(String eventId) { + String sql = "UPDATE events SET status = 'CANCELLED' WHERE id = ?"; + return jdbcTemplate.update(sql, eventId) > 0; + } + + @Override + public boolean softDelete(String eventId) { + String sql = "UPDATE events SET status = 'UNAVAILABLE' WHERE id = ?"; + return jdbcTemplate.update(sql, eventId) > 0; + } + + @Override + public boolean delete(String eventId) { + String sql = "DELETE FROM events WHERE id = ?"; + return jdbcTemplate.update(sql, eventId) > 0; + } + @Override public Optional findById(String id) { - String sql = "SELECT id, organizer_id, title, description, start_time, end_time, max_attendees, status, created_at FROM events WHERE id = ?"; + String sql = "SELECT id, organizer_id, title, description, start_time, end_time, max_attendees, status, " + + "created_at FROM events WHERE id = ? AND status NOT IN ('CANCELLED', 'UNAVAILABLE')"; return jdbcTemplate.query(sql, rowMapper(), id).stream().findFirst(); } + @Override + public List findByOrganizerEmail(String email) { + String sql = "SELECT e.* FROM events e " + + "INNER JOIN users u ON e.organizer_id = u.id " + + "WHERE u.email = ? AND e.status NOT IN ('CANCELLED', 'UNAVAILABLE')"; + return jdbcTemplate.query(sql, rowMapper(), email); + } + @Override public List findUpcomingEvents() { String sql = "SELECT id, organizer_id, title, description, start_time, end_time, max_attendees, status, created_at" + diff --git a/src/main/java/dev/pasinduog/eventsphere/service/EventService.java b/src/main/java/dev/pasinduog/eventsphere/service/EventService.java index 3d38a00..46b5004 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/EventService.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/EventService.java @@ -6,6 +6,10 @@ public interface EventService { boolean createEvent(Event event); + boolean updateEvent(Event event, String eventId); + boolean cancelEvent(String eventId); + boolean softDelete(String eventId); + boolean delete(String eventId); boolean registerUserForEvent(String eventId, String userId); List getUpcomingEvents(); } diff --git a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java index 85df9c6..fec268e 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java @@ -7,14 +7,14 @@ import dev.pasinduog.eventsphere.repository.EventRepository; import dev.pasinduog.eventsphere.service.EventService; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.UUID; -@Repository +@Service @RequiredArgsConstructor public class EventServiceImpl implements EventService { private final EventRepository eventRepository; @@ -28,6 +28,37 @@ public boolean createEvent(Event event) { return eventRepository.save(event); } + @Override + public boolean updateEvent(Event event, String eventId) { + if (event.getId() == null || event.getId().isEmpty()) { + event.setId(eventId); + } + if (eventRepository.findById(eventId).isEmpty()) + throw new EventNotFoundException("Update failed. Event not found"); + return eventRepository.update(event); + } + + @Override + public boolean cancelEvent(String eventId) { + if (eventRepository.findById(eventId).isEmpty()) + throw new EventNotFoundException("Update failed. Event not found"); + return eventRepository.cancelEvent(eventId); + } + + @Override + public boolean softDelete(String eventId) { + if (eventRepository.findById(eventId).isEmpty()) + throw new EventNotFoundException("Update failed. Event not found"); + return eventRepository.softDelete(eventId); + } + + @Override + public boolean delete(String eventId) { + if (eventRepository.findById(eventId).isEmpty()) + throw new EventNotFoundException("Update failed. Event not found"); + return eventRepository.delete(eventId); + } + @Override @Transactional(isolation = Isolation.SERIALIZABLE) public boolean registerUserForEvent(String eventId, String userId) { From c84bc8ac6870abddb92458d46b3f8832bbf3c4a4 Mon Sep 17 00:00:00 2001 From: Pasindu Owa Gamage Date: Wed, 15 Apr 2026 00:15:12 +0530 Subject: [PATCH 2/6] feat: add functionality to retrieve events by organizer's email --- .../pasinduog/eventsphere/controller/EventController.java | 6 ++++++ .../dev/pasinduog/eventsphere/service/EventService.java | 1 + .../eventsphere/service/impl/EventServiceImpl.java | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java index 4cb8457..7861fe5 100644 --- a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java +++ b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java @@ -82,4 +82,10 @@ boolean softDeleteEvent(@PathVariable String eventId) { boolean deleteEvent(@PathVariable String eventId) { return eventService.delete(eventId); } + + @GetMapping + @PreAuthorize("hasAuthority('ADMIN')") + List findEventsByOrganizerEmail(@RequestParam String email) { + return eventService.getEventsByOrganizerEmail(email); + } } diff --git a/src/main/java/dev/pasinduog/eventsphere/service/EventService.java b/src/main/java/dev/pasinduog/eventsphere/service/EventService.java index 46b5004..95bcdec 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/EventService.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/EventService.java @@ -12,4 +12,5 @@ public interface EventService { boolean delete(String eventId); boolean registerUserForEvent(String eventId, String userId); List getUpcomingEvents(); + List getEventsByOrganizerEmail(String email); } diff --git a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java index fec268e..0f236c2 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java @@ -75,4 +75,9 @@ public boolean registerUserForEvent(String eventId, String userId) { public List getUpcomingEvents() { return eventRepository.findUpcomingEvents(); } + + @Override + public List getEventsByOrganizerEmail(String email) { + return eventRepository.findByOrganizerEmail(email); + } } From ab86fc6aa14269d1cd1801b5e4ccbe3d261ab972 Mon Sep 17 00:00:00 2001 From: Pasindu Owa Gamage Date: Sun, 19 Apr 2026 13:17:31 +0530 Subject: [PATCH 3/6] feat: enhance authentication and event management with improved error handling and new endpoints --- .idea/dataSources.xml | 14 +++++++++++++ .idea/data_source_mapping.xml | 6 ------ pom.xml | 2 +- .../eventsphere/config/ExceptionConfig.java | 20 +++++++++++++++++++ .../controller/AuthController.java | 6 +++--- .../controller/EventController.java | 12 +++++++---- .../eventsphere/dto/RegisterRequest.java | 5 +++++ .../exception/InvalidLoginException.java | 10 ++++++++++ .../exception/UserAlreadyExistsException.java | 4 ++-- .../repository/impl/EventRepositoryImpl.java | 3 +-- .../eventsphere/service/EventService.java | 2 +- .../service/impl/EventServiceImpl.java | 19 +++++++++++++----- .../service/impl/UserServiceImpl.java | 2 +- .../db/migration/V3__insert_sample_data.sql | 6 +++--- 14 files changed, 83 insertions(+), 28 deletions(-) delete mode 100644 .idea/data_source_mapping.xml create mode 100644 src/main/java/dev/pasinduog/eventsphere/config/ExceptionConfig.java create mode 100644 src/main/java/dev/pasinduog/eventsphere/exception/InvalidLoginException.java diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index a243ab8..9dc2df3 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -13,5 +13,19 @@ $ProjectFileDir$ + + redis + true + true + $PROJECT_DIR$/src/main/resources/application.yml + jdbc.RedisDriver + jdbc:redis://localhost:6379/0 + + + + + + $ProjectFileDir$ + \ No newline at end of file diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml deleted file mode 100644 index bb91f04..0000000 --- a/.idea/data_source_mapping.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0bfbb1f..2e2e12c 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,7 @@ io.github.og4dev og4dev-spring-response - 1.4.0 + 1.5.0 diff --git a/src/main/java/dev/pasinduog/eventsphere/config/ExceptionConfig.java b/src/main/java/dev/pasinduog/eventsphere/config/ExceptionConfig.java new file mode 100644 index 0000000..5d535bd --- /dev/null +++ b/src/main/java/dev/pasinduog/eventsphere/config/ExceptionConfig.java @@ -0,0 +1,20 @@ +package dev.pasinduog.eventsphere.config; + +import io.github.og4dev.exception.ApiExceptionRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; + +import java.sql.SQLException; + +@Configuration +public class ExceptionConfig { + + @Bean + public ApiExceptionRegistry apiExceptionRegistry() { + return new ApiExceptionRegistry() + .register(SQLException.class, HttpStatus.INTERNAL_SERVER_ERROR, "A Database error occurred") + .register(AuthenticationException.class, HttpStatus.UNAUTHORIZED, "Authentication required"); + } +} diff --git a/src/main/java/dev/pasinduog/eventsphere/controller/AuthController.java b/src/main/java/dev/pasinduog/eventsphere/controller/AuthController.java index 3e1fc41..7618a53 100644 --- a/src/main/java/dev/pasinduog/eventsphere/controller/AuthController.java +++ b/src/main/java/dev/pasinduog/eventsphere/controller/AuthController.java @@ -4,13 +4,13 @@ import dev.pasinduog.eventsphere.dto.LoginResponse; import dev.pasinduog.eventsphere.dto.OAuth2CallbackRequest; import dev.pasinduog.eventsphere.exception.InvalidAuthCodeException; +import dev.pasinduog.eventsphere.exception.InvalidLoginException; import dev.pasinduog.eventsphere.exception.UserNotFoundException; import dev.pasinduog.eventsphere.model.User; import dev.pasinduog.eventsphere.repository.UserRepository; import dev.pasinduog.eventsphere.service.JwtService; import dev.pasinduog.eventsphere.service.OAuth2CodeService; import lombok.RequiredArgsConstructor; -import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @@ -27,10 +27,10 @@ public class AuthController { @PostMapping("/login") public LoginResponse login(@RequestBody LoginRequest request) { User user = userRepository.findByEmail(request.email()) - .orElseThrow(() -> new UserNotFoundException("User not found")); + .orElseThrow(() -> new UserNotFoundException("We couldn't find an account with that email.")); if (!passwordEncoder.matches(request.password(), user.getPasswordHash())) { - throw new BadCredentialsException("Invalid credentials"); + throw new InvalidLoginException("The password you entered is incorrect. Please try again."); } String token = jwtService.generateToken(user); return new LoginResponse(token); diff --git a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java index 7861fe5..43d0b3b 100644 --- a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java +++ b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java @@ -60,9 +60,7 @@ boolean createEvent(@RequestBody Event event, Principal principal) { @PutMapping("/{eventId}") @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") boolean updateEvent(@PathVariable String eventId, @RequestBody Event event, Principal principal) { - User currentUser = userService.getUserEntityByEmail(principal.getName()); - event.setOrganizerId(currentUser.getId()); - return eventService.updateEvent(event, eventId); + return eventService.updateEvent(event, eventId, principal.getName()); } @PutMapping("/{eventId}/cancel") @@ -83,9 +81,15 @@ boolean deleteEvent(@PathVariable String eventId) { return eventService.delete(eventId); } - @GetMapping + @GetMapping("/by-organizer-email") @PreAuthorize("hasAuthority('ADMIN')") List findEventsByOrganizerEmail(@RequestParam String email) { return eventService.getEventsByOrganizerEmail(email); } + + @GetMapping("/my-events") + @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") + List getMyEvents(Principal principal) { + return eventService.getEventsByOrganizerEmail(principal.getName()); + } } diff --git a/src/main/java/dev/pasinduog/eventsphere/dto/RegisterRequest.java b/src/main/java/dev/pasinduog/eventsphere/dto/RegisterRequest.java index 8abc0db..dab7fbd 100644 --- a/src/main/java/dev/pasinduog/eventsphere/dto/RegisterRequest.java +++ b/src/main/java/dev/pasinduog/eventsphere/dto/RegisterRequest.java @@ -1,5 +1,10 @@ package dev.pasinduog.eventsphere.dto; +import io.github.og4dev.annotation.AutoTrim; +import io.github.og4dev.annotation.XssCheck; + +@AutoTrim +@XssCheck public record RegisterRequest( String fullName, String email, diff --git a/src/main/java/dev/pasinduog/eventsphere/exception/InvalidLoginException.java b/src/main/java/dev/pasinduog/eventsphere/exception/InvalidLoginException.java new file mode 100644 index 0000000..7594d5c --- /dev/null +++ b/src/main/java/dev/pasinduog/eventsphere/exception/InvalidLoginException.java @@ -0,0 +1,10 @@ +package dev.pasinduog.eventsphere.exception; +import io.github.og4dev.exception.ApiException; +import org.springframework.http.HttpStatus; + +public class InvalidLoginException extends ApiException { + + public InvalidLoginException(String customMessage) { + super(customMessage, HttpStatus.UNAUTHORIZED); + } +} \ No newline at end of file diff --git a/src/main/java/dev/pasinduog/eventsphere/exception/UserAlreadyExistsException.java b/src/main/java/dev/pasinduog/eventsphere/exception/UserAlreadyExistsException.java index a033407..4c3dd9b 100644 --- a/src/main/java/dev/pasinduog/eventsphere/exception/UserAlreadyExistsException.java +++ b/src/main/java/dev/pasinduog/eventsphere/exception/UserAlreadyExistsException.java @@ -4,7 +4,7 @@ import org.springframework.http.HttpStatus; public class UserAlreadyExistsException extends ApiException { - public UserAlreadyExistsException(String id) { - super("A user with ID '" + id + "' already exists", HttpStatus.CONFLICT); + public UserAlreadyExistsException(String message) { + super(message, HttpStatus.CONFLICT); } } diff --git a/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java b/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java index e7bd4ac..33cfddf 100644 --- a/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java @@ -54,10 +54,9 @@ public boolean save(Event event) { public boolean update(Event event) { try { if (event.getOrganizerId() == null) return false; - String sql = "UPDATE events SET organizer_id = ?, title = ?, description = ?, start_time = ?, end_time = ?, " + + String sql = "UPDATE events SET title = ?, description = ?, start_time = ?, end_time = ?, " + "max_attendees = ?, status = ? WHERE id = ?"; return jdbcTemplate.update(sql, - event.getOrganizerId(), event.getTitle(), event.getDescription(), event.getStartTime(), diff --git a/src/main/java/dev/pasinduog/eventsphere/service/EventService.java b/src/main/java/dev/pasinduog/eventsphere/service/EventService.java index 95bcdec..3025a3f 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/EventService.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/EventService.java @@ -6,7 +6,7 @@ public interface EventService { boolean createEvent(Event event); - boolean updateEvent(Event event, String eventId); + boolean updateEvent(Event event, String eventId, String email); boolean cancelEvent(String eventId); boolean softDelete(String eventId); boolean delete(String eventId); diff --git a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java index 0f236c2..1b9668c 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java @@ -2,11 +2,15 @@ import dev.pasinduog.eventsphere.exception.EventNotFoundException; import dev.pasinduog.eventsphere.exception.OutOfReachException; +import dev.pasinduog.eventsphere.exception.UserNotFoundException; import dev.pasinduog.eventsphere.model.Event; +import dev.pasinduog.eventsphere.model.User; import dev.pasinduog.eventsphere.repository.EventRegistrationRepository; import dev.pasinduog.eventsphere.repository.EventRepository; +import dev.pasinduog.eventsphere.repository.UserRepository; import dev.pasinduog.eventsphere.service.EventService; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @@ -19,6 +23,7 @@ public class EventServiceImpl implements EventService { private final EventRepository eventRepository; private final EventRegistrationRepository eventRegistrationRepository; + private final UserRepository userRepository; @Override public boolean createEvent(Event event) { @@ -29,12 +34,16 @@ public boolean createEvent(Event event) { } @Override - public boolean updateEvent(Event event, String eventId) { - if (event.getId() == null || event.getId().isEmpty()) { - event.setId(eventId); + public boolean updateEvent(Event event, String eventId, String email) { + Event existingEvent = eventRepository.findById(eventId) + .orElseThrow(() -> new EventNotFoundException("Update failed. Event not found.")); + User currentUser = userRepository.findByEmail(email) + .orElseThrow(() -> new UserNotFoundException("Update failed. User not found.")); + if (!existingEvent.getOrganizerId().equals(currentUser.getId()) && !currentUser.getRole().equals("ADMIN")) { + throw new AccessDeniedException("You are not allowed to update this event"); } - if (eventRepository.findById(eventId).isEmpty()) - throw new EventNotFoundException("Update failed. Event not found"); + event.setId(eventId); + event.setOrganizerId(existingEvent.getOrganizerId()); return eventRepository.update(event); } diff --git a/src/main/java/dev/pasinduog/eventsphere/service/impl/UserServiceImpl.java b/src/main/java/dev/pasinduog/eventsphere/service/impl/UserServiceImpl.java index 4b60aa9..5c8db28 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/impl/UserServiceImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/impl/UserServiceImpl.java @@ -23,7 +23,7 @@ public class UserServiceImpl implements UserService { @Override public boolean registerUser(RegisterRequest request) { if (userRepository.findByEmail(request.email()).isPresent()) { - throw new UserAlreadyExistsException("Email already exists"); + throw new UserAlreadyExistsException("A user with email '" + request.email() + "' already exists"); } User user = new User(); user.setId(UUID.randomUUID().toString()); diff --git a/src/main/resources/db/migration/V3__insert_sample_data.sql b/src/main/resources/db/migration/V3__insert_sample_data.sql index f1e7e15..32f01e5 100644 --- a/src/main/resources/db/migration/V3__insert_sample_data.sql +++ b/src/main/resources/db/migration/V3__insert_sample_data.sql @@ -2,12 +2,12 @@ -- Insert Sample Data for Testing -- 0.1. ROLE (3 Records) -INSERT INTO roles (name) VALUES ('ADMIN'), ('ATTENDEE'), ('SPEAKER'); +INSERT INTO roles (name) VALUES ('ADMIN'), ('ORGANIZER'), ('ATTENDEE'), ('SPEAKER'); -- 1. USERS (10 Records - Admins, Speakers, Attendees) INSERT INTO users (id, full_name, email, password_hash, role, skills_and_interests) VALUES ('886a8539-5426-4c95-b50c-c54d72e55d28', 'Pasindu Owa Gamage', 'admin1@gmail.com', '$2a$10$.g3Buxksd/EBNCE8UGoPA.1jCjrJJBspKoh3dfg7e5Tq2OIRF2emi', 'ADMIN', 'System Architecture, Spring Boot, Angular'), - ('eb334f13-7461-4d28-9e0e-7b933a5e5e4e', 'Kasun Perera', 'admin2@gmail.com', '$2a$10$.g3Buxksd/EBNCE8UGoPA.1jCjrJJBspKoh3dfg7e5Tq2OIRF2emi', 'ADMIN', 'Event Management, Marketing'), + ('eb334f13-7461-4d28-9e0e-7b933a5e5e4e', 'Kasun Perera', 'organizer2@gmail.com', '$2a$10$.g3Buxksd/EBNCE8UGoPA.1jCjrJJBspKoh3dfg7e5Tq2OIRF2emi', 'ORGANIZER', 'Event Management, Organizing'), ('1ea3c9fd-ad68-4105-8220-3a15694bc083', 'Dr. Ruwan Kumara', 'speaker1@gmail.com', '$2a$10$.g3Buxksd/EBNCE8UGoPA.1jCjrJJBspKoh3dfg7e5Tq2OIRF2emi', 'SPEAKER', 'AI, Machine Learning, Python'), ('94d5ca84-a17c-45ed-8506-cf4220f5fa80', 'Sarah Jenkins', 'speaker2@gmail.com', '$2a$10$.g3Buxksd/EBNCE8UGoPA.1jCjrJJBspKoh3dfg7e5Tq2OIRF2emi', 'SPEAKER', 'Cloud Computing, AWS, DevOps'), ('7743f6b1-d520-4a8f-b7a5-18e1d384e0f4', 'Nimali Silva', 'nimali@gmail.com', '$2a$10$.g3Buxksd/EBNCE8UGoPA.1jCjrJJBspKoh3dfg7e5Tq2OIRF2emi', 'ATTENDEE', 'Java, Spring Boot, Looking for Internships'), @@ -18,7 +18,7 @@ INSERT INTO users (id, full_name, email, password_hash, role, skills_and_interes ('5ff6e1d8-fa5c-4ff3-8e69-8d41ae1f728d', 'Tharindu Bandara', 'tharindu@gmail.com', '$2a$10$.g3Buxksd/EBNCE8UGoPA.1jCjrJJBspKoh3dfg7e5Tq2OIRF2emi', 'ATTENDEE', 'Spring Boot, Backend Development'); -- 0.2. EVENT STATUS (4 Records) -INSERT INTO event_statuses (name) VALUES ('UPCOMING'), ('LIVE'), ('COMPLETED'), ('UNAVAILABLE'); +INSERT INTO event_statuses (name) VALUES ('UPCOMING'), ('LIVE'), ('COMPLETED'), ('CANCELLED'), ('UNAVAILABLE'); -- 2. EVENTS (10 Records) INSERT INTO events (id, organizer_id, title, description, start_time, end_time, max_attendees, status) VALUES From eef3863ade191c04bdc872340a770bdd36e96cd0 Mon Sep 17 00:00:00 2001 From: Pasindu Owa Gamage Date: Sun, 19 Apr 2026 13:20:52 +0530 Subject: [PATCH 4/6] feat: restrict event cancellation and deletion to ADMIN role only --- .idea/dataSources.xml | 14 -------------- .../eventsphere/controller/EventController.java | 4 ++-- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 9dc2df3..a243ab8 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -13,19 +13,5 @@ $ProjectFileDir$ - - redis - true - true - $PROJECT_DIR$/src/main/resources/application.yml - jdbc.RedisDriver - jdbc:redis://localhost:6379/0 - - - - - - $ProjectFileDir$ - \ No newline at end of file diff --git a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java index 43d0b3b..a4670fe 100644 --- a/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java +++ b/src/main/java/dev/pasinduog/eventsphere/controller/EventController.java @@ -64,13 +64,13 @@ boolean updateEvent(@PathVariable String eventId, @RequestBody Event event, Prin } @PutMapping("/{eventId}/cancel") - @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") + @PreAuthorize("hasAuthority('ADMIN')") boolean cancelEvent(@PathVariable String eventId) { return eventService.cancelEvent(eventId); } @DeleteMapping("/{eventId}/delete") - @PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')") + @PreAuthorize("hasAuthority('ADMIN')") boolean softDeleteEvent(@PathVariable String eventId) { return eventService.softDelete(eventId); } From bad01c90f6b00d76dffc2f90c55c95ff9bc97eb3 Mon Sep 17 00:00:00 2001 From: Pasindu Owa Gamage Date: Sun, 19 Apr 2026 13:30:09 +0530 Subject: [PATCH 5/6] fix: update event retrieval query to include all statuses --- .../eventsphere/repository/impl/EventRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java b/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java index 33cfddf..126be8e 100644 --- a/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/repository/impl/EventRepositoryImpl.java @@ -90,7 +90,7 @@ public boolean delete(String eventId) { @Override public Optional findById(String id) { String sql = "SELECT id, organizer_id, title, description, start_time, end_time, max_attendees, status, " + - "created_at FROM events WHERE id = ? AND status NOT IN ('CANCELLED', 'UNAVAILABLE')"; + "created_at FROM events WHERE id = ?"; return jdbcTemplate.query(sql, rowMapper(), id).stream().findFirst(); } From 32123f4b24f52aea84830ed194e936d1b54f9d88 Mon Sep 17 00:00:00 2001 From: Pasindu Owa Gamage Date: Sun, 19 Apr 2026 13:32:25 +0530 Subject: [PATCH 6/6] fix: improve error messages for event cancellation and deletion --- .../eventsphere/service/impl/EventServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java index 1b9668c..27eb773 100644 --- a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java +++ b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java @@ -50,21 +50,21 @@ public boolean updateEvent(Event event, String eventId, String email) { @Override public boolean cancelEvent(String eventId) { if (eventRepository.findById(eventId).isEmpty()) - throw new EventNotFoundException("Update failed. Event not found"); + throw new EventNotFoundException("Cancel failed. Event not found"); return eventRepository.cancelEvent(eventId); } @Override public boolean softDelete(String eventId) { if (eventRepository.findById(eventId).isEmpty()) - throw new EventNotFoundException("Update failed. Event not found"); + throw new EventNotFoundException("Remove failed. Event not found"); return eventRepository.softDelete(eventId); } @Override public boolean delete(String eventId) { if (eventRepository.findById(eventId).isEmpty()) - throw new EventNotFoundException("Update failed. Event not found"); + throw new EventNotFoundException("Delete failed. Event not found"); return eventRepository.delete(eventId); }