Merge pull request '364-ras-organize-code' (!9) from 364-ras-organize-code into main

Reviewed-on: binarygolem/stktrk#9
Reviewed-by: christian <christian.rauh.cr@gmail.com>
This commit is contained in:
christian 2024-12-07 16:20:37 +01:00
commit 41ec2517a4
20 changed files with 372 additions and 122 deletions

33
.gitignore vendored
View File

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -30,6 +30,10 @@
<java.version>21</java.version> <java.version>21</java.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>

View File

@ -1,63 +1,18 @@
package com.stktrk.app; package com.stktrk.app;
import com.arcadedb.gremlin.ArcadeGraph; import jakarta.annotation.Nullable;
import com.github.javafaker.Faker;
import com.stktrk.app.configuration.GraphDbConfig;
import com.stktrk.app.db.ConnectionPool;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.StringJoiner;
// https://stackoverflow.com/questions/51221777/failed-to-configure-a-datasource-url-attribute-is-not-specified-and-no-embedd // https://stackoverflow.com/questions/51221777/failed-to-configure-a-datasource-url-attribute-is-not-specified-and-no-embedd
@SpringBootApplication(exclude = {FlywayAutoConfiguration.class}) @SpringBootApplication(exclude = {FlywayAutoConfiguration.class})
@RestController
public class AppApplication extends SpringBootServletInitializer { public class AppApplication extends SpringBootServletInitializer {
public static void main(String[] args) { public static void main(@Nullable String[] args) {
SpringApplication.run(AppApplication.class, args); SpringApplication.run(AppApplication.class, args);
} }
// klkljgh
@GetMapping("/hello")
public String sayHello(@RequestParam(value = "myName", defaultValue = "World") String name) {
List<Vertex> x = List.of();
ArcadeGraph g = ConnectionPool.getGraph();
x = g.traversal().V().toList();
Vertex target = !x.isEmpty() ? x.get(new Random().nextInt(x.size())) : null;
Faker faker = new Faker();
Vertex res = g.traversal().addV("Profile").property("name", faker.name().firstName()).property("lastName", faker.name().lastName())
.next();
if (target != null) {
g.traversal().V(res.id()).addE("friend").to(target).iterate();
}
g.close();
x.add(res);
x.sort(Comparator.comparing(a -> a.id().toString()));
StringJoiner sj = new StringJoiner("<br />\n");
x.forEach(v -> sj.add(v.id().toString() + " - " + (String) v.property("name").value() + " " + (String) v.property("lastName").value()));
return "Hello, " + name + "! Added " + res.property("name").value() + ". There are " + x.size() + " vertices.<br/>\n" + sj;
}
@GetMapping("/delete")
public String delete() {
ArcadeGraph g = ConnectionPool.getGraph();
g.traversal().V().hasLabel("Profile").drop().iterate();
g.close();
return "Done.";
}
} }

View File

