diff --git a/api/openapi.yml b/api/openapi.yml
new file mode 100644
index 0000000..5899a97
--- /dev/null
+++ b/api/openapi.yml
@@ -0,0 +1,266 @@
+openapi: 3.0.3
+info:
+ title: Appointment Booking API
+ description: API for managing appointments, treatments, clients, and specialists.
+ version: 1.0.0
+
+servers:
+ - url: http://localhost:8080
+ description: Local development server
+
+paths:
+ /treatments:
+ get:
+ summary: Get a list of available treatments
+ operationId: getTreatments
+ tags:
+ - Treatments
+ responses:
+ "200":
+ description: List of treatments
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Treatment"
+
+ post:
+ summary: Create a new treatment
+ operationId: createTreatment
+ tags:
+ - Treatments
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TreatmentRequest"
+ responses:
+ "201":
+ description: Treatment successfully created
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Treatment"
+ "400":
+ description: Invalid request data
+ "403":
+ description: Only specialists can create treatments
+
+ /treatments/{treatmentId}:
+ get:
+ summary: Get treatment details (with specialist info)
+ operationId: getTreatmentDetails
+ tags:
+ - Treatments
+ parameters:
+ - name: treatmentId
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Treatment details including assigned specialist
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/TreatmentDetails"
+ "404":
+ description: Treatment not found
+
+ /appointments:
+ get:
+ summary: Get all appointments with filtering options
+ operationId: getAppointments
+ tags:
+ - Appointments
+ parameters:
+ - name: clientId
+ in: query
+ schema:
+ type: string
+ - name: specialistId
+ in: query
+ schema:
+ type: string
+ - name: status
+ in: query
+ schema:
+ type: string
+ enum: [SCHEDULED, CANCELLED, COMPLETED]
+ responses:
+ "200":
+ description: List of appointments
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Appointment"
+
+ post:
+ summary: Schedule an appointment
+ operationId: createAppointment
+ tags:
+ - Appointments
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AppointmentRequest"
+ responses:
+ "201":
+ description: Appointment successfully created
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Appointment"
+ "400":
+ description: Invalid request data
+ "409":
+ description: Appointment slot conflict
+
+ /appointments/{appointmentId}:
+ patch:
+ summary: Update appointment status
+ operationId: updateAppointmentStatus
+ tags:
+ - Appointments
+ parameters:
+ - name: appointmentId
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AppointmentStatusUpdate"
+ responses:
+ "200":
+ description: Appointment status updated
+ "400":
+ description: Invalid status
+ "404":
+ description: Appointment not found
+
+ /availability:
+ get:
+ summary: Check appointment slot availability
+ operationId: checkAvailability
+ tags:
+ - Appointments
+ parameters:
+ - name: specialistId
+ in: query
+ required: true
+ schema:
+ type: string
+ - name: dateTime
+ in: query
+ required: true
+ schema:
+ type: string
+ format: date-time
+ responses:
+ "200":
+ description: Slot availability result
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ available:
+ type: boolean
+
+components:
+ schemas:
+ Treatment:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ name:
+ type: string
+ duration:
+ type: integer
+ description: Duration of the treatment in minutes
+ specialistId:
+ type: string
+ format: uuid
+
+ TreatmentRequest:
+ type: object
+ properties:
+ name:
+ type: string
+ duration:
+ type: integer
+ specialistId:
+ type: string
+ format: uuid
+
+ TreatmentDetails:
+ allOf:
+ - $ref: "#/components/schemas/Treatment"
+ - type: object
+ properties:
+ specialist:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ name:
+ type: string
+
+ AppointmentRequest:
+ type: object
+ required:
+ - clientId
+ - treatmentId
+ - dateTime
+ properties:
+ clientId:
+ type: string
+ format: uuid
+ treatmentId:
+ type: string
+ format: uuid
+ dateTime:
+ type: string
+ format: date-time
+
+ Appointment:
+ type: object
+ properties:
+ id:
+ type: string
+ format: uuid
+ clientId:
+ type: string
+ format: uuid
+ treatmentId:
+ type: string
+ format: uuid
+ dateTime:
+ type: string
+ format: date-time
+ status:
+ type: string
+ enum: [SCHEDULED, CANCELLED, COMPLETED]
+
+ AppointmentStatusUpdate:
+ type: object
+ required:
+ - status
+ properties:
+ status:
+ type: string
+ enum: [CANCELLED, COMPLETED]
diff --git a/pom.xml b/pom.xml
index dbec20a..715621d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,25 @@
org.springframework.boot
spring-boot-starter-validation
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.2.28
+
+
+ org.openapitools
+ jackson-databind-nullable
+ 0.2.6
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.8.5
+
@@ -168,6 +187,49 @@
+
+ org.openapitools
+ openapi-generator-maven-plugin
+ 7.11.0
+
+
+ appointmentBooking
+
+ generate
+
+
+ ${project.basedir}/api/openapi.yml
+
+ true
+ spring
+ com.capgemini.training.appointmentbooking.service.api
+ com.capgemini.training.appointmentbooking.service.model
+
+ false
+ false
+
+
+ true
+ spring-boot
+ true
+ true
+ true
+ true
+ true
+ java-time
+ false
+ true
+
+ @lombok.Generated
+ @lombok.ToString
+
+ source
+ swagger2
+
+
+
+
+
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/service/config/ServiceMappingConfiguration.java b/src/main/java/com/capgemini/training/appointmentbooking/service/config/ServiceMappingConfiguration.java
new file mode 100644
index 0000000..586486a
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/service/config/ServiceMappingConfiguration.java
@@ -0,0 +1,20 @@
+package com.capgemini.training.appointmentbooking.service.config;
+
+import com.capgemini.training.appointmentbooking.service.mapper.AppointmentApiMapper;
+import com.capgemini.training.appointmentbooking.service.mapper.TreatmentApiMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ServiceMappingConfiguration {
+
+ @Bean
+ AppointmentApiMapper getAppointmentApiMapper() {
+ return new AppointmentApiMapper();
+ }
+
+ @Bean
+ TreatmentApiMapper getTreatmentApiMapper() {
+ return new TreatmentApiMapper();
+ }
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/service/impl/AppointmentsApiController.java b/src/main/java/com/capgemini/training/appointmentbooking/service/impl/AppointmentsApiController.java
new file mode 100644
index 0000000..eabfd28
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/service/impl/AppointmentsApiController.java
@@ -0,0 +1,78 @@
+package com.capgemini.training.appointmentbooking.service.impl;
+
+import com.capgemini.training.appointmentbooking.common.datatype.AppointmentStatus;
+import com.capgemini.training.appointmentbooking.common.to.*;
+import com.capgemini.training.appointmentbooking.dataaccess.repository.criteria.AppointmentCriteria;
+import com.capgemini.training.appointmentbooking.logic.FindAppointmentUc;
+import com.capgemini.training.appointmentbooking.logic.ManageAppointmentUc;
+import com.capgemini.training.appointmentbooking.service.api.AppointmentsApi;
+import com.capgemini.training.appointmentbooking.service.model.*;
+import com.capgemini.training.appointmentbooking.service.mapper.AppointmentApiMapper;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class AppointmentsApiController implements AppointmentsApi {
+
+ private final FindAppointmentUc findAppointmentUc;
+ private final ManageAppointmentUc manageAppointmentUc;
+ private final AppointmentApiMapper appointmentMapper;
+
+ @Override
+ public ResponseEntity createAppointment(
+ @Valid AppointmentRequest appointmentRequest) {
+ AppointmentBookingEto bookingEto = appointmentMapper.toBookingEto(appointmentRequest);
+ AppointmentCto created = manageAppointmentUc.bookAppointment(bookingEto);
+ return ResponseEntity.status(201).body(appointmentMapper.toApiAppointment(created));
+ }
+
+ @Override
+ public ResponseEntity> getAppointments(
+ @Valid Optional clientId,
+ @Valid Optional specialistId,
+ @Valid Optional status) {
+
+ AppointmentCriteria.AppointmentCriteriaBuilder criteria = AppointmentCriteria.builder();
+ status.ifPresent(t -> criteria.status(AppointmentStatus.valueOf(t)));
+ clientId.ifPresent(t -> criteria.clientId(UUID.fromString(t).getMostSignificantBits()));
+ specialistId.ifPresent(t -> criteria.specialistId(UUID.fromString(t).getMostSignificantBits()));
+
+ List list = findAppointmentUc.findByCriteria(criteria.build());
+ List result =
+ list.stream().map(appointmentMapper::toApiAppointment).collect(Collectors.toList());
+ return ResponseEntity.ok(result);
+ }
+
+ @Override
+ public ResponseEntity updateAppointmentStatus(
+ String appointmentId, @Valid AppointmentStatusUpdate appointmentStatusUpdate) {
+ Long id = UUID.fromString(appointmentId).getMostSignificantBits();
+ AppointmentStatus status =
+ AppointmentStatus.valueOf(appointmentStatusUpdate.getStatus().name());
+ manageAppointmentUc.updateAppointmentStatus(id, status);
+ return ResponseEntity.ok().build();
+ }
+
+ @Override
+ public ResponseEntity checkAvailability(
+ @NotNull @Valid String specialistId,
+ @NotNull @Valid @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date dateTime) {
+ Long specialistLongId = UUID.fromString(specialistId).getMostSignificantBits();
+ boolean available =
+ !findAppointmentUc.hasConflictingAppointment(specialistLongId, dateTime.toInstant());
+
+ CheckAvailability200Response response = new CheckAvailability200Response();
+ response.setAvailable(Optional.of(available));
+ return ResponseEntity.ok(response);
+ }
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/service/impl/TreatmentsApiController.java b/src/main/java/com/capgemini/training/appointmentbooking/service/impl/TreatmentsApiController.java
new file mode 100644
index 0000000..8210b3a
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/service/impl/TreatmentsApiController.java
@@ -0,0 +1,57 @@
+package com.capgemini.training.appointmentbooking.service.impl;
+
+import com.capgemini.training.appointmentbooking.common.to.TreatmentCreationTo;
+import com.capgemini.training.appointmentbooking.common.to.TreatmentCto;
+import com.capgemini.training.appointmentbooking.logic.FindTreatmentUc;
+import com.capgemini.training.appointmentbooking.logic.ManageTreatmentUc;
+import com.capgemini.training.appointmentbooking.service.api.TreatmentsApi;
+import com.capgemini.training.appointmentbooking.service.model.Treatment;
+import com.capgemini.training.appointmentbooking.service.model.TreatmentDetails;
+import com.capgemini.training.appointmentbooking.service.model.TreatmentRequest;
+import com.capgemini.training.appointmentbooking.service.mapper.TreatmentApiMapper;
+import jakarta.validation.Valid;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/")
+@RequiredArgsConstructor
+public class TreatmentsApiController implements TreatmentsApi {
+
+ private final FindTreatmentUc findTreatmentUc;
+ private final ManageTreatmentUc manageTreatmentUc;
+ private final TreatmentApiMapper treatmentMapper;
+
+ @Override
+ public ResponseEntity createTreatment(@Valid TreatmentRequest treatmentRequest) {
+ TreatmentCreationTo to = treatmentMapper.toCreationTo(treatmentRequest);
+ TreatmentCto created = manageTreatmentUc.createTreatment(to);
+ return ResponseEntity.status(HttpStatus.CREATED).body(treatmentMapper.toApiTreatment(created));
+ }
+
+ @Override
+ public ResponseEntity getTreatmentDetails(String treatmentId) {
+ Long id = UUID.fromString(treatmentId).getMostSignificantBits();
+ Optional optional = findTreatmentUc.findById(id);
+
+ return optional
+ .map(treatmentMapper::toApiTreatmentDetails)
+ .map(ResponseEntity::ok)
+ .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).build());
+ }
+
+ @Override
+ public ResponseEntity> getTreatments() {
+ List list = findTreatmentUc.findAll();
+ List result =
+ list.stream().map(treatmentMapper::toApiTreatment).collect(Collectors.toList());
+ return ResponseEntity.ok(result);
+ }
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/service/mapper/AppointmentApiMapper.java b/src/main/java/com/capgemini/training/appointmentbooking/service/mapper/AppointmentApiMapper.java
new file mode 100644
index 0000000..c54642d
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/service/mapper/AppointmentApiMapper.java
@@ -0,0 +1,43 @@
+package com.capgemini.training.appointmentbooking.service.mapper;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import com.capgemini.training.appointmentbooking.common.to.AppointmentBookingEto;
+import com.capgemini.training.appointmentbooking.common.to.AppointmentCto;
+import com.capgemini.training.appointmentbooking.common.to.AppointmentEto;
+import com.capgemini.training.appointmentbooking.service.model.Appointment;
+import com.capgemini.training.appointmentbooking.service.model.AppointmentRequest;
+
+public class AppointmentApiMapper {
+
+ public AppointmentBookingEto toBookingEto(AppointmentRequest request) {
+ return AppointmentBookingEto.builder()
+ .clientId(toLong(request.getClientId()))
+ .treatmentId(toLong(request.getTreatmentId()))
+ .specialistId(
+ 0L) // specjalista nie jest częścią requestu – może być wyciągany przez treatment
+ .dateTime(request.getDateTime().toInstant())
+ .build();
+ }
+
+ public Appointment toApiAppointment(AppointmentCto cto) {
+ AppointmentEto appointmentEto = cto.appointmentEto();
+
+ Appointment result = new Appointment();
+ result.setId(Optional.of(toUuid(appointmentEto.id())));
+ result.setClientId(Optional.of(toUuid(cto.clientEto().id())));
+ result.setTreatmentId(Optional.of(toUuid(cto.treatmentCto().treatmentEto().id())));
+ result.setDateTime(Optional.of(java.util.Date.from(appointmentEto.dateTime())));
+ result.setStatus(Optional.of(Appointment.StatusEnum.valueOf(appointmentEto.status().name())));
+ return result;
+ }
+
+ private Long toLong(UUID uuid) {
+ return uuid.getMostSignificantBits();
+ }
+
+ private UUID toUuid(Long id) {
+ return new UUID(id, 0L);
+ }
+}
diff --git a/src/main/java/com/capgemini/training/appointmentbooking/service/mapper/TreatmentApiMapper.java b/src/main/java/com/capgemini/training/appointmentbooking/service/mapper/TreatmentApiMapper.java
new file mode 100644
index 0000000..8ea6c56
--- /dev/null
+++ b/src/main/java/com/capgemini/training/appointmentbooking/service/mapper/TreatmentApiMapper.java
@@ -0,0 +1,59 @@
+package com.capgemini.training.appointmentbooking.service.mapper;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import com.capgemini.training.appointmentbooking.common.to.SpecialistEto;
+import com.capgemini.training.appointmentbooking.common.to.TreatmentCreationTo;
+import com.capgemini.training.appointmentbooking.common.to.TreatmentCto;
+import com.capgemini.training.appointmentbooking.common.to.TreatmentEto;
+import com.capgemini.training.appointmentbooking.service.model.Treatment;
+import com.capgemini.training.appointmentbooking.service.model.TreatmentDetails;
+import com.capgemini.training.appointmentbooking.service.model.TreatmentDetailsAllOfSpecialist;
+import com.capgemini.training.appointmentbooking.service.model.TreatmentRequest;
+
+public class TreatmentApiMapper {
+
+ public TreatmentCreationTo toCreationTo(TreatmentRequest request) {
+ return TreatmentCreationTo.builder()
+ .name(request.getName().orElse(null))
+ .durationMinutes(request.getDuration().orElse(0))
+ .specialistId(request.getSpecialistId().map(UUID::getMostSignificantBits).orElse(null))
+ .description("Default description")
+ .build();
+ }
+
+ public Treatment toApiTreatment(TreatmentCto cto) {
+ TreatmentEto eto = cto.treatmentEto();
+ SpecialistEto specialist = cto.specialistEto();
+
+ Treatment result = new Treatment();
+ result.setId(Optional.ofNullable(eto.id()).map(this::toUuid));
+ result.setName(Optional.ofNullable(eto.name()));
+ result.setDuration(Optional.of(eto.durationMinutes()));
+ result.setSpecialistId(Optional.ofNullable(specialist.id()).map(this::toUuid));
+ return result;
+ }
+
+ public TreatmentDetails toApiTreatmentDetails(TreatmentCto cto) {
+ TreatmentEto eto = cto.treatmentEto();
+ SpecialistEto specialist = cto.specialistEto();
+
+ TreatmentDetails result = new TreatmentDetails();
+ result.setId(Optional.ofNullable(eto.id()).map(this::toUuid));
+ result.setName(Optional.ofNullable(eto.name()));
+ result.setDuration(Optional.of(eto.durationMinutes()));
+ result.setSpecialistId(Optional.ofNullable(specialist.id()).map(this::toUuid));
+
+ TreatmentDetailsAllOfSpecialist specialistDto = new TreatmentDetailsAllOfSpecialist();
+ specialistDto.setId(Optional.ofNullable(specialist.id()).map(this::toUuid));
+ specialistDto.setName(Optional.ofNullable(specialist.specialization().name()));
+
+ result.setSpecialist(Optional.of(specialistDto));
+ return result;
+ }
+
+ private UUID toUuid(Long id) {
+ return new UUID(id, 0L);
+ }
+}