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 386ab38..a4670fe 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,67 @@ 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) {
+ return eventService.updateEvent(event, eventId, principal.getName());
+ }
+
+ @PutMapping("/{eventId}/cancel")
+ @PreAuthorize("hasAuthority('ADMIN')")
+ boolean cancelEvent(@PathVariable String eventId) {
+ return eventService.cancelEvent(eventId);
+ }
+
+ @DeleteMapping("/{eventId}/delete")
+ @PreAuthorize("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);
+ }
+
+ @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/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..126be8e 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,58 @@ public boolean save(Event event) {
}
}
+ @Override
+ public boolean update(Event event) {
+ try {
+ if (event.getOrganizerId() == null) return false;
+ String sql = "UPDATE events SET title = ?, description = ?, start_time = ?, end_time = ?, " +
+ "max_attendees = ?, status = ? WHERE id = ?";
+ return jdbcTemplate.update(sql,
+ 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 = ?";
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..3025a3f 100644
--- a/src/main/java/dev/pasinduog/eventsphere/service/EventService.java
+++ b/src/main/java/dev/pasinduog/eventsphere/service/EventService.java
@@ -6,6 +6,11 @@
public interface EventService {
boolean createEvent(Event event);
+ boolean updateEvent(Event event, String eventId, String email);
+ boolean cancelEvent(String eventId);
+ boolean softDelete(String eventId);
+ 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 85df9c6..27eb773 100644
--- a/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java
+++ b/src/main/java/dev/pasinduog/eventsphere/service/impl/EventServiceImpl.java
@@ -2,23 +2,28 @@
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.stereotype.Repository;
+import org.springframework.security.access.AccessDeniedException;
+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;
private final EventRegistrationRepository eventRegistrationRepository;
+ private final UserRepository userRepository;
@Override
public boolean createEvent(Event event) {
@@ -28,6 +33,41 @@ public boolean createEvent(Event event) {
return eventRepository.save(event);
}
+ @Override
+ 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");
+ }
+ event.setId(eventId);
+ event.setOrganizerId(existingEvent.getOrganizerId());
+ return eventRepository.update(event);
+ }
+
+ @Override
+ public boolean cancelEvent(String eventId) {
+ if (eventRepository.findById(eventId).isEmpty())
+ 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("Remove failed. Event not found");
+ return eventRepository.softDelete(eventId);
+ }
+
+ @Override
+ public boolean delete(String eventId) {
+ if (eventRepository.findById(eventId).isEmpty())
+ throw new EventNotFoundException("Delete failed. Event not found");
+ return eventRepository.delete(eventId);
+ }
+
@Override
@Transactional(isolation = Isolation.SERIALIZABLE)
public boolean registerUserForEvent(String eventId, String userId) {
@@ -44,4 +84,9 @@ public boolean registerUserForEvent(String eventId, String userId) {
public List getUpcomingEvents() {
return eventRepository.findUpcomingEvents();
}
+
+ @Override
+ public List getEventsByOrganizerEmail(String email) {
+ return eventRepository.findByOrganizerEmail(email);
+ }
}
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