Compare commits
No commits in common. "0b62e06646513af0480d283de33a59e17e9ee228" and "dce0db03850b4bf3ff2c5c4b731c8296a0173877" have entirely different histories.
0b62e06646
...
dce0db0385
27
build.gradle
27
build.gradle
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '3.5.9'
|
id 'org.springframework.boot' version '4.0.1'
|
||||||
id 'io.spring.dependency-management' version '1.1.7'
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,21 +43,16 @@ configurations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
|
||||||
set('springAiVersion', "1.1.2")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// From Spring Initalizr
|
// From Spring Initalizr
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
|
||||||
implementation 'org.springframework.ai:spring-ai-advisors-vector-store'
|
implementation 'org.springframework.boot:spring-boot-starter-jackson'
|
||||||
implementation 'org.springframework.ai:spring-ai-starter-model-ollama'
|
implementation 'org.springframework.boot:spring-boot-starter-flyway'
|
||||||
implementation 'org.springframework.ai:spring-ai-starter-vector-store-pgvector'
|
runtimeOnly 'org.postgresql:postgresql'
|
||||||
implementation 'org.hibernate.orm:hibernate-vector:6.6.39.Final'
|
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
|
||||||
runtimeOnly 'org.postgresql:postgresql'
|
testImplementation 'org.springframework.boot:spring-boot-starter-jackson-test'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
|
|
||||||
@ -72,7 +67,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'org.commonmark:commonmark:0.27.0'
|
implementation 'org.commonmark:commonmark:0.27.0'
|
||||||
implementation 'org.jsoup:jsoup:1.21.2'
|
implementation 'org.jsoup:jsoup:1.21.2'
|
||||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1'
|
implementation 'tools.jackson.dataformat:jackson-dataformat-yaml:3.0.3'
|
||||||
|
|
||||||
implementation 'io.minio:minio:8.6.0'
|
implementation 'io.minio:minio:8.6.0'
|
||||||
|
|
||||||
@ -96,12 +91,6 @@ dependencies {
|
|||||||
testFixturesImplementation 'org.hamcrest:hamcrest:3.0'
|
testFixturesImplementation 'org.hamcrest:hamcrest:3.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyManagement {
|
|
||||||
imports {
|
|
||||||
mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register('integrationTest', Test) {
|
tasks.register('integrationTest', Test) {
|
||||||
description = 'Run integration tests.'
|
description = 'Run integration tests.'
|
||||||
group = 'verification'
|
group = 'verification'
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@ -4,17 +4,17 @@ import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserCreateException;
|
import app.mealsmadeeasy.api.user.UserCreateException;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import jakarta.servlet.http.Cookie;
|
import jakarta.servlet.http.Cookie;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -51,7 +51,7 @@ public class AuthControllerTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockHttpServletRequestBuilder getLoginRequest(String username, String password) throws Exception {
|
private MockHttpServletRequestBuilder getLoginRequest(String username, String password) {
|
||||||
final Map<String, Object> body = Map.of(
|
final Map<String, Object> body = Map.of(
|
||||||
"username", username,
|
"username", username,
|
||||||
"password", password
|
"password", password
|
||||||
|
|||||||
@ -9,12 +9,11 @@ import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec;
|
|||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserCreateException;
|
import app.mealsmadeeasy.api.user.UserCreateException;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
@ -24,6 +23,7 @@ import org.testcontainers.containers.MinIOContainer;
|
|||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
import org.testcontainers.utility.DockerImageName;
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|||||||
@ -14,13 +14,11 @@ import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
|||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserCreateException;
|
import app.mealsmadeeasy.api.user.UserCreateException;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
import org.springframework.test.context.DynamicPropertySource;
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
@ -29,6 +27,7 @@ import org.testcontainers.containers.MinIOContainer;
|
|||||||
import org.testcontainers.junit.jupiter.Container;
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
import org.testcontainers.utility.DockerImageName;
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -224,7 +223,7 @@ public class RecipeControllerTests {
|
|||||||
.andExpect(jsonPath("$.content[*].id").value(hasItems(r0.getId(), r1.getId(), r2.getId())));
|
.andExpect(jsonPath("$.content[*].id").value(hasItems(r0.getId(), r1.getId(), r2.getId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUpdateBody() throws JsonProcessingException {
|
private String getUpdateBody() {
|
||||||
final RecipeUpdateSpec spec = new RecipeUpdateSpec();
|
final RecipeUpdateSpec spec = new RecipeUpdateSpec();
|
||||||
spec.setTitle("Updated Test Recipe");
|
spec.setTitle("Updated Test Recipe");
|
||||||
spec.setPreparationTime(15);
|
spec.setPreparationTime(15);
|
||||||
|
|||||||
@ -3,17 +3,16 @@ package app.mealsmadeeasy.api.signup;
|
|||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
||||||
import app.mealsmadeeasy.api.user.UserCreateException.Type;
|
import app.mealsmadeeasy.api.user.UserCreateException.Type;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -37,8 +36,7 @@ public class SignUpControllerTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
private MockHttpServletRequestBuilder getCheckUsernameRequest(String usernameToCheck)
|
private MockHttpServletRequestBuilder getCheckUsernameRequest(String usernameToCheck) {
|
||||||
throws JsonProcessingException {
|
|
||||||
final Map<String, Object> body = Map.of("username", usernameToCheck);
|
final Map<String, Object> body = Map.of("username", usernameToCheck);
|
||||||
return MockMvcRequestBuilders.get("/sign-up/check-username")
|
return MockMvcRequestBuilders.get("/sign-up/check-username")
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
@ -63,7 +61,7 @@ public class SignUpControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private MockHttpServletRequestBuilder getCheckEmailRequest(String emailToCheck) throws JsonProcessingException {
|
private MockHttpServletRequestBuilder getCheckEmailRequest(String emailToCheck) {
|
||||||
final Map<String, Object> body = Map.of("email", emailToCheck);
|
final Map<String, Object> body = Map.of("email", emailToCheck);
|
||||||
return MockMvcRequestBuilders.get("/sign-up/check-email")
|
return MockMvcRequestBuilders.get("/sign-up/check-email")
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api;
|
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeEmbeddingEntity;
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeEntity;
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeRepository;
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.ai.embedding.EmbeddingModel;
|
|
||||||
import org.springframework.boot.ApplicationArguments;
|
|
||||||
import org.springframework.boot.ApplicationRunner;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@ConditionalOnProperty(name = "backfill.recipe-embeddings.enabled", havingValue = "true")
|
|
||||||
public class BackfillRecipeEmbeddings implements ApplicationRunner {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(BackfillRecipeEmbeddings.class);
|
|
||||||
|
|
||||||
private final RecipeRepository recipeRepository;
|
|
||||||
private final RecipeService recipeService;
|
|
||||||
private final EmbeddingModel embeddingModel;
|
|
||||||
|
|
||||||
public BackfillRecipeEmbeddings(
|
|
||||||
RecipeRepository recipeRepository,
|
|
||||||
RecipeService recipeService,
|
|
||||||
EmbeddingModel embeddingModel
|
|
||||||
) {
|
|
||||||
this.recipeRepository = recipeRepository;
|
|
||||||
this.recipeService = recipeService;
|
|
||||||
this.embeddingModel = embeddingModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(ApplicationArguments args) {
|
|
||||||
final List<RecipeEntity> recipeEntities = this.recipeRepository.findAllByEmbeddingIsNull();
|
|
||||||
for (final RecipeEntity recipeEntity : recipeEntities) {
|
|
||||||
logger.info("Calculating embedding for {}", recipeEntity);
|
|
||||||
final String renderedMarkdown = this.recipeService.getRenderedMarkdown(recipeEntity);
|
|
||||||
final String toEmbed = "<h1>" + recipeEntity.getTitle() + "</h1>" + renderedMarkdown;
|
|
||||||
final float[] embedding = this.embeddingModel.embed(toEmbed);
|
|
||||||
|
|
||||||
final RecipeEmbeddingEntity recipeEmbedding = new RecipeEmbeddingEntity();
|
|
||||||
recipeEmbedding.setRecipe(recipeEntity);
|
|
||||||
recipeEmbedding.setEmbedding(embedding);
|
|
||||||
recipeEmbedding.setTimestamp(OffsetDateTime.now());
|
|
||||||
recipeEntity.setEmbedding(recipeEmbedding);
|
|
||||||
|
|
||||||
this.recipeRepository.save(recipeEntity);
|
|
||||||
}
|
|
||||||
this.recipeRepository.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -8,14 +8,14 @@ import app.mealsmadeeasy.api.recipe.RecipeService;
|
|||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.CommandLineRunner;
|
import org.springframework.boot.CommandLineRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
import tools.jackson.dataformat.yaml.YAMLFactory;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.image.ImageException;
|
import app.mealsmadeeasy.api.image.ImageException;
|
||||||
import app.mealsmadeeasy.api.recipe.body.RecipeSearchBody;
|
|
||||||
import app.mealsmadeeasy.api.recipe.comment.RecipeComment;
|
import app.mealsmadeeasy.api.recipe.comment.RecipeComment;
|
||||||
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentCreateBody;
|
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentCreateBody;
|
||||||
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentService;
|
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentService;
|
||||||
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentView;
|
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentView;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeAiSearchSpec;
|
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
||||||
@ -15,7 +13,6 @@ import app.mealsmadeeasy.api.recipe.view.RecipeExceptionView;
|
|||||||
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
||||||
import app.mealsmadeeasy.api.sliceview.SliceViewService;
|
import app.mealsmadeeasy.api.sliceview.SliceViewService;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Slice;
|
import org.springframework.data.domain.Slice;
|
||||||
@ -26,7 +23,6 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -37,20 +33,17 @@ public class RecipeController {
|
|||||||
private final RecipeStarService recipeStarService;
|
private final RecipeStarService recipeStarService;
|
||||||
private final RecipeCommentService recipeCommentService;
|
private final RecipeCommentService recipeCommentService;
|
||||||
private final SliceViewService sliceViewService;
|
private final SliceViewService sliceViewService;
|
||||||
private final ObjectMapper objectMapper;
|
|
||||||
|
|
||||||
public RecipeController(
|
public RecipeController(
|
||||||
RecipeService recipeService,
|
RecipeService recipeService,
|
||||||
RecipeStarService recipeStarService,
|
RecipeStarService recipeStarService,
|
||||||
RecipeCommentService recipeCommentService,
|
RecipeCommentService recipeCommentService,
|
||||||
SliceViewService sliceViewService,
|
SliceViewService sliceViewService
|
||||||
ObjectMapper objectMapper
|
|
||||||
) {
|
) {
|
||||||
this.recipeService = recipeService;
|
this.recipeService = recipeService;
|
||||||
this.recipeStarService = recipeStarService;
|
this.recipeStarService = recipeStarService;
|
||||||
this.recipeCommentService = recipeCommentService;
|
this.recipeCommentService = recipeCommentService;
|
||||||
this.sliceViewService = sliceViewService;
|
this.sliceViewService = sliceViewService;
|
||||||
this.objectMapper = objectMapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(RecipeException.class)
|
@ExceptionHandler(RecipeException.class)
|
||||||
@ -111,23 +104,6 @@ public class RecipeController {
|
|||||||
return ResponseEntity.ok(this.sliceViewService.getSliceView(slice));
|
return ResponseEntity.ok(this.sliceViewService.getSliceView(slice));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
public ResponseEntity<Map<String, Object>> searchRecipes(
|
|
||||||
@RequestBody(required = false) RecipeSearchBody recipeSearchBody,
|
|
||||||
@AuthenticationPrincipal User user
|
|
||||||
) {
|
|
||||||
if (recipeSearchBody.getType() == RecipeSearchBody.Type.AI_PROMPT) {
|
|
||||||
final RecipeAiSearchSpec spec = this.objectMapper.convertValue(
|
|
||||||
recipeSearchBody.getData(),
|
|
||||||
RecipeAiSearchSpec.class
|
|
||||||
);
|
|
||||||
final List<RecipeInfoView> results = this.recipeService.aiSearch(spec, user);
|
|
||||||
return ResponseEntity.ok(Map.of("results", results));
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Invalid recipeSearchBody type: " + recipeSearchBody.getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{username}/{slug}/star")
|
@PostMapping("/{username}/{slug}/star")
|
||||||
public ResponseEntity<RecipeStar> addStar(
|
public ResponseEntity<RecipeStar> addStar(
|
||||||
@PathVariable String username,
|
@PathVariable String username,
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.Array;
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
|
||||||
import org.hibernate.type.SqlTypes;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "recipe_embedding")
|
|
||||||
public class RecipeEmbeddingEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
@OneToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@MapsId
|
|
||||||
@JoinColumn(name = "recipe_id")
|
|
||||||
private RecipeEntity recipe;
|
|
||||||
|
|
||||||
@JdbcTypeCode(SqlTypes.VECTOR)
|
|
||||||
@Array(length = 1024)
|
|
||||||
@Nullable
|
|
||||||
private float[] embedding;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private OffsetDateTime timestamp;
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RecipeEntity getRecipe() {
|
|
||||||
return this.recipe;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRecipe(RecipeEntity recipe) {
|
|
||||||
this.recipe = recipe;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getEmbedding() {
|
|
||||||
return this.embedding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmbedding(float[] embedding) {
|
|
||||||
this.embedding = embedding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getTimestamp() {
|
|
||||||
return this.timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimestamp(OffsetDateTime timestamp) {
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -78,9 +78,6 @@ public final class RecipeEntity implements Recipe {
|
|||||||
@JoinColumn(name = "main_image_id")
|
@JoinColumn(name = "main_image_id")
|
||||||
private S3ImageEntity mainImage;
|
private S3ImageEntity mainImage;
|
||||||
|
|
||||||
@OneToOne(mappedBy = "recipe", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
|
|
||||||
private RecipeEmbeddingEntity embedding;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
@ -241,12 +238,4 @@ public final class RecipeEntity implements Recipe {
|
|||||||
this.mainImage = image;
|
this.mainImage = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecipeEmbeddingEntity getEmbedding() {
|
|
||||||
return this.embedding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmbedding(RecipeEmbeddingEntity embedding) {
|
|
||||||
this.embedding = embedding;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,34 +41,4 @@ public interface RecipeRepository extends JpaRepository<RecipeEntity, Long> {
|
|||||||
@Query("SELECT r FROM Recipe r WHERE r.isPublic OR r.owner = ?1 OR ?1 MEMBER OF r.viewers")
|
@Query("SELECT r FROM Recipe r WHERE r.isPublic OR r.owner = ?1 OR ?1 MEMBER OF r.viewers")
|
||||||
Slice<RecipeEntity> findAllViewableBy(UserEntity viewer, Pageable pageable);
|
Slice<RecipeEntity> findAllViewableBy(UserEntity viewer, Pageable pageable);
|
||||||
|
|
||||||
List<RecipeEntity> findAllByEmbeddingIsNull();
|
|
||||||
|
|
||||||
@Query(
|
|
||||||
nativeQuery = true,
|
|
||||||
value = """
|
|
||||||
WITH distances AS (SELECT recipe_id, embedding <=> cast(?1 AS vector) AS distance FROM recipe_embedding)
|
|
||||||
SELECT r.* FROM distances d
|
|
||||||
INNER JOIN recipe r ON r.id = d.recipe_id
|
|
||||||
WHERE d.distance < ?2 AND (
|
|
||||||
r.is_public = TRUE
|
|
||||||
OR r.owner_id = ?3
|
|
||||||
OR exists(SELECT 1 FROM recipe_viewer v WHERE v.recipe_id = r.id AND v.viewer_id = ?3)
|
|
||||||
)
|
|
||||||
ORDER BY d.distance;
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
List<RecipeEntity> searchByEmbeddingAndViewableBy(float[] queryEmbedding, float similarity, Integer viewerId);
|
|
||||||
|
|
||||||
@Query(
|
|
||||||
nativeQuery = true,
|
|
||||||
value = """
|
|
||||||
WITH distances AS (SELECT recipe_id, embedding <=> cast(?1 AS vector) AS distance FROM recipe_embedding)
|
|
||||||
SELECT r.* FROM distances d
|
|
||||||
INNER JOIN recipe r ON r.id = d.recipe_id
|
|
||||||
WHERE d.distance < ?2 AND r.is_public = TRUE
|
|
||||||
ORDER BY d.distance;
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
List<RecipeEntity> searchByEmbeddingAndIsPublic(float[] queryEmbedding, float similarity);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.image.ImageException;
|
import app.mealsmadeeasy.api.image.ImageException;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeAiSearchSpec;
|
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
||||||
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@ -37,8 +35,6 @@ public interface RecipeService {
|
|||||||
List<Recipe> getRecipesViewableBy(User viewer);
|
List<Recipe> getRecipesViewableBy(User viewer);
|
||||||
List<Recipe> getRecipesOwnedBy(User owner);
|
List<Recipe> getRecipesOwnedBy(User owner);
|
||||||
|
|
||||||
List<RecipeInfoView> aiSearch(RecipeAiSearchSpec searchSpec, @Nullable User viewer);
|
|
||||||
|
|
||||||
Recipe update(String username, String slug, RecipeUpdateSpec spec, User modifier)
|
Recipe update(String username, String slug, RecipeUpdateSpec spec, User modifier)
|
||||||
throws RecipeException, ImageException;
|
throws RecipeException, ImageException;
|
||||||
|
|
||||||
@ -57,7 +53,4 @@ public interface RecipeService {
|
|||||||
@Contract("_, _, null -> null")
|
@Contract("_, _, null -> null")
|
||||||
@Nullable Boolean isOwner(String username, String slug, @Nullable User viewer);
|
@Nullable Boolean isOwner(String username, String slug, @Nullable User viewer);
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
String getRenderedMarkdown(RecipeEntity entity);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import app.mealsmadeeasy.api.image.ImageService;
|
|||||||
import app.mealsmadeeasy.api.image.S3ImageEntity;
|
import app.mealsmadeeasy.api.image.S3ImageEntity;
|
||||||
import app.mealsmadeeasy.api.image.view.ImageView;
|
import app.mealsmadeeasy.api.image.view.ImageView;
|
||||||
import app.mealsmadeeasy.api.markdown.MarkdownService;
|
import app.mealsmadeeasy.api.markdown.MarkdownService;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeAiSearchSpec;
|
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStarRepository;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStarRepository;
|
||||||
@ -14,10 +13,8 @@ import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
|||||||
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserEntity;
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.springframework.ai.embedding.EmbeddingModel;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Slice;
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
@ -37,20 +34,17 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
private final RecipeStarRepository recipeStarRepository;
|
private final RecipeStarRepository recipeStarRepository;
|
||||||
private final ImageService imageService;
|
private final ImageService imageService;
|
||||||
private final MarkdownService markdownService;
|
private final MarkdownService markdownService;
|
||||||
private final EmbeddingModel embeddingModel;
|
|
||||||
|
|
||||||
public RecipeServiceImpl(
|
public RecipeServiceImpl(
|
||||||
RecipeRepository recipeRepository,
|
RecipeRepository recipeRepository,
|
||||||
RecipeStarRepository recipeStarRepository,
|
RecipeStarRepository recipeStarRepository,
|
||||||
ImageService imageService,
|
ImageService imageService,
|
||||||
MarkdownService markdownService,
|
MarkdownService markdownService
|
||||||
EmbeddingModel embeddingModel
|
|
||||||
) {
|
) {
|
||||||
this.recipeRepository = recipeRepository;
|
this.recipeRepository = recipeRepository;
|
||||||
this.recipeStarRepository = recipeStarRepository;
|
this.recipeStarRepository = recipeStarRepository;
|
||||||
this.imageService = imageService;
|
this.imageService = imageService;
|
||||||
this.markdownService = markdownService;
|
this.markdownService = markdownService;
|
||||||
this.embeddingModel = embeddingModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -99,9 +93,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private String getRenderedMarkdown(RecipeEntity entity) {
|
||||||
@ApiStatus.Internal
|
|
||||||
public String getRenderedMarkdown(RecipeEntity entity) {
|
|
||||||
if (entity.getCachedRenderedText() == null) {
|
if (entity.getCachedRenderedText() == null) {
|
||||||
entity.setCachedRenderedText(this.markdownService.renderAndCleanMarkdown(entity.getRawText()));
|
entity.setCachedRenderedText(this.markdownService.renderAndCleanMarkdown(entity.getRawText()));
|
||||||
entity = this.recipeRepository.save(entity);
|
entity = this.recipeRepository.save(entity);
|
||||||
@ -199,20 +191,6 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
return List.copyOf(this.recipeRepository.findAllByOwner((UserEntity) owner));
|
return List.copyOf(this.recipeRepository.findAllByOwner((UserEntity) owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<RecipeInfoView> aiSearch(RecipeAiSearchSpec searchSpec, @Nullable User viewer) {
|
|
||||||
final float[] queryEmbedding = this.embeddingModel.embed(searchSpec.getPrompt());
|
|
||||||
final List<RecipeEntity> results;
|
|
||||||
if (viewer == null) {
|
|
||||||
results = this.recipeRepository.searchByEmbeddingAndIsPublic(queryEmbedding, 0.5f);
|
|
||||||
} else {
|
|
||||||
results = this.recipeRepository.searchByEmbeddingAndViewableBy(queryEmbedding, 0.5f, viewer.getId());
|
|
||||||
}
|
|
||||||
return results.stream()
|
|
||||||
.map(recipeEntity -> this.getInfoView(recipeEntity, viewer))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PreAuthorize("@recipeSecurity.isOwner(#username, #slug, #modifier)")
|
@PreAuthorize("@recipeSecurity.isOwner(#username, #slug, #modifier)")
|
||||||
public Recipe update(String username, String slug, RecipeUpdateSpec spec, User modifier)
|
public Recipe update(String username, String slug, RecipeUpdateSpec spec, User modifier)
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.body;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class RecipeSearchBody {
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
AI_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type type;
|
|
||||||
private Map<String, Object> data;
|
|
||||||
|
|
||||||
public Type getType() {
|
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(Type type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getData() {
|
|
||||||
return this.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(Map<String, Object> data) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.spec;
|
|
||||||
|
|
||||||
public class RecipeAiSearchSpec {
|
|
||||||
|
|
||||||
private String prompt;
|
|
||||||
|
|
||||||
public String getPrompt() {
|
|
||||||
return this.prompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrompt(String prompt) {
|
|
||||||
this.prompt = prompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.security;
|
package app.mealsmadeeasy.api.security;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.jwt.JwtService;
|
import app.mealsmadeeasy.api.jwt.JwtService;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.JwtException;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
@ -19,6 +18,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
|||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) {
|
||||||
httpSecurity.authorizeHttpRequests(requests -> requests.anyRequest().permitAll());
|
httpSecurity.authorizeHttpRequests(requests -> requests.anyRequest().permitAll());
|
||||||
httpSecurity.csrf(AbstractHttpConfigurer::disable);
|
httpSecurity.csrf(AbstractHttpConfigurer::disable);
|
||||||
httpSecurity.cors(Customizer.withDefaults());
|
httpSecurity.cors(Customizer.withDefaults());
|
||||||
|
|||||||
@ -16,6 +16,3 @@ app.mealsmadeeasy.api.minio.endpoint=http://${MINIO_HOST:localhost}:${MINIO_PORT
|
|||||||
app.mealsmadeeasy.api.minio.accessKey=${MINIO_ROOT_USER}
|
app.mealsmadeeasy.api.minio.accessKey=${MINIO_ROOT_USER}
|
||||||
app.mealsmadeeasy.api.minio.secretKey=${MINIO_ROOT_PASSWORD}
|
app.mealsmadeeasy.api.minio.secretKey=${MINIO_ROOT_PASSWORD}
|
||||||
app.mealsmadeeasy.api.images.bucketName=images
|
app.mealsmadeeasy.api.images.bucketName=images
|
||||||
|
|
||||||
# AI
|
|
||||||
spring.ai.vectorstore.pgvector.dimensions=1024
|
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
CREATE EXTENSION vector;
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
CREATE TABLE recipe_embedding (
|
|
||||||
recipe_id INT NOT NULL,
|
|
||||||
timestamp TIMESTAMPTZ(6) NOT NULL,
|
|
||||||
embedding VECTOR(1024),
|
|
||||||
PRIMARY KEY (recipe_id),
|
|
||||||
FOREIGN KEY (recipe_id) REFERENCES recipe ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
Loading…
Reference in New Issue
Block a user