Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 0 additions & 6 deletions .idea/data_source_mapping.xml

This file was deleted.

2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
<dependency>
<groupId>io.github.og4dev</groupId>
<artifactId>og4dev-spring-response</artifactId>
<version>1.4.0</version>
<version>1.5.0</version>
</dependency>

<!-- Source: https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,6 +20,7 @@
public class EventController {
private final EventService eventService;
private final AiMatchmakingService aiMatchmakingService;
private final UserService userService;

@GetMapping("/upcoming")
List<Event> getUpcomingEvents() {
Expand All @@ -25,25 +29,67 @@ List<Event> getUpcomingEvents() {

@GetMapping("/{eventId}/matches")
@PreAuthorize("isAuthenticated()")
List<MatchSuggestionResponse> getMatchSuggestions(@PathVariable String eventId, @RequestParam String userId) {
return aiMatchmakingService.getMatchSuggestions(eventId, userId);
List<MatchSuggestionResponse> 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<Event> findEventsByOrganizerEmail(@RequestParam String email) {
return eventService.getEventsByOrganizerEmail(email);
}

@GetMapping("/my-events")
@PreAuthorize("hasAuthority('ORGANIZER') or hasAuthority('ADMIN')")
List<Event> getMyEvents(Principal principal) {
return eventService.getEventsByOrganizerEmail(principal.getName());
}
}
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Event> findById(String id);
List<Event> findByOrganizerEmail(String email);
List<Event> findUpcomingEvents();
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ private RowMapper<Event> 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(),
Expand All @@ -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;
}
Comment thread
PasinduOG marked this conversation as resolved.

@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<Event> 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<Event> 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<Event> findUpcomingEvents() {
String sql = "SELECT id, organizer_id, title, description, start_time, end_time, max_attendees, status, created_at" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Event> getUpcomingEvents();
List<Event> getEventsByOrganizerEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -44,4 +84,9 @@ public boolean registerUserForEvent(String eventId, String userId) {
public List<Event> getUpcomingEvents() {
return eventRepository.findUpcomingEvents();
}

@Override
public List<Event> getEventsByOrganizerEmail(String email) {
return eventRepository.findByOrganizerEmail(email);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading