diff --git a/extensions/core/runtime/src/main/java/dev/cloudeko/zenei/extension/core/feature/impl/CreateUserImpl.java b/extensions/core/runtime/src/main/java/dev/cloudeko/zenei/extension/core/feature/impl/CreateUserImpl.java index 8eb8ae6..e323448 100644 --- a/extensions/core/runtime/src/main/java/dev/cloudeko/zenei/extension/core/feature/impl/CreateUserImpl.java +++ b/extensions/core/runtime/src/main/java/dev/cloudeko/zenei/extension/core/feature/impl/CreateUserImpl.java @@ -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; @@ -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 @@ -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); diff --git a/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/BasicUserAccountManagerProcessor.java b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/BasicUserAccountManagerProcessor.java index edd427b..94aac8e 100644 --- a/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/BasicUserAccountManagerProcessor.java +++ b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/BasicUserAccountManagerProcessor.java @@ -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; @@ -40,62 +38,14 @@ 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 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(); @@ -103,14 +53,14 @@ SyntheticBeanBuildItem createBasicUserInitializerConfig(BasicUserAccountRecorder @BuildStep @Record(STATIC_INIT) - SyntheticBeanBuildItem createBasicUserAccountRepository(BasicUserAccountRecorder recorder, + SyntheticBeanBuildItem createBasicUserAccountRepository(DefaultUserAccountRecorder recorder, ReactiveClientBuildItem reactiveClient) { final Map 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() @@ -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 { diff --git a/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/DefaultQuery.java b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/DefaultQuery.java index 8f744ea..6569e21 100644 --- a/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/DefaultQuery.java +++ b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/DefaultQuery.java @@ -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( diff --git a/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/DefaultTableSchema.java b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/DefaultTableSchema.java new file mode 100644 index 0000000..5e81c49 --- /dev/null +++ b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/DefaultTableSchema.java @@ -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 schemaFunction; + + DefaultTableSchema(String tableName, Function 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; + } + } +} diff --git a/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/TableSchema.java b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/TableSchema.java new file mode 100644 index 0000000..e84240f --- /dev/null +++ b/extensions/zenei-user-account/deployment/src/main/java/dev/cloudeko/zenei/user/deployment/TableSchema.java @@ -0,0 +1,9 @@ +package dev.cloudeko.zenei.user.deployment; + +public interface TableSchema { + String getClient(); + + String getDdl(); + + boolean isSupportsIfNotExists(); +} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/BasicUserAccount.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/BasicUserAccount.java new file mode 100644 index 0000000..a5af4de --- /dev/null +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/BasicUserAccount.java @@ -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 extends UserAccount { + + protected String username; + + private List emailAddresses; + private List 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 getEmailAddresses() { + return emailAddresses; + } + + public void setEmailAddresses(List emailAddresses) { + this.emailAddresses = emailAddresses; + } + + public PhoneNumber getPrimaryPhoneNumber() { + return phoneNumbers.stream().filter(PhoneNumber::primary).findFirst().orElse(null); + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + public record EmailAddress(String email, boolean verified, boolean primary) { + } + + public record PhoneNumber(String number, boolean verified, boolean primary) { + } +} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccount.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccount.java index 53a58ef..d42004c 100644 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccount.java +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccount.java @@ -5,27 +5,18 @@ public abstract class UserAccount { private ID id; - private String username; - private String image; private LocalDateTime createdAt; private LocalDateTime updatedAt; public UserAccount() { } - public UserAccount(String username, String image) { - this(null, username, image); + public UserAccount(ID id) { + this(id, null, null); } - public UserAccount(ID id, String username, String image) { - this(id, username, image, null, null); - } - - public UserAccount(ID id, String username, String image, LocalDateTime createdAt, - LocalDateTime updatedAt) { + public UserAccount(ID id, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; - this.username = username; - this.image = image; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -38,22 +29,6 @@ public void setId(ID id) { this.id = id; } - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -69,10 +44,4 @@ public LocalDateTime getUpdatedAt() { public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } - - public record EmailAddress(String email, boolean verified, boolean primary) { - } - - public record PhoneNumber(String number, boolean verified, boolean primary) { - } } diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountManager.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountManager.java index 80f269b..f644d64 100644 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountManager.java +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountManager.java @@ -55,9 +55,9 @@ default ENTITY updateUserBlocking(ENTITY entity) { return updateUser(entity).await().indefinitely(); } - Uni deleteUser(ENTITY entity); + Uni deleteUser(ID identifier); - default boolean deleteUserBlocking(ENTITY entity) { - return deleteUser(entity).await().indefinitely(); + default boolean deleteUserBlocking(ID identifier) { + return deleteUser(identifier).await().indefinitely(); } } diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountRepositoryBase.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountRepositoryBase.java index 47cf558..d8f666c 100644 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountRepositoryBase.java +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/UserAccountRepositoryBase.java @@ -18,5 +18,5 @@ public interface UserAccountRepositoryBase { Uni updateUser(ENTITY entity); - Uni deleteUser(ENTITY entity); + Uni deleteUser(ID identifier); } diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccount.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccount.java deleted file mode 100644 index 7ffd501..0000000 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccount.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.cloudeko.zenei.user.runtime; - -import dev.cloudeko.zenei.user.UserAccount; -import io.vertx.sqlclient.Row; - -public class BasicUserAccount extends UserAccount { - - public BasicUserAccount() { - super(); - } - - public BasicUserAccount(String username, String image) { - super(username, image); - } - - public BasicUserAccount(Row row) { - super(row.getLong("id"), - row.getString("username"), - row.getString("image"), - row.getLocalDateTime("created_at"), - row.getLocalDateTime("updated_at")); - } -} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountManager.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountManager.java deleted file mode 100644 index d7b30a5..0000000 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountManager.java +++ /dev/null @@ -1,64 +0,0 @@ -package dev.cloudeko.zenei.user.runtime; - -import dev.cloudeko.zenei.user.UserAccountManager; -import dev.cloudeko.zenei.user.UserAccountRepositoryBase; -import io.smallrye.mutiny.Uni; -import org.jboss.logging.Logger; - -import java.util.List; - -public class BasicUserAccountManager implements UserAccountManager { - - private static final Logger log = Logger.getLogger(BasicUserAccountManager.class); - - private final UserAccountRepositoryBase userAccountRepository; - - public BasicUserAccountManager(UserAccountRepositoryBase userAccountRepository) { - this.userAccountRepository = userAccountRepository; - } - - @Override - public Uni findUserByIdentifier(Long identifier) { - return userAccountRepository.findUserByIdentifier(identifier); - } - - @Override - public Uni findUserByPrimaryEmailAddress(String email) { - return null; - } - - @Override - public Uni findUserByPrimaryPhoneNumber(String phoneNumber) { - return null; - } - - @Override - public Uni findUserByUsername(String username) { - return userAccountRepository.findUserByUsername(username); - } - - @Override - public Uni> listUsers() { - return userAccountRepository.listUsers(); - } - - @Override - public Uni> listUsers(int page, int pageSize) { - return userAccountRepository.listUsers(page, pageSize); - } - - @Override - public Uni createUser(BasicUserAccount basicUserAccount) { - return userAccountRepository.createUser(basicUserAccount); - } - - @Override - public Uni updateUser(BasicUserAccount basicUserAccount) { - return userAccountRepository.updateUser(basicUserAccount); - } - - @Override - public Uni deleteUser(BasicUserAccount basicUserAccount) { - return userAccountRepository.deleteUser(basicUserAccount); - } -} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountRecorder.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountRecorder.java deleted file mode 100644 index 724b7f8..0000000 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountRecorder.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.cloudeko.zenei.user.runtime; - -import io.quarkus.runtime.annotations.Recorder; - -import java.util.Map; -import java.util.function.Supplier; - -@Recorder -public class BasicUserAccountRecorder { - - public Supplier createBasicUserAccountRepositoryQueryProvider(Map config) { - return () -> new BasicUserAccountRepository.QueryProvider(config); - } - - public Supplier createUserAccountInitializerProps( - String createTableDdl, - boolean supportsIfTableNotExists) { - return () -> new BasicUserAccountInitializer.UserAccountInitializerProperties(createTableDdl, supportsIfTableNotExists); - } -} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccount.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccount.java new file mode 100644 index 0000000..ea73dda --- /dev/null +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccount.java @@ -0,0 +1,20 @@ +package dev.cloudeko.zenei.user.runtime; + +import dev.cloudeko.zenei.user.BasicUserAccount; +import io.vertx.sqlclient.Row; + +public class DefaultUserAccount extends BasicUserAccount { + + public DefaultUserAccount() { + } + + public DefaultUserAccount(String username) { + super(username); + } + + public DefaultUserAccount(Row row) { + super(row.getLong("id"), row.getLocalDateTime("created_at"), row.getLocalDateTime("updated_at")); + + this.username = row.getString("username"); + } +} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountInitializer.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountInitializer.java similarity index 70% rename from extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountInitializer.java rename to extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountInitializer.java index 327ffbc..6f0dd74 100644 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountInitializer.java +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountInitializer.java @@ -7,13 +7,26 @@ import jakarta.enterprise.event.Observes; import org.jboss.logging.Logger; -public class BasicUserAccountInitializer { +import java.util.Arrays; +import java.util.Map; - private static final Logger log = Logger.getLogger(BasicUserAccountInitializer.class); +public class DefaultUserAccountInitializer { + + private static final Logger log = Logger.getLogger(DefaultUserAccountInitializer.class); private static final String FAILED_TO_CREATE_DB_TABLE = "unknown reason, please report the issue and create table manually"; - void initialize(@Observes StartupEvent event, Vertx vertx, Pool pool, UserAccountInitializerProperties initializerProps) { - createDatabaseTable(pool, initializerProps.createTableDdl, initializerProps.supportsIfTableNotExists); + void initialize(@Observes StartupEvent event, Vertx vertx, Pool pool, InitializerProperties properties) { + log.debug("Initializing user account database tables"); + + Map tableSchemas = properties.tables(); + if (tableSchemas == null || tableSchemas.isEmpty()) { + log.warn("No table schemas provided, skipping database table creation"); + return; + } + + tableSchemas.forEach((ddl, supportsIfNotExists) -> { + createDatabaseTable(pool, ddl, supportsIfNotExists); + }); } private static void createDatabaseTable(Pool pool, String createTableDdl, boolean supportsIfTableNotExists) { @@ -34,7 +47,7 @@ private static void createDatabaseTable(Pool pool, String createTableDdl, boolea String errMsg = tableCreationResult.await().indefinitely(); if (errMsg != null) { - throw new RuntimeException("OIDC Token State Manager failed to create database table: " + errMsg); + throw new RuntimeException("Failed to create database table: " + errMsg); } } @@ -53,6 +66,7 @@ private static Uni verifyTableExists(Pool pool) { }); } - public record UserAccountInitializerProperties(String createTableDdl, boolean supportsIfTableNotExists) { + public record InitializerProperties(Map tables) { + } } \ No newline at end of file diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountManager.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountManager.java new file mode 100644 index 0000000..f27ed11 --- /dev/null +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountManager.java @@ -0,0 +1,63 @@ +package dev.cloudeko.zenei.user.runtime; + +import dev.cloudeko.zenei.user.UserAccountManager; +import io.smallrye.mutiny.Uni; +import org.jboss.logging.Logger; + +import java.util.List; + +public class DefaultUserAccountManager implements UserAccountManager { + + private static final Logger log = Logger.getLogger(DefaultUserAccountManager.class); + + private final DefaultUserAccountRepository userAccountRepository; + + public DefaultUserAccountManager(DefaultUserAccountRepository userAccountRepository) { + this.userAccountRepository = userAccountRepository; + } + + @Override + public Uni findUserByIdentifier(Long identifier) { + return userAccountRepository.findUserByIdentifier(identifier); + } + + @Override + public Uni findUserByPrimaryEmailAddress(String email) { + return null; + } + + @Override + public Uni findUserByPrimaryPhoneNumber(String phoneNumber) { + return null; + } + + @Override + public Uni findUserByUsername(String username) { + return userAccountRepository.findUserByUsername(username); + } + + @Override + public Uni> listUsers() { + return userAccountRepository.listUsers(); + } + + @Override + public Uni> listUsers(int page, int pageSize) { + return userAccountRepository.listUsers(page, pageSize); + } + + @Override + public Uni createUser(DefaultUserAccount basicUserAccount) { + return userAccountRepository.createUser(basicUserAccount); + } + + @Override + public Uni updateUser(DefaultUserAccount basicUserAccount) { + return userAccountRepository.updateUser(basicUserAccount); + } + + @Override + public Uni deleteUser(Long identifier) { + return userAccountRepository.deleteUser(identifier); + } +} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountRecorder.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountRecorder.java new file mode 100644 index 0000000..2b76c5f --- /dev/null +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountRecorder.java @@ -0,0 +1,20 @@ +package dev.cloudeko.zenei.user.runtime; + +import io.quarkus.runtime.annotations.Recorder; + +import java.util.Map; +import java.util.function.Supplier; + +@Recorder +public class DefaultUserAccountRecorder { + + public Supplier createBasicUserAccountRepositoryQueryProvider( + Map config) { + return () -> new DefaultUserAccountRepository.QueryProvider(config); + } + + public Supplier createUserAccountInitializerProps( + Map tableSchemas) { + return () -> new DefaultUserAccountInitializer.InitializerProperties(tableSchemas); + } +} diff --git a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountRepository.java b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountRepository.java similarity index 70% rename from extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountRepository.java rename to extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountRepository.java index d52ff84..367fe89 100644 --- a/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/BasicUserAccountRepository.java +++ b/extensions/zenei-user-account/runtime/src/main/java/dev/cloudeko/zenei/user/runtime/DefaultUserAccountRepository.java @@ -9,46 +9,47 @@ import java.util.List; import java.util.Map; +import java.util.Objects; -public class BasicUserAccountRepository implements UserAccountRepositoryBase { +public class DefaultUserAccountRepository implements UserAccountRepositoryBase { - private static final Logger log = Logger.getLogger(BasicUserAccountRepository.class); + private static final Logger log = Logger.getLogger(DefaultUserAccountRepository.class); private static final String FAILED_TO_FIND_USER_BY_IDENTIFIER = "Failed to find user by identifier"; private final Map queries; private final Pool pool; - public BasicUserAccountRepository(QueryProvider queryProvider, Pool pool) { + public DefaultUserAccountRepository(QueryProvider queryProvider, Pool pool) { this.queries = queryProvider.queries(); this.pool = pool; } @Override - public Uni findUserByIdentifier(Long identifier) { + public Uni findUserByIdentifier(Long identifier) { return Uni.createFrom() .completionStage(pool .preparedQuery(queries.get(QueryRegistry.USER_ACCOUNT_FIND_BY_IDENTIFIER)) .execute(Tuple.of(identifier)) .toCompletionStage()) .onItem().transformToUni(this::processNullableRow) - .onItem().ifNotNull().transform(BasicUserAccount::new) + .onItem().ifNotNull().transform(DefaultUserAccount::new) .onFailure().invoke(throwable -> log.error(FAILED_TO_FIND_USER_BY_IDENTIFIER, throwable)); } @Override - public Uni findUserByUsername(String username) { + public Uni findUserByUsername(String username) { return Uni.createFrom() .completionStage(pool .preparedQuery(queries.get(QueryRegistry.USER_ACCOUNT_FIND_BY_USERNAME)) .execute(Tuple.of(username)) .toCompletionStage()) .onItem().transformToUni(this::processNullableRow) - .onItem().ifNotNull().transform(BasicUserAccount::new) + .onItem().ifNotNull().transform(DefaultUserAccount::new) .onFailure().invoke(throwable -> log.error(FAILED_TO_FIND_USER_BY_IDENTIFIER, throwable)); } @Override - public Uni> listUsers() { + public Uni> listUsers() { return Uni.createFrom() .completionStage(pool .preparedQuery(queries.get(QueryRegistry.USER_ACCOUNT_LIST)) @@ -56,13 +57,13 @@ public Uni> listUsers() { .toCompletionStage()) .onItem().transformToUni(rows -> Uni.createFrom().item(rows) .onItem().transformToMulti(rowSet -> Multi.createFrom().iterable(rowSet)) - .onItem().transform(BasicUserAccount::new) + .onItem().transform(DefaultUserAccount::new) .collect().asList()) .onFailure().invoke(throwable -> log.error(FAILED_TO_FIND_USER_BY_IDENTIFIER, throwable)); } @Override - public Uni> listUsers(int page, int pageSize) { + public Uni> listUsers(int page, int pageSize) { return Uni.createFrom() .completionStage(pool .preparedQuery(queries.get(QueryRegistry.USER_ACCOUNT_LIST_PAGINATED)) @@ -70,45 +71,58 @@ public Uni> listUsers(int page, int pageSize) { .toCompletionStage()) .onItem().transformToUni(rows -> Uni.createFrom().item(rows) .onItem().transformToMulti(rowSet -> Multi.createFrom().iterable(rowSet)) - .onItem().transform(BasicUserAccount::new) + .onItem().transform(DefaultUserAccount::new) .collect().asList()) .onFailure().invoke(throwable -> log.error(FAILED_TO_FIND_USER_BY_IDENTIFIER, throwable)); } @Override - public Uni createUser(BasicUserAccount basicUserAccount) { + public Uni createUser(DefaultUserAccount basicUserAccount) { + Objects.requireNonNull(basicUserAccount, "User must not be null"); + Objects.requireNonNull(basicUserAccount.getUsername(), "Username must not be null"); + + if (basicUserAccount.getId() != null) { + throw new IllegalArgumentException("User ID must be null when creating a new user"); + } + return Uni.createFrom() .completionStage(pool .withTransaction(client -> client .preparedQuery(queries.get(QueryRegistry.USER_ACCOUNT_CREATE)) - .execute(Tuple.of(basicUserAccount.getUsername(), basicUserAccount.getImage()))) + .execute(Tuple.of(basicUserAccount.getUsername()))) .toCompletionStage()) .onItem().transformToUni(this::processNullableRow) - .onItem().ifNotNull().transform(BasicUserAccount::new) + .onItem().ifNotNull().transform(DefaultUserAccount::new) .onFailure().transform(throwable -> new RuntimeException("Failed to create user", throwable)); } @Override - public Uni updateUser(BasicUserAccount basicUserAccount) { + public Uni updateUser(DefaultUserAccount basicUserAccount) { + Objects.requireNonNull(basicUserAccount, "User must not be null"); + Objects.requireNonNull(basicUserAccount.getId(), "User ID must not be null"); + Objects.requireNonNull(basicUserAccount.getUsername(), "Username must not be null"); + return Uni.createFrom() .completionStage(pool .withTransaction(client -> client .preparedQuery(queries.get(QueryRegistry.USER_ACCOUNT_UPDATE)) - .execute(Tuple.of(basicUserAccount.getUsername(), basicUserAccount.getImage(), + .execute(Tuple.of(basicUserAccount.getUsername(), basicUserAccount.getId()))) .toCompletionStage()) .onItem().transformToUni(this::processNullableRow) - .onItem().ifNotNull().transform(BasicUserAccount::new) + .onItem().ifNotNull().transform(DefaultUserAccount::new) .onFailure().transform(throwable -> new RuntimeException("Failed to update user", throwable)); } @Override - public Uni deleteUser(BasicUserAccount basicUserAccount) { + public Uni deleteUser(Long identifier) { + Objects.requireNonNull(identifier, "User ID must not be null"); + return Uni.createFrom() .completionStage(pool .withTransaction(client -> client .preparedQuery(queries.get(QueryRegistry.USER_ACCOUNT_DELETE)) - .execute(Tuple.of(basicUserAccount.getId()))) + .execute(Tuple.of(identifier))) .toCompletionStage()) .onItem().transformToUni(rows -> Uni.createFrom().item(rows.rowCount() == 1)) .onFailure().transform(throwable -> new RuntimeException("Failed to delete user", throwable));