I'm trying to create a java application. I am in the system design phase following DDD principles. Can you provide an system design for the app? The app should be similar to Uber, but instead of selling rides, you sell parking spot (public parking spot). It means that if you are leaving you can signal on the app that you are leaving a parking spot, and the estimated time you leave, so another person can go there (if they are trying to find a spot) and take your place. You should be rewarded for this (maybe a variable amount)
System Design for a Parking Spot Exchange Application Using Domain-Driven Design (DDD)

Overview
You are designing a Java application that facilitates the exchange of public parking spots between users. By following Domain-Driven Design (DDD) principles, we'll structure the system into well-defined domains and bounded contexts to manage complexity and ensure scalability.

Core Requirements
User Actions:
Leaving User: Signals they are leaving a parking spot and provides an estimated departure time.
Arriving User: Searches for available spots, reserves one, and goes to take the spot.
Rewards System:
Leaving users are rewarded (variable amount) when their spot is successfully taken by an arriving user.
Additional Considerations:
Real-time notifications.
Handling cancellations and no-shows.
User ratings and feedback.

High-Level Architecture
Frontend:
Mobile applications (iOS/Android) for user interaction.
Backend:
Microservices or modular monolith built with Spring Boot.
Database:
Relational database (e.g., PostgreSQL) or NoSQL database (e.g., MongoDB) for persistence.
External Services:
Mapping and geolocation services.
Payment gateway for processing rewards.

Domain-Driven Design Components
1. Ubiquitous Language
User: An individual using the app.
Leaving User: A user vacating a parking spot.
Arriving User: A user seeking a parking spot.
Parking Spot Availability: The state indicating a spot will be free.
Reservation: An arriving user's claim to an available spot.
Reward: Compensation given to the leaving user.
Notification: Messages sent to users regarding spot availability or status changes.
2. Bounded Contexts
User Context
Manages user profiles, authentication, and ratings.
Parking Context
Handles parking spot availability, reservations, and status updates.
Reward Context
Manages the reward mechanism and transactions.
Notification Context
Takes care of all user notifications.
Mapping Context
Interacts with external mapping services for geolocation.
3. Entities, Value Objects, and Aggregates

User Context
Entity: User
Attributes:
userId (unique identifier)
name
email
phoneNumber
paymentInfo (value object)
rating
Aggregate Root: User
Repository: UserRepository
Service: UserService

Parking Context
Entity: Availability
Attributes:
availabilityId (unique identifier)
leavingUserId
location (value object with latitude and longitude)
estimatedLeavingTime
status (Available, Reserved, Taken, Canceled)
reservedByUserId
timestamps (createdAt, updatedAt)
Entity: Reservation
Attributes:
reservationId
availabilityId
arrivingUserId
status (Active, Completed, Canceled)
timestamps
Aggregate Root: Availability
Repository: AvailabilityRepository
Service: ParkingService

Reward Context
Entity: Reward
Attributes:
rewardId
fromUserId (leaving user)
toUserId (arriving user)
amount
timestamp
status (Pending, Completed, Failed)
Aggregate Root: Reward
Repository: RewardRepository
Service: RewardService

Notification Context
Entity: Notification
Attributes:
notificationId
userId
message
type (SpotAvailable, ReservationConfirmed, etc.)
status (Sent, Read)
timestamp
Aggregate Root: Notification
Service: NotificationService

Mapping Context
Value Object: Location
Attributes:
latitude
longitude
address (optional)
Service: MappingService
Interacts with external APIs to convert between addresses and coordinates.

Domain Services and Interactions
UserService
Methods:
registerUser()
authenticateUser()
updateUserProfile()
getUserDetails(userId)
calculateUserRating(userId)
ParkingService
Methods:
reportAvailability(leavingUserId, location, estimatedLeavingTime)
getAvailableSpots(location, radius)
reserveSpot(availabilityId, arrivingUserId)
confirmSpotTaken(availabilityId, arrivingUserId)
cancelAvailability(availabilityId, userId)
handleNoShow(reservationId)
RewardService
Methods:
calculateReward(availabilityId, reservationId)
The amount can be variable based on factors like demand, location, time.
processReward(rewardId)
handleFailedReward(rewardId)
NotificationService
Methods:
sendNotification(userId, message, type)
broadcastAvailability(availabilityId, location)
notifyReservationStatus(reservationId)
MappingService
Methods:
getCoordinates(address)
getAddress(latitude, longitude)
calculateDistance(location1, location2)

Domain Events
SpotAvailableEvent
Triggered when a leaving user reports availability.
Handled by NotificationService to alert nearby users.
SpotReservedEvent
Triggered when an arriving user reserves a spot.
Updates the status in ParkingService and notifies the leaving user.
SpotTakenEvent
Triggered when the arriving user confirms they've taken the spot.
Initiates reward processing in RewardService.
ReservationCanceledEvent
Triggered when a reservation is canceled.
Frees up the availability for other users.
NoShowEvent
Triggered when the arriving user fails to show up.
May impact user ratings and handle potential penalties.

Workflow Scenarios
1. Leaving User Reports Availability
Action:
Leaving user opens the app and signals they are leaving a spot.
Provides estimated leaving time and confirms location.
Process:
ParkingService.reportAvailability() creates a new Availability with status Available.
SpotAvailableEvent is triggered.
Outcome:
NotificationService broadcasts the availability to nearby users.
2. Arriving User Searches and Reserves a Spot
Action:
Arriving user searches for available spots within a radius.
Process:
ParkingService.getAvailableSpots() returns a list of Availability instances.
User selects a spot and calls ParkingService.reserveSpot().
Status of Availability changes to Reserved, and reservedByUserId is set.
SpotReservedEvent is triggered.
Outcome:
NotificationService notifies the leaving user of the reservation.
The spot is marked as reserved for a specific time window.
3. Arriving User Confirms Spot Taken
Action:
Arriving user reaches the spot and confirms it's taken via ParkingService.confirmSpotTaken().
Process:
Status of Availability changes to Taken.
SpotTakenEvent is triggered.
Outcome:
RewardService.calculateReward() determines the reward amount.
RewardService.processReward() handles payment to the leaving user.
NotificationService updates both users about the successful transaction.
4. Handling Cancellations or No-Shows
Cancellation:
User cancels via ParkingService.cancelAvailability() or ParkingService.cancelReservation().
Status changes accordingly, and ReservationCanceledEvent is triggered.
No-Show:
If the arriving user doesn't confirm within a time window, NoShowEvent is triggered.
ParkingService updates status, and UserService may adjust user ratings.

