Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

tmp/*
### STS ###
.apt_generated
.classpath
Expand Down Expand Up @@ -37,3 +37,11 @@ out/

### Firebase Service Account ###
timetamer-smarcalendar-firebase-adminsdk-fbsvc-8be370036a.json

### Secrets ###
/src/main/resources/application-test-real.properties
.env

### Internal usage ###
/internalDocs
out.txt
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM eclipse-temurin:21-jdk-alpine AS builder

RUN apk add --no-cache bash

WORKDIR /app

COPY gradlew gradlew.bat settings.gradle build.gradle ./
COPY gradle ./gradle
COPY src ./src

RUN --mount=type=cache,target=/root/.gradle \
./gradlew bootJar --no-daemon

FROM eclipse-temurin:21-jdk-alpine

RUN apk add --no-cache ffmpeg curl

WORKDIR /app

COPY --from=builder /app/build/libs/*.jar app.jar

ENV JAVA_OPTS=""

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
11 changes: 10 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,18 @@ 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'

}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform {
excludeTags 'openAI-api'
}
}

tasks.register('testOpenAI', Test) {
useJUnitPlatform {
includeTags 'openAI-api'
}
}
38 changes: 38 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
services:
postgres:
image: postgres:15
container_name: smartcalendar_db
environment:
POSTGRES_DB: smartcalendar
POSTGRES_USER: smartuser
POSTGRES_PASSWORD: smartpass
ports:
- "5433: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:
7 changes: 7 additions & 0 deletions restart-docker.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$ErrorActionPreference = "Stop"

Set-Location $PSScriptRoot

.\gradlew.bat clean build
docker-compose down -v
docker-compose up --build -d
6 changes: 6 additions & 0 deletions run-docker.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
$ErrorActionPreference = "Stop"

Set-Location $PSScriptRoot

.\gradlew.bat clean build
docker-compose up --build -d
5 changes: 5 additions & 0 deletions run-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
set -e

./gradlew clean build
docker-compose up --build -d postgres app
30 changes: 30 additions & 0 deletions src/main/java/com/smartcalendar/config/DataInitializer.java
Original file line number Diff line number Diff line change
@@ -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);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public ResponseEntity<String> askChatGPT(@RequestBody Map<String, String> reques
@PostMapping("/generate")
public ResponseEntity<Map<String, List<?>>> generateEventsAndTasks(@RequestBody Map<String, String> requestBody) {
String userQuery = requestBody.get("query");
Map<String, List<?>> result = chatGPTService.generateEventsAndTasks(userQuery);
Map<String, List<?>> result = chatGPTService.generateEvents(userQuery);
return ResponseEntity.ok(result);
}

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/smartcalendar/model/EventType.java
Original file line number Diff line number Diff line change
@@ -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
}

19 changes: 19 additions & 0 deletions src/main/java/com/smartcalendar/model/EventTypeDeserializer.java
Original file line number Diff line number Diff line change
@@ -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<EventType> {
@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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", file.getResource());
body.add("model", "whisper-1");
Path uploadsDir = Paths.get(System.getProperty("user.dir"), "tmp", "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());
}
}
Expand Down
Loading