From 4fb3412286b2716f2b6dba389c1af919d8c1fe6b Mon Sep 17 00:00:00 2001 From: UsatovPavel Date: Wed, 20 Aug 2025 00:24:33 +0300 Subject: [PATCH 1/5] Docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker-compose.yaml и Dockerfile для запуска приложения в контейнере. В Docker используется Postgresql. JWT_SECRET и CHATGPT_API_KEY передаются через файл .env в общей папке. spring.sql.init.mode=never в test-properties и там где properties т.к. в Docker таблицы пересоздавались hibernate Удалил data.sql и sqema.sql, их логика в DataInitializer, теперь hibernate полностью сам генерирует таблицы. Использование: для удаления базы и рестарта: скрипт на shell, для запуска контейнера: через плагин docker отдельная конфигурация в idea. --- Dockerfile | 9 +++++ docker-compose.yaml | 38 +++++++++++++++++++ .../smartcalendar/config/DataInitializer.java | 30 +++++++++++++++ .../resources/application-test.properties | 3 +- src/main/resources/application.properties | 8 ++-- src/main/resources/data.sql | 4 -- src/main/resources/schema.sql | 6 --- .../AudioControllerIntegrationTest.java | 4 +- .../ChatGPTControllerIntegrationTest.java | 3 +- 9 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yaml create mode 100644 src/main/java/com/smartcalendar/config/DataInitializer.java delete mode 100644 src/main/resources/data.sql delete mode 100644 src/main/resources/schema.sql diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8fac962 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM eclipse-temurin:21-jdk-alpine +WORKDIR /app + +COPY build/libs/*.jar app.jar +RUN ls -la /app + +ENV JAVA_OPTS="" + +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..a112c49 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,38 @@ +services: + postgres: + image: postgres:15 + container_name: smartcalendar_db + environment: + POSTGRES_DB: smartcalendar + POSTGRES_USER: smartuser + POSTGRES_PASSWORD: smartpass + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - backend + + app: + build: + context: . + dockerfile: Dockerfile + container_name: smartcalendar_app + depends_on: + - postgres + ports: + - "8080:8080" + environment: + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/smartcalendar + SPRING_DATASOURCE_USERNAME: smartuser + SPRING_DATASOURCE_PASSWORD: smartpass + JWT_SECRET: ${JWT_SECRET} + CHATGPT_API_KEY: ${CHATGPT_API_KEY} + networks: + - backend + +volumes: + postgres_data: + +networks: + backend: \ No newline at end of file diff --git a/src/main/java/com/smartcalendar/config/DataInitializer.java b/src/main/java/com/smartcalendar/config/DataInitializer.java new file mode 100644 index 0000000..223c74a --- /dev/null +++ b/src/main/java/com/smartcalendar/config/DataInitializer.java @@ -0,0 +1,30 @@ +package com.smartcalendar.config; + +import com.smartcalendar.model.User; +import com.smartcalendar.repository.UserRepository; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DataInitializer { + + @Bean + CommandLineRunner initUsers(UserRepository userRepository) { + return args -> { + if (userRepository.count() == 0) { + User admin = new User(); + admin.setUsername("admin"); + admin.setEmail("admin@example.com"); + admin.setPassword("encoded_password"); // сюда поставь реальный зашифрованный пароль + userRepository.save(admin); + + User user = new User(); + user.setUsername("user"); + user.setEmail("user@example.com"); + user.setPassword("encoded_password"); + userRepository.save(user); + } + }; + } +} \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 8797063..f1fab6c 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -3,4 +3,5 @@ spring.jpa.hibernate.ddl-auto=create-drop spring.datasource.url=jdbc:h2:mem:testdb_test chatgpt.api.url=http://dummy-url chatgpt.api.key=dummy-key -JWT_SECRET=your_test_secret \ No newline at end of file +JWT_SECRET=your_test_secret +spring.sql.init.mode=never diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6f4a937..8800208 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,7 +2,7 @@ # DATABASE (H2) # =============================== spring.datasource.url=jdbc:h2:mem:smartcalendar;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE -spring.datasource.driver-class-name=org.h2.Driver +# spring.datasource.driver-class-name=org.h2.Driver - по дефолту h2, иначе тот что зададим spring.datasource.username=sa spring.datasource.password= @@ -17,7 +17,7 @@ spring.h2.console.settings.web-allow-others=true # =============================== # DATA INITIALIZATION # =============================== -spring.sql.init.mode=always +spring.sql.init.mode=never spring.jpa.defer-datasource-initialization=true # =============================== @@ -67,4 +67,6 @@ spring.mail.username=dimarus06122005@gmail.com spring.mail.password=${MAIL_PASSWORD} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true -spring.mail.from=noreply@ttsc.com \ No newline at end of file +spring.mail.from=noreply@ttsc.com + +spring.jpa.hibernate.ddl-auto=update diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index 16d763c..0000000 --- a/src/main/resources/data.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO users (username, email, password) -VALUES -('admin', 'admin@example.com', 'encoded_password'), -('user', 'user@example.com', 'encoded_password'); \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql deleted file mode 100644 index 0971f93..0000000 --- a/src/main/resources/schema.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL, - password VARCHAR(255) NOT NULL -); \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java b/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java index aa8d869..05129c6 100644 --- a/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java +++ b/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java @@ -12,6 +12,7 @@ import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.util.List; @@ -25,7 +26,8 @@ "JWT_SECRET=test_jwt_secret", "chatgpt.api.url=http://dummy-url", "chatgpt.api.key=dummy-key", - "spring.security.enabled=false" + "spring.security.enabled=false", + "spring.sql.init.mode=never" }) @AutoConfigureMockMvc class AudioControllerIntegrationTest { diff --git a/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java b/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java index 77fe507..a04d2e9 100644 --- a/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java +++ b/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java @@ -23,7 +23,8 @@ "JWT_SECRET=test_jwt_secret", "chatgpt.api.url=http://dummy-url", "chatgpt.api.key=dummy-key", - "spring.security.enabled=false" + "spring.security.enabled=false", + "spring.sql.init.mode=never" }) @AutoConfigureMockMvc class ChatGPTControllerIntegrationTest { From b055d5e4dc4c75d2b44c3a36d5cf2dc11f5f6146 Mon Sep 17 00:00:00 2001 From: UsatovPavel Date: Thu, 21 Aug 2025 10:51:24 +0300 Subject: [PATCH 2/5] Audio: debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Искал причины не работы аудио, добился того что сервер возвращает корректный ответ на запрос. Логировал данные, ключ Chat_API поменял на тот что с 4o-transcriptions работает, хотя на данный момент это не нужно. --- Dockerfile | 3 + .../service/AudioProcessingService.java | 65 ++++++++++++++++--- src/main/resources/application.properties | 1 + 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8fac962..6a92123 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,7 @@ FROM eclipse-temurin:21-jdk-alpine + +RUN apk add --no-cache ffmpeg curl + WORKDIR /app COPY build/libs/*.jar app.jar diff --git a/src/main/java/com/smartcalendar/service/AudioProcessingService.java b/src/main/java/com/smartcalendar/service/AudioProcessingService.java index 6139c75..f39a193 100644 --- a/src/main/java/com/smartcalendar/service/AudioProcessingService.java +++ b/src/main/java/com/smartcalendar/service/AudioProcessingService.java @@ -1,41 +1,88 @@ package com.smartcalendar.service; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; import org.springframework.http.MediaType; +import org.springframework.http.client.MultipartBodyBuilder; import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @Service public class AudioProcessingService { @Value("${whisper.api.url}") private String whisperApiUrl; - + @Value("${gpt4o-mini-transcribe.api.url}") + private String transcribeApiUrl; @Value("${chatgpt.api.key}") private String apiKey; private final WebClient webClient = WebClient.builder().build(); + private final ObjectMapper objectMapper = new ObjectMapper(); + private void convertToWav(Path input, Path output) throws IOException, InterruptedException { + String[] command = { + "ffmpeg", "-y", + "-i", input.toString(), + "-ar", "16000", + "-ac", "1", + "-c:a", "pcm_s16le", + output.toString() + }; + + ProcessBuilder pb = new ProcessBuilder(command); + pb.redirectErrorStream(true); + Process process = pb.start(); + int exitCode = process.waitFor(); + + if (exitCode != 0) { + throw new RuntimeException("ffmpeg failed to convert audio, exit code: " + exitCode); + } + log.info("Converted file with ffmpeg: {}", output.toAbsolutePath()); + } public String transcribeAudio(MultipartFile file) { try { - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("file", file.getResource()); - body.add("model", "whisper-1"); + Path uploadsDir = Paths.get(System.getProperty("user.dir"), "uploads").toAbsolutePath(); + Files.createDirectories(uploadsDir); + + Path path = uploadsDir.resolve(Objects.requireNonNull(file.getOriginalFilename())); + file.transferTo(path.toFile()); + log.info("Saved uploaded file to: {}", path.toAbsolutePath()); + + //Path fixedPath = uploadsDir.resolve("fixed.wav"); + //convertToWav(path, fixedPath); + + MultipartBodyBuilder builder = new MultipartBodyBuilder(); + builder.part("file", new FileSystemResource(path)); + builder.part("model", "whisper-1"); String response = webClient.post() - .uri(whisperApiUrl) + .uri("https://api.openai.com/v1/audio/transcriptions") .header("Authorization", "Bearer " + apiKey) .contentType(MediaType.MULTIPART_FORM_DATA) - .bodyValue(body) + .body(BodyInserters.fromMultipartData(builder.build())) .retrieve() .bodyToMono(String.class) .block(); - return response; } catch (Exception e) { + log.info("Using API key starts with: {}", apiKey.substring(0, 10)); + log.error("Transcription request failed", e); throw new RuntimeException("Failed to transcribe audio: " + e.getMessage()); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8800208..a7a07f6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -57,6 +57,7 @@ spring.main.banner-mode=off chatgpt.api.url=https://api.openai.com/v1/chat/completions whisper.api.url=https://api.openai.com/v1/audio/transcriptions chatgpt.api.key=${CHATGPT_API_KEY} +gpt4o-mini-transcribe.api.url = https://api.openai.com//v1/chat/completions # =============================== # SMTP From c5598287c3da9006362b2bc6b3ac5847d1a6c4c0 Mon Sep 17 00:00:00 2001 From: UsatovPavel Date: Thu, 21 Aug 2025 21:53:54 +0300 Subject: [PATCH 3/5] tests-OpenAI-api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавил тесты использующие реальное OpenAI API: первые через REST API проверят обработку аудиофайлов(они в resources) сервером, вторые корректность service. Для них отдельные test-real.properties Убрал tasks из ChatGPT т.к. не используются на клиенте и возникала при обработке сервера путаница events с tasks. Улучшил распознование промтом типа задания. --- build.gradle | 1 + .../controller/ChatGPTController.java | 2 +- .../com/smartcalendar/model/EventType.java | 4 + .../model/EventTypeDeserializer.java | 19 ++++ .../smartcalendar/service/ChatGPTService.java | 74 ++++------------ .../config/TestSecurityConfig.java | 2 +- .../AudioControllerIntegrationTest.java | 2 - .../AudioControllerRestTemplateTest.java | 81 ++++++++++++++++++ .../ChatGPTControllerIntegrationTest.java | 2 +- .../service/ChatGPTServiceTest.java | 14 ++- .../service/ServiceRealApiTest.java | 69 +++++++++++++++ src/test/resources/DescriptionIneedStudy.mp3 | Bin 0 -> 45696 bytes src/test/resources/Swimming12-14Sport.mp3 | Bin 0 -> 99264 bytes src/test/resources/empty.mp3 | Bin 0 -> 5184 bytes 14 files changed, 202 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/smartcalendar/model/EventTypeDeserializer.java create mode 100644 src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java create mode 100644 src/test/java/com/smartcalendar/service/ServiceRealApiTest.java create mode 100644 src/test/resources/DescriptionIneedStudy.mp3 create mode 100644 src/test/resources/Swimming12-14Sport.mp3 create mode 100644 src/test/resources/empty.mp3 diff --git a/build.gradle b/build.gradle index c8ca27e..5102fc3 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ dependencies { testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'io.projectreactor:reactor-test' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' } diff --git a/src/main/java/com/smartcalendar/controller/ChatGPTController.java b/src/main/java/com/smartcalendar/controller/ChatGPTController.java index 3dfad32..79b46c9 100644 --- a/src/main/java/com/smartcalendar/controller/ChatGPTController.java +++ b/src/main/java/com/smartcalendar/controller/ChatGPTController.java @@ -26,7 +26,7 @@ public ResponseEntity askChatGPT(@RequestBody Map reques @PostMapping("/generate") public ResponseEntity>> generateEventsAndTasks(@RequestBody Map requestBody) { String userQuery = requestBody.get("query"); - Map> result = chatGPTService.generateEventsAndTasks(userQuery); + Map> result = chatGPTService.generateEvents(userQuery); return ResponseEntity.ok(result); } diff --git a/src/main/java/com/smartcalendar/model/EventType.java b/src/main/java/com/smartcalendar/model/EventType.java index 5581f0f..f5917f9 100644 --- a/src/main/java/com/smartcalendar/model/EventType.java +++ b/src/main/java/com/smartcalendar/model/EventType.java @@ -1,5 +1,9 @@ package com.smartcalendar.model; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +@JsonDeserialize(using = EventTypeDeserializer.class) public enum EventType { COMMON, FITNESS, WORK, STUDIES } + diff --git a/src/main/java/com/smartcalendar/model/EventTypeDeserializer.java b/src/main/java/com/smartcalendar/model/EventTypeDeserializer.java new file mode 100644 index 0000000..9f0e692 --- /dev/null +++ b/src/main/java/com/smartcalendar/model/EventTypeDeserializer.java @@ -0,0 +1,19 @@ +package com.smartcalendar.model; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +public class EventTypeDeserializer extends JsonDeserializer { + @Override + public EventType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getText().toUpperCase(); + try { + return EventType.valueOf(value); + } catch (IllegalArgumentException e) { + return EventType.COMMON; + } + } +} diff --git a/src/main/java/com/smartcalendar/service/ChatGPTService.java b/src/main/java/com/smartcalendar/service/ChatGPTService.java index 89a61f8..1067be8 100644 --- a/src/main/java/com/smartcalendar/service/ChatGPTService.java +++ b/src/main/java/com/smartcalendar/service/ChatGPTService.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.smartcalendar.model.Event; import com.smartcalendar.model.EventType; -import com.smartcalendar.model.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -84,10 +83,10 @@ public String askChatGPT(String question, String model) { } } - public Map> generateEventsAndTasks(String userQuery) { - logger.info("Generating events and tasks for query: {}", userQuery); + public Map> generateEvents(String userQuery) { + logger.info("Generating events for query: {}", userQuery); - String prompt = "Based on the user's query: \"" + userQuery + "\", generate a list of events and tasks. " + + String prompt = "Based on the user's query: \"" + userQuery + "\", generate a list of events . " + "If the user mentions a note, description, or additional information related to an event, include it in the 'description' field of the corresponding event, " + "unless it is clearly a separate task. " + "Respond strictly in JSON format with the following structure: " + @@ -99,21 +98,16 @@ public Map> generateEventsAndTasks(String userQuery) { "\"location\": \"string\", " + "\"type\": \"COMMON|FITNESS|STUDIES|WORK\" " + "}], " + - "\"tasks\": [{ " + - "\"title\": \"string\", " + - "\"description\": \"string\", " + - "\"completed\": false, " + - "\"dueDateTime\": \"ISO 8601 datetime\", " + - "\"allDay\": false " + - "}] } " + - "Do not include any additional text or explanation."; + "Do not include any additional text or explanation. The \"type\" field MUST be exactly one of: \"COMMON\", \"FITNESS\", \"STUDIES\", \"WORK\". \n" + + "If the event is about sports or training, always use \"FITNESS\". \n" + + "Do not invent other values (e.g., \"SPORT\")."; String response = askChatGPT(prompt, "gpt-3.5-turbo"); try { return objectMapper.readValue(response, new TypeReference<>() {}); } catch (Exception e) { - logger.error("Error parsing ChatGPT response into events and tasks", e); + logger.error("Error parsing ChatGPT response into events", e); throw new RuntimeException("Failed to parse ChatGPT response: " + e.getMessage()); } } @@ -124,11 +118,17 @@ public List convertToEntities(Map> data) { List entities = new ArrayList<>(); List> events = (List>) data.get("events"); - List> tasks = (List>) data.get("tasks"); if (events != null) { for (Map eventData : events) { Event event = objectMapper.convertValue(eventData, Event.class); + event.setType(EventType.COMMON); + + if (eventData.get("type")!=null && Arrays.stream(EventType.values()).anyMatch(x->Objects.equals(x.toString(),eventData.get("type").toString()))) { + try { + event.setType(EventType.valueOf(eventData.get("type").toString())); + } catch (Exception ignored) {} + } if (event.getId() == null) { event.setId(UUID.randomUUID()); @@ -136,14 +136,10 @@ public List convertToEntities(Map> data) { if (event.getCreationTime() == null) { event.setCreationTime(LocalDateTime.now()); } - if (event.getType() == null && eventData.get("type") != null) { - try { - event.setType(EventType.valueOf(eventData.get("type").toString())); - } catch (Exception ignored) {} - } if (!event.isCompleted() && eventData.get("completed") != null) { event.setCompleted(Boolean.parseBoolean(eventData.get("completed").toString())); } + event.setShared(false); event.setInvitees(new ArrayList<>()); event.setParticipants(new ArrayList<>()); @@ -151,36 +147,14 @@ public List convertToEntities(Map> data) { } } - if (tasks != null) { - for (Map taskData : tasks) { - Task task = objectMapper.convertValue(taskData, Task.class); - - if (task.getId() == null) { - task.setId(UUID.randomUUID()); - } - if (task.getCreationTime() == null) { - task.setCreationTime(LocalDateTime.now()); - } - if (task.getAllDay() == null && taskData.get("allDay") != null) { - task.setAllDay(Boolean.parseBoolean(taskData.get("allDay").toString())); - } - if (task.getDueDateTime() == null && taskData.get("dueDate") != null) { - try { - LocalDate date = LocalDate.parse(taskData.get("dueDate").toString()); - task.setDueDateTime(date.atStartOfDay()); - } catch (Exception ignored) {} - } - entities.add(task); - } - } return entities; } public Map processTranscript(String transcript) { String today = LocalDate.now().toString(); - String prompt = "Today is " + today + ". Based on the following transcript: \"" + transcript + "\", determine if it is related to creating events or tasks. " + - "If it is, generate a list of events and tasks strictly in JSON format with the following structure: " + + String prompt = "Today is " + today + ". Based on the following transcript: \"" + transcript + "\", determine if it is related to creating events. " + + "If it is, generate a list of events strictly in JSON format with the following structure: " + "{ \"events\": [{ " + "\"title\": \"string\", " + "\"description\": \"string\", " + @@ -189,16 +163,9 @@ public Map processTranscript(String transcript) { "\"location\": \"string\", " + "\"type\": \"COMMON|FITNESS|STUDIES|WORK\" " + "}], " + - "\"tasks\": [{ " + - "\"title\": \"string\", " + - "\"description\": \"string\", " + - "\"completed\": false, " + - "\"dueDateTime\": \"ISO 8601 datetime\", " + - "\"allDay\": false " + - "}] } " + "If the transcript contains a note, description, or additional information about an event, include it in the 'description' field of the event, " + - "unless it is clearly a separate task. " + - "If the transcript is not related to events or tasks, respond with: { \"error\": \"Unrelated request\" }. " + + "If the transcript is not related to events, respond with: { \"error\": \"Unrelated request\" }. " + + "Treat any mention of a 'task' as an event. " + "Do not include any additional text or explanation."; String response = askChatGPT(prompt, "gpt-3.5-turbo"); @@ -211,9 +178,6 @@ public Map processTranscript(String transcript) { if (!result.containsKey("events")) { result.put("events", List.of()); } - if (!result.containsKey("tasks")) { - result.put("tasks", List.of()); - } return result; } catch (Exception e) { throw new RuntimeException("Failed to process ChatGPT response: " + e.getMessage()); diff --git a/src/test/java/com/smartcalendar/config/TestSecurityConfig.java b/src/test/java/com/smartcalendar/config/TestSecurityConfig.java index 6be38ce..57e4e48 100644 --- a/src/test/java/com/smartcalendar/config/TestSecurityConfig.java +++ b/src/test/java/com/smartcalendar/config/TestSecurityConfig.java @@ -16,7 +16,7 @@ @TestConfiguration public class TestSecurityConfig { - @Bean + @Bean(name = "securityFilterChain") @Primary public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) diff --git a/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java b/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java index 05129c6..39dad5d 100644 --- a/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java +++ b/src/test/java/com/smartcalendar/controller/AudioControllerIntegrationTest.java @@ -9,10 +9,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.util.List; diff --git a/src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java b/src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java new file mode 100644 index 0000000..8f3d7ff --- /dev/null +++ b/src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java @@ -0,0 +1,81 @@ +package com.smartcalendar.controller; + +import com.smartcalendar.SmartCalendarApplication; +import com.smartcalendar.config.TestSecurityConfig; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.*; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +//@Disabled("call real OpenAI API") +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = {SmartCalendarApplication.class, TestSecurityConfig.class} // подмешиваем TestSecurityConfig +) +@ActiveProfiles("test-real") +public class AudioControllerRestTemplateTest { + + @Autowired + private org.springframework.boot.test.web.client.TestRestTemplate restTemplate; + + ResponseEntity testAudioSend(String filename) throws Exception { + ClassPathResource audioFile = new ClassPathResource(filename); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("file", new org.springframework.core.io.FileSystemResource(audioFile.getFile())); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + HttpEntity> requestEntity = + new HttpEntity<>(body, headers); + + ResponseEntity response = restTemplate.postForEntity( + "/api/audio/process", requestEntity, Object.class + ); + return response; + } + void testSuccessfulAudioSend(String filename, Predicate> checker) throws Exception { + ResponseEntity response = testAudioSend(filename); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + ResponseEntity responseList = (ResponseEntity) response; + Map task = (Map) responseList.getBody().get(0); + assertTrue(checker.test(task)); + } + void testUnrelatedAudio(String filename) throws Exception{ + ResponseEntity response = testAudioSend(filename); + assertThat(response.getStatusCode()).isNotEqualTo(HttpStatus.OK); + Map body = (Map) response.getBody(); + Assertions.assertNotNull(body); + assertThat(body.containsKey("error")); + } + @Test + void testStudyEvent(){ + Predicate> descriptionStudy = task ->(task.get("description").toString()).contains("I need to study"); + assertDoesNotThrow(()->testSuccessfulAudioSend("DescriptionIneedStudy.mp3", descriptionStudy)); + } + @Test + void testSwimmingEvent(){ + Predicate> type = task ->(task.get("type").toString().contains("FITNESS") || task.get("type").toString().contains("COMMON")); + //если CHAT GPT выставляет некорректный(SPORT например) COMMON выставляется + Predicate> time12to14=task->task.get("start").toString().contains("12:00") && task.get("end").toString().contains("14:00"); + Predicate> predicate = task->type.test(task) && time12to14.test(task); + assertDoesNotThrow(()->testSuccessfulAudioSend("Swimming12-14Sport.mp3", predicate)); + } + @Test + void testEmptyAudio(){ + assertDoesNotThrow(()->testUnrelatedAudio("empty.mp3")); + } +} \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java b/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java index a04d2e9..ed3b363 100644 --- a/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java +++ b/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java @@ -57,7 +57,7 @@ void testAskChatGPT() throws Exception { @Test @WithMockUser void testGenerateEventsAndTasks() throws Exception { - Mockito.when(chatGPTService.generateEventsAndTasks(any())).thenReturn( + Mockito.when(chatGPTService.generateEvents(any())).thenReturn( Map.of("events", List.of(), "tasks", List.of()) ); mockMvc.perform(post("/api/chatgpt/generate") diff --git a/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java b/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java index 8de75c4..2e2cafa 100644 --- a/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java +++ b/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java @@ -1,11 +1,11 @@ package com.smartcalendar.service; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.*; -import org.springframework.web.reactive.function.client.WebClient; +import java.io.File; import java.util.List; import java.util.Map; @@ -25,13 +25,11 @@ void setUp() { @Test void testConvertToEntities() { Map> data = Map.of( - "events", List.of(Map.of("title", "Event1")), - "tasks", List.of(Map.of("title", "Task1", "completed", false)) + "events", List.of(Map.of("title", "Event1")) ); var entities = chatGPTService.convertToEntities(data); - assertEquals(2, entities.size()); + assertEquals(1, entities.size()); assertTrue(entities.stream().anyMatch(e -> e.getClass().getSimpleName().equals("Event"))); - assertTrue(entities.stream().anyMatch(e -> e.getClass().getSimpleName().equals("Task"))); } @Test @@ -43,10 +41,10 @@ void testProcessTranscript_Error() { } @Test - void testGenerateEventsAndTasks_ValidJson() { + void testGenerateEvents_ValidJson() { ChatGPTService spyService = spy(chatGPTService); doReturn("{\"events\":[],\"tasks\":[]}").when(spyService).askChatGPT(anyString(), anyString()); - Map> result = spyService.generateEventsAndTasks("test"); + Map> result = spyService.generateEvents("test"); assertTrue(result.containsKey("events")); assertTrue(result.containsKey("tasks")); } diff --git a/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java b/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java new file mode 100644 index 0000000..c4fde97 --- /dev/null +++ b/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java @@ -0,0 +1,69 @@ +package com.smartcalendar.service; + +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; + +@Nested +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test-real") +class ServiceRealApiTest { + + @Autowired + private AudioProcessingService audioProcessingService; + + @Autowired + private ChatGPTService chatGPTService; + + MockMultipartFile convertFileToMultipart(String filename) throws IOException { + File audio = new File("src/test/resources/"+filename); + byte[] content = Files.readAllBytes(audio.toPath()); + + return new MockMultipartFile( + "file", + audio.getName(), + "audio/mpeg", + content + ); + } + + @Test + @Disabled("call real OpenAI API") + void testRealAudioTranscription() { + MockMultipartFile multipartFile = assertDoesNotThrow(() -> convertFileToMultipart("DescriptionIneedStudy.mp3")); + + String result = audioProcessingService.transcribeAudio(multipartFile); + assertNotNull(result); + assertTrue(result.contains("need") && result.contains("description")); + } + + @Test + @Disabled("call real OpenAI API") + void testProcessTranscript_RealRequest() { + String transcript = "Create event type study with exactly this description: I need to study"; + + Map result = chatGPTService.processTranscript(transcript); + assertNotNull(result); + assertTrue(result.containsKey("events")); + + List> events = (List>) result.get("events"); + assertFalse(events.isEmpty()); + Map firstEvent = events.getFirst(); + assertEquals("I need to study", firstEvent.get("description")); + System.out.println("Processed events: " + result); + } +} \ No newline at end of file diff --git a/src/test/resources/DescriptionIneedStudy.mp3 b/src/test/resources/DescriptionIneedStudy.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d98aa3804dbca6f9d01bac018ba645b6ad4b86ec GIT binary patch literal 45696 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/Swimming12-14Sport.mp3 b/src/test/resources/Swimming12-14Sport.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4349074ebc321d8b7d86f35c408ce184c180fe68 GIT binary patch literal 99264 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 Z}(4Vo=Kb&hNprzXQ>) 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 g_Pnm+nZKTKV1O9TpZUK({y(_?KR+Xe9smFU literal 0 HcmV?d00001 diff --git a/src/test/resources/empty.mp3 b/src/test/resources/empty.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e7a87404df06d67e8a65a9ed46adc9b483daae30 GIT binary patch literal 5184 zcmdVd)mIc;xCZc{VL%$BC5DlXAqRmEh8#qYlCGh<1O*Jb!=Yp79zr@rI%kj+kdTl8 z0Rcf!L1m77-(PU9&N}bKzSwKMYyI}Ko|}#T7Bvj`e?0gbKbT>K77!e>&P?zGAfm9g z5Z#DkrJFtrazpJ|Dimm*n#*kcCO%cjQo$rgkX5!&i*AJm(*qJwpQd&Fz()*Vnl> zen~0JVg^CpQ$R|Gzp=PpEl*TIAi=w$k5Fux0;k|KTv?KWiA`8RX$KxwNG5=yu}@JX zD=$Rb!(RWYlzDol?kk;Z5|wPlPhOTG?i6;V5RI0i48$+JmC^q>ebA}|T`p`DTwMFG zIbGn~7Ij7a5^sUW4}HH*w0p4j`5VLJ%9xNH#$&d)?_! zMLlotOee?f_!M~Wg*9(DDlB-cbW2wsMwpa?qeRia5+{)4CRPNzB6`X`0oroKNgJ6x zb9UYNBA{6pI5|#~NisY#Q7;#IX0<*R!yTEdQzEcU9`gJZ z9YJDeY59$xSxAj539$I?4b6uVkiK+*B=x8?F>h`8P?)o;87ZhNwdMm7C^526Fc+g* zw|&V!w2r>vF3}#k-EHII35=IFnMe3bajRlZf%>eIZ=t&s#m1 zw-4Iq*F|5~@M4G~PX1s$U^Nmw7XK7YQovM63>ba$TiruD*{DZjm>6S-JD6y~X19l8 z8SmWTKtV>i*W#mCIuqbRb^rOj(BpM6TnH2O+)?fi+`K=O#2CPZNsVPJuH-je!1i&%e|IK2nJdX zx`e{-Ly2#f+O zapl-^>b{V$p~a|32sn;H7`Vx`!)o^_z;KJ)O~SDc{hN#frRfIAWD(PmXuH|?0;=Lt z(Q0qA^n>qsZ$;Ub8rh{yOXzxhlMh>1Q=K97YoN`z9ds-iA^BFp%J)z5j^`Zm<(2bQ|I0Zf5=qk1W7|`TTk@I410z!; z9Pbmc@+1i+L${#ltA- z6I1+k4-sFO_&Fl(!2-^eR#o0Cu<|sUD=T8CVv~qe73yI+5lPNETT(5~0i8iW;|N$4 zI#nVqByaqpcek120E>$`Jcvlz@kk>WnG#D4Im`H19GobPJIZrB5}N_2W9;)VpM4_w z&)2m|(wGX!)8A8x`K1aqa|htLG{eBM#)#Y5DMJjEI!M$WU#L3*>hpx-$ivbnA-=D2 z{l|z89vvAtN*WvV@>-~Jnjl&Lg0A4Kb~9NQd1a^ij{3lmHbxc$k5s2q|CB{VRS-T% zb--t{+~098bX55c3#k%-Wj~#kfCW~Z%L-8wV;K*Zbm&*58n?UgtAInnfBjLtBE_?7x{rcIJ$K`HGdRZ=X7^pu*%K1Cse%4yFQ0k?|k5#UJ+_|U8UbOWn%KS z=D`6mqmHP^LBnug2%5L%)yBU?p1tmvooNvAY~I_+Xd7XW@mJ&M?2i~7M}N^*qxJom zqq{{O01hY0vCpxiS0Big4oj}!W-5OKDc7CAiL(HCcnQfJ2NVfaREr}cd2+0t3MObI zq>^!vaXC>Tzb9iywH_kVZv5t8LqJRDc|L(~Zgf!y=zO{adx6h9?VvHt7~KJKES z4EbKCIrb0XO15p^qauZ(Ri^6tm1#5Fa>H$eQTj==8;zj1TP(-uiiVKH70RBaoaDyu zCqSh46tD<xmMd z%o>7MWLz7>QZAeYGM4&J@f=gzjbQJOoYs@c)UL}whn<6Snu0_c2K7vh&_Y18!MZ;czJhd*+cPYZkqtK&*rRZ70 zchm8Iu+YBdgQ2(BS;&L|i0Ivx^c`y)7WzEu+?mXp)ivTAI3$9>33L2R$ni`%5}o3E zZp`5I8Sp?GO~mgTe;$AC$AK+RHW6i{ZhDmE5F6A+H@HYniT@t{I!=J#!_`0iRF{WKn`F}+x+%!_vF9NM+!UsDAvgYZ;X?~o zKqzf*h*XWSmt7dIR55YIkzm#zV+$Cov+^d4JgqvO>EcaCz|#pKb?x_uW)-t3f##2H zm z{wZDMA}&vOyua{VMqpOjEGT^okxi2~{w@IpJy*b>O(&&!fC4h=$f_GI-`+Nu zf<3F#Ypx6M^}AvdIJH7juN|suB~UL2cWx)~U8w()z{eZ{}Oe>@4)j*^eKsoE+=R z7QO8biRXj?^}1p<$O@a~yq7(D=%4Wcp?t0t4MJ6(3i&Oo_Zlj_mW+O5JXZCxE%{HX zl=WoL1sMWCC#Nwz4yIMlj!)!l4DRFZ_u=`;oPaC3O;-^+)C_nnHJW>KV`Por>>i_J zBz$9ex<3ysV)bsnd5h7i5T-G`-8)3IRm}>Ff<&+j(o}T%Pns&ZxZe120%brEKT7Xfh{9`73T`Y3H`7FC-oqU%AWsjU`KIFJ>grX=hGrN>(4A<%xKsUBoBS zjRPl}HZVt0az1CG^vk1X!tMMJwn3u05f^8{`XG;{3-~22-#Q1fEy&~w% zErp_AGPP#QNpqiGC@gI^CmNo?#?`ba^nsOeI<*>9Q<|@G$~?cIC86zsKcgLvAf=8H z3?i|#H~xKL5E~U>P_b9TL15X6~9VH zMg7;^%4f;3;GO5JglH}})ohDgZ@zAkb3VpUmKu^Am_JO(&3)rP5fg#_1Wf9#X@L1c zSz9ZtHA8kCQ>4O5-&=@+(bapjP~b@^(!(_rL&Qo zW1FoB8}_vX-MQ7f+i>Jj+w=TAujPy-`JS=R(Km@`3+t{e38Sv|z&ADfsoiNAnn*qz zKAksKMw#)6s+k}=_E&eP^jeFH|CKWQP_x0_=1XxYUt*TCkX%#6EkAQdbsR8DR?{le zkkOOd7UD!=QN4dI%AHD|CG~Ir_l17{=il%NRYU8YvYWkY$IR?2K=cLd)fY0W){vON zwe2l;vE3O(ck+GbOmQZ>l=CSZ>YrWJ+QxKenR*UJXYknvQSi_|l-WMdszWlVD&cF- z@%(K+F%u4C@Z(wPaGi9i5>jR8l3aB@y5OT{d;Vb|EWp2P!&;t>nOgfDc>O0LA*SM1 zcNTwWijzjvABQ})@k9BKK&xaTCk9VLg;j@-V0Il`%jgnbe$lOwdtBCm_#oyv?AZ%=qjPwhgBY^1vw@?M~e1UHu0G z^7J--Yji2dZ8(Z*vA>(mJHZ}nA)bNX#k6C5CcQp*6|L9ow#J(qAMnc{9-M0!=mr^3 jU%Kn6KG2X7la!D^|F7pJ|1;6_f$ Date: Tue, 26 Aug 2025 19:03:08 +0300 Subject: [PATCH 4/5] ChatGPT Suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавил в ChatGPTApi suggestions: по аудио/тексту пользователь получает набор задач.От entices отличается тем, что в промпте отправляются не существующие ивенты, а список свободных интервалов(иначе ии отправляло существующие). Добавлен запрос в DB для поиска ивентов по дате - до этого вроде все перебирались через sort. EventDistributorService для GPT предоставляет данные и исправляет от него. placeEvents гарантирует, что все сгенерированные suggestions не будут с временем противоречить FormatterUtils - для преобразования в промпт данных EventDistributorService и новые методы в GPT Controller и Service протестированы(через Mockito и RestTemplate). --- .../controller/ChatGPTController.java | 39 ++++++ .../java/com/smartcalendar/model/Event.java | 26 +++- .../smartcalendar/model/FormatterUtils.java | 57 ++++++++ .../repository/EventRepository.java | 9 ++ .../smartcalendar/service/ChatGPTService.java | 116 +++++++++++++++- .../service/EventDistributorService.java | 125 ++++++++++++++++++ .../smartcalendar/service/UserService.java | 9 ++ src/main/resources/application.properties | 1 + .../AudioControllerRestTemplateTest.java | 4 +- .../ChatGPTControllerIntegrationTest.java | 17 +++ .../controller/ChatGPTRestTemplateTest.java | 83 ++++++++++++ .../service/ChatGPTServiceTest.java | 106 ++++++++++++++- .../service/EventDistributorServiceTest.java | 73 ++++++++++ .../service/ServiceRealApiTest.java | 87 ++++++++++++ .../service/UserServiceIntegrationTest.java | 104 +++++++++++++++ 15 files changed, 850 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/smartcalendar/model/FormatterUtils.java create mode 100644 src/main/java/com/smartcalendar/service/EventDistributorService.java create mode 100644 src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java create mode 100644 src/test/java/com/smartcalendar/service/EventDistributorServiceTest.java create mode 100644 src/test/java/com/smartcalendar/service/UserServiceIntegrationTest.java diff --git a/src/main/java/com/smartcalendar/controller/ChatGPTController.java b/src/main/java/com/smartcalendar/controller/ChatGPTController.java index 79b46c9..c59b061 100644 --- a/src/main/java/com/smartcalendar/controller/ChatGPTController.java +++ b/src/main/java/com/smartcalendar/controller/ChatGPTController.java @@ -1,10 +1,14 @@ package com.smartcalendar.controller; +import com.smartcalendar.model.Event; +import com.smartcalendar.service.AudioProcessingService; import com.smartcalendar.service.ChatGPTService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,6 +18,7 @@ public class ChatGPTController { private final ChatGPTService chatGPTService; + private final AudioProcessingService audioProcessingService; @PostMapping("/ask") public ResponseEntity askChatGPT(@RequestBody Map requestBody) { @@ -56,4 +61,38 @@ public ResponseEntity generateEntities(@RequestBody Map reque return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } } + @PostMapping("{userId}/generate/suggestions") + public ResponseEntity> generateSuggestions(@PathVariable Long userId, @RequestBody Map body) { + try { + String query = body.get("query"); + + List result = chatGPTService.generateSuggestions(userId, query); + return ResponseEntity.ok(Map.of("events", result)); + + } catch (Exception e) { + return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); + } + } + + @PostMapping("{userId}/generate/suggestions/audio") + public ResponseEntity> generateSuggestionsFromAudio( + @PathVariable Long userId, + @RequestParam("file") MultipartFile audioFile) { + String query; + try { + query = audioProcessingService.transcribeAudio(audioFile); + } catch (Exception e) { + return ResponseEntity.badRequest().body(Map.of("error", "Failed to transcribe: "+e.getMessage())); + } + try{ + List result = chatGPTService.generateSuggestions(userId, query); + + return ResponseEntity.ok(Map.of( + "query", query, + "events", result + )); + } catch (Exception e) { + return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); + } + } } \ No newline at end of file diff --git a/src/main/java/com/smartcalendar/model/Event.java b/src/main/java/com/smartcalendar/model/Event.java index b7c7b46..f38087a 100644 --- a/src/main/java/com/smartcalendar/model/Event.java +++ b/src/main/java/com/smartcalendar/model/Event.java @@ -16,6 +16,15 @@ @NoArgsConstructor @AllArgsConstructor public class Event { + static public class Interval { + public final LocalDateTime start; + public final LocalDateTime end; + + public Interval(LocalDateTime start, LocalDateTime end) { + this.start = start; + this.end = end; + } + } @Id //@GeneratedValue(strategy = GenerationType.AUTO) private UUID id; @@ -30,7 +39,7 @@ public class Event { @Column(name = "end_time") private LocalDateTime end; - private String location; + private String location = ""; @Enumerated(EnumType.STRING) private EventType type; @@ -57,4 +66,19 @@ public class Event { inverseJoinColumns = @JoinColumn(name = "user_id") ) private List participants = new ArrayList<>(); + public Event(Event other) { + this.id = other.id != null ? other.id : UUID.randomUUID(); + this.title = other.title; + this.description = other.description; + this.start = other.start; + this.end = other.end; + this.location = other.location != null ? other.location : ""; + this.type = other.type != null ? other.type : EventType.COMMON; + this.organizer = other.organizer; + this.creationTime = other.creationTime != null ? other.creationTime : LocalDateTime.now(); + this.completed = other.completed; + this.isShared = other.isShared; + this.invitees = other.invitees != null ? new ArrayList<>(other.invitees) : new ArrayList<>(); + this.participants = other.participants != null ? new ArrayList<>(other.participants) : new ArrayList<>(); + } } \ No newline at end of file diff --git a/src/main/java/com/smartcalendar/model/FormatterUtils.java b/src/main/java/com/smartcalendar/model/FormatterUtils.java new file mode 100644 index 0000000..98e0202 --- /dev/null +++ b/src/main/java/com/smartcalendar/model/FormatterUtils.java @@ -0,0 +1,57 @@ +package com.smartcalendar.model; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static java.util.Map.entry; + +@Component +public class FormatterUtils { + private final ObjectMapper objectMapper; + private static final Logger logger = LoggerFactory.getLogger(FormatterUtils.class); + + public FormatterUtils(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public String convertEventsToJson(List events) { + try { + List> simplifiedEvents = events.stream() + .map(e -> Map.ofEntries( + entry("title", Objects.toString(e.getTitle(), "")), + entry("description", Objects.toString(e.getDescription(), "")), + entry("start", e.getStart() != null ? e.getStart().toString() : ""), + entry("end", e.getEnd() != null ? e.getEnd().toString() : ""), + entry("location", Objects.toString(e.getLocation(), "")), + entry("type", e.getType() != null ? e.getType().toString() : "COMMON") + )) + .toList(); + return objectMapper.writeValueAsString(simplifiedEvents); + } catch (Exception ex) { + logger.error("Failed to convert events to JSON", ex); + return "[]"; + } + } + public String convertDayIntervalsToJson(LocalDate date, List intervals){ + try { + List> simplifiedIntervals = intervals.stream() + .map(e -> Map.ofEntries( + entry("date", date.toString()), + entry("start", Objects.toString(e.start, "")), + entry("end", Objects.toString(e.end, "")) + )) + .toList(); + return objectMapper.writeValueAsString(simplifiedIntervals); + } catch (Exception ex) { + logger.error("Failed to convert events to JSON", ex); + return "[]"; + } + } +} diff --git a/src/main/java/com/smartcalendar/repository/EventRepository.java b/src/main/java/com/smartcalendar/repository/EventRepository.java index 7cc86b7..3aaf0c9 100644 --- a/src/main/java/com/smartcalendar/repository/EventRepository.java +++ b/src/main/java/com/smartcalendar/repository/EventRepository.java @@ -2,12 +2,21 @@ import com.smartcalendar.model.Event; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; import java.util.List; import java.util.UUID; @Repository public interface EventRepository extends JpaRepository { List findByOrganizerId(Long organizerId); + @Query("SELECT e FROM Event e " + + "LEFT JOIN e.participants p " + + "WHERE (e.organizer.id = :userId OR p.id = :userId) " + + "AND e.start BETWEEN :startOfDay AND :endOfDay") + List findByUserIdAndDate(Long userId, + LocalDateTime startOfDay, + LocalDateTime endOfDay); } \ No newline at end of file diff --git a/src/main/java/com/smartcalendar/service/ChatGPTService.java b/src/main/java/com/smartcalendar/service/ChatGPTService.java index 1067be8..d540dd5 100644 --- a/src/main/java/com/smartcalendar/service/ChatGPTService.java +++ b/src/main/java/com/smartcalendar/service/ChatGPTService.java @@ -6,8 +6,11 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.smartcalendar.model.Event; import com.smartcalendar.model.EventType; +import com.smartcalendar.model.FormatterUtils; +import com.smartcalendar.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -16,14 +19,23 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.*; +import java.util.stream.Collectors; @Service public class ChatGPTService { - + @Autowired + private EventDistributorService eventDistributorService; private static final Logger logger = LoggerFactory.getLogger(ChatGPTService.class); private final ObjectMapper objectMapper; + @Autowired + private FormatterUtils formatterUtils; + + private final UserService userService; + @Value("${chatgpt.api.url}") private String apiUrl; @@ -32,7 +44,8 @@ public class ChatGPTService { private final WebClient webClient = WebClient.builder().build(); - public ChatGPTService() { + public ChatGPTService(UserService userService) { + this.userService = userService; this.objectMapper = new ObjectMapper(); this.objectMapper.registerModule(new JavaTimeModule()); } @@ -83,6 +96,105 @@ public String askChatGPT(String question, String model) { } } + public List findDates(String userQuery) { + logger.info("Find dates for query: {}", userQuery); + + String prompt = "Today is " + LocalDate.now()+ + " Based on the user's query: \"" + userQuery + "\"," + + " create a JSON array of ISO 8601 dates (YYYY-MM-DD) representing the days the user refers to or intends. " + + "Respond STRICTLY with a JSON array, e.g. [\"2025-08-22\",\"2025-08-23\"]. " + + "If there are no dates, return JSON array of today date . Do not include any extra text."; + + String response = askChatGPT(prompt, "gpt-3.5-turbo"); + if (response == null) { + logger.warn("ChatGPT returned null response for findDates"); + return List.of(); + } + + List result = new ArrayList<>(); + try { + List dateStrings = objectMapper.readValue(response, new TypeReference<>() { + }); + for (String s : dateStrings) { + if (s == null) continue; + try { + LocalDate d = LocalDate.parse(s.trim(), DateTimeFormatter.ISO_LOCAL_DATE); + result.add(d); + } catch (DateTimeParseException ex) { + logger.warn("Skipping unparsable date string from JSON array: '{}'", s); + } + } + return result.stream().distinct().toList(); + } catch (Exception e) { + logger.info("Failed extract dates from query. Response: {}", response); + } + return List.of(); + } + public List generateSuggestions(Long userId, String query) { + List datesList = findDates(query); + Map> intervalsByDate = new HashMap<>(); + StringBuilder intervalsInfo = new StringBuilder(); + for (LocalDate date: datesList){ + List dateIntervals = eventDistributorService.getFreeSlots(userService.findEventsByUserIdAndDate(userId, date), date); + intervalsInfo.append(formatterUtils.convertDayIntervalsToJson(date, dateIntervals)); + intervalsByDate.put(date, dateIntervals); + } + + Map> chatResponse = generateEventsWithTaskInfo(query, intervalsInfo.toString()); + Map> generatedByDate = convertToEntities(chatResponse) + .stream() + .filter(e -> e instanceof Event) + .map(e -> (Event) e) + .collect(Collectors.groupingBy(e -> e.getStart().toLocalDate())); + + + List finalPlaced = new ArrayList<>(); + User organizer = userService.findUserById(userId); + + for (LocalDate date : datesList) { + List freeSlots = intervalsByDate.getOrDefault(date, List.of()); + List dayGenerated = generatedByDate.getOrDefault(date, List.of()); + + finalPlaced.addAll(eventDistributorService.placeEvents(dayGenerated, freeSlots, organizer, date)); + } + return finalPlaced; + } + + public Map> generateEventsWithTaskInfo(String userQuery, String intervalsInfo) { + logger.info("Generating events for query: {}", userQuery); + + String prompt = "Based on the user's query and user's free intervals in that days generate a list of events. "+ + "USER_QUERY:\n"+ userQuery+ "\n " + + "USER FREE INTERVALS: \n "+ intervalsInfo + "\n " + + "Respond strictly in JSON format with the following structure: " + + "{ \"events\": [{ " + + "\"title\": \"string\", " + + "\"description\": \"string\", " + + "\"start\": \"ISO 8601 datetime\", " + + "\"end\": \"ISO 8601 datetime\", " + + "\"location\": \"string\", " + + "\"type\": \"COMMON|FITNESS|STUDIES|WORK\" " + + "}], " + + "If the query does not require adding an event, return an empty list."+ + "If the user mentions a note, description, or additional information related to an event, include it in the 'description' field of the corresponding event, " + + "unless it is clearly a separate event. " + + "Do not include any additional text or explanation. The \"type\" field MUST be exactly one of: \"COMMON\", \"FITNESS\", \"STUDIES\", \"WORK\". \n" + + "If the event is about sports or training, always use \"FITNESS\". \n" + + "Do not invent other values (e.g., \"SPORT\")."+ + "Do not repeat or copy any of the provided user events. +\n" + + "Generate only new events that are required based on the user's query. "+ + "When generating new events, do not duplicate or overlap with existing events in USERS_EVENTS."; + + String response = askChatGPT(prompt, "gpt-3.5-turbo"); + + try { + return objectMapper.readValue(response, new TypeReference<>() {}); + } catch (Exception e) { + logger.error("Error parsing ChatGPT response into events", e); + throw new RuntimeException("Failed to parse ChatGPT response: " + e.getMessage()); + } + } + public Map> generateEvents(String userQuery) { logger.info("Generating events for query: {}", userQuery); diff --git a/src/main/java/com/smartcalendar/service/EventDistributorService.java b/src/main/java/com/smartcalendar/service/EventDistributorService.java new file mode 100644 index 0000000..16fae43 --- /dev/null +++ b/src/main/java/com/smartcalendar/service/EventDistributorService.java @@ -0,0 +1,125 @@ +package com.smartcalendar.service; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import com.smartcalendar.model.Event; +import com.smartcalendar.model.EventType; +import com.smartcalendar.model.User; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.*; +@Service +public class EventDistributorService { + private final Duration dayBegin = Duration.ofHours(6); + public LocalDateTime getDayBegin(LocalDate date){ + return date.atStartOfDay().plusHours(dayBegin.toHoursPart()).plusMinutes(dayBegin.toMinutesPart()); + } + public Duration defaultDuration(EventType type) { + return //switch (String.valueOf(type)) { + switch (type) { + case STUDIES-> Duration.ofHours(2); + case FITNESS -> Duration.ofHours(1); + case WORK -> Duration.ofHours(8); + default -> Duration.ofHours(1); + }; + } + + + List getFreeSlots(List events, LocalDate date) { + LocalDateTime dayStart = getDayBegin(date); + LocalDateTime dayEnd = date.plusDays(1).atStartOfDay(); + events = new ArrayList<>(events); + events.sort(Comparator.comparing(Event::getStart)); + List free = new ArrayList<>(); + LocalDateTime current = dayStart; + + for (Event e : events) { + if (e.getStart().isAfter(current)) { + free.add(new Event.Interval(current, e.getStart())); + } + current = e.getEnd().isAfter(current) ? e.getEnd() : current; + } + + if (current.isBefore(dayEnd)) { + free.add(new Event.Interval(current, dayEnd)); + } + return free; + } + /** changes Id - don't hope Id from GPT + * */ + List normalizeEvents(List events, LocalDate day) { + List normalized = new ArrayList<>(); + for (Event e : events) { + Event normalizedEvent = new Event(e); + + LocalDateTime start = e.getStart() != null ? e.getStart() : day.atStartOfDay(); + LocalDateTime end = e.getEnd() != null ? e.getEnd() : start.plus(defaultDuration(e.getType())); + + normalizedEvent.setStart(start); + normalizedEvent.setEnd(end); + + normalized.add(normalizedEvent); + } + return normalized; + } + + /** traverse in linear, if event not in free slots set default time and place in nearest later slot + * */ + List placeEvents(List generated, List freeSlots, User organizer, LocalDate date) { + List placed = new ArrayList<>(); + generated = new ArrayList<>(normalizeEvents(generated, date)); + generated.sort(Comparator.comparing(Event::getStart)); + List mutableSlots = new ArrayList<>(freeSlots); + + int i = 0, j = 0; + while (i < generated.size() && j < mutableSlots.size()) { + Event generatedEvent = generated.get(i); + Event.Interval slot = mutableSlots.get(j); + + LocalDateTime desiredStart = generatedEvent.getStart(); + Duration duration = defaultDuration(generatedEvent.getType()); + LocalDateTime desiredEnd = generatedEvent.getEnd(); + if (desiredStart.isBefore(slot.start)) { + desiredStart = slot.start; + desiredEnd = desiredStart.plus(duration); + } + if (desiredEnd.isAfter(slot.end)) { + j++; + continue; + } + Event newEvent = new Event(generatedEvent); + newEvent.setStart(desiredStart); + newEvent.setEnd(desiredEnd); + placed.add(newEvent); + + i++; + if (desiredEnd.equals(slot.end)) { + j++; + } else { + mutableSlots.set(j, new Event.Interval(desiredEnd, slot.end)); + } + } + LocalDateTime lastEnd = mutableSlots.isEmpty() ? LocalDateTime.now() : mutableSlots.getLast().end; + while (i < generated.size()) { + Event g = generated.get(i++); + Duration duration = defaultDuration(g.getType()); + + Event newEvent = new Event(); + newEvent.setId(UUID.randomUUID()); + newEvent.setTitle(g.getTitle()); + newEvent.setDescription(g.getDescription()); + newEvent.setStart(lastEnd); + newEvent.setEnd(lastEnd.plus(duration)); + newEvent.setLocation(g.getLocation() != null ? g.getLocation() : ""); + newEvent.setType(g.getType()); + newEvent.setOrganizer(organizer); + placed.add(newEvent); + lastEnd = newEvent.getEnd(); + } + return placed; + } +} diff --git a/src/main/java/com/smartcalendar/service/UserService.java b/src/main/java/com/smartcalendar/service/UserService.java index 0362e4f..99015a0 100644 --- a/src/main/java/com/smartcalendar/service/UserService.java +++ b/src/main/java/com/smartcalendar/service/UserService.java @@ -20,6 +20,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; import java.time.format.DateTimeFormatter; @@ -62,6 +64,13 @@ public List findTasksByUserId(Long userId) { return taskRepository.findByUserId(userId); } + @Transactional(readOnly = true) + public List findEventsByUserIdAndDate(Long userId, LocalDate date) { + LocalDateTime startOfDay = date.atStartOfDay(); + LocalDateTime endOfDay = date.atTime(23, 59, 59); + + return eventRepository.findByUserIdAndDate(userId, startOfDay, endOfDay); + } public Task createTask(Task task) { return taskRepository.save(task); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a7a07f6..064ca55 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -53,6 +53,7 @@ springdoc.swagger-ui.oauth.use-pkce-with-authorization-code-grant=true server.port=8080 server.address=0.0.0.0 spring.main.banner-mode=off +logging.level.org.springframework.boot.autoconfigure=ERROR chatgpt.api.url=https://api.openai.com/v1/chat/completions whisper.api.url=https://api.openai.com/v1/audio/transcriptions diff --git a/src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java b/src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java index 8f3d7ff..5bb2fde 100644 --- a/src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java +++ b/src/test/java/com/smartcalendar/controller/AudioControllerRestTemplateTest.java @@ -18,10 +18,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.*; -//@Disabled("call real OpenAI API") +@Disabled("call real OpenAI API") @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = {SmartCalendarApplication.class, TestSecurityConfig.class} // подмешиваем TestSecurityConfig + classes = {SmartCalendarApplication.class, TestSecurityConfig.class} ) @ActiveProfiles("test-real") public class AudioControllerRestTemplateTest { diff --git a/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java b/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java index ed3b363..bb228c7 100644 --- a/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java +++ b/src/test/java/com/smartcalendar/controller/ChatGPTControllerIntegrationTest.java @@ -1,5 +1,6 @@ package com.smartcalendar.controller; +import com.smartcalendar.model.Event; import com.smartcalendar.service.ChatGPTService; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -16,6 +17,7 @@ import java.util.Map; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -78,4 +80,19 @@ void testGenerateEntities_Error() throws Exception { .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.error").exists()); } + @Test + @WithMockUser + void testGenerateSuggestions() throws Exception { + Event mockEvent = new Event(); + mockEvent.setTitle("Study session"); + + Mockito.when(chatGPTService.generateSuggestions(eq(1L), eq("study"))) + .thenReturn(List.of(mockEvent)); + + mockMvc.perform(post("/api/chatgpt/1/generate/suggestions") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"query\":\"study\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.events[0].title").value("Study session")); + } } \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java b/src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java new file mode 100644 index 0000000..e0d7169 --- /dev/null +++ b/src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java @@ -0,0 +1,83 @@ +package com.smartcalendar.controller; + +import com.smartcalendar.SmartCalendarApplication; +import com.smartcalendar.config.TestSecurityConfig; +import com.smartcalendar.model.Event; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.*; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Disabled("calls real OpenAI API") +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = {SmartCalendarApplication.class, TestSecurityConfig.class} +) +@ActiveProfiles("test-real") +public class ChatGPTRestTemplateTest { + + @Autowired + private org.springframework.boot.test.web.client.TestRestTemplate restTemplate; + + ResponseEntity> sendAudioForSuggestions(Long userId, String filename) throws Exception { + ClassPathResource audioFile = new ClassPathResource(filename); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("file", new org.springframework.core.io.FileSystemResource(audioFile.getFile())); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + HttpEntity> requestEntity = + new HttpEntity<>(body, headers); + + return restTemplate.exchange( + "/api/chatgpt/" + userId + "/generate/suggestions/audio", + HttpMethod.POST, + requestEntity, + new ParameterizedTypeReference>() {} + ); + } + @Test + void testStudyEventFromAudio() throws Exception { + ResponseEntity> response = sendAudioForSuggestions(1L, "DescriptionIneedStudy.mp3"); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + + Map body = response.getBody(); + assertThat(body).containsKeys("query", "events"); + + String query = body.get("query").toString(); + assertThat(query.toLowerCase()).contains("study"); + + List> events = (List>) body.get("events"); + assertThat(events).isNotEmpty(); + + Map event = events.get(0); + assertThat(event.get("title").toString().toLowerCase()).contains("study"); + } + @Test + void testEmptyAudio() throws Exception { + ResponseEntity> response = sendAudioForSuggestions(1L, "empty.mp3"); + assertThat(response.getBody()).isNotNull(); + if (response.getStatusCode()==(HttpStatus.OK)){ + Map body = response.getBody(); + List> events = (List>) body.get("events"); + assertThat(events).isEmpty(); + } else { + assertThat(response.getBody()).containsKey("error"); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java b/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java index 2e2cafa..ec9e4b2 100644 --- a/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java +++ b/src/test/java/com/smartcalendar/service/ChatGPTServiceTest.java @@ -1,19 +1,45 @@ package com.smartcalendar.service; +import com.smartcalendar.model.Event; +import com.smartcalendar.model.EventType; +import com.smartcalendar.model.FormatterUtils; +import com.smartcalendar.model.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; import java.io.File; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; - +@ExtendWith(MockitoExtension.class) class ChatGPTServiceTest { + private Event createEvent(String title, LocalDateTime start, LocalDateTime end, EventType type, String description) { + Event e = new Event(); + e.setId(UUID.randomUUID()); + e.setTitle(title); + e.setStart(start); + e.setEnd(end); + e.setType(type); + e.setDescription(description); + return e; + } + @Mock + private UserService userService; + @Mock + private EventDistributorService eventDistributorService; + @Mock + private FormatterUtils formatterUtils; + @InjectMocks private ChatGPTService chatGPTService; @@ -48,4 +74,82 @@ void testGenerateEvents_ValidJson() { assertTrue(result.containsKey("events")); assertTrue(result.containsKey("tasks")); } + @Test + void findDates(){ + ChatGPTService spyService = spy(chatGPTService); + doReturn("[\"2025-08-22\",\"2025-08-23\"]").when(spyService).askChatGPT(anyString(), anyString()); + List dates = spyService.findDates("I want to create tasks on 22 and 23 August 2025"); + assertNotNull(dates); + assertEquals(2, dates.size()); + assertTrue(dates.contains(LocalDate.of(2025, 8, 22))); + assertTrue(dates.contains(LocalDate.of(2025, 8, 23))); + } + + @Test + void testGenerateSuggestions() { + Long userId = 1L; + String query = "Schedule study session today"; + LocalDate today = LocalDate.of(2025, 8, 25); + + ChatGPTService spyService = Mockito.spy(chatGPTService); + + doReturn("[\"2025-08-25\"]").when(spyService).askChatGPT(anyString(), anyString()); + + Event.Interval slot = new Event.Interval( + today.atTime(20, 0), + today.atTime(23, 59) + ); + when(eventDistributorService.getFreeSlots(anyList(), eq(today))) + .thenReturn(List.of(slot)); + + when(formatterUtils.convertDayIntervalsToJson(eq(today), anyList())) + .thenReturn("[{\"date\":\"2025-08-25\",\"start\":\"2025-08-25T20:00\",\"end\":\"2025-08-25T23:59\"}]"); + + Map> chatResponse = Map.of( + "events", List.of( + Map.of( + "title", "Study Session", + "description", "Scheduled study session today", + "start", "2025-08-25T20:00", + "end", "2025-08-25T23:59", + "location", "", + "type", "STUDIES" + ) + ) + ); + doReturn(chatResponse).when(spyService).generateEventsWithTaskInfo(anyString(), anyString()); + + User mockUser = new User(); + mockUser.setId(userId); + mockUser.setEmail("test@test.com"); + + Event placedEvent = new Event(); + placedEvent.setId(UUID.randomUUID()); + placedEvent.setTitle("Study Session"); + placedEvent.setDescription("Scheduled study session today"); + placedEvent.setStart(today.atTime(20, 0)); + placedEvent.setEnd(today.atTime(23, 59)); + placedEvent.setType(EventType.STUDIES); + + when(eventDistributorService.placeEvents( + anyList(), + anyList(), + isNull(), + eq(today) + )).thenReturn(List.of(placedEvent)); + + List result = spyService.generateSuggestions(userId, query); + + assertNotNull(result); + assertEquals(1, result.size()); + Event e = result.get(0); + assertEquals("Study Session", e.getTitle()); + assertEquals(EventType.STUDIES, e.getType()); + assertEquals(today.atTime(20, 0), e.getStart()); + assertEquals(today.atTime(23, 59), e.getEnd()); + + verify(eventDistributorService).placeEvents(anyList(), + anyList(), + isNull(), eq(today)); + } } \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/service/EventDistributorServiceTest.java b/src/test/java/com/smartcalendar/service/EventDistributorServiceTest.java new file mode 100644 index 0000000..cc8738e --- /dev/null +++ b/src/test/java/com/smartcalendar/service/EventDistributorServiceTest.java @@ -0,0 +1,73 @@ +package com.smartcalendar.service; + +import com.smartcalendar.model.Event; +import com.smartcalendar.model.EventType; +import com.smartcalendar.model.User; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EventDistributorServiceTest { + private final EventDistributorService service = new EventDistributorService(); + + private User organizer = new User(); + private Event createEvent(String title, LocalDateTime start, LocalDateTime end, EventType type) { + Event e = new Event(); + e.setId(UUID.randomUUID()); + e.setTitle(title); + e.setStart(start); + e.setEnd(end); + e.setType(type); + e.setOrganizer(organizer); + return e; + } + LocalDate date = LocalDate.now(); + Event firstEvent = createEvent("Math", date.atTime(9, 0), date.atTime(10, 0), EventType.STUDIES); + Event secondEvent = createEvent("Pool", date.atTime(11, 30), date.atTime(13, 0), EventType.FITNESS); + + @Test + void testGetFreeSlots() { + + List freeSlotsNoEvents = service.getFreeSlots(List.of(), date); + LocalDateTime dayBegin = service.getDayBegin(date); + assertEquals(1, freeSlotsNoEvents.size()); + assertEquals(dayBegin, freeSlotsNoEvents.getFirst().start); + assertEquals(date.plusDays(1).atStartOfDay(), freeSlotsNoEvents.getFirst().end); + + List freeSlots = service.getFreeSlots(List.of(secondEvent, firstEvent), date); + assertEquals(3, freeSlots.size()); + assertEquals(firstEvent.getStart(), freeSlots.getFirst().end); + assertEquals(secondEvent.getEnd(), freeSlots.getLast().start); + } + + @Test + void testPlaceEvents() { + List slots = List.of( + new Event.Interval(date.atTime(8, 0), date.atTime(12, 0)), + new Event.Interval(date.atTime(13, 0), date.atTime(15, 0)) + ); + + Event thirdEvent = createEvent("Gym", date.atTime(14, 30), date.atTime(15, 0), EventType.FITNESS); + + //first ok, second in interval, third ok + List placed = service.placeEvents(List.of(firstEvent, secondEvent, thirdEvent), slots, organizer, date); + + placed.getFirst().setId(firstEvent.getId());//id and creation time will changed + placed.getFirst().setCreationTime(firstEvent.getCreationTime()); + placed.getLast().setId(thirdEvent.getId()); + placed.getLast().setCreationTime(thirdEvent.getCreationTime()); + + assertEquals(3, placed.size()); + assertEquals(firstEvent, placed.getFirst()); + assertEquals(slots.getLast().start.plus(service.defaultDuration(secondEvent.getType())), + placed.get(1).getEnd()); + assertEquals(thirdEvent, placed.get(2)); + } +} diff --git a/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java b/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java index c4fde97..9a4f40f 100644 --- a/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java +++ b/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java @@ -1,5 +1,9 @@ package com.smartcalendar.service; +import com.smartcalendar.model.Event; +import com.smartcalendar.model.FormatterUtils; +import com.smartcalendar.model.User; +import com.smartcalendar.repository.UserRepository; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -11,8 +15,11 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.UUID; import static org.hibernate.validator.internal.util.Contracts.assertNotNull; import static org.junit.jupiter.api.Assertions.*; @@ -21,14 +28,37 @@ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test-real") +@Disabled("call real OpenAI API") class ServiceRealApiTest { @Autowired private AudioProcessingService audioProcessingService; + @Autowired + private UserRepository userRepository; + + @Autowired + private FormatterUtils formatterUtils; @Autowired private ChatGPTService chatGPTService; + private final User testUser = new User(); + private final LocalDate todayDate = LocalDate.now(); + private final Event eventToday = new Event(); + private final LocalDate date = LocalDate.now(); + @BeforeEach + void setup() { + testUser.setId(1L); + testUser.setUsername("testUser"); + testUser.setEmail("test@example.com"); + testUser.setPassword("12345"); + userRepository.save(testUser); + eventToday.setTitle("Morning Meeting"); + eventToday.setId(UUID.randomUUID()); + eventToday.setStart(date.atTime(0, 0)); + eventToday.setEnd(date.atTime(23, 0)); + eventToday.setOrganizer(testUser); + } MockMultipartFile convertFileToMultipart(String filename) throws IOException { File audio = new File("src/test/resources/"+filename); byte[] content = Files.readAllBytes(audio.toPath()); @@ -66,4 +96,61 @@ void testProcessTranscript_RealRequest() { assertEquals("I need to study", firstEvent.get("description")); System.out.println("Processed events: " + result); } + @Test + @Disabled("call real OpenAI API") + void testFindDates_RealRequest() { + String userQuery = "I want to create tasks on 22nd and 23rd August 2025"; + List dates = chatGPTService.findDates(userQuery); + assertNotNull(dates); + assertFalse(dates.isEmpty()); + assertTrue(dates.stream().anyMatch(d -> d.toString().equals("2025-08-22"))); + assertTrue(dates.stream().anyMatch(d -> d.toString().equals("2025-08-23"))); + } + + @Test + @Disabled("call real OpenAI API") + void testGenerateEventsWithTaskInfo_RealRequest() { + LocalDate date = LocalDate.now(); + List slots = List.of( + new Event.Interval(date.atTime(20, 0), date.atTime(23, 59)) + ); + String intervalsInfo = formatterUtils.convertDayIntervalsToJson(date, slots); + + String userQuery = "Schedule study session today"; + List userEvents = List.of(eventToday); + Map> result = chatGPTService.generateEventsWithTaskInfo(userQuery, intervalsInfo); + + assertNotNull(result); + assertTrue(result.containsKey("events")); + + List events = result.get("events"); + assertFalse(events.isEmpty()); + + Map firstEvent = (Map) events.get(0); + String startStr = (String) firstEvent.get("start"); + assertNotNull(startStr); + + LocalDateTime startDateTime = LocalDateTime.parse(startStr); + + assertTrue(startDateTime.getHour() >= 20, "Event should start after 20:00"); + System.out.println("Generated events: " + events); + } + @Test + @Disabled("call real OpenAI API") + void testGenerateSuggestions_RealRequest() { + Long userId = testUser.getId(); + String query = "Schedule study session today"; + + List result = chatGPTService.generateSuggestions(userId, query); + + assertNotNull(result); + assertFalse(result.isEmpty()); + + Event first = result.getFirst(); + assertNotNull(first.getTitle()); + assertNotNull(first.getStart()); + assertNotNull(first.getEnd()); + + System.out.println("AI generated event: " + first.getTitle() + " " + first.getStart() + " - " + first.getEnd()); + } } \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/service/UserServiceIntegrationTest.java b/src/test/java/com/smartcalendar/service/UserServiceIntegrationTest.java new file mode 100644 index 0000000..8800aa1 --- /dev/null +++ b/src/test/java/com/smartcalendar/service/UserServiceIntegrationTest.java @@ -0,0 +1,104 @@ +package com.smartcalendar.service; + +import com.smartcalendar.SmartCalendarApplication; +import com.smartcalendar.config.TestSecurityConfig; +import com.smartcalendar.model.Event; +import com.smartcalendar.model.User; +import com.smartcalendar.repository.EventRepository; +import com.smartcalendar.repository.UserRepository; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest( + classes = {SmartCalendarApplication.class, TestSecurityConfig.class} +) +@ActiveProfiles("test") +class UserServiceIntegrationTest { + + @Autowired + private UserService userService; + + @Autowired + private UserRepository userRepository; + + @Autowired + private EventRepository eventRepository; + + private User testUser; + private User partisipantUser; + private final Event eventToday = new Event(); + private final Event eventTodayEvening = new Event(); + private final Event eventTomorrow = new Event(); + private final LocalDate date = LocalDate.now(); + @BeforeEach + void setup() { + testUser = new User(); + testUser.setId(1L); + testUser.setUsername("testUser"); + testUser.setEmail("test@example.com"); + testUser.setPassword("12345"); + userRepository.save(testUser); + partisipantUser = new User(); + partisipantUser.setId(2L); + partisipantUser.setUsername("invited"); + partisipantUser.setEmail("invited@example.com"); + partisipantUser.setPassword("12345"); + userRepository.save(partisipantUser); + + eventToday.setTitle("Morning Meeting"); + eventToday.setId(UUID.randomUUID()); + eventToday.setStart(date.atTime(9, 0)); + eventToday.setEnd(date.atTime(10, 0)); + eventToday.setOrganizer(testUser); + + eventTodayEvening.setId(UUID.randomUUID()); + eventTodayEvening.setTitle("Workshop"); + eventTodayEvening.setStart(date.atTime(15, 0)); + eventTodayEvening.setEnd(date.atTime(17, 0)); + eventTodayEvening.setOrganizer(testUser); + + eventTomorrow.setTitle("Workshop"); + eventTomorrow.setId(UUID.randomUUID()); + eventTomorrow.setStart(date.plusDays(1).atTime(15, 0)); + eventTomorrow.setEnd(date.plusDays(1).atTime(17, 0)); + eventTomorrow.setOrganizer(testUser); + } + + @Transactional + @Test + void testSaveAndFindEventsByUserIdAndDate() { + userService.saveEvent(eventToday); + userService.saveEvent(eventTodayEvening); + userService.saveEvent(eventTomorrow); + + List events = userService.findEventsByUserIdAndDate(testUser.getId(), date); + + assertEquals(2, events.size()); + assertTrue(events.stream().anyMatch(e -> e.getTitle().equals("Morning Meeting"))); + assertTrue(events.stream().anyMatch(e -> e.getTitle().equals("Workshop"))); + List eventsNull = userService.findEventsByUserIdAndDate(testUser.getId(), date.minusDays(1)); + assertTrue(eventsNull.isEmpty()); + } + + @Transactional + @Test + void testFindEventsByUserIdAndDate_Participants() { + userService.saveEvent(eventToday); + + eventToday.getParticipants().add(partisipantUser); + userService.saveEvent(eventToday); + + List events = userService.findEventsByUserIdAndDate(partisipantUser.getId(), date); + assertTrue(events.stream().anyMatch(e -> e.getTitle().equals("Morning Meeting"))); + } +} \ No newline at end of file From 6184c5f367b8c74d32f3e5e8af690ccfe7654cff Mon Sep 17 00:00:00 2001 From: Usatov Pavel Date: Sat, 24 Jan 2026 00:44:24 +0300 Subject: [PATCH 5/5] Update README, use .env in test-properties About Docker and tests in README Use modern docker in scripts --- .gitignore | 3 +-- README.md | 21 ++++++++++++++++--- .../application-test-real.properties | 16 ++++++++++++++ .../controller/ChatGPTRestTemplateTest.java | 2 +- .../service/ServiceRealApiTest.java | 6 ------ 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/application-test-real.properties diff --git a/.gitignore b/.gitignore index f99d6f8..87f53ea 100644 --- a/.gitignore +++ b/.gitignore @@ -38,8 +38,7 @@ out/ ### Firebase Service Account ### timetamer-smarcalendar-firebase-adminsdk-fbsvc-8be370036a.json -### Secrets ### -/src/main/resources/application-test-real.properties +### Secrets ### .env ### Internal usage ### diff --git a/README.md b/README.md index 5dd5358..7917402 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,15 @@ Spring Boot backend for the "TimeTamer" SmartCalendar application. Provides REST --- +## Docker + +### Run with Docker Compose +```bash +docker compose up -d --build +``` +or use sh/ps1 scripts +--- + ## Configuration ### Essential Environment Variables @@ -151,9 +160,14 @@ Run tests with: ```bash ./gradlew test ``` -- Uses separate in-memory H2 database -- External services (OpenAI) are mocked -- Test coverage reports: `build/reports/tests` +### Notes +- Uses separate in-memory H2 database via test profiles +- Real OpenAI tests are tagged `openAI-api` and excluded from default `test` +- Run real-API tests explicitly: + ```bash + ./gradlew testOpenAI + ``` +- Real-API tests use `application-test-real.properties` and require valid `CHATGPT_API_KEY` ### Postman Collection @@ -176,3 +190,4 @@ MIT License - see [LICENSE](LICENSE.txt) file ## Contributors - [Dmitry Rusanov](https://github.com/DimaRus05) - [Mikhail Minaev](https://github.com/minmise) +- [Usatov Pavel](https://github.com/UsatovPavel) \ No newline at end of file diff --git a/src/main/resources/application-test-real.properties b/src/main/resources/application-test-real.properties new file mode 100644 index 0000000..b0eb722 --- /dev/null +++ b/src/main/resources/application-test-real.properties @@ -0,0 +1,16 @@ +spring.main.allow-bean-definition-overriding=true +spring.jpa.hibernate.ddl-auto=create-drop +spring.datasource.url=jdbc:h2:mem:realtestdb +spring.sql.init.mode=never + +# реальные настройки OpenAI API +chatgpt.api.url=https://api.openai.com/v1/chat/completions +whisper.api.url=https://api.openai.com/v1/audio/transcriptions +chatgpt.api.key=${CHATGPT_API_KEY} +JWT_SECRET=${JWT_SECRET} + +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java b/src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java index e0d7169..5198edf 100644 --- a/src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java +++ b/src/test/java/com/smartcalendar/controller/ChatGPTRestTemplateTest.java @@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; -@Disabled("calls real OpenAI API") +@Tag("openAI-api") @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {SmartCalendarApplication.class, TestSecurityConfig.class} diff --git a/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java b/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java index bebfb40..ba3deae 100644 --- a/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java +++ b/src/test/java/com/smartcalendar/service/ServiceRealApiTest.java @@ -13,7 +13,6 @@ import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -76,7 +75,6 @@ MockMultipartFile convertFileToMultipart(String filename) throws IOException { } @Test - @Disabled("call real OpenAI API") void testRealAudioTranscription() { MockMultipartFile multipartFile = assertDoesNotThrow(() -> convertFileToMultipart("DescriptionIneedStudy.mp3")); @@ -86,7 +84,6 @@ void testRealAudioTranscription() { } @Test - @Disabled("call real OpenAI API") void testProcessTranscript_RealRequest() { String transcript = "Create event type study with exactly this description: I need to study"; @@ -102,7 +99,6 @@ void testProcessTranscript_RealRequest() { } @Test - @Disabled("call real OpenAI API") void testFindDates_RealRequest() { String userQuery = "I want to create tasks on 22nd and 23rd August 2025"; List dates = chatGPTService.findDates(userQuery); @@ -113,7 +109,6 @@ void testFindDates_RealRequest() { } @Test - @Disabled("call real OpenAI API") void testGenerateEventsWithTaskInfo_RealRequest() { List slots = List.of( new Event.Interval(todayDate.atTime(20, 0), todayDate.atTime(23, 59)) @@ -140,7 +135,6 @@ void testGenerateEventsWithTaskInfo_RealRequest() { } @Test - @Disabled("call real OpenAI API") void testGenerateSuggestions_RealRequest() { Long userId = testUser.getId(); String query = "Schedule study session today";