Data Model Examples
User Entity
public class User {
    private UUID userId;
    private String name;
    private String email;
    private String phoneNumber;
    private PaymentInfo paymentInfo;
    private double rating;
    // Getters and setters omitted for brevity
}


Availability Entity
public class Availability {
    private UUID availabilityId;
    private UUID leavingUserId;
    private Location location;
    private LocalDateTime estimatedLeavingTime;
    private AvailabilityStatus status;
    private UUID reservedByUserId;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    // Getters and setters omitted for brevity
}


Reward Entity
public class Reward {
    private UUID rewardId;
    private UUID fromUserId;
    private UUID toUserId;
    private BigDecimal amount;
    private RewardStatus status;
    private LocalDateTime timestamp;
    // Getters and setters omitted for brevity
}



Technical Considerations
Concurrency and Consistency:
Use optimistic locking or transactions to handle concurrent reservations.
Scalability:
Implement caching strategies for frequently accessed data.
Use asynchronous messaging (e.g., RabbitMQ, Kafka) for events.
Security:
Implement authentication and authorization (e.g., JWT tokens).
Secure sensitive data like payment info.
Performance:
Optimize database queries.
Use indexing on location data for efficient searches.
Reliability:
Implement retry mechanisms for external service calls.
Validate data at every layer.

Example Sequence Diagram
Scenario: Arriving user reserves and takes a parking spot.
Arriving User searches for spots.
Calls ParkingService.getAvailableSpots().
Receives available spots list.
Selects one and calls ParkingService.reserveSpot().
ParkingService updates the Availability status.
SpotReservedEvent is triggered.
NotificationService notifies the leaving user.
Arriving user goes to the spot and calls ParkingService.confirmSpotTaken().
ParkingService updates the status to Taken.
SpotTakenEvent is triggered.
RewardService processes the reward.
NotificationService notifies both users of the successful transaction.

Conclusion
By following DDD principles, we've structured the parking spot exchange application into clear bounded contexts, each managing its specific domain logic. Entities, value objects, aggregates, and domain events are defined to encapsulate the business rules and workflows.
This modular design promotes scalability, maintainability, and a clear separation of concerns, which are essential for the complex interactions in an application akin to Uber but for parking spots.

Next Steps:
Prototyping: Begin implementing the core services, starting with the UserService and ParkingService.
Integration: Set up the infrastructure for domain events and ensure services can communicate effectively.
Testing: Write unit and integration tests for each service.
Feedback Loop: Continuously gather user feedback to refine the reward system and user experience.

Feel free to ask if you need more detailed explanations on any of the components or further assistance with the implementation strategy.



as of now we have the availability service

package com.parkingexchange.parking.application.dto;


import java.time.LocalDateTime;
import java.util.UUID;

public class AvailabilityRequest {

    private UUID leavingUserId;
    private double latitude;
    private double longitude;
    private LocalDateTime estimatedLeavingTime;


    // Getters and Setters

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public LocalDateTime getEstimatedLeavingTime() {
        return estimatedLeavingTime;
    }

    public void setEstimatedLeavingTime(LocalDateTime estimatedLeavingTime) {
        this.estimatedLeavingTime = estimatedLeavingTime;
    }

}package com.parkingexchange.parking.application.dto;


import com.parkingexchange.parking.domain.model.AvailabilityStatus;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

public class AvailabilityResponse {
    private UUID availabilityId;
    private UUID leavingUserId;
    private double latitude;
    private double longitude;
    private LocalDateTime estimatedLeavingTime;
    private AvailabilityStatus status;
    private UUID reservedByUserId;
    private List<ReservationRequestResponse> reservationRequests;

    // Getters and setters


    // Getters and Setters

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public void setAvailabilityId(UUID availabilityId) {
        this.availabilityId = availabilityId;
    }

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public LocalDateTime getEstimatedLeavingTime() {
        return estimatedLeavingTime;
    }

    public void setEstimatedLeavingTime(LocalDateTime estimatedLeavingTime) {
        this.estimatedLeavingTime = estimatedLeavingTime;
    }

    public AvailabilityStatus getStatus() {
        return status;
    }

    public void setStatus(AvailabilityStatus status) {
        this.status = status;
    }

    public UUID getReservedByUserId() {
        return reservedByUserId;
    }

    public void setReservedByUserId(UUID reservedByUserId) {
        this.reservedByUserId = reservedByUserId;
    }

    public List<ReservationRequestResponse> getReservationRequests() {
        return reservationRequests;
    }

    public void setReservationRequests(List<ReservationRequestResponse> reservationRequests) {
        this.reservationRequests = reservationRequests;
    }
}package com.parkingexchange.parking.application.dto;

import java.util.UUID;

public class CancelAvailabilityRequest {

    private UUID userId;

    // Getters and Setters

    public UUID getUserId() {
        return userId;
    }

    public void setUserId(UUID userId) {
        this.userId = userId;
    }
}
package com.parkingexchange.parking.application.dto;

import java.util.UUID;

public class ConfirmSpotTakenRequest {

    private UUID arrivingUserId;

    // Getters and Setters

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    public void setArrivingUserId(UUID arrivingUserId) {
        this.arrivingUserId = arrivingUserId;
    }
}
package com.parkingexchange.parking.application.dto.mapper;

import com.parkingexchange.parking.application.dto.AvailabilityResponse;
import com.parkingexchange.parking.application.dto.ReservationRequestResponse;
import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.model.AvailabilityId;
import com.parkingexchange.parking.domain.model.ReservationRequest;
import org.mapstruct.Mapper;

import java.util.UUID;

@Mapper(componentModel = "spring")
public interface AvailabilityMapper {

    AvailabilityResponse toResponse(Availability availability);

    ReservationRequestResponse toReservationResponse(ReservationRequest request);

    default UUID map (AvailabilityId value){
        return value.getValue();
    }
    // Add other mapping methods if needed
}
package com.parkingexchange.parking.application.dto;

