Portal Community

IAppStudioService Interface

// AppStudio/Services/IAppStudioService.cs
public interface IAppStudioService
{
    // App definitions
    Task GetAppAsync(string tenantId, string appId);
    Task> GetAppsAsync(string tenantId);
    Task SaveAppAsync(string tenantId, AppDefinition app, string changedByUserId);
    Task DeleteAppAsync(string tenantId, string appId, string deletedByUserId);

    // Pages
    Task> GetPagesAsync(string tenantId, string appId);
    Task GetPageAsync(string tenantId, string appId, string pageId);
    Task SavePageAsync(string tenantId, string appId, AppPage page, string changedByUserId);
    Task DeletePageAsync(string tenantId, string appId, string pageId, string deletedByUserId);

    // Widgets and layout
    Task> GetWidgetsAsync(string tenantId, string appId, string pageId);
    Task SaveLayoutAsync(string tenantId, string appId, string pageId, IList placements, string changedByUserId);

    // Export / Import
    Task ExportAppAsync(string tenantId, string appId, string exportedByUserId);
    Task ImportAppAsync(string tenantId, AppExportBundle bundle, ImportOptions options, string importedByUserId);

    // Audit
    Task> GetAuditLogAsync(string tenantId, string appId, AuditQuery query);
}

Key Methods Explained

GetAppAsync

// Returns the full AppDefinition (including pages, navigation, variables, permissions)
// Served from Redis cache if available — fast path for app loads
Task GetAppAsync(string tenantId, string appId);

// Returns null if the app doesn't exist or the tenantId doesn't match
// Never throws on "not found" — callers handle null

SaveAppAsync

// Writes the complete AppDefinition to the database
// Automatically:
//   - invalidates Redis cache for this app
//   - writes an audit log entry
//   - validates tenantId ownership
Task SaveAppAsync(string tenantId, AppDefinition app, string changedByUserId);

SaveLayoutAsync

// Saves the widget placements for a single page
// Used by the designer's drag-drop layout operations
// More granular than SaveAppAsync — only persists placement positions
// Also cache-invalidating and audit-logged
Task SaveLayoutAsync(string tenantId, string appId, string pageId,
                     IList placements, string changedByUserId);

ExportAppAsync / ImportAppAsync

// Export — produces a complete AppExportBundle
Task ExportAppAsync(string tenantId, string appId, string exportedByUserId);

// Import — applies the bundle with the given options
// ImportOptions controls: conflictStrategy, dryRun, includePermissions
Task ImportAppAsync(string tenantId, AppExportBundle bundle,
                                     ImportOptions options, string importedByUserId);

How Controllers Use the Service

// AppController.cs — representative pattern
[ApiController]
[Route("api/tenants/{tenantId}/apps")]
public class AppController : ControllerBase
{
    private readonly IAppStudioService _service;

    public AppController(IAppStudioService service) => _service = service;

    [HttpGet("{appId}")]
    public async Task GetApp(string tenantId, string appId)
    {
        // TenantId from route is validated against the JWT by middleware
        var app = await _service.GetAppAsync(tenantId, appId);
        return app == null ? NotFound() : Ok(app);
    }

    [HttpPost("{appId}")]
    public async Task SaveApp(string tenantId, string appId,
                                             [FromBody] AppDefinition app)
    {
        var changedBy = User.GetUserId();  // From Passport JWT
        var saved = await _service.SaveAppAsync(tenantId, app, changedBy);
        return Ok(saved);
    }
}