Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sources/api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
<parent>
<groupId>tools.dynamia.modules</groupId>
<artifactId>tools.dynamia.modules.saas.parent</artifactId>
<version>3.4.4</version>
<version>3.5.0</version>
</parent>
<artifactId>tools.dynamia.modules.saas.api</artifactId>

<name>DynamiaModules - SaaS API</name>
<url>https://www.dynamia.tools/modules/saas</url>
<version>3.4.4</version>
<version>3.5.0</version>

<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,31 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tools.dynamia.integration.ms.MessageService;

/**
* Default Account SaaS configuration
* Default Spring configuration for the SaaS Account API module.
* <p>
* This configuration class sets up the essential beans required for multi-tenant
* account management, including the custom account scope and a fallback no-op
* implementation of {@link AccountServiceAPI}.
* <p>
* The configuration is automatically loaded when the module is present in the classpath,
* and provides sensible defaults that can be overridden by custom implementations.
*
* @author Mario Serrano Leones
*/
@Configuration
public class AccountAPIConfig {


/**
* This bean is used to register the AccountScope in the application context.
* @return CustomScopeConfigurer
* Registers the custom "account" scope with the Spring container.
* <p>
* This bean configures the {@link AccountScope} which enables beans to be scoped
* at the account level, ensuring proper isolation in multi-tenant environments.
* Once registered, beans can use {@code @Scope("account")} to be managed at the account level.
*
* @return a {@link CustomScopeConfigurer} with the account scope registered
* @see AccountScope
*/
@Bean
public CustomScopeConfigurer accountScopeConfigurer() {
Expand All @@ -42,10 +55,14 @@ public CustomScopeConfigurer accountScopeConfigurer() {
}

/**
* If no AccountServiceAPI is configured, this bean will be used as a default.
* This is useful for testing.
* Provides a no-op implementation of {@link AccountServiceAPI} as a fallback.
* <p>
* This bean is only created if no other {@link AccountServiceAPI} implementation
* is found in the application context. It's primarily useful for testing scenarios
* or when the SaaS features are not fully configured.
*
* @return NoOpAccountServiceAPI instance
* @return a {@link NoOpAccountServiceAPI} instance
* @see NoOpAccountServiceAPI
*/
@Bean
@ConditionalOnMissingBean(AccountServiceAPI.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,49 @@
import tools.dynamia.actions.ActionSelfFilter;
import tools.dynamia.integration.Containers;

/**
* Base class for administrative actions that require authorization in a SaaS environment.
* <p>
* This class extends {@link AbstractAction} and provides built-in support for authorization
* checks before executing administrative operations. It integrates with
* {@link AccountAdminActionAuthorizationProvider} to enforce security policies.
* <p>
* Actions extending this class can require authorization by setting the
* {@code authorizationRequired} flag. When enabled, the action will delegate to the
* configured authorization provider before executing.
* <p>
* Example usage:
* <pre>{@code
* public class DeleteAccountAction extends AccountAdminAction {
* public DeleteAccountAction() {
* setName("Delete Account");
* setAuthorizationRequired(true);
* }
*
* @Override
* public void actionPerformed(ActionEvent evt) {
* // Delete account logic
* }
* }
* }</pre>
*
* @author Mario Serrano Leones
* @see AccountAdminActionAuthorizationProvider
* @see AbstractAction
*/
public abstract class AccountAdminAction extends AbstractAction implements ActionSelfFilter {

private boolean authorizationRequired;


/**
* Hook method executed before the action is performed.
* <p>
* If authorization is required, this method will delegate to the
* {@link AccountAdminActionAuthorizationProvider} to perform authorization checks.
* The actual action will only execute if authorization is granted.
*
* @param evt the action event
*/
@Override
public void beforeActionPerformed(ActionEvent evt) {
if (isAuthorizationRequired()) {
Expand All @@ -39,15 +77,33 @@ public void beforeActionPerformed(ActionEvent evt) {
}
}

/**
* Hook method executed after the action is performed.
* <p>
* This implementation does nothing and can be overridden by subclasses
* to add post-execution logic.
*
* @param evt the action event
*/
@Override
public void afterActionPerformed(ActionEvent evt) {
//do nothing
}

/**
* Checks if authorization is required before executing this action.
*
* @return true if authorization is required, false otherwise
*/
public boolean isAuthorizationRequired() {
return authorizationRequired;
}

/**
* Sets whether authorization is required before executing this action.
*
* @param authorizationRequired true to require authorization, false otherwise
*/
public void setAuthorizationRequired(boolean authorizationRequired) {
this.authorizationRequired = authorizationRequired;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,51 @@
import tools.dynamia.actions.ActionEvent;
import tools.dynamia.commons.Callback;

/**
* Interface for providing custom authorization logic for administrative actions in a SaaS environment.
* <p>
* This interface allows modules to implement custom authorization checks before executing
* administrative actions on accounts. It provides a hook point for adding security layers,
* audit requirements, or business rules that must be validated before an action proceeds.
* <p>
* The authorization process is asynchronous, using a callback mechanism to either grant or deny access.
* This design allows for complex authorization workflows, including user confirmations, multi-factor
* authentication, or external authorization services.
* <p>
* Example usage:
* <pre>{@code
* @Component
* public class MFAAuthorizationProvider implements AccountAdminActionAuthorizationProvider {
* @Override
* public void authorize(Action action, ActionEvent evt, Callback onAuthorization) {
* if (requiresMFA(action)) {
* promptForMFA(success -> {
* if (success) {
* onAuthorization.doSomething();
* }
* });
* } else {
* onAuthorization.doSomething();
* }
* }
* }
* }</pre>
*
* @author Mario Serrano Leones
*/
public interface AccountAdminActionAuthorizationProvider {

/**
* Performs authorization checks for an administrative action.
* <p>
* This method is called before executing administrative actions on accounts.
* Implementations should perform any necessary authorization checks and invoke
* the callback if authorization is granted. If authorization is denied, the
* callback should not be invoked.
*
* @param action the action being authorized
* @param evt the event context containing information about the action execution
* @param onAuthorization callback to invoke if authorization is granted
*/
void authorize(Action action, ActionEvent evt, Callback onAuthorization);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,45 @@
package tools.dynamia.modules.saas.api;

/**
* Interface for entities or components that belong to a specific account in a multi-tenant SaaS environment.
* <p>
* Implementing this interface allows objects to be associated with an account, enabling proper data isolation
* and tenant-specific operations. This is a fundamental interface for multi-tenancy support, ensuring that
* each entity knows which account it belongs to.
* <p>
* Example usage:
* <pre>{@code
* @Entity
* public class Customer implements AccountAware {
* @Column
* private Long accountId;
*
* public Long getAccountId() {
* return accountId;
* }
*
* public void setAccountId(Long accountId) {
* this.accountId = accountId;
* }
* }
* }</pre>
*
* @author Mario Serrano Leones
*/
public interface AccountAware {

/**
* Returns the unique identifier of the account this entity belongs to.
*
* @return the account ID
*/
Long getAccountId();

/**
* Sets the account this entity belongs to.
*
* @param account the account ID to assign to this entity
*/
void setAccountId(Long account);

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,48 @@

package tools.dynamia.modules.saas.api;

/**
* Provider interface for defining account features in a SaaS multi-tenant environment.
* <p>
* This interface allows modules to register features that can be enabled or disabled
* per account. Features can be used to control access to specific functionality,
* allowing different subscription tiers or custom feature sets for different accounts.
* <p>
* Implementations should be registered as Spring beans and will be automatically
* discovered by the SaaS module to populate the available features list.
* <p>
* Example usage:
* <pre>{@code
* @Component
* public class AdvancedReportsFeature implements AccountFeatureProvider {
* public String getId() {
* return "advanced-reports";
* }
*
* public String getName() {
* return "Advanced Reports";
* }
* }
* }</pre>
*
* @author Mario Serrano Leones
*/
public interface AccountFeatureProvider {

/**
* Returns the unique identifier for this feature.
* This ID is used to reference the feature programmatically throughout the application.
*
* @return the feature identifier (e.g., "advanced-reports", "api-access")
*/
String getId();

/**
* Returns the human-readable name of this feature.
* This name is typically displayed in the user interface for feature management.
*
* @return the feature display name
*/
String getName();

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,75 @@
import java.util.Locale;

/**
* Interface for components that perform initialization tasks when a new account is created in a SaaS environment.
* <p>
* Implementations of this interface are automatically discovered and executed during account creation,
* allowing modules to set up initial data, configurations, or resources for new accounts.
* Multiple initializers can be chained together, with execution order controlled by priority.
* <p>
* Common use cases include:
* <ul>
* <li>Creating default entities or master data</li>
* <li>Setting up initial configurations</li>
* <li>Creating default user roles</li>
* <li>Initializing account-specific resources</li>
* </ul>
* <p>
* Example usage:
* <pre>{@code
* @Component
* public class DefaultRolesInitializer implements AccountInitializer {
* @Override
* public void init(AccountDTO accountDTO) {
* // Create default roles for the new account
* createRole(accountDTO, "ADMIN");
* createRole(accountDTO, "USER");
* }
*
* @Override
* public int getPriority() {
* return 100; // Execute after basic setup
* }
* }
* }</pre>
*
* @author Mario Serrano Leones
*/
public interface AccountInitializer {

/**
* Performs initialization tasks for a newly created account.
* <p>
* This method is called automatically after an account is created and persisted.
* Implementations should create any necessary initial data or configurations
* required for the account to function properly.
*
* @param accountDTO the newly created account's data transfer object
*/
void init(AccountDTO accountDTO);

/**
* Define account initializer priority or order. Lower value is high priority
* Defines the execution priority for this initializer.
* <p>
* When multiple initializers exist, they are executed in order from lowest to highest priority value.
* Lower values indicate higher priority and will execute first.
*
* @return
* @return the priority value (default is 0, lower values execute first)
*/
default int getPriority() {
return 0;
}


/**
* Retrieves a localized message for the account's configured locale.
* <p>
* This helper method simplifies obtaining localized strings during account initialization,
* using the account's locale setting if available, or falling back to the system default.
*
* @param key the message key to retrieve
* @param accountDTO the account containing locale information
* @return the localized message string, or the key itself if no translation is found
*/
default String localizedMessage(String key, AccountDTO accountDTO) {
try {
var provider = Containers.get().findObjects(LocalizedMessagesProvider.class)
Expand Down
Loading