Skip to content

Commit

Permalink
feat(user): added user account extension
Browse files Browse the repository at this point in the history
  • Loading branch information
zZHorizonZz committed Aug 22, 2024
1 parent a923637 commit 4d04784
Show file tree
Hide file tree
Showing 27 changed files with 945 additions and 0 deletions.
4 changes: 4 additions & 0 deletions extensions/core/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<groupId>dev.cloudeko</groupId>
<artifactId>external-authentication-extension-deployment</artifactId>
</dependency>
<dependency>
<groupId>dev.cloudeko</groupId>
<artifactId>zenei-user-account-extension-deployment</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
Expand Down
4 changes: 4 additions & 0 deletions extensions/core/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<groupId>dev.cloudeko</groupId>
<artifactId>external-authentication-extension</artifactId>
</dependency>
<dependency>
<groupId>dev.cloudeko</groupId>
<artifactId>zenei-user-account-extension</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +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 jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
Expand All @@ -33,6 +36,8 @@ public class CreateUserImpl implements CreateUser {
private final UserRepository userRepository;
private final UserPasswordRepository userPasswordRepository;

private final BasicUserAccountManager userAccountManager;

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

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

userRepository.createUser(user);

if (createUserInput.isPasswordEnabled()) {
Expand Down
1 change: 1 addition & 0 deletions extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<modules>
<module>external-authentication</module>
<module>core</module>
<module>zenei-user-account</module>
<module>rest</module>
<module>jdbc-panache</module>
</modules>
Expand Down
70 changes: 70 additions & 0 deletions extensions/zenei-user-account/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.cloudeko</groupId>
<artifactId>zenei-user-account-extension-parent</artifactId>
<version>0.0.1</version>
</parent>

<name>Zenei - Extensions - Rest - Deployment</name>
<artifactId>zenei-user-account-extension-deployment</artifactId>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jsonp-deployment</artifactId>
</dependency>

<dependency>
<groupId>dev.cloudeko</groupId>
<artifactId>zenei-user-account-extension</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package dev.cloudeko.zenei.user.deployment;

import dev.cloudeko.zenei.user.UserAccountRepositoryBase;
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 io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.runtime.configuration.ConfigurationException;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Singleton;

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

import static io.quarkus.deployment.Capability.*;
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;

public class BasicUserAccountManagerProcessor {

private static final String[] SUPPORTED_REACTIVE_CLIENTS = new String[] { REACTIVE_PG_CLIENT, REACTIVE_MYSQL_CLIENT,
REACTIVE_MSSQL_CLIENT, REACTIVE_DB2_CLIENT, REACTIVE_ORACLE_CLIENT };

@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem createDbTokenStateInitializerProps(BasicUserAccountRecorder recorder, Capabilities capabilities) {
final String reactiveClient = capabilities.getCapabilities().stream()
.filter(c -> Arrays.asList(SUPPORTED_REACTIVE_CLIENTS).contains(c))
.findFirst()
.orElseThrow(() -> new RuntimeException("No supported reactive SQL client found"));

final String createTableDdl;
final boolean supportsIfTableNotExists = switch (reactiveClient) {
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);
};

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

@BuildStep
AdditionalBeanBuildItem createBasicUserAccountInitializerBean() {
return new AdditionalBeanBuildItem(BasicUserAccountInitializer.class);
}

@BuildStep
@Record(STATIC_INIT)
SyntheticBeanBuildItem syntheticBeanBuildItem(BasicUserAccountRecorder recorder, Capabilities capabilities) {
final String reactiveClient = capabilities.getCapabilities().stream()
.filter(c -> Arrays.asList(SUPPORTED_REACTIVE_CLIENTS).contains(c))
.findFirst()
.orElseThrow(() -> new RuntimeException("No supported reactive SQL client found"));

final Map<String, String> config = Arrays.stream(DefaultQuery.values())
.collect(Collectors.toMap(defaultQuery -> defaultQuery.getMetadata().queryKey(),
defaultQuery -> defaultQuery.getMetadata().getQuery(reactiveClient)));

return SyntheticBeanBuildItem
.configure(BasicUserAccountRepository.class)
.addType(UserAccountRepositoryBase.class)
.unremovable()
.scope(Singleton.class)
.supplier(recorder.createBasicUserAccountRepository(config))
.done();
}

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

@BuildStep
@Record(RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
void setSqlClientPool(BasicUserAccountRecorder recorder, BeanContainerBuildItem beanContainer) {
recorder.setSqlClientPool(beanContainer.getValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dev.cloudeko.zenei.user.deployment;

import dev.cloudeko.zenei.user.QueryRegistry;

public enum DefaultQuery {
USER_ACCOUNT_FIND_BY_IDENTIFIER(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM user_account WHERE id = ?",
1,
QueryRegistry.USER_ACCOUNT_FIND_BY_IDENTIFIER
)),
USER_ACCOUNT_FIND_BY_USERNAME(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM user_account WHERE username = ?",
1,
QueryRegistry.USER_ACCOUNT_FIND_BY_USERNAME
)),
USER_ACCOUNT_LIST(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM user_account",
0,
QueryRegistry.USER_ACCOUNT_LIST
)),
USER_ACCOUNT_LIST_PAGINATED(QueryMetadata.of(
"SELECT id, username, image, created_at, updated_at FROM user_account LIMIT ? OFFSET ?",
2,
QueryRegistry.USER_ACCOUNT_LIST_PAGINATED
)),
USER_ACCOUNT_CREATE(QueryMetadata.of(
"INSERT INTO user_account (username, image, created_at, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id, username, image, created_at, updated_at",
2,
QueryRegistry.USER_ACCOUNT_CREATE
)),
USER_ACCOUNT_UPDATE(QueryMetadata.of(
"UPDATE user_account SET username = ?, image = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? RETURNING id, username, image, created_at, updated_at",
3,
QueryRegistry.USER_ACCOUNT_UPDATE
)),
USER_ACCOUNT_DELETE(QueryMetadata.of(
"DELETE FROM user_account WHERE id = ? RETURNING id, username, image, created_at, updated_at",
1,
QueryRegistry.USER_ACCOUNT_DELETE
));

private final QueryMetadata metadata;

DefaultQuery(QueryMetadata metadata) {
this.metadata = metadata;
}

public QueryMetadata getMetadata() {
return metadata;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.cloudeko.zenei.user.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;

public class ExternalAuthBuildSteps {

private static final String FEATURE = "zenei-user-account";

@BuildStep
public FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}

/*@BuildStep
public RouteBuildItem route(NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {
return nonApplicationRootPathBuildItem.routeBuilder()
.route("user")
.handler(new UserInfoHandler())
.displayOnNotFoundPage()
.build();
}*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.cloudeko.zenei.user.deployment;

import io.quarkus.deployment.Capability;

public record QueryMetadata(String query, int parameters, String queryKey) {

public QueryMetadata {
if (query == null || query.isBlank()) {
throw new IllegalArgumentException("Query cannot be null or empty");
}
if (queryKey == null || queryKey.isBlank()) {
throw new IllegalArgumentException("Query key cannot be null or empty");
}
}

public static QueryMetadata of(String query, int parameters, String queryKey) {
return new QueryMetadata(query, parameters, queryKey);
}

public String getQuery(String client) {
final String[] placeholders = new String[parameters];
for (int i = 0; i < parameters; i++) {
placeholders[i] = switch (client) {
case Capability.REACTIVE_PG_CLIENT -> "$" + (i + 1);
case Capability.REACTIVE_MSSQL_CLIENT -> "@p" + (i + 1);
case Capability.REACTIVE_DB2_CLIENT, Capability.REACTIVE_ORACLE_CLIENT, Capability.REACTIVE_MYSQL_CLIENT -> "?";
default -> throw new IllegalArgumentException("Unknown Reactive Sql Client " + client);
};
}

return String.format(query, (Object[]) placeholders);
}
}
20 changes: 20 additions & 0 deletions extensions/zenei-user-account/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.cloudeko</groupId>
<artifactId>extensions</artifactId>
<version>0.0.1</version>
</parent>

<name>Zenei - Extensions - User Account - Parent</name>
<artifactId>zenei-user-account-extension-parent</artifactId>
<packaging>pom</packaging>

<modules>
<module>deployment</module>
<module>runtime</module>
</modules>
</project>
Loading

0 comments on commit 4d04784

Please sign in to comment.