import java.util.UUID;

public class ReservationRequestDto {

    private UUID arrivingUserId;
    private String message;

    // Getters and Setters

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    public void setArrivingUserId(UUID arrivingUserId) {
        this.arrivingUserId = arrivingUserId;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
package com.parkingexchange.parking.application.dto;

import java.util.UUID;


import com.parkingexchange.parking.domain.model.RequestStatus;
import java.time.LocalDateTime;
import java.util.UUID;

public class ReservationRequestResponse {

    private UUID requestId;
    private UUID arrivingUserId;
    private UUID leavingUserId;
    private RequestStatus status;
    private LocalDateTime requestedAt;
    private LocalDateTime respondedAt;
    private String message;

    // Getters and Setters

    public UUID getRequestId() {
        return requestId;
    }

    public void setRequestId(UUID requestId) {
        this.requestId = requestId;
    }

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    public void setArrivingUserId(UUID arrivingUserId) {
        this.arrivingUserId = arrivingUserId;
    }

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public RequestStatus getStatus() {
        return status;
    }

    public void setStatus(RequestStatus status) {
        this.status = status;
    }

    public LocalDateTime getRequestedAt() {
        return requestedAt;
    }

    public void setRequestedAt(LocalDateTime requestedAt) {
        this.requestedAt = requestedAt;
    }

    public LocalDateTime getRespondedAt() {
        return respondedAt;
    }

    public void setRespondedAt(LocalDateTime respondedAt) {
        this.respondedAt = respondedAt;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}package com.parkingexchange.parking.application.dto;

import com.parkingexchange.parking.domain.model.RequestStatus;

import java.time.LocalDateTime;
import java.util.UUID;

public class ReservationResponseDto {

    private UUID leavingUserId;
    private boolean accepted;

    // Getters and Setters

    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    public void setLeavingUserId(UUID leavingUserId) {
        this.leavingUserId = leavingUserId;
    }

    public boolean isAccepted() {
        return accepted;
    }

    public void setAccepted(boolean accepted) {
        this.accepted = accepted;
    }
}package com.parkingexchange.parking.application.dto;

public class SearchCriteria {

    private double latitude;
    private double longitude;
    private double radius;

    // Getters and Setters

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }
}package com.parkingexchange.parking.application.service;



import com.parkingexchange.common.domain.model.DomainEvent;
import com.parkingexchange.common.domain.model.DomainEventPublisher;
import com.parkingexchange.parking.application.dto.*;
import com.parkingexchange.parking.application.dto.mapper.AvailabilityMapper;
import com.parkingexchange.parking.domain.model.*;
import com.parkingexchange.parking.domain.repository.AvailabilityRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;


@Service
public class AvailabilityApplicationServiceImpl implements AvailabilityApplicationService {

    private final AvailabilityRepository availabilityRepository;
    private final AvailabilityMapper availabilityMapper;
    private final DomainEventPublisher domainEventPublisher;

    @Autowired
    public AvailabilityApplicationServiceImpl(AvailabilityRepository availabilityRepository,
                                              AvailabilityMapper availabilityMapper,
                                              DomainEventPublisher domainEventPublisher) {
        this.availabilityRepository = availabilityRepository;
        this.availabilityMapper = availabilityMapper;
        this.domainEventPublisher = domainEventPublisher;
    }

    @Transactional
    @Override
    public AvailabilityResponse reportAvailability(AvailabilityRequest request) {
        Location location = new Location(request.getLatitude(), request.getLongitude());
        Availability availability = Availability.create(
                request.getLeavingUserId(),
                location,
                request.getEstimatedLeavingTime()
        );

        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toResponse(availability);
    }

    @Override
    public List<AvailabilityResponse> searchAvailableSpots(SearchCriteria criteria) {
        List<Availability> availabilities = availabilityRepository.findAvailableInArea(
                criteria.getLatitude(),
                criteria.getLongitude(),
                criteria.getRadius()
        );

        return availabilities.stream()
                .map(availabilityMapper::toResponse)
                .collect(Collectors.toList());
    }

    @Transactional
    @Override
    public ReservationRequestResponse requestReservation(AvailabilityId availabilityId, ReservationRequestDto requestDto) {

        Availability availability = availabilityRepository.findByAvailabilityId(availabilityId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        // Delegate to the aggregate root
        ReservationRequest reservationRequest = availability.createReservationRequest(
                requestDto.getArrivingUserId(),
                requestDto.getMessage()
        );

        // Save the aggregate
        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toReservationResponse(reservationRequest);

    }

    @Transactional
    @Override
    public ReservationRequestResponse respondToReservation(UUID requestId, ReservationResponseDto responseDto) {
        Availability availability = availabilityRepository.findByReservationRequestId(requestId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        // Delegate to the aggregate root
        availability.respondToReservationRequest(requestId, responseDto.getLeavingUserId(), responseDto.isAccepted());

        // Save the aggregate
        availabilityRepository.save(availability);

        // Retrieve the updated reservation request
        ReservationRequest reservationRequest = availability.getReservationRequests().stream()
                .filter(r -> r.getRequestId().equals(requestId))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Reservation request not found"));

        publishDomainEvents(availability);

        return availabilityMapper.toReservationResponse(reservationRequest);
    }

    @Transactional
    @Override
    public AvailabilityResponse confirmSpotTaken(AvailabilityId availabilityId, ConfirmSpotTakenRequest request) {
        Availability availability = availabilityRepository.findByAvailabilityId(availabilityId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        availability.confirmTaken(request.getArrivingUserId());

        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toResponse(availability);
    }

    @Transactional
    @Override
    public AvailabilityResponse cancelAvailability(AvailabilityId availabilityId, CancelAvailabilityRequest request) {
        Availability availability = availabilityRepository.findByAvailabilityId(availabilityId)
                .orElseThrow(() -> new IllegalArgumentException("Availability not found"));

        availability.cancel(request.getUserId());

        availabilityRepository.save(availability);

        publishDomainEvents(availability);

        return availabilityMapper.toResponse(availability);
    }

    private void publishDomainEvents(Availability availability) {
        List<DomainEvent> events = availability.domainEvents();
        for (DomainEvent event : events) {
            domainEventPublisher.publish(event);
        }
    }
}package com.parkingexchange.parking.application.service;


import com.parkingexchange.parking.application.dto.*;
import com.parkingexchange.parking.domain.model.AvailabilityId;

import java.util.List;
import java.util.UUID;

public interface AvailabilityApplicationService {

    AvailabilityResponse reportAvailability(AvailabilityRequest request);

    List<AvailabilityResponse> searchAvailableSpots(SearchCriteria criteria);

    ReservationRequestResponse requestReservation(AvailabilityId availabilityId, ReservationRequestDto requestDto);

    ReservationRequestResponse respondToReservation(UUID requestId, ReservationResponseDto responseDto);

    AvailabilityResponse confirmSpotTaken(AvailabilityId availabilityId, ConfirmSpotTakenRequest request);

    AvailabilityResponse cancelAvailability(AvailabilityId availabilityId, CancelAvailabilityRequest request);
}package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class AvailabilityCanceledEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID arrivingUserId;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually


    public AvailabilityCanceledEvent(UUID availabilityId, UUID arrivingUserId) {
        this.occurredOn = Instant.now();
        this.availabilityId = availabilityId;
        this.arrivingUserId = arrivingUserId;
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

}
package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class ReservationRequestCreatedEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID requestId;
    private final UUID arrivingUserId;
    private final String message;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually

    public ReservationRequestCreatedEvent(UUID availabilityId, UUID requestId, UUID arrivingUserId, String message) {
        this.availabilityId = availabilityId;
        this.requestId = requestId;
        this.arrivingUserId = arrivingUserId;
        this.message = message;
        this.occurredOn = Instant.now();
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

}

package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class ReservationRequestRespondedEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID requestId;
    private final boolean accepted;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually


    public ReservationRequestRespondedEvent(UUID availabilityId, UUID requestId, boolean accepted) {
        this.occurredOn = Instant.now();
        this.availabilityId = availabilityId;
        this.requestId = requestId;
        this.accepted = accepted;
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public UUID getRequestId() {
        return requestId;
    }

    public boolean isAccepted() {
        return accepted;
    }
}
package com.parkingexchange.parking.domain.event;

import com.parkingexchange.common.domain.model.DomainEvent;

import java.time.Instant;
import java.util.UUID;

public class SpotTakenConfirmedEvent implements DomainEvent {

    private final UUID availabilityId;
    private final UUID arrivingUserId;
    private final Instant occurredOn;
    private final String moduleName = "parking"; // TODO you should't set this manually


    public SpotTakenConfirmedEvent(UUID availabilityId, UUID arrivingUserId) {
        this.occurredOn = Instant.now();
        this.availabilityId = availabilityId;
        this.arrivingUserId = arrivingUserId;
    }

    @Override
    public int eventVersion() {
        return 1;
    }

    @Override
    public Instant occurredOn() {
        return occurredOn;
    }

    @Override
    public String moduleName() {
        return moduleName;
    }

    public UUID getAvailabilityId() {
        return availabilityId;
    }

    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

}
package com.parkingexchange.parking.domain.model;


import jakarta.persistence.Embeddable;

import java.util.UUID;

@Embeddable
public class AvailabilityId {

    private final UUID id;

    private AvailabilityId() {
        this.id = UUID.randomUUID();
    }

    public AvailabilityId(UUID id) {
        if (id == null) {
            throw new IllegalArgumentException("AvailabilityId cannot be null");
        }
        this.id = id;
    }

    public static AvailabilityId generate() {
        return new AvailabilityId();
    }

    public static AvailabilityId of(UUID id) {
        return new AvailabilityId(id);
    }

    public UUID getValue() {
        return id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        AvailabilityId that = (AvailabilityId) o;

        return id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    // For logging and debugging
    @Override
    public String toString() {
        return id.toString();
    }
}package com.parkingexchange.parking.domain.model;

import com.parkingexchange.common.domain.model.BaseEntity;
import com.parkingexchange.parking.domain.event.AvailabilityCanceledEvent;
import com.parkingexchange.parking.domain.event.ReservationRequestCreatedEvent;
import com.parkingexchange.parking.domain.event.ReservationRequestRespondedEvent;
import com.parkingexchange.parking.domain.event.SpotTakenConfirmedEvent;
import jakarta.persistence.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;
import java.util.*;


/**
 * Aggregate Root Entity representing the availability of a parking spot.
 */
@Entity
public class Availability extends BaseEntity {

    @EmbeddedId
    private AvailabilityId availabilityId;

    @NotNull
    @Column(name = "leaving_user_id", nullable = false)
    private UUID leavingUserId;

    @NotNull
    @Valid
    @Embedded
    private Location location;

    @NotNull
    @Future(message = "Estimated leaving time must be in the future")
    @Column(name = "estimated_leaving_time", nullable = false)
    private LocalDateTime estimatedLeavingTime;

    @NotNull
    @Enumerated(EnumType.STRING)
    @Column(name = "status", nullable = false)
    private AvailabilityStatus status;

    @Column(name = "reserved_by_user_id")
    private UUID reservedByUserId;

    @OneToMany(mappedBy = "availability", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<ReservationRequest> reservationRequests = new ArrayList<>();

    protected Availability() {
        // Required by JPA
    }

    private Availability(AvailabilityId availabilityId,
                         UUID leavingUserId,
                         Location location,
                         LocalDateTime estimatedLeavingTime) {
        this.setAvailabilityId(availabilityId);
        this.setLeavingUserId(leavingUserId);
        this.setLocation(location);
        this.setEstimatedLeavingTime(estimatedLeavingTime);
        this.setStatus(AvailabilityStatus.AVAILABLE);
    }

    // Static factory method for creation
    public static Availability create(UUID leavingUserId, Location location, LocalDateTime estimatedLeavingTime) {
        AvailabilityId availabilityId = AvailabilityId.generate();
        return new Availability(availabilityId, leavingUserId, location, estimatedLeavingTime);
    }

    // Business methods

    public ReservationRequest createReservationRequest(UUID arrivingUserId, String message) {
        // Check if there's already a pending request from this user
        boolean hasExistingRequest = reservationRequests.stream()
                .anyMatch(request -> request.getArrivingUserId().equals(arrivingUserId)
                        && request.getStatus() == RequestStatus.PENDING);

        assertStateFalse(hasExistingRequest, "You already have a pending reservation request for this availability.");

        // Create a new reservation request
        ReservationRequest request = ReservationRequest.create(
                arrivingUserId,
                this.leavingUserId,
                message,
                this
        );

        // Add the request to the collection
        this.reservationRequests.add(request);

        // Update status if necessary
        this.requestReservation(arrivingUserId);

        this.publish(new ReservationRequestCreatedEvent(
                this.availabilityId.getValue(),
                request.getRequestId(),
                arrivingUserId,
                message
        ));

        return request;
    }

    public void respondToReservationRequest(UUID requestId, UUID leavingUserId, boolean accepted) {
        assertArgumentTrue(this.leavingUserId.equals(leavingUserId),
                "You do not have permission to respond to this request");

        ReservationRequest request = this.reservationRequests.stream()
                .filter(r -> r.getRequestId().equals(requestId))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Reservation request not found"));

        request.respond(leavingUserId, accepted);

        this.publish(new ReservationRequestRespondedEvent(
                this.availabilityId.getValue(),
                requestId,
                accepted
        ));

        if (accepted) {
            this.reserve(request.getArrivingUserId());
        } else {
            this.makeAvailableAfterRejection();
        }
    }

    public void requestReservation(UUID arrivingUserId) {
        assertArgumentTrue(this.status.equals(AvailabilityStatus.AVAILABLE),
                "Spot is not available for reservation");
        assertArgumentNotNull(arrivingUserId, "Arriving user ID cannot be null");
        this.setStatus(AvailabilityStatus.REQUEST_PENDING);
    }

    /**
     * Reserves this availability for a given arriving user.
     *
     * @param arrivingUserId the ID of the arriving user
     * @throws IllegalArgumentException if the spot is not available or the arrivingUserId is null
     */
    public void reserve(UUID arrivingUserId) {
        this.assertArgumentTrue(this.getStatus().equals(AvailabilityStatus.REQUEST_PENDING),
                "Only spots with a reqeust pending can be reserved");
        this.assertArgumentNotNull(arrivingUserId, "ArrivingUserId cannot be null");
        this.setReservedByUserId(arrivingUserId);
        this.setStatus(AvailabilityStatus.RESERVED);
    }

    private void setStatus(AvailabilityStatus aStatus){
        this.assertArgumentNotNull(aStatus, "status cannot be null");
        this.status = aStatus;
    }

    /**
     * Makes the availability available again after a reservation request is rejected.
     *
     * This method ensures that the availability's status is updated appropriately
     * and any business rules are enforced.
     */
    public void makeAvailableAfterRejection() {
        assertStateTrue(this.status == AvailabilityStatus.REQUEST_PENDING,
                "Availability status must be REQUEST_PENDING to make it available again.");
        this.setStatus(AvailabilityStatus.AVAILABLE);
    }

    /**
     * Confirms that the spot has been taken by the arriving user.
     *
     * @param arrivingUserId the ID of the arriving user
     * @throws IllegalArgumentException if the spot is not reserved or reserved by another user
     */
    public void confirmTaken(UUID arrivingUserId) {
        this.assertArgumentTrue(this.getStatus().equals(AvailabilityStatus.RESERVED),
                "Only reserved spots can be confirmed as taken");
        this.assertArgumentTrue(this.getReservedByUserId().equals(arrivingUserId),
                "This spot is not reserved by the provided user");
        this.setStatus(AvailabilityStatus.TAKEN);

        this.publish(new SpotTakenConfirmedEvent(
                this.availabilityId.getValue(),
                arrivingUserId
        ));
    }

    /**
     * Cancels this availability.
     *
     * <p>This method can be called by the leaving user.
     * It checks for the necessary permissions and updates the status accordingly.</p>
     *
     * @param userId the ID of the user attempting to cancel
     * @throws IllegalStateException if the user does not have permission or the availability is already canceled
     */

    public void cancel(UUID userId) {
        boolean hasPermission = this.leavingUserId.equals(userId);
        assertStateTrue(hasPermission, "You do not have permission to cancel this availability");
        assertStateFalse(this.status == AvailabilityStatus.CANCELED, "Availability is already canceled");
        this.setStatus(AvailabilityStatus.CANCELED);

        // Cancel all pending reservation requests
        for (ReservationRequest request : reservationRequests) {
            if (request.getStatus() == RequestStatus.PENDING) {
                request.cancel();
            }
        }

        this.publish(new AvailabilityCanceledEvent(
                this.availabilityId.getValue(),
                userId
        ));
    }

    // Getters and Setters with validation where applicable

    /**
     * Returns the unique identifier of this {@code Availability}.
     *
     * @return the {@code AvailabilityId}
     */
    public AvailabilityId getAvailabilityId() {
        return availabilityId;
    }

    private void setAvailabilityId(AvailabilityId availabilityId) {
        this.assertArgumentNotNull(availabilityId, "AvailabilityId cannot be null");
        this.availabilityId = availabilityId;
    }

    /**
     * Returns the ID of the user who is leaving the parking spot.
     *
     * @return the leaving user's ID
     */
    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    private void setLeavingUserId(UUID leavingUserId) {
        this.assertArgumentNotNull(leavingUserId, "LeavingUserId cannot be null");
        this.leavingUserId = leavingUserId;
    }

    /**
     * Returns the ID of the user who has reserved this spot, if any.
     *
     * @return the reserving user's ID, or null if not reserved
     */
    public UUID getReservedByUserId() {
        return reservedByUserId;
    }

    private void setReservedByUserId(UUID reservedByUserId) {
        this.assertArgumentNotNull(reservedByUserId, "ReservedByUserId cannot be null");
        this.reservedByUserId = reservedByUserId;
    }

    /**
     * Returns the location of the parking spot.
     *
     * @return the {@code Location}
     */
    public Location getLocation() {
        return location;
    }

    private void setLocation(Location location) {
        this.assertArgumentNotNull(location, "Location cannot be null");
        this.location = location;
    }

    /**
     * Returns the estimated time when the user will leave the spot.
     *
     * @return the estimated leaving time
     */
    public LocalDateTime getEstimatedLeavingTime() {
        return estimatedLeavingTime;
    }

    private void setEstimatedLeavingTime(LocalDateTime estimatedLeavingTime) {
        this.assertArgumentNotNull(estimatedLeavingTime, "Estimated leaving time cannot be null");
        this.assertArgumentTrue(estimatedLeavingTime.isAfter(LocalDateTime.now()),
                "Estimated leaving time must be in the future");
        this.estimatedLeavingTime = estimatedLeavingTime;
    }

    public List<ReservationRequest> getReservationRequests() {
        return Collections.unmodifiableList(reservationRequests);
    }

    /**
     * Returns the current status of this availability.
     *
     * @return the {@code AvailabilityStatus}
     */
    public AvailabilityStatus getStatus() {
        return status;
    }

    // equals and hashCode based on 'availabilityId'

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Availability that = (Availability) o;

        return availabilityId.equals(that.availabilityId);
    }

    @Override
    public int hashCode() {
        return availabilityId.hashCode();
    }

    @Override
    public String toString() {
        return "Availability{" +
                "availabilityId=" + availabilityId +
                ", leavingUserId=" + leavingUserId +
                ", location=" + location +
                ", estimatedLeavingTime=" + estimatedLeavingTime +
                ", status=" + status +
                ", reservedByUserId=" + reservedByUserId +
                '}';
    }
}package com.parkingexchange.parking.domain.model;

public enum AvailabilityStatus {
    AVAILABLE,
    REQUEST_PENDING,
    RESERVED,
    TAKEN,
    CANCELED
}
package com.parkingexchange.parking.domain.model;


import com.parkingexchange.common.AssertionConcern;
import jakarta.persistence.Embeddable;

import java.util.Objects;

/**
 * Immutable Value Object representing a geographical location.
 *
 * <p>This class encapsulates latitude and longitude coordinates, ensuring they are within valid ranges.</p>
 *
 * <p>Instances of this class are immutable and can be safely shared. The class uses assertion methods from
 * {@link AssertionConcern} to validate the input parameters.</p>
 */
@Embeddable
public class Location extends AssertionConcern {

    private final double latitude;
    private final double longitude;

    /**
     * Protected no-argument constructor for JPA.
     *
     * <p>Sets the latitude and longitude to 0.0 by default. This constructor should not be used directly.</p>
     */
    protected Location() {
        // Required by JPA
        this.latitude = 0.0;
        this.longitude = 0.0;
    }

    /**
     * Constructs a new {@code Location} with the specified latitude and longitude.
     *
     * @param latitude  the latitude coordinate, must be between -90 and 90 degrees
     * @param longitude the longitude coordinate, must be between -180 and 180 degrees
     * @throws IllegalArgumentException if the latitude or longitude are out of valid ranges
     */
    public Location(double latitude, double longitude) {
        // Validate latitude range
        this.assertArgumentRange(latitude, -90.0, 90.0,
                "Latitude must be between -90 and 90 degrees");

        // Validate longitude range
        this.assertArgumentRange(longitude, -180.0, 180.0,
                "Longitude must be between -180 and 180 degrees");

        this.latitude = latitude;
        this.longitude = longitude;
    }

    /**
     * Returns the latitude coordinate of this {@code Location}.
     *
     * @return the latitude value
     */
    public double getLatitude() {
        return latitude;
    }

    /**
     * Returns the longitude coordinate of this {@code Location}.
     *
     * @return the longitude value
     */
    public double getLongitude() {
        return longitude;
    }

    /**
     * Determines whether this location is equal to another object.
     *
     * <p>Two locations are considered equal if they have the same latitude and longitude values.</p>
     *
     * @param o the reference object with which to compare
     * @return {@code true} if this location is the same as the argument; {@code false} otherwise
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Location location = (Location) o;

        return Double.compare(location.latitude, latitude) == 0 &&
                Double.compare(location.longitude, longitude) == 0;
    }

    /**
     * Returns a hash code value for this location.
     *
     * <p>This method is supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.</p>
     *
     * @return the hash code value for this location
     */
    @Override
    public int hashCode() {
        return Objects.hash(latitude, longitude);
    }

    /**
     * Returns a string representation of this location.
     *
     * <p>The string representation consists of the latitude and longitude enclosed in parentheses.</p>
     *
     * @return a string representation of this location
     */
    @Override
    public String toString() {
        return "Location(" + "latitude=" + latitude + ", longitude=" + longitude + ')';
    }
}package com.parkingexchange.parking.domain.model;

public enum RequestStatus {
    PENDING,
    ACCEPTED,
    REJECTED,
    CANCELED
}package com.parkingexchange.parking.domain.model;

import com.parkingexchange.common.AssertionConcern;
import jakarta.persistence.*;

import java.time.LocalDateTime;
import java.util.UUID;

/**
 * The {@code ReservationRequest} class represents a reservation request in the parking exchange system.
 * It models the interaction between an arriving user who wants to reserve a parking spot and a leaving user
 * who is offering the spot. The class includes details such as the users involved, the status of the request,
 * timestamps of when the request was made and responded to, any messages included with the request, and
 * the associated parking availability.
 *
 * <p>This class is an entity managed by JPA (Java Persistence API), making it persistable in a relational database.</p>
 */

@Entity
public class ReservationRequest extends AssertionConcern {

    @Id
    private UUID requestId;

    /**
     * Identifier of the user who is arriving and making the reservation request.
     */
    @Column(nullable = false)
    private UUID arrivingUserId;

    /**
     * Identifier of the user who is leaving and offering the parking spot.
     */
    @Column(nullable = false)
    private UUID leavingUserId;

    /**
     * Current status of the reservation request.
     */
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private RequestStatus status;

    /**
     * Timestamp when the reservation request was created.
     */
    @Column(nullable = false)
    private LocalDateTime requestedAt;

    /**
     * Timestamp when the reservation request was responded to.
     */
    private LocalDateTime respondedAt;

    /**
     * Optional message included with the reservation request.
     */
    private String message;

    /**
     * The associated parking availability related to this request.
     */
    @ManyToOne
    private Availability availability;

    /**
     * Protected constructor for JPA use only.
     */
    protected ReservationRequest() {
    }

    /**
     * Creates a new reservation request.
     *
     * @param arrivingUserId the UUID of the arriving user
     * @param leavingUserId  the UUID of the leaving user
     * @param message        an optional message for the request
     * @param availability   the associated parking availability
     * @return a new instance of {@code ReservationRequest}
     */
    public static ReservationRequest create(UUID arrivingUserId, UUID leavingUserId,
                                            String message, Availability availability) {
        return new ReservationRequest(arrivingUserId, leavingUserId, message, availability);
    }

    /**
     * Private constructor to initialize a new reservation request with the provided details.
     *
     * @param arrivingUserId the UUID of the arriving user
     * @param leavingUserId  the UUID of the leaving user
     * @param message        an optional message for the request
     * @param availability   the associated parking availability
     */
    private ReservationRequest(UUID arrivingUserId, UUID leavingUserId,
                               String message, Availability availability) {
        this.setRequestId(UUID.randomUUID());
        this.setArrivingUserId(arrivingUserId);
        this.setLeavingUserId(leavingUserId);
        this.setStatus(RequestStatus.PENDING);
        this.setRequestedAt(LocalDateTime.now());
        this.setMessage(message);
        this.setAvailability(availability);
    }
    // Business methods

    private void setRequestId(UUID requestId){
        this.requestId = requestId;
    }
    /**
     * Allows the leaving user to respond to the reservation request, updating the status to accepted or rejected
     * and recording the response time.
     *
     * @param leavingUserId the UUID of the leaving user responding to the request
     * @param accepted      {@code true} if the request is accepted; {@code false} otherwise
     * @throws IllegalArgumentException if the leavingUserId does not match or the request is not pending
     */
    public void respond(UUID leavingUserId, boolean accepted) {
        assertArgumentTrue(this.leavingUserId.equals(leavingUserId), "You do not have permission to respond to this request");
        assertArgumentTrue(this.status.equals(RequestStatus.PENDING), "Request has already been responded to");

        this.setRespondedAt(LocalDateTime.now());
        this.setStatus(accepted ? RequestStatus.ACCEPTED : RequestStatus.REJECTED);
    }

    /**
     * Allows the arriving user to cancel the reservation request if it's still pending, updating the status
     * to canceled and recording the cancellation time.
     *
     * @throws IllegalArgumentException if the request is not pending
     */
    public void cancel() {
        assertArgumentTrue(this.status == RequestStatus.PENDING, "Only pending requests can be canceled.");
        this.setStatus(RequestStatus.CANCELED);
        this.setRespondedAt(LocalDateTime.now());
    }

    // Getters and Setters with validation

    /**
     * Returns the unique identifier for the reservation request.
     *
     * @return the {@code requestId}
     */
    public UUID getRequestId() {
        return requestId;
    }

    private void setAvailability(Availability availability) {
        this.availability = availability;
    }

    /**
     * Returns the associated availability related to this request.
     *
     * @return the {@code availability}
     */
    public Availability getAvailability() {
        return availability;
    }

    /**
     * Returns the UUID of the arriving user.
     *
     * @return the {@code arrivingUserId}
     */
    public UUID getArrivingUserId() {
        return arrivingUserId;
    }

    private void setArrivingUserId(UUID arrivingUserId) {
        assertArgumentNotNull(arrivingUserId, "ArrivingUserId cannot be null");
        this.arrivingUserId = arrivingUserId;
    }

    /**
     * Returns the UUID of the leaving user.
     *
     * @return the {@code leavingUserId}
     */
    public UUID getLeavingUserId() {
        return leavingUserId;
    }

    private void setLeavingUserId(UUID leavingUserId) {
        assertArgumentNotNull(leavingUserId, "LeavingUserId cannot be null");
        this.leavingUserId = leavingUserId;
    }

    /**
     * Returns the current status of the reservation request.
     *
     * @return the {@code status}
     */
    public RequestStatus getStatus() {
        return status;
    }

    private void setStatus(RequestStatus status) {
        assertArgumentNotNull(status, "Status cannot be null");
        this.status = status;
    }

    /**
     * Returns the timestamp when the reservation request was created.
     *
     * @return the {@code requestedAt} timestamp
     */
    public LocalDateTime getRequestedAt() {
        return requestedAt;
    }

    private void setRequestedAt(LocalDateTime requestedAt) {
        this.requestedAt = requestedAt;
    }

    /**
     * Returns the timestamp when the reservation request was responded to or canceled.
     *
     * @return the {@code respondedAt} timestamp
     */
    public LocalDateTime getRespondedAt() {
        return respondedAt;
    }

    private void setRespondedAt(LocalDateTime respondedAt) {
        this.respondedAt = respondedAt;
    }

    /**
     * Returns the optional message included with the reservation request.
     *
     * @return the {@code message}
     */
    public String getMessage() {
        return message;
    }

    private void setMessage(String message) {
        this.message = message;
    }
}package com.parkingexchange.parking.domain.repository;

import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.model.AvailabilityId;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface AvailabilityRepository {

    Availability save(Availability availability);

    Optional<Availability> findByAvailabilityId(AvailabilityId availabilityId);

    List<Availability> findAvailableInArea(double latitude, double longitude, double radius);

    Optional<Availability> findByReservationRequestId(@Param("requestId") UUID requestId);
    // Additional methods as needed
}package com.parkingexchange.parking.infrastructure.config;

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ModelMapperConfig {

    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }
}package com.parkingexchange.parking.infrastructure.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient userServiceWebClient(@Value("${user.context.base.url}") String baseUrl) {
        return WebClient.builder()
                .baseUrl(baseUrl)
                .build();
    }
}package com.parkingexchange.parking.infrastructure.interfaces.controller;

import com.parkingexchange.parking.application.service.AvailabilityApplicationService;
import com.parkingexchange.parking.application.dto.*;
import com.parkingexchange.parking.domain.model.AvailabilityId;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api/availability")
public class AvailabilityController {

    private final AvailabilityApplicationService availabilityService;

    public AvailabilityController(AvailabilityApplicationService availabilityService) {
        this.availabilityService = availabilityService;
    }

    @PostMapping
    public AvailabilityResponse reportAvailability(@RequestBody AvailabilityRequest request) {
        return availabilityService.reportAvailability(request);
    }

    @GetMapping("/search")
    public List<AvailabilityResponse> searchAvailableSpots(@RequestParam double latitude,
                                                           @RequestParam double longitude,
                                                           @RequestParam double radius) {
        SearchCriteria criteria = new SearchCriteria();
        criteria.setLatitude(latitude);
        criteria.setLongitude(longitude);
        criteria.setRadius(radius);
        return availabilityService.searchAvailableSpots(criteria);
    }

    @PostMapping("/{availabilityId}/request")
    public ReservationRequestResponse requestReservation(@PathVariable UUID availabilityId,
                                                         @RequestBody ReservationRequestDto requestDto) {
        return availabilityService.requestReservation(AvailabilityId.of(availabilityId), requestDto);
    }

    @PostMapping("/requests/{requestId}/respond")
    public ReservationRequestResponse respondToReservation(@PathVariable UUID requestId,
                                                           @RequestBody ReservationResponseDto responseDto) {
        return availabilityService.respondToReservation(requestId, responseDto);
    }

    @PostMapping("/{availabilityId}/confirm")
    public AvailabilityResponse confirmSpotTaken(@PathVariable UUID availabilityId,
                                                 @RequestBody ConfirmSpotTakenRequest request) {
        return availabilityService.confirmSpotTaken(AvailabilityId.of(availabilityId), request);
    }

    @PostMapping("/{availabilityId}/cancel")
    public AvailabilityResponse cancelAvailability(@PathVariable UUID availabilityId,
                                                   @RequestBody CancelAvailabilityRequest request) {
        return availabilityService.cancelAvailability(AvailabilityId.of(availabilityId), request);
    }
}package com.parkingexchange.parking.infrastructure.interfaces.dto;

public class UserExistsResponse {

    private boolean exists;

    // Getters and Setters

    public boolean isExists() {
        return exists;
    }

    public void setExists(boolean exists) {
        this.exists = exists;
    }
}package com.parkingexchange.parking.infrastructure.interfaces.dto;


public class UserReputationResponse {

    private double reputation;

    // Getters and Setters

    public double getReputation() {
        return reputation;
    }

    public void setReputation(double reputation) {
        this.reputation = reputation;
    }
}
package com.parkingexchange.parking.infrastructure.resporitory;

import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.repository.AvailabilityRepository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Repository
public interface JpaAvailabilityRepository extends AvailabilityRepository, JpaRepository<Availability, UUID> {

    @Query("SELECT a FROM Availability a WHERE "
            + "(6371 * acos(cos(radians(:latitude)) * cos(radians(a.location.latitude)) "
            + "* cos(radians(a.location.longitude) - radians(:longitude)) "
            + "+ sin(radians(:latitude)) * sin(radians(a.location.latitude)))) < :radius")
    List<Availability> findAvailableInArea(
            @Param("latitude") double latitude,
            @Param("longitude") double longitude,
            @Param("radius") double radius);

    @Query("SELECT a FROM Availability a JOIN a.reservationRequests r WHERE r.requestId = :requestId")
    Optional<Availability> findByReservationRequestId(@Param("requestId") UUID requestId);


}package com.parkingexchange.parking;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@ComponentScan(basePackages = {"com.parkingexchange"})
@EnableJpaRepositories(basePackages = "com.parkingexchange")
@EntityScan(basePackages = "com.parkingexchange")
public class ParkingApplication {

	public static void main(String[] args) {
		SpringApplication.run(ParkingApplication.class, args);
	}

}
package com.parkingexchange.parking;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ParkingApplicationTests {

	@Test
	void contextLoads() {
	}

}
package com.parkingexchange.parking.application.dto.mapper;

import com.parkingexchange.parking.application.dto.AvailabilityResponse;
import com.parkingexchange.parking.application.dto.ReservationRequestResponse;
import com.parkingexchange.parking.domain.model.Availability;
import com.parkingexchange.parking.domain.model.ReservationRequest;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.processing.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2024-09-29T18:39:11+0200",
    comments = "version: 1.6.2, compiler: javac, environment: Java 21 (Oracle Corporation)"
)
@Component
public class AvailabilityMapperImpl implements AvailabilityMapper {

    @Override
    public AvailabilityResponse toResponse(Availability availability) {
        if ( availability == null ) {
            return null;
        }

        AvailabilityResponse availabilityResponse = new AvailabilityResponse();

        availabilityResponse.setAvailabilityId( map( availability.getAvailabilityId() ) );
        availabilityResponse.setLeavingUserId( availability.getLeavingUserId() );
        availabilityResponse.setEstimatedLeavingTime( availability.getEstimatedLeavingTime() );
        availabilityResponse.setStatus( availability.getStatus() );
        availabilityResponse.setReservedByUserId( availability.getReservedByUserId() );
        availabilityResponse.setReservationRequests( reservationRequestListToReservationRequestResponseList( availability.getReservationRequests() ) );

        return availabilityResponse;
    }

    @Override
    public ReservationRequestResponse toReservationResponse(ReservationRequest request) {
        if ( request == null ) {
            return null;
        }

        ReservationRequestResponse reservationRequestResponse = new ReservationRequestResponse();

        reservationRequestResponse.setRequestId( request.getRequestId() );
        reservationRequestResponse.setArrivingUserId( request.getArrivingUserId() );
        reservationRequestResponse.setLeavingUserId( request.getLeavingUserId() );
        reservationRequestResponse.setStatus( request.getStatus() );
        reservationRequestResponse.setRequestedAt( request.getRequestedAt() );
        reservationRequestResponse.setRespondedAt( request.getRespondedAt() );
        reservationRequestResponse.setMessage( request.getMessage() );

        return reservationRequestResponse;
    }

    protected List<ReservationRequestResponse> reservationRequestListToReservationRequestResponseList(List<ReservationRequest> list) {
        if ( list == null ) {
            return null;
        }

        List<ReservationRequestResponse> list1 = new ArrayList<ReservationRequestResponse>( list.size() );
        for ( ReservationRequest reservationRequest : list ) {
            list1.add( toReservationResponse( reservationRequest ) );
        }

        return list1;
    }
}


can you provide the implementation of the reward system