@ -0,0 +1,74 @@
package com.stktrk.app.application.profile;
import com.stktrk.app.domain.profile.ProfileService;
import com.stktrk.app.domain.profile.types.Profile;
import jakarta.annotation.Nonnull;
import jakarta.validation.Valid;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("profile")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class ProfileController {
@Autowired
@Nonnull
private final ProfileService profileService;
// TODO figure out how to handle the exception.
@Nonnull
@GetMapping("/")
public List<?> findAll() {
return profileService.findAll();
}
@PostMapping ("/")
public void addProfile (@Nonnull @Valid @RequestBody Profile profile){
profileService.addProfile(profile);
}
@GetMapping("/_delete")
@Nonnull
public ResponseEntity<String> delete() {
profileService.deleteAll ();
return ResponseEntity.status(HttpStatus.OK)
.body("Deleted all Profiles");
}
@GetMapping ("/_fake")
@Nonnull
public ResponseEntity<String> _fake() {
profileService.createFakeProfile();
return ResponseEntity.status(HttpStatus.OK)
.body("Created Profile");
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
@Nonnull
public Map<String, String> handleValidationExceptions(
@Nonnull MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
}

View File

@ -0,0 +1,17 @@
package com.stktrk.app.domain.profile;
import com.stktrk.app.domain.profile.types.Profile;
import jakarta.annotation.Nonnull;
import java.util.List;
public interface ProfileRepository {
@Nonnull
List<?> findAll();
void addProfile(@Nonnull Profile profile);
void createFakeProfile();
void deleteAll();
}

View File

@ -0,0 +1,31 @@
package com.stktrk.app.domain.profile;
import com.stktrk.app.domain.profile.types.Profile;
import jakarta.annotation.Nonnull;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@AllArgsConstructor
public class ProfileService {
@Nonnull private final ProfileRepository profileRepository;
@Nonnull
public List<?> findAll() {
return profileRepository.findAll ();
}
public void addProfile (@Nonnull Profile profile) {
profileRepository.addProfile (profile);
}
public void createFakeProfile() {
profileRepository.createFakeProfile();
}
public void deleteAll() {
profileRepository.deleteAll();
}
}

View File

@ -0,0 +1,20 @@
package com.stktrk.app.domain.profile.types;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotEmpty;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class Profile {
@Nullable
private String id;
@NotEmpty(message = "Name is mandatory")
private String name;
@NotEmpty(message = "Last name is mandatory")
private String lastName;
}

View File

@ -1,7 +1,6 @@
package com.stktrk.app.configuration; package com.stktrk.app.integration.configuration.types;
import jakarta.annotation.Nonnull;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@ -13,11 +12,15 @@ import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "graph") @ConfigurationProperties(prefix = "graph")
public class GraphDbConfig { public class GraphDbConfig {
String host; @Nonnull
int httpPort; String host;
int postgresPort; int httpPort;
String graphName; int postgresPort;
String user; @Nonnull
String password; String graphName;
@Nonnull
String user;
@Nonnull
String password;
} }

View File

@ -1,9 +1,10 @@
package com.stktrk.app.db; package com.stktrk.app.integration.flyway;
import com.arcadedb.gremlin.ArcadeGraph; import com.arcadedb.gremlin.ArcadeGraph;
import com.arcadedb.gremlin.ArcadeGraphFactory; import com.arcadedb.gremlin.ArcadeGraphFactory;
import com.stktrk.app.configuration.GraphDbConfig; import com.stktrk.app.integration.configuration.types.GraphDbConfig;
import org.apache.commons.configuration2.Configuration; import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -11,21 +12,23 @@ import javax.annotation.PostConstruct;
@Component @Component
public class ConnectionPool { public class ConnectionPool {
static ArcadeGraphFactory pool; @Nullable
static ArcadeGraphFactory pool;
@Autowired @Autowired
@Nullable
GraphDbConfig graphDbConfig; GraphDbConfig graphDbConfig;
@PostConstruct @PostConstruct
void init () { void init() {
try { pool = ArcadeGraphFactory.withRemote(graphDbConfig.getHost(),
pool = ArcadeGraphFactory.withRemote(graphDbConfig.getHost(), graphDbConfig.getHttpPort(), graphDbConfig.getGraphName(), graphDbConfig.getUser(), graphDbConfig.getPassword()); graphDbConfig.getHttpPort(),
graphDbConfig.getGraphName(),
} catch (Exception e) { graphDbConfig.getUser(),
throw new RuntimeException(e); graphDbConfig.getPassword());
}
} }
@Nonnull
public static ArcadeGraph getGraph() { public static ArcadeGraph getGraph() {
return pool.get(); return pool.get();
} }

View File

@ -1,5 +1,6 @@
package com.stktrk.app.db; package com.stktrk.app.integration.flyway;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import org.flywaydb.core.Flyway; import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.migration.JavaMigration; import org.flywaydb.core.api.migration.JavaMigration;
@ -13,13 +14,17 @@ import java.sql.SQLException;
@Configuration @Configuration
public class FlywayConfig { public class FlywayConfig {
// TODO move to properties
// Custom migration history table name // Custom migration history table name
@Nullable
private static final String SCHEMA_HISTORY_TABLE = "flyway_schema_history"; private static final String SCHEMA_HISTORY_TABLE = "flyway_schema_history";
@Autowired @Autowired
@Nullable
private DataSource dataSource; private DataSource dataSource;
@Autowired @Autowired
@Nullable
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
@PostConstruct @PostConstruct
@ -35,7 +40,7 @@ public class FlywayConfig {
Flyway flyway = Flyway.configure() Flyway flyway = Flyway.configure()
.dataSource(dataSource) // No need to configure flyway JDBC URL by using the original DataSource .dataSource(dataSource) // No need to configure flyway JDBC URL by using the original DataSource
.defaultSchema(dataSource.getConnection().getSchema()) .defaultSchema(dataSource.getConnection().getSchema())
.locations("db/migration") // Default migration script path .locations("integration/flyway/migration") // Default migration script path
.table(SCHEMA_HISTORY_TABLE) // Default migration history table is `flyway_schema_history` .table(SCHEMA_HISTORY_TABLE) // Default migration history table is `flyway_schema_history`
.baselineOnMigrate(true) // Default false, must be set to true for initial migration on existing databases .baselineOnMigrate(true) // Default false, must be set to true for initial migration on existing databases
.baselineVersion("0") // Default "1" .baselineVersion("0") // Default "1"

View File

@ -1,6 +1,6 @@
package com.stktrk.app.db; package com.stktrk.app.integration.flyway;
import com.stktrk.app.configuration.GraphDbConfig; import com.stktrk.app.integration.configuration.types.GraphDbConfig;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import java.sql.Connection; import java.sql.Connection;

View File

@ -1,20 +1,24 @@
package com.stktrk.app.db.migrations; package com.stktrk.app.integration.flyway.migrations;
import com.stktrk.app.configuration.GraphDbConfig; import com.stktrk.app.integration.configuration.types.GraphDbConfig;
import com.stktrk.app.db.MigrationWrapper; import com.stktrk.app.integration.flyway.MigrationWrapper;
import lombok.Data; import jakarta.annotation.Nonnull;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.flywaydb.core.api.migration.BaseJavaMigration; import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context; import org.flywaydb.core.api.migration.Context;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@EqualsAndHashCode(callSuper = true)
@Component @Component
@Data @Value
public class V1__CreateTest extends BaseJavaMigration implements MigrationWrapper { public class V1__CreateTest extends BaseJavaMigration implements MigrationWrapper {
@Autowired @Autowired
@Nonnull
GraphDbConfig graphDbConfig; GraphDbConfig graphDbConfig;
public void migrate(Context context) throws Exception { public void migrate(@Nonnull Context context) throws Exception {
this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE test"); this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE test");
} }

View File

@ -1,20 +1,24 @@
package com.stktrk.app.db.migrations; package com.stktrk.app.integration.flyway.migrations;
import com.stktrk.app.configuration.GraphDbConfig; import com.stktrk.app.integration.configuration.types.GraphDbConfig;
import com.stktrk.app.db.MigrationWrapper; import com.stktrk.app.integration.flyway.MigrationWrapper;
import lombok.Data; import jakarta.annotation.Nonnull;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.flywaydb.core.api.migration.BaseJavaMigration; import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context; import org.flywaydb.core.api.migration.Context;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@EqualsAndHashCode(callSuper = true)
@Component @Component
@Data @Value
public class V2__CreateUser extends BaseJavaMigration implements MigrationWrapper { public class V2__CreateUser extends BaseJavaMigration implements MigrationWrapper {
@Autowired @Autowired
@Nonnull
GraphDbConfig graphDbConfig; GraphDbConfig graphDbConfig;
public void migrate(Context context) throws Exception { public void migrate(@Nonnull Context context) throws Exception {
this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE user"); this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE user");
} }

View File

@ -0,0 +1,24 @@
package com.stktrk.app.integration.flyway.migrations;
import com.stktrk.app.integration.configuration.types.GraphDbConfig;
import com.stktrk.app.integration.flyway.MigrationWrapper;
import jakarta.annotation.Nonnull;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@EqualsAndHashCode(callSuper = true)
@Component
@Value
public class V3__CreateProfile extends BaseJavaMigration implements MigrationWrapper {
@Nonnull
@Autowired
GraphDbConfig graphDbConfig;
public void migrate(@Nonnull Context context) throws Exception {
this.executeQuery(graphDbConfig,"CREATE VERTEX TYPE Profile IF NOT EXISTS; CREATE PROPERTY Profile.name STRING (MANDATORY true); CREATE PROPERTY Profile.lastName STRING (MANDATORY true, notnull true);");
}
}

View File

@ -0,0 +1,70 @@
package com.stktrk.app.integration.profile;
import com.arcadedb.gremlin.ArcadeGraph;
import com.github.javafaker.Faker;
import com.stktrk.app.integration.flyway.ConnectionPool;
import com.stktrk.app.domain.profile.ProfileRepository;
import com.stktrk.app.domain.profile.types.Profile;
import com.stktrk.app.utils.EncryptId;
import jakarta.annotation.Nonnull;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Random;
@Repository
public class ProfileRepositoryImpl implements ProfileRepository {
@Nonnull
@Override
public List<?> findAll() {
ArcadeGraph g = ConnectionPool.getGraph();
List<? extends Vertex> profiles = g.traversal().V().hasLabel("Profile").toList();
g.close();
return profiles.stream()
.map(this::buildProfile)
.toList();
}
@Override
public void addProfile(@Nonnull Profile profile) {
ArcadeGraph g = ConnectionPool.getGraph();
Vertex profiles = g.traversal()
.addV("Profile")
.property("name", profile.getName())
.property("lastName", profile.getLastName())
.next();
g.close();
}
@Override
public void createFakeProfile() {
Faker faker = new Faker();
ArcadeGraph g = ConnectionPool.getGraph();
List<Vertex> vertices = g.traversal().V().hasLabel("Profile").toList();
Vertex target = vertices.isEmpty() ? null : vertices.get(new Random().nextInt(vertices.size()));
Vertex res = g.traversal().addV("Profile").property("name", faker.name().firstName()).property("lastName", faker.name().lastName())
.next();
if (target != null) {
g.traversal().V(res.id()).addE("friend").to(target).iterate();
}
g.close();
}
@Override
public void deleteAll () {
ArcadeGraph g = ConnectionPool.getGraph();
g.traversal().V().hasLabel("Profile").drop().iterate();
}
@Nonnull
private Profile buildProfile (@Nonnull Vertex v) {
return Profile.builder()
.name((String) v.property("name").value())
.lastName((String) v.property("lastName").value())
.id(EncryptId.encrypt ((String)v.id()))
.build();
}
}

View File

@ -0,0 +1,44 @@
package com.stktrk.app.utils;
import com.arcadedb.database.RID;
import jakarta.annotation.Nonnull;
import jakarta.validation.constraints.NotEmpty;
import org.hashids.Hashids;
import java.security.InvalidKeyException;
public class EncryptId {
// TODO move salt into properties
@Nonnull
private static final String SALT = "yG8kWVG0kei4wqwXGgt99AXxJWD7K2fnK4xv3BDR0M0AHchvFFCjFJK4VH1nLvm1";
@Nonnull
public static String encrypt(@Nonnull RID rid) {
return new Hashids(SALT).encode(rid.getBucketId(), rid.getPosition());
}
// TODO illegal argument exception thrown from RID - if we only allow this to come from the DB, we should be good.
@Nonnull
public static String encrypt(@NotEmpty String rid) {
RID obj = new RID(rid);
return new Hashids(SALT).encode(obj.getBucketId(), obj.getPosition());
}
@Nonnull
public static RID decrypt(@NotEmpty String hash) throws InvalidKeyException {
long[] res = new Hashids(SALT).decode(hash);
if (res == null || res.length != 2 ) {
throw new InvalidKeyException("invalid id");
}
return new RID((int) res[0], res[1]);
}
@Nonnull
private Boolean isValidRid (String rid) {
return rid.matches("#\\d+\\.\\d+");
}
}

View File

@ -10,16 +10,16 @@ springdoc.swagger-ui.path=/swagger-ui.html
spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url= jdbc:postgresql://localhost:7654/flyway_db spring.datasource.url= jdbc:postgresql://192.168.178.50:7654/flyway_db
spring.datasource.username=flyway_user spring.datasource.username=flyway_user
spring.datasource.password=stdUNmu7KTUQV6KGMKjzHIiwQ2gbipArvg02 spring.datasource.password=7e7v55UcYGrY0e3UPYI0qtyMA4YJ1ZkTEaoyZ252GluFkiEMHVT9U5ULS7Rg2rGi
spring.datasource.defaultSchema=flyway_db spring.datasource.defaultSchema=flyway_db
spring.datasource.hikari.schema=flyway_db spring.datasource.hikari.schema=flyway_db
graph.user=root graph.user=root
graph.password=playwithdata graph.password=playwithdata
graph.connection=jdbc:postgresql://192.168.178.29:5432/graph graph.connection=jdbc:postgresql://192.168.178.50:5432/graph
graph.http-port:2480 graph.http-port:2480
graph.postgres-port:5432 graph.postgres-port:5432
graph.host=192.168.178.29 graph.host=192.168.178.50
graph.graph-name=graph graph.graph-name=graph

View File

@ -1,10 +1,11 @@
package com.stktrk.app; package com.stktrk.app;
import jakarta.annotation.Nonnull;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
public class TestAppApplication { public class TestAppApplication {
public static void main(String[] args) { public static void main(@Nonnull String[] args) {
SpringApplication.from(AppApplication::main).with(TestcontainersConfiguration.class).run(args); SpringApplication.from(AppApplication::main).with(TestcontainersConfiguration.class).run(args);
} }

View File

@ -1,5 +1,6 @@
package com.stktrk.app; package com.stktrk.app;
import jakarta.annotation.Nonnull;
import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -11,6 +12,7 @@ class TestcontainersConfiguration {
@Bean @Bean
@ServiceConnection @ServiceConnection
@Nonnull
RabbitMQContainer rabbitContainer() { RabbitMQContainer rabbitContainer() {
return new RabbitMQContainer(DockerImageName.parse("rabbitmq:latest")); return new RabbitMQContainer(DockerImageName.parse("rabbitmq:latest"));
} }

View File

@ -0,0 +1,22 @@
package com.stktrk.app.utils;
import com.arcadedb.database.RID;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.security.InvalidKeyException;
class EncryptIdTest {
@Test
void encrypt() {
Assertions.assertEquals("nOua", EncryptId.encrypt(new RID(1,2L)));
}
@Test
void decrypt() throws InvalidKeyException {
Assertions.assertEquals(new RID(1,2L), EncryptId.decrypt("nOua"));
}
}