Compare commits
No commits in common. "a38ac6b6f5a9b3c281028a2505f31fa84b562ff8" and "0013d07273dd798362caf4280b8bf63fb3caf46d" have entirely different histories.
a38ac6b6f5
...
0013d07273
34
build.gradle
34
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,28 +43,18 @@ 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'
|
runtimeOnly 'com.mysql:mysql-connector-j'
|
||||||
implementation 'org.springframework.ai:spring-ai-starter-vector-store-pgvector'
|
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
|
||||||
implementation 'org.hibernate.orm:hibernate-vector:6.6.39.Final'
|
testImplementation 'org.springframework.boot:spring-boot-starter-jackson-test'
|
||||||
runtimeOnly 'org.postgresql:postgresql'
|
|
||||||
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'
|
||||||
|
|
||||||
// flyway
|
|
||||||
implementation 'org.flywaydb:flyway-core'
|
|
||||||
implementation 'org.flywaydb:flyway-database-postgresql'
|
|
||||||
|
|
||||||
// Custom
|
// Custom
|
||||||
implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
|
implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
|
||||||
implementation 'io.jsonwebtoken:jjwt-jackson:0.13.0'
|
implementation 'io.jsonwebtoken:jjwt-jackson:0.13.0'
|
||||||
@ -72,7 +62,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'
|
||||||
|
|
||||||
@ -88,20 +78,14 @@ dependencies {
|
|||||||
runtimeOnly 'org.apache.xmlgraphics:batik-all:1.19'
|
runtimeOnly 'org.apache.xmlgraphics:batik-all:1.19'
|
||||||
|
|
||||||
// Custom testing
|
// Custom testing
|
||||||
|
testRuntimeOnly 'com.h2database:h2'
|
||||||
testImplementation 'org.testcontainers:testcontainers:1.21.4'
|
testImplementation 'org.testcontainers:testcontainers:1.21.4'
|
||||||
testImplementation 'org.testcontainers:junit-jupiter:1.21.4'
|
testImplementation 'org.testcontainers:junit-jupiter:1.21.4'
|
||||||
testImplementation 'org.testcontainers:postgresql:1.21.4'
|
testImplementation "org.testcontainers:minio:1.21.4"
|
||||||
testImplementation 'org.testcontainers:minio:1.21.4'
|
|
||||||
|
|
||||||
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'
|
||||||
|
|||||||
@ -1,26 +1,26 @@
|
|||||||
name: meals-made-easy-api-dev
|
name: meals-made-easy-api-dev
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: pgvector/pgvector:pg18-trixie
|
image: mysql:latest
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- '55001:3306'
|
||||||
|
- '55000:33060'
|
||||||
env_file: .env
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: meals_made_easy_api
|
MYSQL_DATABASE: meals_made_easy_api
|
||||||
POSTGRES_USER: meals-made-easy-api-user
|
MYSQL_USER: meals-made-easy-api-user
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB
|
test: mysqladmin ping -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 10
|
||||||
start_period: 10s
|
|
||||||
volumes:
|
volumes:
|
||||||
- postgres-data:/var/lib/postgresql
|
- mysql-data:/var/lib/mysql
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:latest
|
image: minio/minio:latest
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000"
|
- 9000:9000
|
||||||
- "9001:9001"
|
- 9001:9001
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@ -32,6 +32,8 @@ services:
|
|||||||
- /data
|
- /data
|
||||||
- --console-address
|
- --console-address
|
||||||
- :9001
|
- :9001
|
||||||
|
profiles:
|
||||||
|
- deps
|
||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
mysql-data:
|
||||||
minio-data:
|
minio-data:
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.extension.BeforeAllCallback;
|
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
|
||||||
import org.testcontainers.containers.PostgreSQLContainer;
|
|
||||||
|
|
||||||
public class IntegrationTestsExtension implements BeforeAllCallback {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeAll(ExtensionContext context) {
|
|
||||||
final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("pgvector/pgvector:pg18-trixie")
|
|
||||||
.withDatabaseName("meals_made_easy_api");
|
|
||||||
postgres.start();
|
|
||||||
System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
|
|
||||||
System.setProperty("spring.datasource.username", postgres.getUsername());
|
|
||||||
System.setProperty("spring.datasource.password", postgres.getPassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,14 +1,12 @@
|
|||||||
package app.mealsmadeeasy.api;
|
package app.mealsmadeeasy.api;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
class MealsMadeEasyApiApplicationTests {
|
||||||
public class MealsMadeEasyApiApplicationTests {
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void contextLoads() {}
|
void contextLoads() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,22 @@
|
|||||||
package app.mealsmadeeasy.api.auth;
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
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.annotation.DirtiesContext;
|
||||||
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 static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
@ -28,11 +27,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class AuthControllerTests {
|
public class AuthControllerTests {
|
||||||
|
|
||||||
private static final String TEST_PASSWORD = "test";
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@ -42,38 +38,43 @@ public class AuthControllerTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
private User seedUser() {
|
private User createTestUser() {
|
||||||
final String uuid = UUID.randomUUID().toString();
|
|
||||||
try {
|
try {
|
||||||
return this.userService.createUser(uuid, uuid + "@test.com", TEST_PASSWORD);
|
return this.userService.createUser("test", "test@test.com", "test");
|
||||||
} catch (UserCreateException e) {
|
} catch (UserCreateException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockHttpServletRequestBuilder getLoginRequest(String username, String password) throws Exception {
|
private MockHttpServletRequestBuilder getLoginRequest() {
|
||||||
final Map<String, Object> body = Map.of(
|
final Map<String, ?> body = Map.of(
|
||||||
"username", username,
|
"username", "test",
|
||||||
"password", password
|
"password", "test"
|
||||||
);
|
);
|
||||||
return post("/auth/login")
|
return post("/auth/login")
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.with(user(username).password(password));
|
.with(user("test").password("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
final User testUser = this.createTestUser();
|
||||||
|
System.out.println("Created testUser: " + testUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void simpleLogin() throws Exception {
|
public void simpleLogin() throws Exception {
|
||||||
final User user = this.seedUser();
|
this.mockMvc.perform(this.getLoginRequest())
|
||||||
this.mockMvc.perform(this.getLoginRequest(user.getUsername(), TEST_PASSWORD))
|
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.username").value(user.getUsername()))
|
.andExpect(jsonPath("$.username").value("test"))
|
||||||
.andExpect(jsonPath("$.accessToken").isString())
|
.andExpect(jsonPath("$.accessToken").isString())
|
||||||
.andExpect(cookie().exists("refresh-token"));
|
.andExpect(cookie().exists("refresh-token"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cookie getRefreshTokenCookie(String username, String password) throws Exception {
|
private Cookie getRefreshTokenCookie() throws Exception {
|
||||||
final MvcResult loginResult = this.mockMvc.perform(this.getLoginRequest(username, password)).andReturn();
|
final MvcResult loginResult = this.mockMvc.perform(this.getLoginRequest()).andReturn();
|
||||||
final Cookie refreshTokenCookie = loginResult.getResponse().getCookie("refresh-token");
|
final Cookie refreshTokenCookie = loginResult.getResponse().getCookie("refresh-token");
|
||||||
if (refreshTokenCookie == null) {
|
if (refreshTokenCookie == null) {
|
||||||
throw new NullPointerException("refreshTokenCookie is null");
|
throw new NullPointerException("refreshTokenCookie is null");
|
||||||
@ -82,24 +83,24 @@ public class AuthControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void simpleLogout() throws Exception {
|
public void simpleLogout() throws Exception {
|
||||||
final User user = this.seedUser();
|
|
||||||
final MockHttpServletRequestBuilder req = post("/auth/logout")
|
final MockHttpServletRequestBuilder req = post("/auth/logout")
|
||||||
.cookie(this.getRefreshTokenCookie(user.getUsername(), TEST_PASSWORD));
|
.cookie(this.getRefreshTokenCookie());
|
||||||
this.mockMvc.perform(req)
|
this.mockMvc.perform(req)
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(cookie().maxAge("refresh-token", 0));
|
.andExpect(cookie().maxAge("refresh-token", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void simpleRefresh() throws Exception {
|
public void simpleRefresh() throws Exception {
|
||||||
final User user = this.seedUser();
|
final Cookie firstRefreshTokenCookie = this.getRefreshTokenCookie();
|
||||||
final Cookie firstRefreshTokenCookie = this.getRefreshTokenCookie(user.getUsername(), TEST_PASSWORD);
|
|
||||||
final MockHttpServletRequestBuilder req = post("/auth/refresh")
|
final MockHttpServletRequestBuilder req = post("/auth/refresh")
|
||||||
.cookie(firstRefreshTokenCookie);
|
.cookie(firstRefreshTokenCookie);
|
||||||
final MvcResult res = this.mockMvc.perform(req)
|
final MvcResult res = this.mockMvc.perform(req)
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.username").value(user.getUsername()))
|
.andExpect(jsonPath("$.username").value("test"))
|
||||||
.andExpect(jsonPath("$.accessToken").isString())
|
.andExpect(jsonPath("$.accessToken").isString())
|
||||||
.andExpect(cookie().exists("refresh-token"))
|
.andExpect(cookie().exists("refresh-token"))
|
||||||
.andReturn();
|
.andReturn();
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package app.mealsmadeeasy.api.image;
|
package app.mealsmadeeasy.api.image;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
||||||
import app.mealsmadeeasy.api.auth.AuthService;
|
import app.mealsmadeeasy.api.auth.AuthService;
|
||||||
import app.mealsmadeeasy.api.auth.LoginException;
|
import app.mealsmadeeasy.api.auth.LoginException;
|
||||||
import app.mealsmadeeasy.api.image.body.ImageUpdateInfoBody;
|
import app.mealsmadeeasy.api.image.body.ImageUpdateInfoBody;
|
||||||
@ -9,14 +8,13 @@ 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.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.annotation.DirtiesContext;
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
import org.springframework.test.context.DynamicPropertySource;
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
@ -24,11 +22,12 @@ 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;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
@ -39,9 +38,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@Testcontainers
|
@Testcontainers
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class ImageControllerTests {
|
public class ImageControllerTests {
|
||||||
|
|
||||||
|
private static final String USER_FILENAME = "HAL9000.svg";
|
||||||
|
|
||||||
@Container
|
@Container
|
||||||
private static final MinIOContainer container = new MinIOContainer(
|
private static final MinIOContainer container = new MinIOContainer(
|
||||||
DockerImageName.parse("minio/minio:latest")
|
DockerImageName.parse("minio/minio:latest")
|
||||||
@ -54,7 +54,7 @@ public class ImageControllerTests {
|
|||||||
registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword);
|
registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InputStream getHal9000InputStream() {
|
private static InputStream getHal9000() {
|
||||||
return ImageControllerTests.class.getResourceAsStream("HAL9000.svg");
|
return ImageControllerTests.class.getResourceAsStream("HAL9000.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,73 +73,65 @@ public class ImageControllerTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
private static final String TEST_PASSWORD = "test";
|
private User createTestUser(String username) {
|
||||||
private static final long HAL_SIZE = 27881L;
|
|
||||||
|
|
||||||
private static String makeUserFilename() {
|
|
||||||
return UUID.randomUUID() + ".svg";
|
|
||||||
}
|
|
||||||
|
|
||||||
private User seedUser() {
|
|
||||||
final String uuid = UUID.randomUUID().toString();
|
|
||||||
try {
|
try {
|
||||||
return this.userService.createUser(uuid, uuid + "@test.com", TEST_PASSWORD);
|
return this.userService.createUser(username, username + "@test.com", "test");
|
||||||
} catch (UserCreateException e) {
|
} catch (UserCreateException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Image seedImage(User owner, ImageCreateInfoSpec spec) {
|
private Image createHal9000(User owner) throws ImageException, IOException {
|
||||||
try {
|
try (final InputStream hal9000 = getHal9000()) {
|
||||||
return this.imageService.create(
|
return this.imageService.create(
|
||||||
owner,
|
owner,
|
||||||
makeUserFilename(),
|
USER_FILENAME,
|
||||||
getHal9000InputStream(),
|
hal9000,
|
||||||
HAL_SIZE,
|
27881L,
|
||||||
spec
|
new ImageCreateInfoSpec()
|
||||||
);
|
);
|
||||||
} catch (IOException | ImageException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Image seedImage(User owner) {
|
private String getAccessToken(String username) {
|
||||||
return this.seedImage(owner, new ImageCreateInfoSpec());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getImageUrl(User owner, Image image) {
|
|
||||||
return "/images/" + owner.getUsername() + "/" + image.getUserFilename();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAccessToken(User user) {
|
|
||||||
try {
|
try {
|
||||||
return this.authService.login(user.getUsername(), TEST_PASSWORD).getAccessToken().getToken();
|
return this.authService.login(username, "test").getAccessToken().getToken();
|
||||||
} catch (LoginException e) {
|
} catch (LoginException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private Image makePublic(Image image, User modifier) {
|
||||||
public void getPublicImageNoPrincipal() throws Exception {
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
final User owner = this.seedUser();
|
|
||||||
final ImageCreateInfoSpec spec = new ImageCreateInfoSpec();
|
|
||||||
spec.setPublic(true);
|
spec.setPublic(true);
|
||||||
final Image image = this.seedImage(owner, spec);
|
return this.imageService.update(image, modifier, spec);
|
||||||
|
}
|
||||||
|
|
||||||
// Assert bytes the same and proper mime type
|
private Image addViewer(Image image, User modifier, User viewerToAdd) {
|
||||||
try (final InputStream hal9000 = getHal9000InputStream()) {
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
|
spec.setViewersToAdd(Set.of(viewerToAdd));
|
||||||
|
return this.imageService.update(image, modifier, spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void getImageNoPrincipal() throws Exception {
|
||||||
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
final Image image = this.createHal9000(owner);
|
||||||
|
this.makePublic(image, owner);
|
||||||
|
try (final InputStream hal9000 = getHal9000()) {
|
||||||
final byte[] halBytes = hal9000.readAllBytes();
|
final byte[] halBytes = hal9000.readAllBytes();
|
||||||
this.mockMvc.perform(get(getImageUrl(owner, image)))
|
this.mockMvc.perform(get("/images/imageOwner/HAL9000.svg"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().contentType("image/svg+xml"))
|
.andExpect(content().contentType("image/svg+xml"))
|
||||||
.andExpect(content().bytes(halBytes));
|
.andExpect(content().bytes(halBytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doGetImageTestWithAccessToken(User owner, Image image, String accessToken) throws Exception {
|
private void doGetImageTestWithViewer(String accessToken) throws Exception {
|
||||||
try (final InputStream hal9000 = getHal9000InputStream()) {
|
try (final InputStream hal9000 = getHal9000()) {
|
||||||
final byte[] halBytes = hal9000.readAllBytes();
|
final byte[] halBytes = hal9000.readAllBytes();
|
||||||
this.mockMvc.perform(get(getImageUrl(owner, image))
|
this.mockMvc.perform(get("/images/imageOwner/HAL9000.svg")
|
||||||
.header("Authorization", "Bearer " + accessToken))
|
.header("Authorization", "Bearer " + accessToken))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().contentType("image/svg+xml"))
|
.andExpect(content().contentType("image/svg+xml"))
|
||||||
@ -148,73 +140,88 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getImageForOwner() throws Exception {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void getImageWithOwner() throws Exception {
|
||||||
final Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
this.doGetImageTestWithAccessToken(owner, image, this.getAccessToken(owner));
|
this.createHal9000(owner);
|
||||||
|
final String accessToken = this.getAccessToken(owner.getUsername());
|
||||||
|
this.doGetImageTestWithViewer(accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getImageForViewer() throws Exception {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void getImageWithViewer() throws Exception {
|
||||||
final User viewer = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final Image image = this.seedImage(owner);
|
final User viewer = this.createTestUser("viewer");
|
||||||
|
final Image image = this.createHal9000(owner);
|
||||||
// add viewer
|
this.addViewer(image, owner, viewer);
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
final String accessToken = this.getAccessToken(viewer.getUsername());
|
||||||
spec.setViewersToAdd(Set.of(viewer));
|
this.doGetImageTestWithViewer(accessToken);
|
||||||
this.imageService.update(image, owner, spec);
|
|
||||||
|
|
||||||
this.doGetImageTestWithAccessToken(owner, image, this.getAccessToken(viewer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getNonPublicImageNoPrincipalForbidden() throws Exception {
|
public void getNonPublicImageNoPrincipalForbidden() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final Image image = this.seedImage(owner);
|
this.createHal9000(owner);
|
||||||
this.mockMvc.perform(get(getImageUrl(owner, image)))
|
|
||||||
.andExpect(status().isForbidden());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getNonPublicImageWithPrincipalForbidden() throws Exception {
|
|
||||||
final User owner = this.seedUser();
|
|
||||||
final Image image = this.seedImage(owner);
|
|
||||||
final User nonViewer = this.seedUser();
|
|
||||||
final String nonViewerToken = this.getAccessToken(nonViewer);
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
get(getImageUrl(owner, image))
|
get("/images/imageOwner/HAL9000.svg")
|
||||||
.header("Authorization", "Bearer " + nonViewerToken)
|
|
||||||
).andExpect(status().isForbidden());
|
).andExpect(status().isForbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getImageWithViewersNoPrincipalForbidden() throws Exception {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void getNonPublicImageWithPrincipalForbidden() throws Exception {
|
||||||
final Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final User viewer = this.seedUser();
|
final User viewer = this.createTestUser("viewer");
|
||||||
|
this.createHal9000(owner);
|
||||||
// add viewer
|
final String accessToken = this.getAccessToken(viewer.getUsername());
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
this.mockMvc.perform(
|
||||||
spec.setViewersToAdd(Set.of(viewer));
|
get("/images/imageOwner/HAL9000.svg")
|
||||||
this.imageService.update(image, owner, spec);
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
|
).andExpect(status().isForbidden());
|
||||||
this.mockMvc.perform(get(getImageUrl(owner, image))).andExpect(status().isForbidden());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void getImageWithViewersNoPrincipalForbidden() throws Exception {
|
||||||
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
final User viewer = this.createTestUser("viewer");
|
||||||
|
final Image image = this.createHal9000(owner);
|
||||||
|
this.addViewer(image, owner, viewer);
|
||||||
|
this.mockMvc.perform(
|
||||||
|
get("/images/imageOwner/HAL9000.svg")
|
||||||
|
).andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void getImageWithViewersWrongViewerForbidden() throws Exception {
|
||||||
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
final User viewer = this.createTestUser("viewer");
|
||||||
|
final User wrongViewer = this.createTestUser("wrongViewer");
|
||||||
|
final Image image = this.createHal9000(owner);
|
||||||
|
this.addViewer(image, owner, viewer);
|
||||||
|
final String accessToken = this.getAccessToken(wrongViewer.getUsername());
|
||||||
|
this.mockMvc.perform(
|
||||||
|
get("/images/imageOwner/HAL9000.svg")
|
||||||
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
|
).andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void putImage() throws Exception {
|
public void putImage() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final String accessToken = this.getAccessToken(owner);
|
final String accessToken = this.getAccessToken(owner.getUsername());
|
||||||
try (final InputStream hal9000 = getHal9000InputStream()) {
|
try (final InputStream hal9000 = getHal9000()) {
|
||||||
final String userFilename = makeUserFilename();
|
|
||||||
final MockMultipartFile mockMultipartFile = new MockMultipartFile(
|
final MockMultipartFile mockMultipartFile = new MockMultipartFile(
|
||||||
"image", userFilename, "image/svg+xml", hal9000
|
"image", "HAL9000.svg", "image/svg+xml", hal9000
|
||||||
);
|
);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
multipart("/images")
|
multipart("/images")
|
||||||
.file(mockMultipartFile)
|
.file(mockMultipartFile)
|
||||||
.param("filename", userFilename)
|
.param("filename", "HAL9000.svg")
|
||||||
.param("alt", "HAL 9000")
|
.param("alt", "HAL 9000")
|
||||||
.param("caption", "HAL 9000, from 2001: A Space Odyssey")
|
.param("caption", "HAL 9000, from 2001: A Space Odyssey")
|
||||||
.param("isPublic", "true")
|
.param("isPublic", "true")
|
||||||
@ -227,26 +234,31 @@ public class ImageControllerTests {
|
|||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andExpect(jsonPath("$.created").exists())
|
.andExpect(jsonPath("$.created").exists())
|
||||||
.andExpect(jsonPath("$.modified").value(nullValue()))
|
.andExpect(jsonPath("$.modified").value(nullValue()))
|
||||||
.andExpect(jsonPath("$.filename").value(userFilename))
|
.andExpect(jsonPath("$.filename").value(USER_FILENAME))
|
||||||
.andExpect(jsonPath("$.mimeType").value("image/svg+xml"))
|
.andExpect(jsonPath("$.mimeType").value("image/svg+xml"))
|
||||||
.andExpect(jsonPath("$.alt").value("HAL 9000"))
|
.andExpect(jsonPath("$.alt").value("HAL 9000"))
|
||||||
.andExpect(jsonPath("$.caption").value("HAL 9000, from 2001: A Space Odyssey"))
|
.andExpect(jsonPath("$.caption").value("HAL 9000, from 2001: A Space Odyssey"))
|
||||||
.andExpect(jsonPath("$.isPublic").value(true))
|
.andExpect(jsonPath("$.isPublic").value(true))
|
||||||
.andExpect(jsonPath("$.owner.username").value(owner.getUsername()))
|
.andExpect(jsonPath("$.owner.username").value("imageOwner"))
|
||||||
.andExpect(jsonPath("$.owner.id").value(owner.getId()))
|
.andExpect(jsonPath("$.owner.id").value(owner.getId()))
|
||||||
.andExpect(jsonPath("$.viewers").value(empty()));
|
.andExpect(jsonPath("$.viewers").value(empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String prepUpdate() throws ImageException, IOException {
|
||||||
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
this.createHal9000(owner);
|
||||||
|
return this.getAccessToken(owner.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateAlt() throws Exception {
|
public void updateAlt() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final String accessToken = this.prepUpdate();
|
||||||
final Image image = this.seedImage(owner);
|
|
||||||
final String accessToken = this.getAccessToken(owner);
|
|
||||||
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
||||||
body.setAlt("HAL 9000");
|
body.setAlt("HAL 9000");
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
post("/images/imageOwner/HAL9000.svg")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -257,14 +269,13 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateCaption() throws Exception {
|
public void updateCaption() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final String accessToken = this.prepUpdate();
|
||||||
final Image image = this.seedImage(owner);
|
|
||||||
final String accessToken = this.getAccessToken(owner);
|
|
||||||
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
||||||
body.setCaption("HAL 9000 from 2001: A Space Odyssey");
|
body.setCaption("HAL 9000 from 2001: A Space Odyssey");
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
post("/images/imageOwner/HAL9000.svg")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -275,14 +286,13 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateIsPublic() throws Exception {
|
public void updateIsPublic() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final String accessToken = this.prepUpdate();
|
||||||
final Image image = this.seedImage(owner);
|
|
||||||
final String accessToken = this.getAccessToken(owner);
|
|
||||||
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
||||||
body.setPublic(true);
|
body.setPublic(true);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
post("/images/imageOwner/HAL9000.svg")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -293,18 +303,16 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void addViewers() throws Exception {
|
public void addViewers() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final String accessToken = this.prepUpdate();
|
||||||
final User viewerToAdd = this.seedUser();
|
|
||||||
final Image image = this.seedImage(owner);
|
|
||||||
final String accessToken = this.getAccessToken(owner);
|
|
||||||
|
|
||||||
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
||||||
final Set<String> viewerUsernames = Set.of(viewerToAdd.getUsername());
|
final Set<String> viewerUsernames = Set.of(this.createTestUser("imageViewer")).stream()
|
||||||
|
.map(User::getUsername)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
body.setViewersToAdd(viewerUsernames);
|
body.setViewersToAdd(viewerUsernames);
|
||||||
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(owner, image))
|
post("/images/imageOwner/HAL9000.svg")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -312,15 +320,15 @@ public class ImageControllerTests {
|
|||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.modified").value(notNullValue()))
|
.andExpect(jsonPath("$.modified").value(notNullValue()))
|
||||||
.andExpect(jsonPath("$.viewers").value(not(empty())))
|
.andExpect(jsonPath("$.viewers").value(not(empty())))
|
||||||
.andExpect(jsonPath("$.viewers[0].username").value(viewerToAdd.getUsername()));
|
.andExpect(jsonPath("$.viewers[0].username").value("imageViewer"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private record OwnerViewerImage(User owner, User viewer, Image image) {}
|
private record OwnerViewerImage(User owner, User viewer, Image image) {}
|
||||||
|
|
||||||
private OwnerViewerImage prepOwnerViewerImage() {
|
private OwnerViewerImage prepOwnerViewerImage() throws ImageException, IOException {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final User viewer = this.seedUser();
|
final User viewer = this.createTestUser("imageViewer");
|
||||||
final Image image = this.seedImage(owner);
|
final Image image = this.createHal9000(owner);
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
spec.setViewersToAdd(Set.of(viewer));
|
spec.setViewersToAdd(Set.of(viewer));
|
||||||
this.imageService.update(image, owner, spec);
|
this.imageService.update(image, owner, spec);
|
||||||
@ -328,15 +336,14 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void removeViewers() throws Exception {
|
public void removeViewers() throws Exception {
|
||||||
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
||||||
final String accessToken = this.getAccessToken(ownerViewerImage.owner());
|
final String accessToken = this.getAccessToken(ownerViewerImage.owner().getUsername());
|
||||||
|
|
||||||
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
||||||
body.setViewersToRemove(Set.of(ownerViewerImage.viewer().getUsername()));
|
body.setViewersToRemove(Set.of(ownerViewerImage.viewer().getUsername()));
|
||||||
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
post("/images/imageOwner/HAL9000.svg")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -347,13 +354,14 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void clearAllViewers() throws Exception {
|
public void clearAllViewers() throws Exception {
|
||||||
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
||||||
final String accessToken = this.getAccessToken(ownerViewerImage.owner());
|
final String accessToken = this.getAccessToken(ownerViewerImage.owner().getUsername());
|
||||||
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
||||||
body.setClearAllViewers(true);
|
body.setClearAllViewers(true);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
post("/images/imageOwner/HAL9000.svg")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
@ -364,13 +372,14 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateInfoByViewerForbidden() throws Exception {
|
public void updateInfoByViewerForbidden() throws Exception {
|
||||||
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
||||||
final String accessToken = this.getAccessToken(ownerViewerImage.viewer()); // viewer
|
final String accessToken = this.getAccessToken(ownerViewerImage.viewer().getUsername()); // viewer
|
||||||
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
final ImageUpdateInfoBody body = new ImageUpdateInfoBody();
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
post("/images/imageOwner/HAL9000.svg")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON )
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
)
|
)
|
||||||
@ -380,12 +389,13 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void deleteImageWithOwner() throws Exception {
|
public void deleteImageWithOwner() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final Image image = this.seedImage(owner);
|
final Image image = this.createHal9000(owner);
|
||||||
final String accessToken = this.getAccessToken(owner);
|
final String accessToken = this.getAccessToken(owner.getUsername());
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
delete(getImageUrl(owner, image))
|
delete("/images/imageOwner/HAL9000.svg")
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
)
|
)
|
||||||
.andExpect(status().isNoContent());
|
.andExpect(status().isNoContent());
|
||||||
@ -393,11 +403,12 @@ public class ImageControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void deleteImageByViewerForbidden() throws Exception {
|
public void deleteImageByViewerForbidden() throws Exception {
|
||||||
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
final OwnerViewerImage ownerViewerImage = this.prepOwnerViewerImage();
|
||||||
final String accessToken = this.getAccessToken(ownerViewerImage.viewer());
|
final String accessToken = this.getAccessToken(ownerViewerImage.viewer().getUsername());
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
delete(getImageUrl(ownerViewerImage.owner(), ownerViewerImage.image()))
|
delete("/images/imageOwner/HAL9000.svg")
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + accessToken)
|
||||||
)
|
)
|
||||||
.andExpect(status().isForbidden());
|
.andExpect(status().isForbidden());
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
package app.mealsmadeeasy.api.image;
|
package app.mealsmadeeasy.api.image;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
||||||
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec;
|
import app.mealsmadeeasy.api.image.spec.ImageCreateInfoSpec;
|
||||||
import app.mealsmadeeasy.api.image.spec.ImageUpdateInfoSpec;
|
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 org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
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.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
import org.springframework.test.context.DynamicPropertySource;
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
import org.testcontainers.containers.MinIOContainer;
|
import org.testcontainers.containers.MinIOContainer;
|
||||||
@ -21,7 +20,6 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static app.mealsmadeeasy.api.image.ContainsImagesMatcher.containsImages;
|
import static app.mealsmadeeasy.api.image.ContainsImagesMatcher.containsImages;
|
||||||
import static app.mealsmadeeasy.api.user.ContainsUsersMatcher.containsUsers;
|
import static app.mealsmadeeasy.api.user.ContainsUsersMatcher.containsUsers;
|
||||||
@ -32,13 +30,13 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
||||||
|
|
||||||
@Testcontainers
|
@Testcontainers
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class S3ImageServiceTests {
|
public class S3ImageServiceTests {
|
||||||
|
|
||||||
|
private static final String USER_FILENAME = "HAL9000.svg";
|
||||||
|
|
||||||
@Container
|
@Container
|
||||||
private static final MinIOContainer container = new MinIOContainer(
|
private static final MinIOContainer container = new MinIOContainer(
|
||||||
DockerImageName.parse("minio/minio:latest")
|
DockerImageName.parse("minio/minio:latest")
|
||||||
@ -51,7 +49,7 @@ public class S3ImageServiceTests {
|
|||||||
registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword);
|
registry.add("app.mealsmadeeasy.api.minio.secretKey", container::getPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InputStream getHal9000InputStream() {
|
private static InputStream getHal9000() {
|
||||||
return S3ImageServiceTests.class.getResourceAsStream("HAL9000.svg");
|
return S3ImageServiceTests.class.getResourceAsStream("HAL9000.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,40 +59,26 @@ public class S3ImageServiceTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ImageService imageService;
|
private ImageService imageService;
|
||||||
|
|
||||||
private static final String TEST_PASSWORD = "test";
|
private User createTestUser(String username) {
|
||||||
private static final long HAL_LENGTH = 27881L;
|
|
||||||
|
|
||||||
private User seedUser() {
|
|
||||||
final String uuid = UUID.randomUUID().toString();
|
|
||||||
try {
|
try {
|
||||||
return this.userService.createUser(uuid, uuid + "@test.com", TEST_PASSWORD);
|
return this.userService.createUser(username, username + "@test.com", "test");
|
||||||
} catch (UserCreateException e) {
|
} catch (UserCreateException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String makeUserFilename() {
|
private Image createHal9000(User owner) throws ImageException, IOException {
|
||||||
return UUID.randomUUID() + ".svg";
|
try (final InputStream hal9000 = getHal9000()) {
|
||||||
}
|
|
||||||
|
|
||||||
private Image seedImage(User owner, ImageCreateInfoSpec spec) {
|
|
||||||
try (final InputStream hal9000 = getHal9000InputStream()) {
|
|
||||||
return this.imageService.create(
|
return this.imageService.create(
|
||||||
owner,
|
owner,
|
||||||
makeUserFilename(),
|
USER_FILENAME,
|
||||||
hal9000,
|
hal9000,
|
||||||
HAL_LENGTH,
|
27881L,
|
||||||
spec
|
new ImageCreateInfoSpec()
|
||||||
);
|
);
|
||||||
} catch (ImageException | IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Image seedImage(User owner) {
|
|
||||||
return this.seedImage(owner, new ImageCreateInfoSpec());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Image makePublic(Image image, User modifier) {
|
private Image makePublic(Image image, User modifier) {
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
spec.setPublic(true);
|
spec.setPublic(true);
|
||||||
@ -105,13 +89,14 @@ public class S3ImageServiceTests {
|
|||||||
public void smokeScreen() {}
|
public void smokeScreen() {}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void simpleCreate() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void simpleCreate() throws ImageException, IOException {
|
||||||
final Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
final Image image = this.createHal9000(owner);
|
||||||
assertThat(image.getOwner(), isUser(owner));
|
assertThat(image.getOwner(), isUser(owner));
|
||||||
assertThat(image.getCreated(), is(notNullValue()));
|
assertThat(image.getCreated(), is(notNullValue()));
|
||||||
assertThat(image.getModified(), is(nullValue()));
|
assertThat(image.getModified(), is(nullValue()));
|
||||||
assertThat(image.getUserFilename(), is(notNullValue()));
|
assertThat(image.getUserFilename(), is("HAL9000.svg"));
|
||||||
assertThat(image.getMimeType(), is("image/svg+xml"));
|
assertThat(image.getMimeType(), is("image/svg+xml"));
|
||||||
assertThat(image.getAlt(), is(nullValue()));
|
assertThat(image.getAlt(), is(nullValue()));
|
||||||
assertThat(image.getCaption(), is(nullValue()));
|
assertThat(image.getCaption(), is(nullValue()));
|
||||||
@ -120,55 +105,54 @@ public class S3ImageServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void properlyLoadsContent() throws IOException {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void loadImageWithOwnerAsViewer() throws ImageException, IOException {
|
||||||
final Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner));
|
final Image image = this.createHal9000(owner);
|
||||||
//noinspection DataFlowIssue
|
try (final InputStream stored =
|
||||||
final byte[] contentBytes = content.readAllBytes();
|
this.imageService.getImageContent(image, owner)) {
|
||||||
assertThat(contentBytes.length, is((int) HAL_LENGTH));
|
final byte[] storedBytes = stored.readAllBytes();
|
||||||
content.close();
|
assertThat(storedBytes.length, is(27881));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadImageWithOwnerAsViewer() throws IOException {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void loadPublicImage() throws ImageException, IOException {
|
||||||
final Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(image, owner));
|
Image image = this.createHal9000(owner);
|
||||||
//noinspection DataFlowIssue
|
image = this.makePublic(image, owner);
|
||||||
content.close();
|
try (final InputStream stored =
|
||||||
|
this.imageService.getImageContent(image, null)) {
|
||||||
|
final byte[] storedBytes = stored.readAllBytes();
|
||||||
|
assertThat(storedBytes.length, is(27881));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadPublicImage() throws IOException {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void loadImageWithViewer() throws ImageException, IOException {
|
||||||
final Image seedImage = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final Image publicImage = this.makePublic(seedImage, owner);
|
final User viewer = this.createTestUser("imageViewer");
|
||||||
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(publicImage, null));
|
Image image = this.createHal9000(owner);
|
||||||
//noinspection DataFlowIssue
|
|
||||||
content.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void loadImageWithViewer() throws IOException {
|
|
||||||
final User owner = this.seedUser();
|
|
||||||
final User viewer = this.seedUser();
|
|
||||||
Image seedImage = this.seedImage(owner);
|
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
spec.setViewersToAdd(Set.of(viewer));
|
spec.setViewersToAdd(Set.of(viewer));
|
||||||
final Image imageWithViewer = this.imageService.update(seedImage, owner, spec);
|
image = this.imageService.update(image, owner, spec);
|
||||||
final InputStream content = assertDoesNotThrow(() -> this.imageService.getImageContent(imageWithViewer, viewer));
|
try (final InputStream stored =
|
||||||
//noinspection DataFlowIssue
|
this.imageService.getImageContent(image, viewer)) {
|
||||||
content.close();
|
final byte[] storedBytes = stored.readAllBytes();
|
||||||
|
assertThat(storedBytes.length, is(27881));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getImagesOwnedBy() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void getImagesOwnedBy() throws ImageException, IOException {
|
||||||
final User otherOwner = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final Image image0 = this.seedImage(owner);
|
final User otherOwner = this.createTestUser("otherImageOwner");
|
||||||
final Image image1 = this.seedImage(owner);
|
final Image image0 = this.createHal9000(owner);
|
||||||
final Image image2 = this.seedImage(otherOwner);
|
final Image image1 = this.createHal9000(owner);
|
||||||
|
final Image image2 = this.createHal9000(otherOwner);
|
||||||
|
|
||||||
final List<Image> ownedImages = this.imageService.getImagesOwnedBy(owner);
|
final List<Image> ownedImages = this.imageService.getImagesOwnedBy(owner);
|
||||||
assertThat(ownedImages.size(), is(2));
|
assertThat(ownedImages.size(), is(2));
|
||||||
@ -180,9 +164,10 @@ public class S3ImageServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateAlt() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void updateAlt() throws Exception {
|
||||||
Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
Image image = this.createHal9000(owner);
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
spec.setAlt("HAL 9000");
|
spec.setAlt("HAL 9000");
|
||||||
image = this.imageService.update(image, owner, spec);
|
image = this.imageService.update(image, owner, spec);
|
||||||
@ -190,9 +175,10 @@ public class S3ImageServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateCaption() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void updateCaption() throws Exception {
|
||||||
Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
Image image = this.createHal9000(owner);
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
spec.setCaption("HAL 9000 from 2001: A Space Odyssey");
|
spec.setCaption("HAL 9000 from 2001: A Space Odyssey");
|
||||||
image = this.imageService.update(image, owner, spec);
|
image = this.imageService.update(image, owner, spec);
|
||||||
@ -200,9 +186,10 @@ public class S3ImageServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateIsPublic() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void updateIsPublic() throws Exception {
|
||||||
Image image = this.seedImage(owner);
|
final User owner = this.createTestUser("imageOwner");
|
||||||
|
Image image = this.createHal9000(owner);
|
||||||
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
final ImageUpdateInfoSpec spec = new ImageUpdateInfoSpec();
|
||||||
spec.setPublic(true);
|
spec.setPublic(true);
|
||||||
image = this.imageService.update(image, owner, spec);
|
image = this.imageService.update(image, owner, spec);
|
||||||
@ -216,19 +203,21 @@ public class S3ImageServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void addViewers() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void addViewers() throws Exception {
|
||||||
final User viewer = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
Image image = this.seedImage(owner);
|
final User viewer = this.createTestUser("imageViewer");
|
||||||
|
Image image = this.createHal9000(owner);
|
||||||
image = this.addViewer(image, owner, viewer);
|
image = this.addViewer(image, owner, viewer);
|
||||||
assertThat(image.getViewers(), containsUsers(viewer));
|
assertThat(image.getViewers(), containsUsers(viewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void removeViewers() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void removeViewers() throws Exception {
|
||||||
final User viewer = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
Image image = this.seedImage(owner);
|
final User viewer = this.createTestUser("imageViewer");
|
||||||
|
Image image = this.createHal9000(owner);
|
||||||
image = this.addViewer(image, owner, viewer);
|
image = this.addViewer(image, owner, viewer);
|
||||||
assertThat(image.getViewers(), containsUsers(viewer));
|
assertThat(image.getViewers(), containsUsers(viewer));
|
||||||
|
|
||||||
@ -239,10 +228,11 @@ public class S3ImageServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void clearAllViewers() {
|
@DirtiesContext
|
||||||
final User owner = this.seedUser();
|
public void clearAllViewers() throws Exception {
|
||||||
final User viewer = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
Image image = this.seedImage(owner);
|
final User viewer = this.createTestUser("imageViewer");
|
||||||
|
Image image = this.createHal9000(owner);
|
||||||
image = this.addViewer(image, owner, viewer);
|
image = this.addViewer(image, owner, viewer);
|
||||||
assertThat(image.getViewers(), containsUsers(viewer));
|
assertThat(image.getViewers(), containsUsers(viewer));
|
||||||
|
|
||||||
@ -253,9 +243,10 @@ public class S3ImageServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void deleteImage() throws Exception {
|
public void deleteImage() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("imageOwner");
|
||||||
final Image image = this.seedImage(owner);
|
final Image image = this.createHal9000(owner);
|
||||||
this.imageService.deleteImage(image, owner);
|
this.imageService.deleteImage(image, owner);
|
||||||
assertThrows(ImageException.class, () -> this.imageService.getById(image.getId(), owner));
|
assertThrows(ImageException.class, () -> this.imageService.getById(image.getId(), owner));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
||||||
import app.mealsmadeeasy.api.auth.AuthService;
|
import app.mealsmadeeasy.api.auth.AuthService;
|
||||||
import app.mealsmadeeasy.api.auth.LoginDetails;
|
import app.mealsmadeeasy.api.auth.LoginDetails;
|
||||||
import app.mealsmadeeasy.api.auth.LoginException;
|
import app.mealsmadeeasy.api.auth.LoginException;
|
||||||
@ -14,14 +13,12 @@ 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.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.annotation.DirtiesContext;
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
import org.springframework.test.context.DynamicPropertySource;
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
@ -29,11 +26,12 @@ 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 static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
@ -41,7 +39,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@Testcontainers
|
@Testcontainers
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class RecipeControllerTests {
|
public class RecipeControllerTests {
|
||||||
|
|
||||||
@Container
|
@Container
|
||||||
@ -81,20 +78,17 @@ public class RecipeControllerTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
private static final String TEST_PASSWORD = "test";
|
private User createTestUser(String username) {
|
||||||
|
|
||||||
private User seedUser() {
|
|
||||||
final String uuid = UUID.randomUUID().toString();
|
|
||||||
try {
|
try {
|
||||||
return this.userService.createUser(uuid, uuid + "@test.com", TEST_PASSWORD);
|
return this.userService.createUser(username, username + "@test.com", "test");
|
||||||
} catch (UserCreateException e) {
|
} catch (UserCreateException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipe createTestRecipe(User owner, boolean isPublic) {
|
private Recipe createTestRecipe(User owner, boolean isPublic, String slug) {
|
||||||
final RecipeCreateSpec spec = new RecipeCreateSpec();
|
final RecipeCreateSpec spec = new RecipeCreateSpec();
|
||||||
spec.setSlug(UUID.randomUUID().toString());
|
spec.setSlug(slug);
|
||||||
spec.setTitle("Test Recipe");
|
spec.setTitle("Test Recipe");
|
||||||
spec.setPreparationTime(10);
|
spec.setPreparationTime(10);
|
||||||
spec.setCookingTime(20);
|
spec.setCookingTime(20);
|
||||||
@ -104,8 +98,12 @@ public class RecipeControllerTests {
|
|||||||
return this.recipeService.create(owner, spec);
|
return this.recipeService.create(owner, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Recipe createTestRecipe(User owner, boolean isPublic) {
|
||||||
|
return this.createTestRecipe(owner, isPublic, "test-recipe");
|
||||||
|
}
|
||||||
|
|
||||||
private String getAccessToken(User user) throws LoginException {
|
private String getAccessToken(User user) throws LoginException {
|
||||||
return this.authService.login(user.getUsername(), TEST_PASSWORD)
|
return this.authService.login(user.getUsername(), "test")
|
||||||
.getAccessToken()
|
.getAccessToken()
|
||||||
.getToken();
|
.getToken();
|
||||||
}
|
}
|
||||||
@ -114,7 +112,7 @@ public class RecipeControllerTests {
|
|||||||
try (final InputStream hal9000 = getHal9000()) {
|
try (final InputStream hal9000 = getHal9000()) {
|
||||||
return this.imageService.create(
|
return this.imageService.create(
|
||||||
owner,
|
owner,
|
||||||
UUID.randomUUID() + ".svg",
|
"HAL9000.svg",
|
||||||
hal9000,
|
hal9000,
|
||||||
27881L,
|
27881L,
|
||||||
new ImageCreateInfoSpec()
|
new ImageCreateInfoSpec()
|
||||||
@ -125,14 +123,15 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getRecipePageViewByIdPublicRecipeNoPrincipal() throws Exception {
|
public void getRecipePageViewByIdPublicRecipeNoPrincipal() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())
|
get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())
|
||||||
)
|
)
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.recipe.id").value(recipe.getId()))
|
.andExpect(jsonPath("$.recipe.id").value(1))
|
||||||
.andExpect(jsonPath("$.recipe.created").exists()) // TODO: better matching of exact LocalDateTime
|
.andExpect(jsonPath("$.recipe.created").exists()) // TODO: better matching of exact LocalDateTime
|
||||||
.andExpect(jsonPath("$.recipe.modified").doesNotExist())
|
.andExpect(jsonPath("$.recipe.modified").doesNotExist())
|
||||||
.andExpect(jsonPath("$.recipe.slug").value(recipe.getSlug()))
|
.andExpect(jsonPath("$.recipe.slug").value(recipe.getSlug()))
|
||||||
@ -153,8 +152,9 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getFullRecipeViewIncludeRawText() throws Exception {
|
public void getFullRecipeViewIncludeRawText() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
get(
|
get(
|
||||||
@ -168,10 +168,11 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getFullRecipeViewPrincipalIsStarer() throws Exception {
|
public void getFullRecipeViewPrincipalIsStarer() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, false);
|
final Recipe recipe = this.createTestRecipe(owner, false);
|
||||||
this.recipeStarService.create(recipe.getId(), owner.getId());
|
this.recipeStarService.create(recipe.getId(), owner.getUsername());
|
||||||
final String accessToken = this.getAccessToken(owner);
|
final String accessToken = this.getAccessToken(owner);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())
|
get("/recipes/{username}/{slug}", recipe.getOwner().getUsername(), recipe.getSlug())
|
||||||
@ -182,8 +183,9 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getFullRecipeViewPrincipalIsNotStarer() throws Exception {
|
public void getFullRecipeViewPrincipalIsNotStarer() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, false);
|
final Recipe recipe = this.createTestRecipe(owner, false);
|
||||||
final String accessToken = this.getAccessToken(owner);
|
final String accessToken = this.getAccessToken(owner);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
@ -195,23 +197,38 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getRecipeInfoViewsNoPrincipal() throws Exception {
|
public void getRecipeInfoViewsNoPrincipal() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
this.mockMvc.perform(get("/recipes"))
|
this.mockMvc.perform(get("/recipes"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.slice.number").value(0))
|
.andExpect(jsonPath("$.slice.number").value(0))
|
||||||
.andExpect(jsonPath("$.slice.size").value(20))
|
.andExpect(jsonPath("$.slice.size").value(20))
|
||||||
.andExpect(jsonPath("$.content").isArray())
|
.andExpect(jsonPath("$.content").isArray())
|
||||||
.andExpect(jsonPath("$.content[*].id").value(hasItem(recipe.getId())));
|
.andExpect(jsonPath("$.content", hasSize(1)))
|
||||||
|
.andExpect(jsonPath("$.content[0].id").value(recipe.getId()))
|
||||||
|
.andExpect(jsonPath("$.content[0].created").exists()) // TODO: better matching of exact LocalDateTime
|
||||||
|
.andExpect(jsonPath("$.content[0].modified").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.content[0].slug").value(recipe.getSlug()))
|
||||||
|
.andExpect(jsonPath("$.content[0].title").value(recipe.getTitle()))
|
||||||
|
.andExpect(jsonPath("$.content[0].preparationTime").value(recipe.getPreparationTime()))
|
||||||
|
.andExpect(jsonPath("$.content[0].cookingTime").value(recipe.getCookingTime()))
|
||||||
|
.andExpect(jsonPath("$.content[0].totalTime").value(recipe.getTotalTime()))
|
||||||
|
.andExpect(jsonPath("$.content[0].owner.id").value(owner.getId()))
|
||||||
|
.andExpect(jsonPath("$.content[0].owner.username").value(owner.getUsername()))
|
||||||
|
.andExpect(jsonPath("$.content[0].isPublic").value(true))
|
||||||
|
.andExpect(jsonPath("$.content[0].starCount").value(0))
|
||||||
|
.andExpect(jsonPath("$.content[0].mainImage").value(nullValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getRecipeInfoViewsWithPrincipalIncludesPrivate() throws Exception {
|
public void getRecipeInfoViewsWithPrincipalIncludesPrivate() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
final Recipe r0 = this.createTestRecipe(owner, true);
|
final Recipe r0 = this.createTestRecipe(owner, true, "r0");
|
||||||
final Recipe r1 = this.createTestRecipe(owner, true);
|
final Recipe r1 = this.createTestRecipe(owner, true, "r1");
|
||||||
final Recipe r2 = this.createTestRecipe(owner, false);
|
final Recipe r2 = this.createTestRecipe(owner, false, "r2");
|
||||||
final LoginDetails loginDetails = this.authService.login(owner.getUsername(), "test");
|
final LoginDetails loginDetails = this.authService.login(owner.getUsername(), "test");
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
get("/recipes")
|
get("/recipes")
|
||||||
@ -221,10 +238,10 @@ public class RecipeControllerTests {
|
|||||||
.andExpect(jsonPath("$.slice.number").value(0))
|
.andExpect(jsonPath("$.slice.number").value(0))
|
||||||
.andExpect(jsonPath("$.slice.size").value(20))
|
.andExpect(jsonPath("$.slice.size").value(20))
|
||||||
.andExpect(jsonPath("$.content").isArray())
|
.andExpect(jsonPath("$.content").isArray())
|
||||||
.andExpect(jsonPath("$.content[*].id").value(hasItems(r0.getId(), r1.getId(), r2.getId())));
|
.andExpect(jsonPath("$.content", hasSize(3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@ -236,8 +253,9 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateRecipe() throws Exception {
|
public void updateRecipe() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, false);
|
final Recipe recipe = this.createTestRecipe(owner, false);
|
||||||
final String accessToken = this.getAccessToken(owner);
|
final String accessToken = this.getAccessToken(owner);
|
||||||
final String body = this.getUpdateBody();
|
final String body = this.getUpdateBody();
|
||||||
@ -266,8 +284,9 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateRecipeReturnsViewWithMainImage() throws Exception {
|
public void updateRecipeReturnsViewWithMainImage() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("owner");
|
||||||
|
|
||||||
final Image hal9000 = this.createHal9000(owner);
|
final Image hal9000 = this.createHal9000(owner);
|
||||||
|
|
||||||
@ -300,24 +319,26 @@ public class RecipeControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void addStarToRecipe() throws Exception {
|
public void addStarToRecipe() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipe-owner");
|
||||||
final User starer = this.seedUser();
|
final User starer = this.createTestUser("recipe-starer");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
post("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
||||||
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
||||||
)
|
)
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andExpect(jsonPath("$.timestamp").exists());
|
.andExpect(jsonPath("$.date").exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getStarForRecipe() throws Exception {
|
public void getStarForRecipe() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipe-owner");
|
||||||
final User starer = this.seedUser();
|
final User starer = this.createTestUser("recipe-starer");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
this.recipeStarService.create(recipe.getId(), starer.getId());
|
this.recipeStarService.create(recipe.getId(), starer.getUsername());
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
get("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
get("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
||||||
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
||||||
@ -325,15 +346,16 @@ public class RecipeControllerTests {
|
|||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.isStarred").value(true))
|
.andExpect(jsonPath("$.isStarred").value(true))
|
||||||
.andExpect(jsonPath("$.star").isMap())
|
.andExpect(jsonPath("$.star").isMap())
|
||||||
.andExpect(jsonPath("$.star.timestamp").exists());
|
.andExpect(jsonPath("$.star.date").exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void deleteStarFromRecipe() throws Exception {
|
public void deleteStarFromRecipe() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipe-owner");
|
||||||
final User starer = this.seedUser();
|
final User starer = this.createTestUser("recipe-starer");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
this.recipeStarService.create(recipe.getId(), starer.getId());
|
this.recipeStarService.create(recipe.getId(), starer.getUsername());
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
delete("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
delete("/recipes/{username}/{slug}/star", recipe.getOwner().getUsername(), recipe.getSlug())
|
||||||
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
.header("Authorization", "Bearer " + this.getAccessToken(starer))
|
||||||
|
|||||||
@ -1,23 +1,20 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
||||||
import app.mealsmadeeasy.api.user.UserEntity;
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
import app.mealsmadeeasy.api.user.UserRepository;
|
import app.mealsmadeeasy.api.user.UserRepository;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
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.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class RecipeRepositoryTests {
|
public class RecipeRepositoryTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -26,55 +23,65 @@ public class RecipeRepositoryTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
private UserEntity seedUser() {
|
private UserEntity getOwnerUser() {
|
||||||
final String uuid = UUID.randomUUID().toString();
|
final UserEntity recipeUser = UserEntity.getDefaultDraft();
|
||||||
final UserEntity draft = UserEntity.getDefaultDraft();
|
recipeUser.setUsername("recipeUser");
|
||||||
draft.setUsername(uuid);
|
recipeUser.setEmail("recipe@user.com");
|
||||||
draft.setEmail(uuid + "@test.com");
|
recipeUser.setPassword("test");
|
||||||
draft.setPassword("test");
|
return this.userRepository.save(recipeUser);
|
||||||
return this.userRepository.save(draft);
|
}
|
||||||
|
|
||||||
|
private UserEntity getViewerUser() {
|
||||||
|
final UserEntity viewerUser = UserEntity.getDefaultDraft();
|
||||||
|
viewerUser.setUsername("recipeViewerUser");
|
||||||
|
viewerUser.setEmail("recipe-viewer@user.com");
|
||||||
|
viewerUser.setPassword("test");
|
||||||
|
return this.userRepository.save(viewerUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void findsAllPublicRecipes() {
|
public void findsAllPublicRecipes() {
|
||||||
final RecipeEntity publicRecipe = new RecipeEntity();
|
final RecipeEntity publicRecipe = new RecipeEntity();
|
||||||
publicRecipe.setCreated(OffsetDateTime.now());
|
publicRecipe.setCreated(LocalDateTime.now());
|
||||||
publicRecipe.setSlug(UUID.randomUUID().toString());
|
publicRecipe.setSlug("public-recipe");
|
||||||
publicRecipe.setPublic(true);
|
publicRecipe.setPublic(true);
|
||||||
publicRecipe.setOwner(this.seedUser());
|
publicRecipe.setOwner(this.getOwnerUser());
|
||||||
publicRecipe.setTitle("Public Recipe");
|
publicRecipe.setTitle("Public Recipe");
|
||||||
publicRecipe.setRawText("Hello, World!");
|
publicRecipe.setRawText("Hello, World!");
|
||||||
final RecipeEntity savedRecipe = this.recipeRepository.save(publicRecipe);
|
this.recipeRepository.save(publicRecipe);
|
||||||
|
|
||||||
final List<RecipeEntity> publicRecipes = this.recipeRepository.findAllByIsPublicIsTrue();
|
final List<RecipeEntity> publicRecipes = this.recipeRepository.findAllByIsPublicIsTrue();
|
||||||
assertThat(publicRecipes).anyMatch(recipeEntity -> recipeEntity.getId().equals(savedRecipe.getId()));
|
assertThat(publicRecipes.size()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void doesNotFindNonPublicRecipe() {
|
public void doesNotFindNonPublicRecipe() {
|
||||||
final RecipeEntity nonPublicRecipe = new RecipeEntity();
|
final RecipeEntity nonPublicRecipe = new RecipeEntity();
|
||||||
nonPublicRecipe.setCreated(OffsetDateTime.now());
|
nonPublicRecipe.setCreated(LocalDateTime.now());
|
||||||
nonPublicRecipe.setSlug(UUID.randomUUID().toString());
|
nonPublicRecipe.setSlug("non-public-recipe");
|
||||||
nonPublicRecipe.setOwner(this.seedUser());
|
nonPublicRecipe.setOwner(this.getOwnerUser());
|
||||||
nonPublicRecipe.setTitle("Non-Public Recipe");
|
nonPublicRecipe.setTitle("Non-Public Recipe");
|
||||||
nonPublicRecipe.setRawText("Hello, World!");
|
nonPublicRecipe.setRawText("Hello, World!");
|
||||||
final RecipeEntity savedRecipe = this.recipeRepository.save(nonPublicRecipe);
|
this.recipeRepository.save(nonPublicRecipe);
|
||||||
|
|
||||||
final List<RecipeEntity> publicRecipes = this.recipeRepository.findAllByIsPublicIsTrue();
|
final List<RecipeEntity> publicRecipes = this.recipeRepository.findAllByIsPublicIsTrue();
|
||||||
assertThat(publicRecipes).noneMatch(recipeEntity -> recipeEntity.getId().equals(savedRecipe.getId()));
|
assertThat(publicRecipes.size()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void findsAllForViewer() {
|
public void findsAllForViewer() {
|
||||||
final RecipeEntity recipe = new RecipeEntity();
|
final RecipeEntity recipe = new RecipeEntity();
|
||||||
recipe.setCreated(OffsetDateTime.now());
|
recipe.setCreated(LocalDateTime.now());
|
||||||
recipe.setSlug(UUID.randomUUID().toString());
|
recipe.setSlug("test-recipe");
|
||||||
recipe.setOwner(this.seedUser());
|
recipe.setOwner(this.getOwnerUser());
|
||||||
recipe.setTitle("Test Recipe");
|
recipe.setTitle("Test Recipe");
|
||||||
recipe.setRawText("Hello, World!");
|
recipe.setRawText("Hello, World!");
|
||||||
final RecipeEntity saved = this.recipeRepository.save(recipe);
|
final RecipeEntity saved = this.recipeRepository.save(recipe);
|
||||||
|
|
||||||
final UserEntity viewer = this.seedUser();
|
final UserEntity viewer = this.getViewerUser();
|
||||||
final Set<UserEntity> viewers = new HashSet<>(recipe.getViewerEntities());
|
final Set<UserEntity> viewers = new HashSet<>(recipe.getViewerEntities());
|
||||||
viewers.add(viewer);
|
viewers.add(viewer);
|
||||||
saved.setViewers(viewers);
|
saved.setViewers(viewers);
|
||||||
@ -86,16 +93,17 @@ public class RecipeRepositoryTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void doesNotIncludeNonViewable() {
|
public void doesNotIncludeNonViewable() {
|
||||||
final RecipeEntity recipe = new RecipeEntity();
|
final RecipeEntity recipe = new RecipeEntity();
|
||||||
recipe.setCreated(OffsetDateTime.now());
|
recipe.setCreated(LocalDateTime.now());
|
||||||
recipe.setSlug(UUID.randomUUID().toString());
|
recipe.setSlug("test-recipe");
|
||||||
recipe.setOwner(this.seedUser());
|
recipe.setOwner(this.getOwnerUser());
|
||||||
recipe.setTitle("Test Recipe");
|
recipe.setTitle("Test Recipe");
|
||||||
recipe.setRawText("Hello, World!");
|
recipe.setRawText("Hello, World!");
|
||||||
this.recipeRepository.save(recipe);
|
this.recipeRepository.save(recipe);
|
||||||
|
|
||||||
final UserEntity viewer = this.seedUser();
|
final UserEntity viewer = this.getViewerUser();
|
||||||
final List<RecipeEntity> viewable = this.recipeRepository.findAllByViewersContaining(viewer);
|
final List<RecipeEntity> viewable = this.recipeRepository.findAllByViewersContaining(viewer);
|
||||||
assertThat(viewable.size()).isEqualTo(0);
|
assertThat(viewable.size()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
||||||
import app.mealsmadeeasy.api.image.ImageException;
|
import app.mealsmadeeasy.api.image.ImageException;
|
||||||
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;
|
||||||
@ -12,28 +11,25 @@ import app.mealsmadeeasy.api.user.UserEntity;
|
|||||||
import app.mealsmadeeasy.api.user.UserRepository;
|
import app.mealsmadeeasy.api.user.UserRepository;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
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.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
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;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static app.mealsmadeeasy.api.recipe.ContainsRecipeInfoViewsForRecipesMatcher.containsRecipeInfoViewsForRecipes;
|
import static app.mealsmadeeasy.api.recipe.ContainsRecipeInfoViewsForRecipesMatcher.containsRecipeInfoViewsForRecipes;
|
||||||
import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes;
|
import static app.mealsmadeeasy.api.recipe.ContainsRecipesMatcher.containsRecipes;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
// TODO: test mainImage included
|
// TODO: test mainImage included
|
||||||
// TODO: test prep/cooking/total times included
|
// TODO: test prep/cooking/total times included
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class RecipeServiceTests {
|
public class RecipeServiceTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -45,22 +41,25 @@ public class RecipeServiceTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
private UserEntity seedUser() {
|
private UserEntity createTestUser(String username) {
|
||||||
final String uuid = UUID.randomUUID().toString();
|
|
||||||
final UserEntity draft = UserEntity.getDefaultDraft();
|
final UserEntity draft = UserEntity.getDefaultDraft();
|
||||||
draft.setUsername(uuid);
|
draft.setUsername(username);
|
||||||
draft.setEmail(uuid + "@test.com");
|
draft.setEmail(username + "@test.com");
|
||||||
draft.setPassword("test");
|
draft.setPassword("test");
|
||||||
return this.userRepository.save(draft);
|
return this.userRepository.save(draft);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipe createTestRecipe(@Nullable User owner) {
|
private Recipe createTestRecipe(@Nullable User owner) {
|
||||||
return this.createTestRecipe(owner, false);
|
return this.createTestRecipe(owner, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipe createTestRecipe(@Nullable User owner, boolean isPublic) {
|
private Recipe createTestRecipe(@Nullable User owner, boolean isPublic) {
|
||||||
|
return this.createTestRecipe(owner, isPublic, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Recipe createTestRecipe(@Nullable User owner, boolean isPublic, @Nullable String slug) {
|
||||||
final RecipeCreateSpec spec = new RecipeCreateSpec();
|
final RecipeCreateSpec spec = new RecipeCreateSpec();
|
||||||
spec.setSlug(UUID.randomUUID().toString());
|
spec.setSlug(slug != null ? slug : "my-recipe");
|
||||||
spec.setTitle("My Recipe");
|
spec.setTitle("My Recipe");
|
||||||
spec.setRawText("Hello!");
|
spec.setRawText("Hello!");
|
||||||
spec.setPublic(isPublic);
|
spec.setPublic(isPublic);
|
||||||
@ -71,8 +70,9 @@ public class RecipeServiceTests {
|
|||||||
public void smokeScreen() {}
|
public void smokeScreen() {}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void create() {
|
public void create() {
|
||||||
final User user = this.seedUser();
|
final User user = this.createTestUser("recipeOwner");
|
||||||
final Recipe recipe = this.createTestRecipe(user);
|
final Recipe recipe = this.createTestRecipe(user);
|
||||||
assertThat(recipe.getOwner().getUsername(), is(user.getUsername()));
|
assertThat(recipe.getOwner().getUsername(), is(user.getUsername()));
|
||||||
assertThat(recipe.getTitle(), is("My Recipe"));
|
assertThat(recipe.getTitle(), is("My Recipe"));
|
||||||
@ -80,20 +80,23 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void createWithoutOwnerThrowsAccessDenied() {
|
public void createWithoutOwnerThrowsAccessDenied() {
|
||||||
assertThrows(AccessDeniedException.class, () -> this.recipeService.create(null, new RecipeCreateSpec()));
|
assertThrows(AccessDeniedException.class, () -> this.recipeService.create(null, new RecipeCreateSpec()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdPublicNoViewerDoesNotThrow() {
|
public void getByIdPublicNoViewerDoesNotThrow() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), null));
|
assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdHasCorrectProperties() throws RecipeException {
|
public void getByIdHasCorrectProperties() throws RecipeException {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
final Recipe byId = this.recipeService.getById(recipe.getId(), null);
|
final Recipe byId = this.recipeService.getById(recipe.getId(), null);
|
||||||
assertThat(byId.getId(), is(recipe.getId()));
|
assertThat(byId.getId(), is(recipe.getId()));
|
||||||
@ -104,40 +107,45 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdThrowsWhenNotPublicAndNoViewer() {
|
public void getByIdThrowsWhenNotPublicAndNoViewer() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, false); // not public
|
final Recipe recipe = this.createTestRecipe(owner, false); // not public
|
||||||
assertThrows(AccessDeniedException.class, () -> this.recipeService.getById(recipe.getId(), null));
|
assertThrows(AccessDeniedException.class, () -> this.recipeService.getById(recipe.getId(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdThrowsWhenNotViewer() {
|
public void getByIdThrowsWhenNotViewer() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User notViewer = this.seedUser();
|
final User notViewer = this.createTestUser("notViewer");
|
||||||
final Recipe recipe = this.createTestRecipe(owner);
|
final Recipe recipe = this.createTestRecipe(owner);
|
||||||
assertThrows(AccessDeniedException.class, () -> this.recipeService.getById(recipe.getId(), notViewer));
|
assertThrows(AccessDeniedException.class, () -> this.recipeService.getById(recipe.getId(), notViewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdOkayWhenPublicAndNoViewer() {
|
public void getByIdOkayWhenPublicAndNoViewer() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), null));
|
assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdOkayWhenPublicRecipeWithViewer() {
|
public void getByIdOkayWhenPublicRecipeWithViewer() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User viewer = this.seedUser();
|
final User viewer = this.createTestUser("viewer");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), viewer));
|
assertDoesNotThrow(() -> this.recipeService.getById(recipe.getId(), viewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdOkayWithStarsPublicAndNoViewer() {
|
public void getByIdOkayWithStarsPublicAndNoViewer() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner, true);
|
final Recipe recipe = this.createTestRecipe(owner, true);
|
||||||
final RecipeStar star = this.recipeStarService.create(recipe.getId(), owner.getId());
|
final RecipeStar star = this.recipeStarService.create(recipe.getId(), owner.getUsername());
|
||||||
final Recipe byIdWithStars = assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(
|
final Recipe byIdWithStars = assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(
|
||||||
recipe.getId(), null
|
recipe.getId(), null
|
||||||
));
|
));
|
||||||
@ -145,17 +153,19 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdOkayWithStarsThrowsWhenNotViewer() {
|
public void getByIdOkayWithStarsThrowsWhenNotViewer() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User notViewer = this.seedUser();
|
final User notViewer = this.createTestUser("notViewer");
|
||||||
final Recipe recipe = this.createTestRecipe(owner);
|
final Recipe recipe = this.createTestRecipe(owner);
|
||||||
assertThrows(AccessDeniedException.class, () -> this.recipeService.getByIdWithStars(recipe.getId(), notViewer));
|
assertThrows(AccessDeniedException.class, () -> this.recipeService.getByIdWithStars(recipe.getId(), notViewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() throws RecipeException, ImageException {
|
public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() throws RecipeException, ImageException {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User viewer = this.seedUser();
|
final User viewer = this.createTestUser("viewer");
|
||||||
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
||||||
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(notYetPublicRecipe);
|
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(notYetPublicRecipe);
|
||||||
updateSpec.setIsPublic(true);
|
updateSpec.setIsPublic(true);
|
||||||
@ -169,39 +179,45 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByMinimumStarsAllPublic() {
|
public void getByMinimumStarsAllPublic() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User u0 = this.seedUser();
|
final User u0 = this.createTestUser("u0");
|
||||||
final User u1 = this.seedUser();
|
final User u1 = this.createTestUser("u1");
|
||||||
|
|
||||||
final Recipe r0 = this.createTestRecipe(owner, true);
|
final Recipe r0 = this.createTestRecipe(owner, true, "r0");
|
||||||
final Recipe r1 = this.createTestRecipe(owner, true);
|
final Recipe r1 = this.createTestRecipe(owner, true, "r1");
|
||||||
final Recipe r2 = this.createTestRecipe(owner, true);
|
final Recipe r2 = this.createTestRecipe(owner, true, "r2");
|
||||||
|
|
||||||
// r0.stars = 0, r1.stars = 1, r2.stars = 2
|
// r0.stars = 0, r1.stars = 1, r2.stars = 2
|
||||||
this.recipeStarService.create(r1.getId(), u0.getId());
|
this.recipeStarService.create(r1.getId(), u0.getUsername());
|
||||||
this.recipeStarService.create(r2.getId(), u0.getId());
|
this.recipeStarService.create(r2.getId(), u0.getUsername());
|
||||||
this.recipeStarService.create(r2.getId(), u1.getId());
|
this.recipeStarService.create(r2.getId(), u1.getUsername());
|
||||||
|
|
||||||
final List<Recipe> zeroStars = this.recipeService.getByMinimumStars(0, null);
|
final List<Recipe> zeroStars = this.recipeService.getByMinimumStars(0, null);
|
||||||
final List<Recipe> oneStar = this.recipeService.getByMinimumStars(1, null);
|
final List<Recipe> oneStar = this.recipeService.getByMinimumStars(1, null);
|
||||||
final List<Recipe> twoStars = this.recipeService.getByMinimumStars(2, null);
|
final List<Recipe> twoStars = this.recipeService.getByMinimumStars(2, null);
|
||||||
|
|
||||||
|
assertThat(zeroStars.size(), is(3));
|
||||||
|
assertThat(oneStar.size(), is(2));
|
||||||
|
assertThat(twoStars.size(), is(1));
|
||||||
|
|
||||||
assertThat(zeroStars, containsRecipes(r0, r1, r2));
|
assertThat(zeroStars, containsRecipes(r0, r1, r2));
|
||||||
assertThat(oneStar, containsRecipes(r1, r2));
|
assertThat(oneStar, containsRecipes(r1, r2));
|
||||||
assertThat(twoStars, containsRecipes(r2));
|
assertThat(twoStars, containsRecipes(r2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getByMinimumStarsOnlySomeViewable() throws RecipeException {
|
public void getByMinimumStarsOnlySomeViewable() throws RecipeException {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User u0 = this.seedUser();
|
final User u0 = this.createTestUser("u0");
|
||||||
final User u1 = this.seedUser();
|
final User u1 = this.createTestUser("u1");
|
||||||
final User viewer = this.seedUser();
|
final User viewer = this.createTestUser("recipeViewer");
|
||||||
|
|
||||||
Recipe r0 = this.createTestRecipe(owner, false); // not public
|
Recipe r0 = this.createTestRecipe(owner, false, "r0"); // not public
|
||||||
Recipe r1 = this.createTestRecipe(owner, false);
|
Recipe r1 = this.createTestRecipe(owner, false, "r1");
|
||||||
Recipe r2 = this.createTestRecipe(owner, false);
|
Recipe r2 = this.createTestRecipe(owner, false, "r2");
|
||||||
|
|
||||||
for (final User starer : List.of(u0, u1)) {
|
for (final User starer : List.of(u0, u1)) {
|
||||||
r0 = this.recipeService.addViewer(r0.getId(), owner, starer);
|
r0 = this.recipeService.addViewer(r0.getId(), owner, starer);
|
||||||
@ -210,17 +226,17 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// r0.stars = 0, r1.stars = 1, r2.stars = 2
|
// r0.stars = 0, r1.stars = 1, r2.stars = 2
|
||||||
this.recipeStarService.create(r1.getId(), u0.getId());
|
this.recipeStarService.create(r1.getId(), u0.getUsername());
|
||||||
this.recipeStarService.create(r2.getId(), u0.getId());
|
this.recipeStarService.create(r2.getId(), u0.getUsername());
|
||||||
this.recipeStarService.create(r2.getId(), u1.getId());
|
this.recipeStarService.create(r2.getId(), u1.getUsername());
|
||||||
|
|
||||||
final List<Recipe> zeroStarsNoneViewable = this.recipeService.getByMinimumStars(0, viewer);
|
final List<Recipe> zeroStarsNoneViewable = this.recipeService.getByMinimumStars(0, viewer);
|
||||||
final List<Recipe> oneStarNoneViewable = this.recipeService.getByMinimumStars(1, viewer);
|
final List<Recipe> oneStarNoneViewable = this.recipeService.getByMinimumStars(1, viewer);
|
||||||
final List<Recipe> twoStarsNoneViewable = this.recipeService.getByMinimumStars(2, viewer);
|
final List<Recipe> twoStarsNoneViewable = this.recipeService.getByMinimumStars(2, viewer);
|
||||||
|
|
||||||
assertThat(zeroStarsNoneViewable, not(containsRecipes(r0, r1, r2)));
|
assertThat(zeroStarsNoneViewable.size(), is(0));
|
||||||
assertThat(oneStarNoneViewable, not(containsRecipes(r1, r2)));
|
assertThat(oneStarNoneViewable.size(), is(0));
|
||||||
assertThat(twoStarsNoneViewable, not(containsRecipes(r2)));
|
assertThat(twoStarsNoneViewable.size(), is(0));
|
||||||
|
|
||||||
// Now make them viewable
|
// Now make them viewable
|
||||||
r0 = this.recipeService.addViewer(r0.getId(), owner, viewer);
|
r0 = this.recipeService.addViewer(r0.getId(), owner, viewer);
|
||||||
@ -231,40 +247,49 @@ public class RecipeServiceTests {
|
|||||||
final List<Recipe> oneStarViewable = this.recipeService.getByMinimumStars(1, viewer);
|
final List<Recipe> oneStarViewable = this.recipeService.getByMinimumStars(1, viewer);
|
||||||
final List<Recipe> twoStarsViewable = this.recipeService.getByMinimumStars(2, viewer);
|
final List<Recipe> twoStarsViewable = this.recipeService.getByMinimumStars(2, viewer);
|
||||||
|
|
||||||
|
assertThat(zeroStarsViewable.size(), is(3));
|
||||||
|
assertThat(oneStarViewable.size(), is(2));
|
||||||
|
assertThat(twoStarsViewable.size(), is (1));
|
||||||
|
|
||||||
assertThat(zeroStarsViewable, containsRecipes(r0, r1, r2));
|
assertThat(zeroStarsViewable, containsRecipes(r0, r1, r2));
|
||||||
assertThat(oneStarViewable, containsRecipes(r1, r2));
|
assertThat(oneStarViewable, containsRecipes(r1, r2));
|
||||||
assertThat(twoStarsViewable, containsRecipes(r2));
|
assertThat(twoStarsViewable, containsRecipes(r2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getPublicRecipes() {
|
public void getPublicRecipes() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
|
|
||||||
Recipe r0 = this.createTestRecipe(owner, true);
|
Recipe r0 = this.createTestRecipe(owner, true, "r0");
|
||||||
Recipe r1 = this.createTestRecipe(owner, true);
|
Recipe r1 = this.createTestRecipe(owner, true, "r1");
|
||||||
|
|
||||||
final List<Recipe> publicRecipes = this.recipeService.getPublicRecipes();
|
final List<Recipe> publicRecipes = this.recipeService.getPublicRecipes();
|
||||||
|
assertThat(publicRecipes.size(), is(2));
|
||||||
assertThat(publicRecipes, containsRecipes(r0, r1));
|
assertThat(publicRecipes, containsRecipes(r0, r1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getRecipeInfoViewsViewableByOwnerWhenPublicAndPrivate() {
|
public void getRecipeInfoViewsViewableByOwnerWhenPublicAndPrivate() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
Recipe r0 = this.createTestRecipe(owner, true);
|
Recipe r0 = this.createTestRecipe(owner, true, "r0");
|
||||||
Recipe r1 = this.createTestRecipe(owner, false);
|
Recipe r1 = this.createTestRecipe(owner, false, "r1");
|
||||||
|
|
||||||
final Slice<RecipeInfoView> viewableInfoViewsSlice = this.recipeService.getInfoViewsViewableBy(
|
final Slice<RecipeInfoView> viewableInfoViewsSlice = this.recipeService.getInfoViewsViewableBy(
|
||||||
Pageable.ofSize(20),
|
Pageable.ofSize(20),
|
||||||
owner
|
owner
|
||||||
);
|
);
|
||||||
final List<RecipeInfoView> viewableInfos = viewableInfoViewsSlice.getContent();
|
final List<RecipeInfoView> viewableInfos = viewableInfoViewsSlice.getContent();
|
||||||
|
assertThat(viewableInfos.size(), is(2));
|
||||||
assertThat(viewableInfos, containsRecipeInfoViewsForRecipes(r0, r1));
|
assertThat(viewableInfos, containsRecipeInfoViewsForRecipes(r0, r1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getRecipesViewableByUser() throws RecipeException {
|
public void getRecipesViewableByUser() throws RecipeException {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User viewer = this.seedUser();
|
final User viewer = this.createTestUser("recipeViewer");
|
||||||
|
|
||||||
Recipe r0 = this.createTestRecipe(owner);
|
Recipe r0 = this.createTestRecipe(owner);
|
||||||
r0 = this.recipeService.addViewer(r0.getId(), owner, viewer);
|
r0 = this.recipeService.addViewer(r0.getId(), owner, viewer);
|
||||||
@ -274,8 +299,9 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void getRecipesOwnedByUser() {
|
public void getRecipesOwnedByUser() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe r0 = this.createTestRecipe(owner);
|
final Recipe r0 = this.createTestRecipe(owner);
|
||||||
final List<Recipe> ownedRecipes = this.recipeService.getRecipesOwnedBy(owner);
|
final List<Recipe> ownedRecipes = this.recipeService.getRecipesOwnedBy(owner);
|
||||||
assertThat(ownedRecipes.size(), is(1));
|
assertThat(ownedRecipes.size(), is(1));
|
||||||
@ -283,8 +309,9 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateRawText() throws RecipeException, ImageException {
|
public void updateRawText() throws RecipeException, ImageException {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final RecipeCreateSpec createSpec = new RecipeCreateSpec();
|
final RecipeCreateSpec createSpec = new RecipeCreateSpec();
|
||||||
createSpec.setSlug("my-recipe");
|
createSpec.setSlug("my-recipe");
|
||||||
createSpec.setTitle("My Recipe");
|
createSpec.setTitle("My Recipe");
|
||||||
@ -303,9 +330,10 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void updateRawTextThrowsIfNotOwner() {
|
public void updateRawTextThrowsIfNotOwner() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User notOwner = this.seedUser();
|
final User notOwner = this.createTestUser("notOwner");
|
||||||
final Recipe recipe = this.createTestRecipe(owner);
|
final Recipe recipe = this.createTestRecipe(owner);
|
||||||
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec();
|
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec();
|
||||||
updateSpec.setRawText("should fail");
|
updateSpec.setRawText("should fail");
|
||||||
@ -321,17 +349,19 @@ public class RecipeServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void deleteRecipe() {
|
public void deleteRecipe() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe toDelete = this.createTestRecipe(owner);
|
final Recipe toDelete = this.createTestRecipe(owner);
|
||||||
this.recipeService.deleteRecipe(toDelete.getId(), owner);
|
this.recipeService.deleteRecipe(toDelete.getId(), owner);
|
||||||
assertThrows(RecipeException.class, () -> this.recipeService.getById(toDelete.getId(), owner));
|
assertThrows(RecipeException.class, () -> this.recipeService.getById(toDelete.getId(), owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void deleteRecipeThrowsIfNotOwner() {
|
public void deleteRecipeThrowsIfNotOwner() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User notOwner = this.seedUser();
|
final User notOwner = this.createTestUser("notOwner");
|
||||||
final Recipe toDelete = this.createTestRecipe(owner);
|
final Recipe toDelete = this.createTestRecipe(owner);
|
||||||
assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete.getId(), notOwner));
|
assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete.getId(), notOwner));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,20 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.star;
|
package app.mealsmadeeasy.api.recipe.star;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeEntity;
|
import app.mealsmadeeasy.api.recipe.RecipeEntity;
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeRepository;
|
import app.mealsmadeeasy.api.recipe.RecipeRepository;
|
||||||
import app.mealsmadeeasy.api.user.UserEntity;
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
import app.mealsmadeeasy.api.user.UserRepository;
|
import app.mealsmadeeasy.api.user.UserRepository;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
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.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class RecipeStarRepositoryTests {
|
public class RecipeStarRepositoryTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -29,19 +26,18 @@ public class RecipeStarRepositoryTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
private UserEntity seedUser() {
|
private UserEntity getOwnerUser() {
|
||||||
final UserEntity draft = UserEntity.getDefaultDraft();
|
final UserEntity draft = UserEntity.getDefaultDraft();
|
||||||
final String uuid = UUID.randomUUID().toString();
|
draft.setUsername("test-user");
|
||||||
draft.setUsername(uuid);
|
draft.setEmail("test-user@test.com");
|
||||||
draft.setEmail(uuid + "@test.com");
|
|
||||||
draft.setPassword("test");
|
draft.setPassword("test");
|
||||||
return this.userRepository.save(draft);
|
return this.userRepository.save(draft);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RecipeEntity getTestRecipe(UserEntity owner) {
|
private RecipeEntity getTestRecipe(UserEntity owner) {
|
||||||
final RecipeEntity recipeDraft = new RecipeEntity();
|
final RecipeEntity recipeDraft = new RecipeEntity();
|
||||||
recipeDraft.setCreated(OffsetDateTime.now());
|
recipeDraft.setCreated(LocalDateTime.now());
|
||||||
recipeDraft.setSlug(UUID.randomUUID().toString());
|
recipeDraft.setSlug("test-recipe");
|
||||||
recipeDraft.setOwner(owner);
|
recipeDraft.setOwner(owner);
|
||||||
recipeDraft.setTitle("Test Recipe");
|
recipeDraft.setTitle("Test Recipe");
|
||||||
recipeDraft.setRawText("Hello, World!");
|
recipeDraft.setRawText("Hello, World!");
|
||||||
@ -49,14 +45,15 @@ public class RecipeStarRepositoryTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void returnsTrueIfStarer() {
|
public void returnsTrueIfStarer() {
|
||||||
final UserEntity owner = this.seedUser();
|
final UserEntity owner = this.getOwnerUser();
|
||||||
final RecipeEntity recipe = this.getTestRecipe(owner);
|
final RecipeEntity recipe = this.getTestRecipe(owner);
|
||||||
|
|
||||||
final RecipeStarEntity starDraft = new RecipeStarEntity();
|
final RecipeStarEntity starDraft = new RecipeStarEntity();
|
||||||
final RecipeStarId starId = new RecipeStarId();
|
final RecipeStarId starId = new RecipeStarId();
|
||||||
starId.setRecipeId(recipe.getId());
|
starId.setRecipeId(recipe.getId());
|
||||||
starId.getOwnerId(owner.getId());
|
starId.setOwnerUsername(owner.getUsername());
|
||||||
starDraft.setId(starId);
|
starDraft.setId(starId);
|
||||||
this.recipeStarRepository.save(starDraft);
|
this.recipeStarRepository.save(starDraft);
|
||||||
|
|
||||||
@ -64,21 +61,22 @@ public class RecipeStarRepositoryTests {
|
|||||||
this.recipeStarRepository.isStarer(
|
this.recipeStarRepository.isStarer(
|
||||||
recipe.getOwner().getUsername(),
|
recipe.getOwner().getUsername(),
|
||||||
recipe.getSlug(),
|
recipe.getSlug(),
|
||||||
owner.getId()
|
owner.getUsername()
|
||||||
),
|
),
|
||||||
is(true)
|
is(true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void returnsFalseIfNotStarer() {
|
public void returnsFalseIfNotStarer() {
|
||||||
final UserEntity owner = this.seedUser();
|
final UserEntity owner = this.getOwnerUser();
|
||||||
final RecipeEntity recipe = this.getTestRecipe(owner);
|
final RecipeEntity recipe = this.getTestRecipe(owner);
|
||||||
assertThat(
|
assertThat(
|
||||||
this.recipeStarRepository.isStarer(
|
this.recipeStarRepository.isStarer(
|
||||||
recipe.getOwner().getUsername(),
|
recipe.getOwner().getUsername(),
|
||||||
recipe.getSlug(),
|
recipe.getSlug(),
|
||||||
owner.getId()
|
owner.getUsername()
|
||||||
),
|
),
|
||||||
is(false)
|
is(false)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.star;
|
package app.mealsmadeeasy.api.recipe.star;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.IntegrationTestsExtension;
|
|
||||||
import app.mealsmadeeasy.api.recipe.Recipe;
|
import app.mealsmadeeasy.api.recipe.Recipe;
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeException;
|
import app.mealsmadeeasy.api.recipe.RecipeException;
|
||||||
import app.mealsmadeeasy.api.recipe.RecipeService;
|
import app.mealsmadeeasy.api.recipe.RecipeService;
|
||||||
@ -10,11 +9,9 @@ import app.mealsmadeeasy.api.user.UserCreateException;
|
|||||||
import app.mealsmadeeasy.api.user.UserService;
|
import app.mealsmadeeasy.api.user.UserService;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
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.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
@ -22,7 +19,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class RecipeStarServiceTests {
|
public class RecipeStarServiceTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -34,57 +30,57 @@ public class RecipeStarServiceTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RecipeService recipeService;
|
private RecipeService recipeService;
|
||||||
|
|
||||||
private User seedUser() {
|
private User getTestUser(String username) {
|
||||||
final String uuid = UUID.randomUUID().toString();
|
|
||||||
try {
|
try {
|
||||||
return this.userService.createUser(uuid, uuid + "@test.com", "test");
|
return this.userService.createUser(username, username + "@test.com", "test");
|
||||||
} catch (UserCreateException e) {
|
} catch (UserCreateException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipe seedRecipe(User owner) {
|
private Recipe getTestRecipe(User owner, String slug, boolean isPublic) {
|
||||||
final RecipeCreateSpec spec = new RecipeCreateSpec();
|
final RecipeCreateSpec spec = new RecipeCreateSpec();
|
||||||
spec.setSlug(UUID.randomUUID().toString());
|
spec.setSlug(slug);
|
||||||
spec.setTitle("Test Recipe");
|
spec.setTitle("Test Recipe");
|
||||||
spec.setRawText("My great recipe has five ingredients.");
|
spec.setRawText("My great recipe has five ingredients.");
|
||||||
spec.setPublic(true);
|
spec.setPublic(isPublic);
|
||||||
return this.recipeService.create(owner, spec);
|
return this.recipeService.create(owner, spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void createViaUsernameAndSlug() {
|
public void createViaUsernameAndSlug() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.getTestUser("recipe-owner");
|
||||||
final User starer = this.seedUser();
|
final User starer = this.getTestUser("recipe-starer");
|
||||||
final Recipe recipe = this.seedRecipe(owner);
|
final Recipe recipe = this.getTestRecipe(owner, "test-recipe", true);
|
||||||
final RecipeStar star = assertDoesNotThrow(() -> this.recipeStarService.create(
|
final RecipeStar star = assertDoesNotThrow(() -> this.recipeStarService.create(
|
||||||
recipe.getOwner().getUsername(),
|
recipe.getOwner().getUsername(),
|
||||||
recipe.getSlug(),
|
recipe.getSlug(),
|
||||||
starer
|
starer
|
||||||
));
|
));
|
||||||
//noinspection DataFlowIssue
|
assertThat(star.getDate(), is(notNullValue()));
|
||||||
assertThat(star.getTimestamp(), is(notNullValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void createViaId() {
|
public void createViaId() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.getTestUser("recipe-owner");
|
||||||
final User starer = this.seedUser();
|
final User starer = this.getTestUser("recipe-starer");
|
||||||
final Recipe recipe = this.seedRecipe(owner);
|
final Recipe recipe = this.getTestRecipe(owner, "test-recipe", true);
|
||||||
final RecipeStar star = assertDoesNotThrow(() -> this.recipeStarService.create(
|
final RecipeStar star = assertDoesNotThrow(() -> this.recipeStarService.create(
|
||||||
recipe.getId(),
|
recipe.getId(),
|
||||||
starer.getId()
|
starer.getUsername()
|
||||||
));
|
));
|
||||||
//noinspection DataFlowIssue
|
assertThat(star.getDate(), is(notNullValue()));
|
||||||
assertThat(star.getTimestamp(), is(notNullValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void find() throws RecipeException {
|
public void find() throws RecipeException {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.getTestUser("recipe-owner");
|
||||||
final User starer = this.seedUser();
|
final User starer = this.getTestUser("recipe-starer");
|
||||||
final Recipe recipe = this.seedRecipe(owner);
|
final Recipe recipe = this.getTestRecipe(owner, "test-recipe", true);
|
||||||
this.recipeStarService.create(recipe.getId(), starer.getId());
|
this.recipeStarService.create(recipe.getId(), starer.getUsername());
|
||||||
final @Nullable RecipeStar star = this.recipeStarService.find(
|
final @Nullable RecipeStar star = this.recipeStarService.find(
|
||||||
recipe.getOwner().getUsername(),
|
recipe.getOwner().getUsername(),
|
||||||
recipe.getSlug(),
|
recipe.getSlug(),
|
||||||
@ -94,11 +90,12 @@ public class RecipeStarServiceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void deleteViaUsernameAndSlug() {
|
public void deleteViaUsernameAndSlug() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.getTestUser("recipe-owner");
|
||||||
final User starer = this.seedUser();
|
final User starer = this.getTestUser("recipe-starer");
|
||||||
final Recipe recipe = this.seedRecipe(owner);
|
final Recipe recipe = this.getTestRecipe(owner, "test-recipe", true);
|
||||||
this.recipeStarService.create(recipe.getId(), starer.getId());
|
this.recipeStarService.create(recipe.getId(), starer.getUsername());
|
||||||
assertDoesNotThrow(() -> this.recipeStarService.delete(
|
assertDoesNotThrow(() -> this.recipeStarService.delete(
|
||||||
recipe.getOwner().getUsername(),
|
recipe.getOwner().getUsername(),
|
||||||
recipe.getSlug(),
|
recipe.getSlug(),
|
||||||
|
|||||||
@ -1,22 +1,19 @@
|
|||||||
package app.mealsmadeeasy.api.signup;
|
package app.mealsmadeeasy.api.signup;
|
||||||
|
|
||||||
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.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.annotation.DirtiesContext;
|
||||||
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 static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
@ -25,7 +22,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ExtendWith(IntegrationTestsExtension.class)
|
|
||||||
public class SignUpControllerTests {
|
public class SignUpControllerTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -37,8 +33,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))
|
||||||
@ -46,24 +41,24 @@ public class SignUpControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void checkUsernameExpectAvailable() throws Exception {
|
public void checkUsernameExpectAvailable() throws Exception {
|
||||||
final String username = UUID.randomUUID().toString();
|
this.mockMvc.perform(this.getCheckUsernameRequest("isAvailable"))
|
||||||
this.mockMvc.perform(this.getCheckUsernameRequest(username))
|
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.isAvailable").value(true));
|
.andExpect(jsonPath("$.isAvailable").value(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void checkUsernameExpectNotAvailable() throws Exception {
|
public void checkUsernameExpectNotAvailable() throws Exception {
|
||||||
final String username = UUID.randomUUID().toString();
|
this.userService.createUser("notAvailable", "not-available@notavailable.com", "test");
|
||||||
this.userService.createUser(username, username + "@notavailable.com", "test");
|
this.mockMvc.perform(this.getCheckUsernameRequest("notAvailable"))
|
||||||
this.mockMvc.perform(this.getCheckUsernameRequest(username))
|
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.isAvailable").value(false));
|
.andExpect(jsonPath("$.isAvailable").value(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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))
|
||||||
@ -71,43 +66,44 @@ public class SignUpControllerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void checkEmailExpectAvailable() throws Exception {
|
public void checkEmailExpectAvailable() throws Exception {
|
||||||
this.mockMvc.perform(this.getCheckEmailRequest(UUID.randomUUID() + "@available.com"))
|
this.mockMvc.perform(this.getCheckEmailRequest("available@available.com"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.isAvailable").value(true));
|
.andExpect(jsonPath("$.isAvailable").value(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void checkEmailExpectNotAvailable() throws Exception {
|
public void checkEmailExpectNotAvailable() throws Exception {
|
||||||
final String notAvailable = UUID.randomUUID().toString();
|
this.userService.createUser("notAvailable", "not-available@notavailable.com", "test");
|
||||||
this.userService.createUser(notAvailable, notAvailable + "@notavailable.com", "test");
|
this.mockMvc.perform(this.getCheckEmailRequest("not-available@notavailable.com"))
|
||||||
this.mockMvc.perform(this.getCheckEmailRequest(notAvailable + "@notavailable.com"))
|
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.isAvailable").value(false));
|
.andExpect(jsonPath("$.isAvailable").value(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void simpleSignUp() throws Exception {
|
public void simpleSignUp() throws Exception {
|
||||||
final SignUpBody body = new SignUpBody();
|
final SignUpBody body = new SignUpBody();
|
||||||
final String username = UUID.randomUUID().toString();
|
body.setUsername("newUser");
|
||||||
body.setUsername(username);
|
body.setEmail("new@user.com");
|
||||||
body.setEmail(username + "@user.com");
|
|
||||||
body.setPassword("test");
|
body.setPassword("test");
|
||||||
final MockHttpServletRequestBuilder req = post("/sign-up")
|
final MockHttpServletRequestBuilder req = post("/sign-up")
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
.contentType(MediaType.APPLICATION_JSON);
|
.contentType(MediaType.APPLICATION_JSON);
|
||||||
this.mockMvc.perform(req)
|
this.mockMvc.perform(req)
|
||||||
.andExpect(status().isCreated())
|
.andExpect(status().isCreated())
|
||||||
.andExpect(jsonPath("$.username").value(username));
|
.andExpect(jsonPath("$.username").value("newUser"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void signUpBadRequestWhenUsernameTaken() throws Exception {
|
public void signUpBadRequestWhenUsernameTaken() throws Exception {
|
||||||
final String takenUsername = UUID.randomUUID().toString();
|
this.userService.createUser("taken", "taken@taken.com", "test");
|
||||||
this.userService.createUser(takenUsername, takenUsername + "@taken.com", "test");
|
|
||||||
final SignUpBody body = new SignUpBody();
|
final SignUpBody body = new SignUpBody();
|
||||||
body.setUsername(takenUsername);
|
body.setUsername("taken");
|
||||||
body.setEmail(UUID.randomUUID() + "@taken.com"); // n.b.: not taken email
|
body.setEmail("not-taken@taken.com"); // n.b.
|
||||||
body.setPassword("test");
|
body.setPassword("test");
|
||||||
final MockHttpServletRequestBuilder req = post("/sign-up")
|
final MockHttpServletRequestBuilder req = post("/sign-up")
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
@ -115,16 +111,16 @@ public class SignUpControllerTests {
|
|||||||
this.mockMvc.perform(req)
|
this.mockMvc.perform(req)
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(jsonPath("$.error.type").value(Type.USERNAME_TAKEN.toString()))
|
.andExpect(jsonPath("$.error.type").value(Type.USERNAME_TAKEN.toString()))
|
||||||
.andExpect(jsonPath("$.error.message").value(containsString(takenUsername)));
|
.andExpect(jsonPath("$.error.message").value(containsString("taken")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
public void signUpBadRequestWhenEmailTaken() throws Exception {
|
public void signUpBadRequestWhenEmailTaken() throws Exception {
|
||||||
final String takenEmail = UUID.randomUUID() + "@taken.com";
|
this.userService.createUser("taken", "taken@taken.com", "test");
|
||||||
this.userService.createUser(UUID.randomUUID().toString(), takenEmail, "test");
|
|
||||||
final SignUpBody body = new SignUpBody();
|
final SignUpBody body = new SignUpBody();
|
||||||
body.setUsername(UUID.randomUUID().toString()); // n.b.: random username
|
body.setUsername("notTaken"); // n.b.
|
||||||
body.setEmail(takenEmail);
|
body.setEmail("taken@taken.com");
|
||||||
body.setPassword("test");
|
body.setPassword("test");
|
||||||
final MockHttpServletRequestBuilder req = post("/sign-up")
|
final MockHttpServletRequestBuilder req = post("/sign-up")
|
||||||
.content(this.objectMapper.writeValueAsString(body))
|
.content(this.objectMapper.writeValueAsString(body))
|
||||||
@ -132,7 +128,7 @@ public class SignUpControllerTests {
|
|||||||
this.mockMvc.perform(req)
|
this.mockMvc.perform(req)
|
||||||
.andExpect(status().isBadRequest())
|
.andExpect(status().isBadRequest())
|
||||||
.andExpect(jsonPath("$.error.type").value(Type.EMAIL_TAKEN.toString()))
|
.andExpect(jsonPath("$.error.type").value(Type.EMAIL_TAKEN.toString()))
|
||||||
.andExpect(jsonPath("$.error.message").value(containsString(takenEmail)));
|
.andExpect(jsonPath("$.error.message").value(containsString("taken@taken.com")));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
spring.datasource.driver-class-name=org.h2.Driver
|
||||||
|
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
|
||||||
|
spring.datasource.username=sa
|
||||||
|
spring.datasource.password=sa
|
||||||
app.mealsmadeeasy.api.baseUrl=http://localhost:8080
|
app.mealsmadeeasy.api.baseUrl=http://localhost:8080
|
||||||
app.mealsmadeeasy.api.security.access-token-lifetime=60
|
app.mealsmadeeasy.api.security.access-token-lifetime=60
|
||||||
app.mealsmadeeasy.api.security.refresh-token-lifetime=120
|
app.mealsmadeeasy.api.security.refresh-token-lifetime=120
|
||||||
@ -5,8 +9,3 @@ app.mealsmadeeasy.api.minio.endpoint=http://localhost:9000
|
|||||||
app.mealsmadeeasy.api.minio.accessKey=minio-root
|
app.mealsmadeeasy.api.minio.accessKey=minio-root
|
||||||
app.mealsmadeeasy.api.minio.secretKey=test0123
|
app.mealsmadeeasy.api.minio.secretKey=test0123
|
||||||
app.mealsmadeeasy.api.images.bucketName=images
|
app.mealsmadeeasy.api.images.bucketName=images
|
||||||
|
|
||||||
# Source - https://stackoverflow.com/questions/3164072/large-objects-may-not-be-used-in-auto-commit-mode
|
|
||||||
# Posted by Iogui, modified by community. See post 'Timeline' for change history
|
|
||||||
# Retrieved 2025-12-25, License - CC BY-SA 4.0
|
|
||||||
spring.datasource.hikari.auto-commit=false
|
|
||||||
|
|||||||
@ -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,5 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.auth;
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.security.AuthToken;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@ -7,8 +8,6 @@ import org.springframework.http.ResponseCookie;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
public final class AuthController {
|
public final class AuthController {
|
||||||
@ -32,9 +31,9 @@ public final class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<LoginView> getLoginViewResponseEntity(LoginDetails loginDetails) {
|
private ResponseEntity<LoginView> getLoginViewResponseEntity(LoginDetails loginDetails) {
|
||||||
final RefreshToken refreshToken = loginDetails.getRefreshToken();
|
final AuthToken refreshToken = loginDetails.getRefreshToken();
|
||||||
final ResponseCookie refreshCookie = getRefreshTokenCookie(
|
final ResponseCookie refreshCookie = getRefreshTokenCookie(
|
||||||
refreshToken.getToken().toString(),
|
refreshToken.getToken(),
|
||||||
refreshToken.getLifetime()
|
refreshToken.getLifetime()
|
||||||
);
|
);
|
||||||
final var loginView = new LoginView(
|
final var loginView = new LoginView(
|
||||||
@ -61,7 +60,7 @@ public final class AuthController {
|
|||||||
|
|
||||||
@PostMapping("/refresh")
|
@PostMapping("/refresh")
|
||||||
public ResponseEntity<LoginView> refresh(
|
public ResponseEntity<LoginView> refresh(
|
||||||
@CookieValue(value = "refresh-token", required = false) @Nullable UUID oldRefreshToken
|
@CookieValue(value = "refresh-token", required = false) @Nullable String oldRefreshToken
|
||||||
) throws LoginException {
|
) throws LoginException {
|
||||||
final LoginDetails loginDetails = this.authService.refresh(oldRefreshToken);
|
final LoginDetails loginDetails = this.authService.refresh(oldRefreshToken);
|
||||||
return this.getLoginViewResponseEntity(loginDetails);
|
return this.getLoginViewResponseEntity(loginDetails);
|
||||||
@ -69,7 +68,7 @@ public final class AuthController {
|
|||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public ResponseEntity<?> logout(
|
public ResponseEntity<?> logout(
|
||||||
@CookieValue(value = "refresh-token", required = false) @Nullable UUID refreshToken
|
@CookieValue(value = "refresh-token", required = false) @Nullable String refreshToken
|
||||||
) {
|
) {
|
||||||
if (refreshToken != null) {
|
if (refreshToken != null) {
|
||||||
this.authService.logout(refreshToken);
|
this.authService.logout(refreshToken);
|
||||||
|
|||||||
@ -2,10 +2,8 @@ package app.mealsmadeeasy.api.auth;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface AuthService {
|
public interface AuthService {
|
||||||
LoginDetails login(String username, String password) throws LoginException;
|
LoginDetails login(String username, String password) throws LoginException;
|
||||||
void logout(UUID refreshToken);
|
void logout(String refreshToken);
|
||||||
LoginDetails refresh(@Nullable UUID refreshToken) throws LoginException;
|
LoginDetails refresh(@Nullable String refreshToken) throws LoginException;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -38,9 +38,9 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
|
|
||||||
private RefreshToken createRefreshToken(UserEntity principal) {
|
private RefreshToken createRefreshToken(UserEntity principal) {
|
||||||
final RefreshTokenEntity refreshTokenDraft = new RefreshTokenEntity();
|
final RefreshTokenEntity refreshTokenDraft = new RefreshTokenEntity();
|
||||||
refreshTokenDraft.setToken(UUID.randomUUID());
|
refreshTokenDraft.setToken(UUID.randomUUID().toString());
|
||||||
refreshTokenDraft.setIssued(OffsetDateTime.now());
|
refreshTokenDraft.setIssued(LocalDateTime.now());
|
||||||
refreshTokenDraft.setExpiration(OffsetDateTime.now().plusSeconds(this.refreshTokenLifetime));
|
refreshTokenDraft.setExpiration(LocalDateTime.now().plusSeconds(this.refreshTokenLifetime));
|
||||||
refreshTokenDraft.setOwner(principal);
|
refreshTokenDraft.setOwner(principal);
|
||||||
return this.refreshTokenRepository.save(refreshTokenDraft);
|
return this.refreshTokenRepository.save(refreshTokenDraft);
|
||||||
}
|
}
|
||||||
@ -64,13 +64,13 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void logout(UUID refreshToken) {
|
public void logout(String refreshToken) {
|
||||||
this.refreshTokenRepository.findByToken(refreshToken).ifPresent(this.refreshTokenRepository::delete);
|
this.refreshTokenRepository.findByToken(refreshToken).ifPresent(this.refreshTokenRepository::delete);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public LoginDetails refresh(@Nullable UUID refreshToken) throws LoginException {
|
public LoginDetails refresh(@Nullable String refreshToken) throws LoginException {
|
||||||
if (refreshToken == null) {
|
if (refreshToken == null) {
|
||||||
throw new LoginException(LoginExceptionReason.NO_REFRESH_TOKEN, "No refresh token provided.");
|
throw new LoginException(LoginExceptionReason.NO_REFRESH_TOKEN, "No refresh token provided.");
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
if (old.isRevoked() || old.isDeleted()) {
|
if (old.isRevoked() || old.isDeleted()) {
|
||||||
throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "Invalid refresh token.");
|
throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "Invalid refresh token.");
|
||||||
}
|
}
|
||||||
if (old.getExpires().isBefore(OffsetDateTime.now())) {
|
if (old.getExpires().isBefore(LocalDateTime.now())) {
|
||||||
throw new LoginException(LoginExceptionReason.EXPIRED_REFRESH_TOKEN, "Refresh token is expired.");
|
throw new LoginException(LoginExceptionReason.EXPIRED_REFRESH_TOKEN, "Refresh token is expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,9 @@ public final class LoginDetails {
|
|||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
private final AuthToken accessToken;
|
private final AuthToken accessToken;
|
||||||
private final RefreshToken refreshToken;
|
private final AuthToken refreshToken;
|
||||||
|
|
||||||
public LoginDetails(String username, AuthToken accessToken, RefreshToken refreshToken) {
|
public LoginDetails(String username, AuthToken accessToken, AuthToken refreshToken) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
@ -22,7 +22,7 @@ public final class LoginDetails {
|
|||||||
return this.accessToken;
|
return this.accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RefreshToken getRefreshToken() {
|
public AuthToken getRefreshToken() {
|
||||||
return this.refreshToken;
|
return this.refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
package app.mealsmadeeasy.api.auth;
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import app.mealsmadeeasy.api.security.AuthToken;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface RefreshToken {
|
import java.time.LocalDateTime;
|
||||||
UUID getToken();
|
|
||||||
long getLifetime();
|
public interface RefreshToken extends AuthToken {
|
||||||
OffsetDateTime getExpires();
|
LocalDateTime getIssued();
|
||||||
OffsetDateTime getIssued();
|
|
||||||
boolean isRevoked();
|
boolean isRevoked();
|
||||||
boolean isDeleted();
|
boolean isDeleted();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,58 +3,68 @@ package app.mealsmadeeasy.api.auth;
|
|||||||
import app.mealsmadeeasy.api.user.UserEntity;
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity(name = "RefreshToken")
|
@Entity(name = "RefreshToken")
|
||||||
@Table(name = "refresh_token")
|
|
||||||
public class RefreshTokenEntity implements RefreshToken {
|
public class RefreshTokenEntity implements RefreshToken {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column(nullable = false)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
private UUID token;
|
@Column(nullable = false, updatable = false)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
private String token;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private OffsetDateTime issued;
|
private LocalDateTime issued;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private OffsetDateTime expiration;
|
private LocalDateTime expiration;
|
||||||
|
|
||||||
@ManyToOne(optional = false)
|
@Column(nullable = false)
|
||||||
@JoinColumn(name = "owner_id", nullable = false)
|
private Boolean revoked = false;
|
||||||
|
|
||||||
|
@JoinColumn(nullable = false)
|
||||||
|
@ManyToOne
|
||||||
private UserEntity owner;
|
private UserEntity owner;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Boolean deleted = false;
|
private Boolean deleted = false;
|
||||||
|
|
||||||
@Column(nullable = false)
|
public Long getId() {
|
||||||
private Boolean revoked = false;
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UUID getToken() {
|
public String getToken() {
|
||||||
return this.token;
|
return this.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setToken(UUID token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OffsetDateTime getIssued() {
|
public LocalDateTime getIssued() {
|
||||||
return this.issued;
|
return this.issued;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIssued(OffsetDateTime issued) {
|
public void setIssued(LocalDateTime issued) {
|
||||||
this.issued = issued;
|
this.issued = issued;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OffsetDateTime getExpires() {
|
public LocalDateTime getExpires() {
|
||||||
return this.expiration;
|
return this.expiration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExpiration(OffsetDateTime expiration) {
|
public void setExpiration(LocalDateTime expiration) {
|
||||||
this.expiration = expiration;
|
this.expiration = expiration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,10 @@ import org.springframework.data.jpa.repository.Modifying;
|
|||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long> {
|
public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long> {
|
||||||
|
|
||||||
Optional<RefreshTokenEntity> findByToken(UUID token);
|
Optional<RefreshTokenEntity> findByToken(String token);
|
||||||
|
|
||||||
@Modifying
|
@Modifying
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|||||||
@ -3,13 +3,13 @@ package app.mealsmadeeasy.api.image;
|
|||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface Image {
|
public interface Image {
|
||||||
Integer getId();
|
Long getId();
|
||||||
OffsetDateTime getCreated();
|
LocalDateTime getCreated();
|
||||||
@Nullable OffsetDateTime getModified();
|
@Nullable LocalDateTime getModified();
|
||||||
String getUserFilename();
|
String getUserFilename();
|
||||||
String getMimeType();
|
String getMimeType();
|
||||||
@Nullable String getAlt();
|
@Nullable String getAlt();
|
||||||
|
|||||||
@ -5,23 +5,22 @@ import app.mealsmadeeasy.api.user.UserEntity;
|
|||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Entity(name = "Image")
|
@Entity(name = "Image")
|
||||||
@Table(name = "image")
|
|
||||||
public class S3ImageEntity implements Image {
|
public class S3ImageEntity implements Image {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
@Column(nullable = false, updatable = false)
|
@Column(nullable = false, updatable = false)
|
||||||
private Integer id;
|
private Long id;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private OffsetDateTime created = OffsetDateTime.now();
|
private LocalDateTime created = LocalDateTime.now();
|
||||||
|
|
||||||
private OffsetDateTime modified;
|
private LocalDateTime modified;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String userFilename;
|
private String userFilename;
|
||||||
@ -48,37 +47,32 @@ public class S3ImageEntity implements Image {
|
|||||||
private Boolean isPublic = false;
|
private Boolean isPublic = false;
|
||||||
|
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
@JoinTable(
|
|
||||||
name = "image_viewer",
|
|
||||||
joinColumns = @JoinColumn(name = "image_id"),
|
|
||||||
inverseJoinColumns = @JoinColumn(name = "viewer_id")
|
|
||||||
)
|
|
||||||
private Set<UserEntity> viewers = new HashSet<>();
|
private Set<UserEntity> viewers = new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getId() {
|
public Long getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(Long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OffsetDateTime getCreated() {
|
public LocalDateTime getCreated() {
|
||||||
return this.created;
|
return this.created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreated(OffsetDateTime created) {
|
public void setCreated(LocalDateTime created) {
|
||||||
this.created = created;
|
this.created = created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable OffsetDateTime getModified() {
|
public @Nullable LocalDateTime getModified() {
|
||||||
return this.modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModified(OffsetDateTime modified) {
|
public void setModified(LocalDateTime modified) {
|
||||||
this.modified = modified;
|
this.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -225,7 +225,7 @@ public class S3ImageService implements ImageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (didUpdate) {
|
if (didUpdate) {
|
||||||
entity.setModified(OffsetDateTime.now());
|
entity.setModified(LocalDateTime.now());
|
||||||
}
|
}
|
||||||
return this.imageRepository.save(entity);
|
return this.imageRepository.save(entity);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import app.mealsmadeeasy.api.image.Image;
|
|||||||
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -33,8 +33,8 @@ public class ImageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String url;
|
private String url;
|
||||||
private OffsetDateTime created;
|
private LocalDateTime created;
|
||||||
private @Nullable OffsetDateTime modified;
|
private @Nullable LocalDateTime modified;
|
||||||
private String filename;
|
private String filename;
|
||||||
private String mimeType;
|
private String mimeType;
|
||||||
private @Nullable String alt;
|
private @Nullable String alt;
|
||||||
@ -53,19 +53,19 @@ public class ImageView {
|
|||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OffsetDateTime getCreated() {
|
public LocalDateTime getCreated() {
|
||||||
return this.created;
|
return this.created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreated(OffsetDateTime created) {
|
public void setCreated(LocalDateTime created) {
|
||||||
this.created = created;
|
this.created = created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable OffsetDateTime getModified() {
|
public @Nullable LocalDateTime getModified() {
|
||||||
return this.modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModified(@Nullable OffsetDateTime modified) {
|
public void setModified(@Nullable LocalDateTime modified) {
|
||||||
this.modified = modified;
|
this.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.inference;
|
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.http.codec.ServerSentEvent;
|
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/inferences")
|
|
||||||
public class InferenceController {
|
|
||||||
|
|
||||||
private final InferenceService inferenceService;
|
|
||||||
|
|
||||||
public InferenceController(InferenceService inferenceService) {
|
|
||||||
this.inferenceService = inferenceService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("/recipe-extract")
|
|
||||||
public ResponseEntity<String> recipeExtract(@RequestParam MultipartFile recipeImageFile) throws IOException {
|
|
||||||
return ResponseEntity.ok(this.inferenceService.extractRecipe(recipeImageFile.getInputStream()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping(value = "/recipe-extract-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
|
||||||
public Flux<ServerSentEvent<Map<String, String>>> recipeExtractStream(@RequestParam MultipartFile recipeImageFile) throws IOException {
|
|
||||||
return this.inferenceService.extractRecipeStream(recipeImageFile.getInputStream())
|
|
||||||
.map(data -> ServerSentEvent.builder(Map.of("delta", data)).build());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.inference;
|
|
||||||
|
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
|
||||||
import org.springframework.ai.content.Media;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.core.io.InputStreamResource;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.MimeType;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class InferenceService {
|
|
||||||
|
|
||||||
private final ChatClient chatClient;
|
|
||||||
|
|
||||||
public InferenceService(ChatClient.Builder chatClientBuilder) {
|
|
||||||
this.chatClient = chatClientBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String extractRecipe(InputStream recipeImageInputStream) {
|
|
||||||
final Media media = Media.builder()
|
|
||||||
.data(new InputStreamResource(recipeImageInputStream))
|
|
||||||
.mimeType(MimeType.valueOf("image/jpeg"))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
final String markdownResponse = this.chatClient.prompt()
|
|
||||||
.user(u ->
|
|
||||||
u.text(new ClassPathResource("app/mealsmadeeasy/api/inference/recipe-extract-user-prompt.md"))
|
|
||||||
.media(media)
|
|
||||||
)
|
|
||||||
.call()
|
|
||||||
.content();
|
|
||||||
return markdownResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Flux<String> extractRecipeStream(InputStream recipeImageInputStream) {
|
|
||||||
final Media media = Media.builder()
|
|
||||||
.data(new InputStreamResource(recipeImageInputStream))
|
|
||||||
.mimeType(MimeType.valueOf("image/jpeg"))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return this.chatClient.prompt()
|
|
||||||
.user(u ->
|
|
||||||
u.text(new ClassPathResource("app/mealsmadeeasy/api/inference/recipe-extract-user-prompt.md"))
|
|
||||||
.media(media)
|
|
||||||
)
|
|
||||||
.stream().content();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.inference;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "recipe_extraction")
|
|
||||||
public class RecipeExtractionEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.UUID)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@Lob
|
|
||||||
@Column(columnDefinition = "TEXT")
|
|
||||||
@Basic(fetch = FetchType.LAZY)
|
|
||||||
private String markdown;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkdown() {
|
|
||||||
return this.markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMarkdown(String markdown) {
|
|
||||||
this.markdown = markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.inference;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class RecipeExtractionView {
|
|
||||||
|
|
||||||
public static RecipeExtractionView from(RecipeExtractionEntity entity) {
|
|
||||||
final var view = new RecipeExtractionView();
|
|
||||||
view.setId(entity.getId());
|
|
||||||
view.setMarkdown(entity.getMarkdown());
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UUID id;
|
|
||||||
private String markdown;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMarkdown() {
|
|
||||||
return this.markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMarkdown(String markdown) {
|
|
||||||
this.markdown = markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package app.mealsmadeeasy.api.job;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
|
|
||||||
public class JobEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(nullable = false, updatable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String key;
|
|
||||||
|
|
||||||
@Column(nullable = false, columnDefinition = "jsonb")
|
|
||||||
private String payload;
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -6,13 +6,13 @@ import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
|||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface Recipe {
|
public interface Recipe {
|
||||||
Integer getId();
|
Long getId();
|
||||||
OffsetDateTime getCreated();
|
LocalDateTime getCreated();
|
||||||
@Nullable OffsetDateTime getModified();
|
@Nullable LocalDateTime getModified();
|
||||||
String getSlug();
|
String getSlug();
|
||||||
String getTitle();
|
String getTitle();
|
||||||
@Nullable Integer getPreparationTime();
|
@Nullable Integer getPreparationTime();
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -10,7 +10,7 @@ import app.mealsmadeeasy.api.user.UserEntity;
|
|||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -18,14 +18,14 @@ import java.util.Set;
|
|||||||
public final class RecipeEntity implements Recipe {
|
public final class RecipeEntity implements Recipe {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
@Column(nullable = false, updatable = false)
|
@Column(nullable = false, updatable = false)
|
||||||
private Integer id;
|
private Long id;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private OffsetDateTime created;
|
private LocalDateTime created;
|
||||||
|
|
||||||
private OffsetDateTime modified;
|
private LocalDateTime modified;
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
@Column(nullable = false, unique = true)
|
||||||
private String slug;
|
private String slug;
|
||||||
@ -57,7 +57,7 @@ public final class RecipeEntity implements Recipe {
|
|||||||
private UserEntity owner;
|
private UserEntity owner;
|
||||||
|
|
||||||
@OneToMany
|
@OneToMany
|
||||||
@JoinColumn(name = "recipe_id")
|
@JoinColumn(name = "recipeId")
|
||||||
private Set<RecipeStarEntity> stars = new HashSet<>();
|
private Set<RecipeStarEntity> stars = new HashSet<>();
|
||||||
|
|
||||||
@OneToMany(mappedBy = "recipe")
|
@OneToMany(mappedBy = "recipe")
|
||||||
@ -67,44 +67,35 @@ public final class RecipeEntity implements Recipe {
|
|||||||
private Boolean isPublic = false;
|
private Boolean isPublic = false;
|
||||||
|
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
@JoinTable(
|
|
||||||
name = "recipe_viewer",
|
|
||||||
joinColumns = @JoinColumn(name = "recipe_id"),
|
|
||||||
inverseJoinColumns = @JoinColumn(name = "viewer_id")
|
|
||||||
)
|
|
||||||
private Set<UserEntity> viewers = new HashSet<>();
|
private Set<UserEntity> viewers = new HashSet<>();
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@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 Long getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(Long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OffsetDateTime getCreated() {
|
public LocalDateTime getCreated() {
|
||||||
return this.created;
|
return this.created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreated(OffsetDateTime created) {
|
public void setCreated(LocalDateTime created) {
|
||||||
this.created = created;
|
this.created = created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable OffsetDateTime getModified() {
|
public @Nullable LocalDateTime getModified() {
|
||||||
return this.modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModified(@Nullable OffsetDateTime modified) {
|
public void setModified(@Nullable LocalDateTime modified) {
|
||||||
this.modified = modified;
|
this.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,12 +232,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;
|
||||||
@ -25,7 +22,7 @@ import org.springframework.security.access.prepost.PostAuthorize;
|
|||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -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
|
||||||
@ -59,7 +53,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
throw new AccessDeniedException("Must be logged in.");
|
throw new AccessDeniedException("Must be logged in.");
|
||||||
}
|
}
|
||||||
final RecipeEntity draft = new RecipeEntity();
|
final RecipeEntity draft = new RecipeEntity();
|
||||||
draft.setCreated(OffsetDateTime.now());
|
draft.setCreated(LocalDateTime.now());
|
||||||
draft.setOwner((UserEntity) owner);
|
draft.setOwner((UserEntity) owner);
|
||||||
draft.setSlug(spec.getSlug());
|
draft.setSlug(spec.getSlug());
|
||||||
draft.setTitle(spec.getTitle());
|
draft.setTitle(spec.getTitle());
|
||||||
@ -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)
|
||||||
@ -244,7 +222,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
recipe.setMainImage(mainImage);
|
recipe.setMainImage(mainImage);
|
||||||
|
|
||||||
recipe.setModified(OffsetDateTime.now());
|
recipe.setModified(LocalDateTime.now());
|
||||||
return this.recipeRepository.save(recipe);
|
return this.recipeRepository.save(recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,7 +279,7 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
if (viewer == null) {
|
if (viewer == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.recipeStarRepository.isStarer(username, slug, viewer.getId());
|
return this.recipeStarRepository.isStarer(username, slug, viewer.getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -4,12 +4,12 @@ import app.mealsmadeeasy.api.recipe.Recipe;
|
|||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
public interface RecipeComment {
|
public interface RecipeComment {
|
||||||
Integer getId();
|
Long getId();
|
||||||
OffsetDateTime getCreated();
|
LocalDateTime getCreated();
|
||||||
@Nullable OffsetDateTime getModified();
|
@Nullable LocalDateTime getModified();
|
||||||
String getRawText();
|
String getRawText();
|
||||||
User getOwner();
|
User getOwner();
|
||||||
Recipe getRecipe();
|
Recipe getRecipe();
|
||||||
|
|||||||
@ -4,21 +4,20 @@ import app.mealsmadeeasy.api.recipe.RecipeEntity;
|
|||||||
import app.mealsmadeeasy.api.user.UserEntity;
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Entity(name = "RecipeComment")
|
@Entity(name = "RecipeComment")
|
||||||
@Table(name = "recipe_comment")
|
|
||||||
public final class RecipeCommentEntity implements RecipeComment {
|
public final class RecipeCommentEntity implements RecipeComment {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Integer id;
|
private Long id;
|
||||||
|
|
||||||
@Column(nullable = false, updatable = false)
|
@Column(nullable = false, updatable = false)
|
||||||
private OffsetDateTime created = OffsetDateTime.now();
|
private LocalDateTime created = LocalDateTime.now();
|
||||||
|
|
||||||
private OffsetDateTime modified;
|
private LocalDateTime modified;
|
||||||
|
|
||||||
@Lob
|
@Lob
|
||||||
@Basic(fetch = FetchType.LAZY)
|
@Basic(fetch = FetchType.LAZY)
|
||||||
@ -36,29 +35,29 @@ public final class RecipeCommentEntity implements RecipeComment {
|
|||||||
@JoinColumn(name = "recipe_id", nullable = false, updatable = false)
|
@JoinColumn(name = "recipe_id", nullable = false, updatable = false)
|
||||||
private RecipeEntity recipe;
|
private RecipeEntity recipe;
|
||||||
|
|
||||||
public Integer getId() {
|
public Long getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(Long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OffsetDateTime getCreated() {
|
public LocalDateTime getCreated() {
|
||||||
return this.created;
|
return this.created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreated(OffsetDateTime created) {
|
public void setCreated(LocalDateTime created) {
|
||||||
this.created = created;
|
this.created = created;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OffsetDateTime getModified() {
|
public LocalDateTime getModified() {
|
||||||
return this.modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModified(OffsetDateTime modified) {
|
public void setModified(LocalDateTime modified) {
|
||||||
this.modified = modified;
|
this.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import org.springframework.security.access.prepost.PostAuthorize;
|
|||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ public class RecipeCommentServiceImpl implements RecipeCommentService {
|
|||||||
) throws RecipeException {
|
) throws RecipeException {
|
||||||
requireNonNull(commenter);
|
requireNonNull(commenter);
|
||||||
final RecipeCommentEntity draft = new RecipeCommentEntity();
|
final RecipeCommentEntity draft = new RecipeCommentEntity();
|
||||||
draft.setCreated(OffsetDateTime.now());
|
draft.setCreated(LocalDateTime.now());
|
||||||
draft.setRawText(body.getText());
|
draft.setRawText(body.getText());
|
||||||
draft.setCachedRenderedText(this.markdownService.renderAndCleanMarkdown(body.getText()));
|
draft.setCachedRenderedText(this.markdownService.renderAndCleanMarkdown(body.getText()));
|
||||||
draft.setOwner((UserEntity) commenter);
|
draft.setOwner((UserEntity) commenter);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package app.mealsmadeeasy.api.recipe.comment;
|
|||||||
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
public class RecipeCommentView {
|
public class RecipeCommentView {
|
||||||
|
|
||||||
@ -21,35 +21,35 @@ public class RecipeCommentView {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer id;
|
private Long id;
|
||||||
private OffsetDateTime created;
|
private LocalDateTime created;
|
||||||
private @Nullable OffsetDateTime modified;
|
private @Nullable LocalDateTime modified;
|
||||||
private String text;
|
private String text;
|
||||||
private @Nullable String rawText;
|
private @Nullable String rawText;
|
||||||
private UserInfoView owner;
|
private UserInfoView owner;
|
||||||
private Integer recipeId;
|
private Long recipeId;
|
||||||
|
|
||||||
public Integer getId() {
|
public Long getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(Long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OffsetDateTime getCreated() {
|
public LocalDateTime getCreated() {
|
||||||
return this.created;
|
return this.created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreated(OffsetDateTime created) {
|
public void setCreated(LocalDateTime created) {
|
||||||
this.created = created;
|
this.created = created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable OffsetDateTime getModified() {
|
public @Nullable LocalDateTime getModified() {
|
||||||
return this.modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModified(@Nullable OffsetDateTime modified) {
|
public void setModified(@Nullable LocalDateTime modified) {
|
||||||
this.modified = modified;
|
this.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,11 +77,11 @@ public class RecipeCommentView {
|
|||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getRecipeId() {
|
public Long getRecipeId() {
|
||||||
return this.recipeId;
|
return this.recipeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRecipeId(Integer recipeId) {
|
public void setRecipeId(Long recipeId) {
|
||||||
this.recipeId = recipeId;
|
this.recipeId = recipeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,7 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.star;
|
package app.mealsmadeeasy.api.recipe.star;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
public interface RecipeStar {
|
public interface RecipeStar {
|
||||||
OffsetDateTime getTimestamp();
|
LocalDateTime getDate();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,19 +3,17 @@ package app.mealsmadeeasy.api.recipe.star;
|
|||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.EmbeddedId;
|
import jakarta.persistence.EmbeddedId;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.Table;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Entity(name = "RecipeStar")
|
@Entity(name = "RecipeStar")
|
||||||
@Table(name = "recipe_star")
|
|
||||||
public final class RecipeStarEntity implements RecipeStar {
|
public final class RecipeStarEntity implements RecipeStar {
|
||||||
|
|
||||||
@EmbeddedId
|
@EmbeddedId
|
||||||
private RecipeStarId id;
|
private RecipeStarId id;
|
||||||
|
|
||||||
@Column(nullable = false, updatable = false)
|
@Column(nullable = false, updatable = false)
|
||||||
private OffsetDateTime timestamp = OffsetDateTime.now();
|
private LocalDateTime date = LocalDateTime.now();
|
||||||
|
|
||||||
public RecipeStarId getId() {
|
public RecipeStarId getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
@ -25,12 +23,13 @@ public final class RecipeStarEntity implements RecipeStar {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OffsetDateTime getTimestamp() {
|
@Override
|
||||||
return this.timestamp;
|
public LocalDateTime getDate() {
|
||||||
|
return this.date;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTimestamp(OffsetDateTime date) {
|
public void setDate(LocalDateTime date) {
|
||||||
this.timestamp = date;
|
this.date = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -8,25 +8,25 @@ import java.util.Objects;
|
|||||||
@Embeddable
|
@Embeddable
|
||||||
public class RecipeStarId {
|
public class RecipeStarId {
|
||||||
|
|
||||||
@Column(name = "owner_id", nullable = false)
|
@Column(nullable = false)
|
||||||
private Integer ownerId;
|
private String ownerUsername;
|
||||||
|
|
||||||
@Column(name = "recipe_id", nullable = false)
|
@Column(nullable = false)
|
||||||
private Integer recipeId;
|
private Long recipeId;
|
||||||
|
|
||||||
public Integer getOwnerId() {
|
public String getOwnerUsername() {
|
||||||
return this.ownerId;
|
return this.ownerUsername;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getOwnerId(Integer ownerId) {
|
public void setOwnerUsername(String ownerUsername) {
|
||||||
this.ownerId = ownerId;
|
this.ownerUsername = ownerUsername;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getRecipeId() {
|
public Long getRecipeId() {
|
||||||
return this.recipeId;
|
return this.recipeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRecipeId(Integer recipeId) {
|
public void setRecipeId(Long recipeId) {
|
||||||
this.recipeId = recipeId;
|
this.recipeId = recipeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,19 +34,19 @@ public class RecipeStarId {
|
|||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o instanceof RecipeStarId other) {
|
if (o instanceof RecipeStarId other) {
|
||||||
return this.recipeId.equals(other.recipeId) && this.ownerId.equals(other.ownerId);
|
return this.recipeId.equals(other.recipeId) && this.ownerUsername.equals(other.ownerUsername);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(this.recipeId, this.ownerId);
|
return Objects.hash(this.recipeId, this.ownerUsername);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "RecipeStarId(" + this.recipeId + ", " + this.ownerId + ")";
|
return "RecipeStarId(" + this.recipeId + ", " + this.ownerUsername + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,15 +9,15 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface RecipeStarRepository extends JpaRepository<RecipeStarEntity, Long> {
|
public interface RecipeStarRepository extends JpaRepository<RecipeStarEntity, Long> {
|
||||||
|
|
||||||
@Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerId = ?2")
|
@Query("SELECT star FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2")
|
||||||
Optional<RecipeStarEntity> findByRecipeIdAndOwnerId(Integer recipeId, Integer ownerId);
|
Optional<RecipeStarEntity> findByRecipeIdAndOwnerUsername(Long recipeId, String username);
|
||||||
|
|
||||||
@Query("SELECT count(rs) > 0 FROM RecipeStar rs, Recipe r WHERE r.owner.username = ?1 AND r.slug = ?2 AND r.id = rs.id.recipeId AND rs.id.ownerId = ?3")
|
@Query("SELECT count(rs) > 0 FROM RecipeStar rs, Recipe r WHERE r.owner.username = ?1 AND r.slug = ?2 AND r.id = rs.id.recipeId AND rs.id.ownerUsername = ?3")
|
||||||
boolean isStarer(String ownerUsername, String slug, Integer viewerId);
|
boolean isStarer(String ownerUsername, String slug, String viewerUsername);
|
||||||
|
|
||||||
@Modifying
|
@Modifying
|
||||||
@Transactional
|
@Transactional
|
||||||
@Query("DELETE FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerId = ?2")
|
@Query("DELETE FROM RecipeStar star WHERE star.id.recipeId = ?1 AND star.id.ownerUsername = ?2")
|
||||||
void deleteByRecipeIdAndOwnerId(Integer recipeId, Integer ownerId);
|
void deleteByRecipeIdAndOwnerUsername(Long recipeId, String username);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import app.mealsmadeeasy.api.user.User;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface RecipeStarService {
|
public interface RecipeStarService {
|
||||||
RecipeStar create(Integer recipeId, Integer ownerId);
|
RecipeStar create(long recipeId, String ownerUsername);
|
||||||
RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException;
|
RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException;
|
||||||
|
|
||||||
Optional<RecipeStar> find(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException;
|
Optional<RecipeStar> find(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException;
|
||||||
|
|
||||||
void delete(Integer recipeId, Integer ownerId);
|
void delete(long recipeId, String ownerUsername);
|
||||||
void delete(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException;
|
void delete(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import app.mealsmadeeasy.api.recipe.RecipeService;
|
|||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -21,45 +21,45 @@ public class RecipeStarServiceImpl implements RecipeStarService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecipeStar create(Integer recipeId, Integer ownerId) {
|
public RecipeStar create(long recipeId, String ownerUsername) {
|
||||||
final RecipeStarEntity draft = new RecipeStarEntity();
|
final RecipeStarEntity draft = new RecipeStarEntity();
|
||||||
final RecipeStarId id = new RecipeStarId();
|
final RecipeStarId id = new RecipeStarId();
|
||||||
id.setRecipeId(recipeId);
|
id.setRecipeId(recipeId);
|
||||||
id.getOwnerId(ownerId);
|
id.setOwnerUsername(ownerUsername);
|
||||||
draft.setId(id);
|
draft.setId(id);
|
||||||
draft.setTimestamp(OffsetDateTime.now());
|
draft.setDate(LocalDateTime.now());
|
||||||
return this.recipeStarRepository.save(draft);
|
return this.recipeStarRepository.save(draft);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException {
|
public RecipeStar create(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException {
|
||||||
final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer);
|
final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer);
|
||||||
final Optional<RecipeStarEntity> existing = this.recipeStarRepository.findByRecipeIdAndOwnerId(
|
final Optional<RecipeStarEntity> existing = this.recipeStarRepository.findByRecipeIdAndOwnerUsername(
|
||||||
recipe.getId(),
|
recipe.getId(),
|
||||||
starer.getId()
|
starer.getUsername()
|
||||||
);
|
);
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
return existing.get();
|
return existing.get();
|
||||||
}
|
}
|
||||||
return this.create(recipe.getId(), starer.getId());
|
return this.create(recipe.getId(), starer.getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<RecipeStar> find(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException {
|
public Optional<RecipeStar> find(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException {
|
||||||
final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer);
|
final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer);
|
||||||
return this.recipeStarRepository.findByRecipeIdAndOwnerId(recipe.getId(), starer.getId())
|
return this.recipeStarRepository.findByRecipeIdAndOwnerUsername(recipe.getId(), starer.getUsername())
|
||||||
.map(RecipeStar.class::cast);
|
.map(RecipeStar.class::cast);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(Integer recipeId, Integer ownerId) {
|
public void delete(long recipeId, String ownerUsername) {
|
||||||
this.recipeStarRepository.deleteByRecipeIdAndOwnerId(recipeId, ownerId);
|
this.recipeStarRepository.deleteByRecipeIdAndOwnerUsername(recipeId, ownerUsername);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException {
|
public void delete(String recipeOwnerUsername, String recipeSlug, User starer) throws RecipeException {
|
||||||
final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer);
|
final Recipe recipe = this.recipeService.getByUsernameAndSlug(recipeOwnerUsername, recipeSlug, starer);
|
||||||
this.delete(recipe.getId(), starer.getId());
|
this.delete(recipe.getId(), starer.getUsername());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
|||||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
public class FullRecipeView {
|
public class FullRecipeView {
|
||||||
|
|
||||||
@ -41,8 +41,8 @@ public class FullRecipeView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long id;
|
private long id;
|
||||||
private OffsetDateTime created;
|
private LocalDateTime created;
|
||||||
private @Nullable OffsetDateTime modified;
|
private @Nullable LocalDateTime modified;
|
||||||
private String slug;
|
private String slug;
|
||||||
private String title;
|
private String title;
|
||||||
private @Nullable Integer preparationTime;
|
private @Nullable Integer preparationTime;
|
||||||
@ -64,19 +64,19 @@ public class FullRecipeView {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OffsetDateTime getCreated() {
|
public LocalDateTime getCreated() {
|
||||||
return this.created;
|
return this.created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreated(OffsetDateTime created) {
|
public void setCreated(LocalDateTime created) {
|
||||||
this.created = created;
|
this.created = created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable OffsetDateTime getModified() {
|
public @Nullable LocalDateTime getModified() {
|
||||||
return this.modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModified(@Nullable OffsetDateTime modified) {
|
public void setModified(@Nullable LocalDateTime modified) {
|
||||||
this.modified = modified;
|
this.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import app.mealsmadeeasy.api.recipe.Recipe;
|
|||||||
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
public final class RecipeInfoView {
|
public final class RecipeInfoView {
|
||||||
|
|
||||||
@ -26,9 +26,9 @@ public final class RecipeInfoView {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer id;
|
private long id;
|
||||||
private OffsetDateTime created;
|
private LocalDateTime created;
|
||||||
private OffsetDateTime modified;
|
private LocalDateTime modified;
|
||||||
private String slug;
|
private String slug;
|
||||||
private String title;
|
private String title;
|
||||||
private @Nullable Integer preparationTime;
|
private @Nullable Integer preparationTime;
|
||||||
@ -39,27 +39,27 @@ public final class RecipeInfoView {
|
|||||||
private int starCount;
|
private int starCount;
|
||||||
private @Nullable ImageView mainImage;
|
private @Nullable ImageView mainImage;
|
||||||
|
|
||||||
public Integer getId() {
|
public long getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OffsetDateTime getCreated() {
|
public LocalDateTime getCreated() {
|
||||||
return this.created;
|
return this.created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreated(OffsetDateTime created) {
|
public void setCreated(LocalDateTime created) {
|
||||||
this.created = created;
|
this.created = created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OffsetDateTime getModified() {
|
public LocalDateTime getModified() {
|
||||||
return this.modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModified(OffsetDateTime modified) {
|
public void setModified(LocalDateTime modified) {
|
||||||
this.modified = modified;
|
this.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
public interface User extends UserDetails {
|
public interface User extends UserDetails {
|
||||||
|
|
||||||
Integer getId();
|
Long getId();
|
||||||
|
|
||||||
String getEmail();
|
String getEmail();
|
||||||
void setEmail(String email);
|
void setEmail(String email);
|
||||||
|
|||||||
@ -24,7 +24,7 @@ public final class UserEntity implements User {
|
|||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Integer id;
|
private Long id;
|
||||||
|
|
||||||
@Column(unique = true, nullable = false)
|
@Column(unique = true, nullable = false)
|
||||||
private String username;
|
private String username;
|
||||||
@ -51,11 +51,11 @@ public final class UserEntity implements User {
|
|||||||
private Boolean credentialsExpired;
|
private Boolean credentialsExpired;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer getId() {
|
public Long getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(Long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,19 +2,18 @@ package app.mealsmadeeasy.api.user;
|
|||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
@Entity(name = "UserGrantedAuthority")
|
@Entity
|
||||||
@Table(name = "user_granted_authority")
|
|
||||||
public final class UserGrantedAuthorityEntity implements UserGrantedAuthority {
|
public final class UserGrantedAuthorityEntity implements UserGrantedAuthority {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Integer id;
|
private Long id;
|
||||||
|
|
||||||
private String authority;
|
private String authority;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "user_id")
|
@JoinColumn(name = "user_entity_id")
|
||||||
private UserEntity userEntity;
|
private UserEntity userEntity;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -11,14 +11,14 @@ public class UserInfoView {
|
|||||||
return userInfoView;
|
return userInfoView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer id;
|
private long id;
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
public Integer getId() {
|
public long getId() {
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(Integer id) {
|
public void setId(long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
Convert the recipe in the image to Markdown.
|
|
||||||
@ -1,14 +1,9 @@
|
|||||||
spring.application.name=meals-made-easy-api
|
spring.application.name=meals-made-easy-api
|
||||||
spring.jpa.hibernate.ddl-auto=none
|
spring.jpa.hibernate.ddl-auto=create-drop
|
||||||
spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:meals_made_easy_api}
|
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:meals_made_easy_api}
|
||||||
spring.datasource.username=${POSTGRES_USER:meals-made-easy-api-user}
|
spring.datasource.username=${MYSQL_USERNAME:meals-made-easy-api-user}
|
||||||
spring.datasource.password=${POSTGRES_PASSWORD}
|
spring.datasource.password=${MYSQL_PASSWORD}
|
||||||
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
# Source - https://stackoverflow.com/questions/3164072/large-objects-may-not-be-used-in-auto-commit-mode
|
|
||||||
# Posted by Iogui, modified by community. See post 'Timeline' for change history
|
|
||||||
# Retrieved 2025-12-25, License - CC BY-SA 4.0
|
|
||||||
spring.datasource.hikari.auto-commit=false
|
|
||||||
|
|
||||||
app.mealsmadeeasy.api.baseUrl=http://localhost:8080
|
app.mealsmadeeasy.api.baseUrl=http://localhost:8080
|
||||||
app.mealsmadeeasy.api.security.access-token-lifetime=60
|
app.mealsmadeeasy.api.security.access-token-lifetime=60
|
||||||
app.mealsmadeeasy.api.security.refresh-token-lifetime=3600
|
app.mealsmadeeasy.api.security.refresh-token-lifetime=3600
|
||||||
@ -16,8 +11,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
|
|
||||||
spring.ai.ollama.chat.options.model=deepseek-ocr:latest
|
|
||||||
spring.ai.ollama.init.pull-model-strategy=never
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
CREATE TABLE "user" (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
email VARCHAR(255) NOT NULL UNIQUE,
|
|
||||||
password VARCHAR(255) NOT NULL,
|
|
||||||
username VARCHAR(255) NOT NULL UNIQUE,
|
|
||||||
locked BOOLEAN NOT NULL,
|
|
||||||
expired BOOLEAN NOT NULL,
|
|
||||||
enabled BOOLEAN NOT NULL,
|
|
||||||
credentials_expired BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE user_granted_authority (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
authority VARCHAR(255),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES "user"
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE refresh_token (
|
|
||||||
token UUID PRIMARY KEY,
|
|
||||||
issued TIMESTAMPTZ(6) NOT NULL,
|
|
||||||
expiration TIMESTAMPTZ(6) NOT NULL,
|
|
||||||
owner_id INT NOT NULL,
|
|
||||||
deleted BOOLEAN NOT NULL,
|
|
||||||
revoked BOOLEAN NOT NULL,
|
|
||||||
FOREIGN KEY (owner_id) REFERENCES "user"
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE image (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
created TIMESTAMPTZ(6) NOT NULL,
|
|
||||||
modified TIMESTAMPTZ(6),
|
|
||||||
|
|
||||||
owner_id INT NOT NULL,
|
|
||||||
is_public BOOLEAN NOT NULL,
|
|
||||||
|
|
||||||
object_name VARCHAR(255) NOT NULL,
|
|
||||||
user_filename VARCHAR(255) NOT NULL,
|
|
||||||
mime_type VARCHAR(255) NOT NULL,
|
|
||||||
alt VARCHAR(255),
|
|
||||||
caption VARCHAR(255),
|
|
||||||
|
|
||||||
height INTEGER,
|
|
||||||
width INTEGER,
|
|
||||||
|
|
||||||
FOREIGN KEY (owner_id) REFERENCES "user"
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE image_viewer (
|
|
||||||
image_id INT NOT NULL,
|
|
||||||
viewer_id INT NOT NULL,
|
|
||||||
PRIMARY KEY (image_id, viewer_id),
|
|
||||||
FOREIGN KEY (image_id) REFERENCES image,
|
|
||||||
FOREIGN KEY (viewer_id) REFERENCES "user"
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE recipe (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
created TIMESTAMPTZ(6) NOT NULL,
|
|
||||||
modified TIMESTAMPTZ(6),
|
|
||||||
|
|
||||||
owner_id INT NOT NULL,
|
|
||||||
slug VARCHAR(255) NOT NULL,
|
|
||||||
is_public BOOLEAN NOT NULL,
|
|
||||||
|
|
||||||
title VARCHAR(255) NOT NULL,
|
|
||||||
raw_text TEXT NOT NULL,
|
|
||||||
cached_rendered_text TEXT,
|
|
||||||
main_image_id INT,
|
|
||||||
|
|
||||||
preparation_time INT,
|
|
||||||
cooking_time INT,
|
|
||||||
total_time INT,
|
|
||||||
|
|
||||||
FOREIGN KEY (owner_id) REFERENCES "user",
|
|
||||||
FOREIGN KEY (main_image_id) REFERENCES image
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX recipe_username_slug ON recipe (owner_id, slug);
|
|
||||||
|
|
||||||
CREATE TABLE recipe_viewer (
|
|
||||||
recipe_id INT NOT NULL,
|
|
||||||
viewer_id INT NOT NULL,
|
|
||||||
PRIMARY KEY (recipe_id, viewer_id),
|
|
||||||
FOREIGN KEY (recipe_id) REFERENCES recipe,
|
|
||||||
FOREIGN KEY (viewer_id) REFERENCES "user"
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE recipe_comment (
|
|
||||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
||||||
created TIMESTAMPTZ(6) NOT NULL,
|
|
||||||
modified TIMESTAMPTZ(6),
|
|
||||||
owner_id INT NOT NULL,
|
|
||||||
recipe_id INT NOT NULL,
|
|
||||||
raw_text TEXT NOT NULL,
|
|
||||||
cached_rendered_text TEXT,
|
|
||||||
FOREIGN KEY (owner_id) REFERENCES "user",
|
|
||||||
FOREIGN KEY (recipe_id) REFERENCES recipe
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE recipe_star (
|
|
||||||
timestamp TIMESTAMPTZ(6) NOT NULL,
|
|
||||||
recipe_id INT NOT NULL,
|
|
||||||
owner_id INT NOT NULL,
|
|
||||||
PRIMARY KEY (recipe_id, owner_id),
|
|
||||||
FOREIGN KEY (recipe_id) references recipe,
|
|
||||||
FOREIGN KEY (owner_id) references "user"
|
|
||||||
);
|
|
||||||
@ -1 +0,0 @@
|
|||||||
CREATE UNIQUE INDEX image_owner_id_user_filename ON image (owner_id, user_filename);
|
|
||||||
@ -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
|
|
||||||
);
|
|
||||||
@ -4,7 +4,7 @@ import app.mealsmadeeasy.api.matchers.ContainsItemsMatcher;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class ContainsImagesMatcher extends ContainsItemsMatcher<Image, Image, Integer> {
|
public final class ContainsImagesMatcher extends ContainsItemsMatcher<Image, Image, Long> {
|
||||||
|
|
||||||
public static ContainsImagesMatcher containsImages(Image... expected) {
|
public static ContainsImagesMatcher containsImages(Image... expected) {
|
||||||
return new ContainsImagesMatcher(expected);
|
return new ContainsImagesMatcher(expected);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import app.mealsmadeeasy.api.recipe.view.RecipeInfoView;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ContainsRecipeInfoViewsForRecipesMatcher extends ContainsItemsMatcher<RecipeInfoView, Recipe, Integer> {
|
public class ContainsRecipeInfoViewsForRecipesMatcher extends ContainsItemsMatcher<RecipeInfoView, Recipe, Long> {
|
||||||
|
|
||||||
public static ContainsRecipeInfoViewsForRecipesMatcher containsRecipeInfoViewsForRecipes(Recipe... expected) {
|
public static ContainsRecipeInfoViewsForRecipesMatcher containsRecipeInfoViewsForRecipes(Recipe... expected) {
|
||||||
return new ContainsRecipeInfoViewsForRecipesMatcher(List.of(expected));
|
return new ContainsRecipeInfoViewsForRecipesMatcher(List.of(expected));
|
||||||
|
|||||||
@ -21,7 +21,7 @@ public class ContainsRecipeStarsMatcher extends ContainsItemsMatcher<RecipeStar,
|
|||||||
recipeStar -> ((RecipeStarEntity) recipeStar).getId(),
|
recipeStar -> ((RecipeStarEntity) recipeStar).getId(),
|
||||||
recipeStar -> ((RecipeStarEntity) recipeStar).getId(),
|
recipeStar -> ((RecipeStarEntity) recipeStar).getId(),
|
||||||
(id0, id1) -> Objects.equals(id0.getRecipeId(), id1.getRecipeId())
|
(id0, id1) -> Objects.equals(id0.getRecipeId(), id1.getRecipeId())
|
||||||
&& Objects.equals(id0.getOwnerId(), id1.getOwnerId())
|
&& Objects.equals(id0.getOwnerUsername(), id1.getOwnerUsername())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import app.mealsmadeeasy.api.matchers.ContainsItemsMatcher;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class ContainsRecipesMatcher extends ContainsItemsMatcher<Recipe, Recipe, Integer> {
|
public final class ContainsRecipesMatcher extends ContainsItemsMatcher<Recipe, Recipe, Long> {
|
||||||
|
|
||||||
public static ContainsRecipesMatcher containsRecipes(Recipe... expected) {
|
public static ContainsRecipesMatcher containsRecipes(Recipe... expected) {
|
||||||
return new ContainsRecipesMatcher(expected);
|
return new ContainsRecipesMatcher(expected);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import app.mealsmadeeasy.api.matchers.ContainsItemsMatcher;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ContainsUsersMatcher extends ContainsItemsMatcher<User, User, Integer> {
|
public class ContainsUsersMatcher extends ContainsItemsMatcher<User, User, Long> {
|
||||||
|
|
||||||
public static ContainsUsersMatcher containsUsers(User... allExpected) {
|
public static ContainsUsersMatcher containsUsers(User... allExpected) {
|
||||||
return new ContainsUsersMatcher(allExpected);
|
return new ContainsUsersMatcher(allExpected);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user