Skip to content

Commit

Permalink
feat(user): added support for easier table schema interaction
Browse files Browse the repository at this point in the history
  • Loading branch information
zZHorizonZz committed Aug 23, 2024
1 parent 8fd4da3 commit 6b7a63d
Show file tree
Hide file tree
Showing 17 changed files with 372 additions and 248 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import dev.cloudeko.zenei.extension.core.model.user.UserPassword;
import dev.cloudeko.zenei.extension.core.repository.UserPasswordRepository;
import dev.cloudeko.zenei.extension.core.repository.UserRepository;
import dev.cloudeko.zenei.user.UserAccountManager;
import dev.cloudeko.zenei.user.runtime.BasicUserAccount;
import dev.cloudeko.zenei.user.runtime.BasicUserAccountManager;
import dev.cloudeko.zenei.user.BasicUserAccount;
import dev.cloudeko.zenei.user.runtime.DefaultUserAccount;
import dev.cloudeko.zenei.user.runtime.DefaultUserAccountManager;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
Expand All @@ -36,7 +36,7 @@ public class CreateUserImpl implements CreateUser {
private final UserRepository userRepository;
private final UserPasswordRepository userPasswordRepository;

private final BasicUserAccountManager userAccountManager;
private final DefaultUserAccountManager userAccountManager;

@Override
@Transactional
Expand All @@ -58,7 +58,7 @@ public User handle(CreateUserInput createUserInput) {
checkExistingUsername(user.getUsername());
checkExistingEmail(user.getPrimaryEmailAddress().getEmail());

BasicUserAccount basic = userAccountManager.createUserBlocking(new BasicUserAccount(user.getUsername(), "test"));
DefaultUserAccount basic = userAccountManager.createUserBlocking(new DefaultUserAccount(user.getUsername()));

userRepository.createUser(user);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package dev.cloudeko.zenei.user.deployment;

import dev.cloudeko.zenei.user.runtime.BasicUserAccountInitializer;
import dev.cloudeko.zenei.user.runtime.BasicUserAccountManager;
import dev.cloudeko.zenei.user.runtime.BasicUserAccountRecorder;
import dev.cloudeko.zenei.user.runtime.BasicUserAccountRepository;
import dev.cloudeko.zenei.user.runtime.DefaultUserAccountInitializer;
import dev.cloudeko.zenei.user.runtime.DefaultUserAccountManager;
import dev.cloudeko.zenei.user.runtime.DefaultUserAccountRecorder;
import dev.cloudeko.zenei.user.runtime.DefaultUserAccountRepository;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.runtime.configuration.ConfigurationException;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Singleton;
import org.jboss.jandex.DotName;
Expand Down Expand Up @@ -40,77 +38,29 @@ ReactiveClientBuildItem findReactiveClient(Capabilities capabilities) {

@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem createBasicUserInitializerConfig(BasicUserAccountRecorder recorder,
ReactiveClientBuildItem reactiveClient) {
final String createTableDdl;
final boolean supportsIfTableNotExists = switch (reactiveClient.getReactiveClient()) {
case REACTIVE_PG_CLIENT -> {
createTableDdl = "CREATE TABLE IF NOT EXISTS zenei_user_account ("
+ "id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "
+ "username VARCHAR(100) NOT NULL, "
+ "image VARCHAR(1000) NULL, "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL)";
yield true;
}
case REACTIVE_MYSQL_CLIENT -> {
createTableDdl = "CREATE TABLE IF NOT EXISTS zenei_user_account ("
+ "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+ "username VARCHAR(100) NOT NULL, "
+ "image VARCHAR(1000) NULL, "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL)";
yield true;
}
case REACTIVE_MSSQL_CLIENT -> {
createTableDdl = "CREATE TABLE zenei_user_account ("
+ "id BIGINT IDENTITY(1,1) PRIMARY KEY, "
+ "username NVARCHAR(100) NOT NULL, "
+ "image NVARCHAR(1000) NULL, "
+ "created_at DATETIME2 NOT NULL, "
+ "updated_at DATETIME2 NOT NULL)";
yield false; // MSSQL doesn't support `IF NOT EXISTS` directly
}
case REACTIVE_DB2_CLIENT -> {
createTableDdl = "CREATE TABLE zenei_user_account ("
+ "id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), "
+ "username VARCHAR(100) NOT NULL, "
+ "image VARCHAR(1000), "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL, "
+ "PRIMARY KEY (id))";
yield false; // DB2 doesn't support `IF NOT EXISTS` directly
}
case REACTIVE_ORACLE_CLIENT -> {
createTableDdl = "CREATE TABLE zenei_user_account ("
+ "id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "
+ "username VARCHAR2(100) NOT NULL, "
+ "image VARCHAR2(1000), "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL)";
yield true;
}
default -> throw new ConfigurationException("Unknown Reactive Sql Client " + reactiveClient);
};
SyntheticBeanBuildItem createBasicUserInitializerConfig(DefaultUserAccountRecorder recorder,
Capabilities capabilities) {
final Map<String, Boolean> tableSchemas = Arrays.stream(DefaultTableSchema.schemas(capabilities))
.collect(Collectors.toMap(TableSchema::getDdl, TableSchema::isSupportsIfNotExists));

return SyntheticBeanBuildItem
.configure(BasicUserAccountInitializer.UserAccountInitializerProperties.class)
.supplier(recorder.createUserAccountInitializerProps(createTableDdl, supportsIfTableNotExists))
.configure(DefaultUserAccountInitializer.InitializerProperties.class)
.supplier(recorder.createUserAccountInitializerProps(tableSchemas))
.scope(Dependent.class)
.unremovable()
.done();
}

@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem createBasicUserAccountRepository(BasicUserAccountRecorder recorder,
SyntheticBeanBuildItem createBasicUserAccountRepository(DefaultUserAccountRecorder recorder,
ReactiveClientBuildItem reactiveClient) {
final Map<String, String> config = Arrays.stream(DefaultQuery.values())
.collect(Collectors.toMap(defaultQuery -> defaultQuery.getMetadata().queryKey(),
defaultQuery -> defaultQuery.getMetadata().getQuery(reactiveClient.getReactiveClient())));

return SyntheticBeanBuildItem
.configure(BasicUserAccountRepository.QueryProvider.class)
.configure(DefaultUserAccountRepository.QueryProvider.class)
.supplier(recorder.createBasicUserAccountRepositoryQueryProvider(config))
.scope(Dependent.class)
.unremovable()
Expand All @@ -122,15 +72,15 @@ AdditionalBeanBuildItem setupBeans() {
AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder();
builder.setDefaultScope(DotName.createSimple(Singleton.class.getName()));

builder.addBeanClass(BasicUserAccountInitializer.class);
builder.addBeanClass(BasicUserAccountRepository.class);
builder.addBeanClass(DefaultUserAccountInitializer.class);
builder.addBeanClass(DefaultUserAccountRepository.class);

return builder.build();
}

@BuildStep
AdditionalBeanBuildItem setupManager() {
return new AdditionalBeanBuildItem(BasicUserAccountManager.class);
return new AdditionalBeanBuildItem(DefaultUserAccountManager.class);
}

private static final class ReactiveClientBuildItem extends SimpleBuildItem {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,33 @@

public enum DefaultQuery {
USER_ACCOUNT_FIND_BY_IDENTIFIER(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM zenei_user_account WHERE id = %s",
"SELECT id, username, created_at, updated_at FROM zenei_user_account WHERE id = %s",
1,
QueryRegistry.USER_ACCOUNT_FIND_BY_IDENTIFIER
)),
USER_ACCOUNT_FIND_BY_USERNAME(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM zenei_user_account WHERE username = %s",
"SELECT id, username, created_at, updated_at FROM zenei_user_account WHERE username = %s",
1,
QueryRegistry.USER_ACCOUNT_FIND_BY_USERNAME
)),
USER_ACCOUNT_LIST(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM zenei_user_account",
"SELECT id, username, created_at, updated_at FROM zenei_user_account",
0,
QueryRegistry.USER_ACCOUNT_LIST
)),
USER_ACCOUNT_LIST_PAGINATED(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM zenei_user_account LIMIT %s OFFSET %s",
"SELECT id, username, created_at, updated_at FROM zenei_user_account LIMIT %s OFFSET %s",
2,
QueryRegistry.USER_ACCOUNT_LIST_PAGINATED
)),
USER_ACCOUNT_CREATE(QueryMetadata.of(
"INSERT INTO zenei_user_account (username, image, created_at, updated_at) VALUES (%s, %s, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id, username, image, created_at, updated_at",
2,
"INSERT INTO zenei_user_account (username, created_at, updated_at) VALUES (%s, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id, username, created_at, updated_at",
1,
QueryRegistry.USER_ACCOUNT_CREATE
)),
USER_ACCOUNT_UPDATE(QueryMetadata.of(
"UPDATE zenei_user_account SET username = %s, image = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s RETURNING id, username, image, created_at, updated_at",
3,
"UPDATE zenei_user_account SET username = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s RETURNING id, username, created_at, updated_at",
2,
QueryRegistry.USER_ACCOUNT_UPDATE
)),
USER_ACCOUNT_DELETE(QueryMetadata.of(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package dev.cloudeko.zenei.user.deployment;

import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;

import java.util.Arrays;
import java.util.function.Function;

public enum DefaultTableSchema {
USER_ACCOUNT("zenei_user_account", UserAccountTableSchema::fromClient);

private final String tableName;
private final Function<Capabilities, TableSchema> schemaFunction;

DefaultTableSchema(String tableName, Function<Capabilities, TableSchema> schemaFunction) {
this.tableName = tableName;
this.schemaFunction = schemaFunction;
}

public static TableSchema[] schemas(Capabilities capabilities) {
return Arrays.stream(values())
.map(schema -> schema.getSchema(capabilities))
.toArray(TableSchema[]::new);
}

public String getTableName() {
return tableName;
}

public TableSchema getSchema(Capabilities capabilities) {
return schemaFunction.apply(capabilities);
}

public enum UserAccountTableSchema implements TableSchema {
REACTIVE_PG_CLIENT(Capability.REACTIVE_PG_CLIENT,
"CREATE TABLE IF NOT EXISTS zenei_user_account ("
+ "id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "
+ "username VARCHAR(100) NOT NULL, "
+ "image VARCHAR(1000) NULL, "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL)",
true),
REACTIVE_MYSQL_CLIENT(Capability.REACTIVE_MYSQL_CLIENT,
"CREATE TABLE IF NOT EXISTS zenei_user_account ("
+ "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+ "username VARCHAR(100) NOT NULL, "
+ "image VARCHAR(1000) NULL, "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL)",
true),
REACTIVE_MSSQL_CLIENT(Capability.REACTIVE_MSSQL_CLIENT,
"CREATE TABLE zenei_user_account ("
+ "id BIGINT IDENTITY(1,1) PRIMARY KEY, "
+ "username NVARCHAR(100) NOT NULL, "
+ "image NVARCHAR(1000) NULL, "
+ "created_at DATETIME2 NOT NULL, "
+ "updated_at DATETIME2 NOT NULL)",
false),
REACTIVE_DB2_CLIENT(Capability.REACTIVE_DB2_CLIENT,
"CREATE TABLE zenei_user_account ("
+ "id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), "
+ "username VARCHAR(100) NOT NULL, "
+ "image VARCHAR(1000), "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL, "
+ "PRIMARY KEY (id))",
false),
REACTIVE_ORACLE_CLIENT(Capability.REACTIVE_ORACLE_CLIENT,
"CREATE TABLE zenei_user_account ("
+ "id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "
+ "username VARCHAR2(100) NOT NULL, "
+ "image VARCHAR2(1000), "
+ "created_at TIMESTAMP NOT NULL, "
+ "updated_at TIMESTAMP NOT NULL)",
true);

private final String client;
private final String ddl;
private final boolean supportsIfNotExists;

UserAccountTableSchema(String client, String ddl, boolean supportsIfNotExists) {
this.client = client;
this.ddl = ddl;
this.supportsIfNotExists = supportsIfNotExists;
}

public static UserAccountTableSchema fromClient(Capabilities capabilities) {
for (UserAccountTableSchema schema : values()) {
if (capabilities.isPresent(schema.client)) {
return schema;
}
}
throw new IllegalArgumentException("No table schema found for the given capabilities");
}

public String getClient() {
return client;
}

public String getDdl() {
return ddl;
}

public boolean isSupportsIfNotExists() {
return supportsIfNotExists;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.cloudeko.zenei.user.deployment;

public interface TableSchema {
String getClient();

String getDdl();

boolean isSupportsIfNotExists();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dev.cloudeko.zenei.user;

import io.vertx.sqlclient.Row;

import java.time.LocalDateTime;
import java.util.List;

public abstract class BasicUserAccount<ID> extends UserAccount<ID> {

protected String username;

private List<EmailAddress> emailAddresses;
private List<PhoneNumber> phoneNumbers;

public BasicUserAccount() {

}

public BasicUserAccount(String username) {
this.username = username;
}

protected BasicUserAccount(ID id, LocalDateTime createdAt, LocalDateTime updatedAt) {
super(id, createdAt, updatedAt);
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public EmailAddress getPrimaryEmailAddress() {
return emailAddresses.stream().filter(EmailAddress::primary).findFirst().orElse(null);
}

public List<EmailAddress> getEmailAddresses() {
return emailAddresses;
}

public void setEmailAddresses(List<EmailAddress> emailAddresses) {
this.emailAddresses = emailAddresses;
}

public PhoneNumber getPrimaryPhoneNumber() {
return phoneNumbers.stream().filter(PhoneNumber::primary).findFirst().orElse(null);
}

public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}

public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
this.phoneNumbers = phoneNumbers;
}

public record EmailAddress(String email, boolean verified, boolean primary) {
}

public record PhoneNumber(String number, boolean verified, boolean primary) {
}
}
Loading

0 comments on commit 6b7a63d

Please sign in to comment.