add Job Tasks to enable the execution of adhoc asynchronous site-based workloads

This commit is contained in:
sbwalker
2026-02-19 08:23:11 -05:00
parent 13a58ed099
commit 0fd97d34d9
23 changed files with 633 additions and 252 deletions

View File

@@ -41,6 +41,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<IJobTaskService, JobTaskService>();
services.AddScoped<INotificationService, Oqtane.Services.NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();

View File

@@ -1,7 +1,7 @@
@namespace Oqtane.Modules.Admin.GlobalReplace
@using System.Text.Json
@inherits ModuleBase
@inject ISettingService SettingService
@inject IJobTaskService JobTaskService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@@ -97,9 +97,9 @@
Content = bool.Parse(_content)
};
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
settings = SettingService.SetSetting(settings, "GlobalReplace_" + DateTime.UtcNow.ToString("yyyyMMddHHmmss"), JsonSerializer.Serialize(replace));
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
var jobTask = new JobTask(PageState.Site.SiteId, "Global Replace", "Oqtane.Infrastructure.GlobalReplaceTask, Oqtane.Server", JsonSerializer.Serialize(replace));
await JobTaskService.AddJobTaskAsync(jobTask);
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
}
else

View File

@@ -1,7 +1,7 @@
@namespace Oqtane.Modules.Admin.Users
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IJobTaskService JobTaskService
@inject IStringLocalizer<Users> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@@ -43,17 +43,9 @@
var fileid = _filemanager.GetFileId();
if (fileid != -1)
{
ShowProgressIndicator();
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
if (bool.Parse(results["Success"]))
{
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
}
HideProgressIndicator();
var jobTask = new JobTask(PageState.Site.SiteId, "Import Users", "Oqtane.Infrastructure.ImportUsersTask, Oqtane.Server", $"{fileid}:{_notify}");
await JobTaskService.AddJobTaskAsync(jobTask);
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
}
else
{

View File

@@ -154,7 +154,7 @@
<value>Specify if module properties should be updated (ie. title, header, footer)</value>
</data>
<data name="Success.Save" xml:space="preserve">
<value>Your Global Replace Request Has Been Submitted And Will Be Executed Shortly</value>
<value>Your Global Replace Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient.</value>
</data>
<data name="Error.Save" xml:space="preserve">
<value>Error Saving Global Replace</value>

View File

@@ -129,11 +129,8 @@
<data name="Import" xml:space="preserve">
<value>Import</value>
</data>
<data name="Message.Import.Failure" xml:space="preserve">
<value>User Import Failed. Please Review Your Event Log For More Detailed Information.</value>
</data>
<data name="Message.Import.Success" xml:space="preserve">
<value>User Import Successful. {0} Users Imported.</value>
<value>Your User Import Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient.</value>
</data>
<data name="Message.Import.Validation" xml:space="preserve">
<value>You Must Specify A User File For Import</value>

View File

@@ -0,0 +1,46 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Net.Http;
using Oqtane.Documentation;
using Oqtane.Shared;
namespace Oqtane.Services
{
/// <summary>
/// Service to manage tasks (<see cref="JobTask"/>)
/// </summary>
public interface IJobTaskService
{
/// <summary>
/// Return a specific task
/// </summary>
/// <param name="jobTaskId"></param>
/// <returns></returns>
Task<JobTask> GetJobTaskAsync(int jobTaskId);
/// <summary>
/// Adds a new task
/// </summary>
/// <param name="jobTask"></param>
/// <returns></returns>
Task<JobTask> AddJobTaskAsync(JobTask jobTask);
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class JobTaskService : ServiceBase, IJobTaskService
{
public JobTaskService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string Apiurl => CreateApiUrl("JobTask");
public async Task<JobTask> GetJobTaskAsync(int jobTaskId)
{
return await GetJsonAsync<JobTask>($"{Apiurl}/{jobTaskId}");
}
public async Task<JobTask> AddJobTaskAsync(JobTask jobTask)
{
return await PostJsonAsync<JobTask>(Apiurl, jobTask);
}
}
}

View File

@@ -161,15 +161,6 @@ namespace Oqtane.Services
/// <returns></returns>
Task<string> GetPasswordRequirementsAsync(int siteId);
/// <summary>
/// Bulk import of users
/// </summary>
/// <param name="siteId">ID of a <see cref="Site"/></param>
/// <param name="fileId">ID of a <see cref="File"/></param>
/// <param name="notify">Indicates if new users should be notified by email</param>
/// <returns></returns>
Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify);
/// <summary>
/// Get passkeys for a user
/// </summary>
@@ -351,11 +342,6 @@ namespace Oqtane.Services
return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
}
public async Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify)
{
return await PostJsonAsync<Dictionary<string, string>>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}&notify={notify}", null);
}
public async Task<List<UserPasskey>> GetPasskeysAsync(int userId)
{
return await GetJsonAsync<List<UserPasskey>>($"{Apiurl}/passkey?id={userId}");

View File

@@ -0,0 +1,63 @@
using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.ApiRoute)]
public class JobTaskController : Controller
{
private readonly IJobTaskRepository _jobTasks;
private readonly ILogManager _logger;
private readonly Alias _alias;
public JobTaskController(IJobTaskRepository jobTasks, ILogManager logger, ITenantManager tenantManager)
{
_jobTasks = jobTasks;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public JobTask Get(int id)
{
var jobTask = _jobTasks.GetJobTask(id);
if (jobTask.SiteId == _alias.SiteId)
{
return jobTask;
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Admin)]
public JobTask Post([FromBody] JobTask jobTask)
{
if (ModelState.IsValid && jobTask.SiteId == _alias.SiteId)
{
jobTask.IsCompleted = false;
jobTask = _jobTasks.AddJobTask(jobTask);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Job Task Added {JobTask}", jobTask);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Job Task Post Attempt {JobTask}", jobTask);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
jobTask = null;
}
return jobTask;
}
}
}

View File

@@ -418,42 +418,6 @@ namespace Oqtane.Controllers
return requirements;
}
// POST api/<controller>/import?siteid=x&fileid=y&notify=z
[HttpPost("import")]
[Authorize(Roles = RoleNames.Admin)]
public async Task<Dictionary<string, string>> Import(string siteid, string fileid, string notify)
{
if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId && int.TryParse(fileid, out int FileId) && bool.TryParse(notify, out bool Notify))
{
var file = _files.GetFile(FileId);
if (file != null)
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList))
{
return await _userManager.ImportUsers(SiteId, _files.GetFilePath(file), Notify);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Import File Does Not Exist {SiteId} {FileId}", siteid, fileid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return null;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET: api/<controller>/passkey?id=x
[HttpGet("passkey")]
[Authorize]

View File

@@ -214,6 +214,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<IJobTaskService, JobTaskService>();
services.AddScoped<INotificationService, Oqtane.Services.NotificationService>();
services.AddScoped<IFolderService, FolderService>();
services.AddScoped<IFileService, FileService>();
@@ -274,6 +275,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddTransient<ILogRepository, LogRepository>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<IJobTaskRepository, JobTaskRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<IFolderRepository, FolderRepository>();
services.AddTransient<IFileRepository, FileRepository>();

View File

@@ -0,0 +1,13 @@
using System;
using System.Threading.Tasks;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public interface IJobTask
{
string ExecuteTask(IServiceProvider provider, Site site, string parameters);
Task<string> ExecuteTaskAsync(IServiceProvider provider, Site site, string parameters);
}
}

View File

@@ -1,178 +0,0 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class GlobalReplaceJob : HostedServiceBase
{
public GlobalReplaceJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "Global Replace Job";
Frequency = "m"; // run every minute.
Interval = 1;
IsEnabled = true;
}
public override async Task<string> ExecuteJobAsync(IServiceProvider provider)
{
string log = "";
// get services
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var settingRepository = provider.GetRequiredService<ISettingRepository>();
var tenantManager = provider.GetRequiredService<ITenantManager>();
var pageRepository = provider.GetRequiredService<IPageRepository>();
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
var sites = siteRepository.GetSites().ToList();
foreach (var site in sites.Where(item => !item.IsDeleted))
{
log += $"Processing Site: {site.Name}<br />";
// get global replace items in order by date/time submitted
var globalReplaceSettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId)
.Where(item => item.SettingName.StartsWith("GlobalReplace_"))
.OrderBy(item => item.SettingName);
if (globalReplaceSettings != null && globalReplaceSettings.Any())
{
// get first item
var globalReplace = JsonSerializer.Deserialize<GlobalReplace>(globalReplaceSettings.First().SettingValue);
var find = globalReplace.Find;
var replace = globalReplace.Replace;
var comparisonType = (globalReplace.CaseSensitive) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
log += $"Replacing: '{find}' With: '{replace}' Case Sensitive: {globalReplace.CaseSensitive}<br />";
var tenantId = tenantManager.GetTenant().TenantId;
tenantManager.SetAlias(tenantId, site.SiteId);
var changed = false;
if (site.Name != null && site.Name.Contains(find, comparisonType))
{
site.Name = site.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (site.HeadContent != null && site.HeadContent.Contains(find, comparisonType))
{
site.HeadContent = site.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (site.BodyContent != null && site.BodyContent.Contains(find, comparisonType))
{
site.BodyContent = site.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Site)
{
siteRepository.UpdateSite(site);
log += $"Site Updated<br />";
}
var pages = pageRepository.GetPages(site.SiteId);
var pageModules = pageModuleRepository.GetPageModules(site.SiteId);
// iterate pages
foreach (var page in pages)
{
// page properties
changed = false;
if (page.Name != null && page.Name.Contains(find, comparisonType))
{
page.Name = page.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (page.Title != null && page.Title.Contains(find, comparisonType))
{
page.Title = page.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (page.HeadContent != null && page.HeadContent.Contains(find, comparisonType))
{
page.HeadContent = page.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (page.BodyContent != null && page.BodyContent.Contains(find, comparisonType))
{
page.BodyContent = page.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Pages)
{
pageRepository.UpdatePage(page);
log += $"Page Updated: /{page.Path}<br />";
}
foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId))
{
// pagemodule properties
changed = false;
if (pageModule.Title != null && pageModule.Title.Contains(find, comparisonType))
{
pageModule.Title = pageModule.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Header != null && pageModule.Header.Contains(find, comparisonType))
{
pageModule.Header = pageModule.Header.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Footer != null && pageModule.Footer.Contains(find, comparisonType))
{
pageModule.Footer = pageModule.Footer.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Modules)
{
pageModuleRepository.UpdatePageModule(pageModule);
log += $"Module Updated: {pageModule.Title} - /{page.Path}<br />";
}
// module content
if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "")
{
Type moduleType = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType);
if (moduleType != null && moduleType.GetInterface(nameof(IPortable)) != null)
{
try
{
// module content
var moduleObject = ActivatorUtilities.CreateInstance(provider, moduleType);
var moduleContent = ((IPortable)moduleObject).ExportModule(pageModule.Module);
if (!string.IsNullOrEmpty(moduleContent) && moduleContent.Contains(find, comparisonType) && globalReplace.Content)
{
moduleContent = moduleContent.Replace(find, replace, comparisonType);
((IPortable)moduleObject).ImportModule(pageModule.Module, moduleContent, pageModule.Module.ModuleDefinition.Version);
log += $"Module Content Updated: {pageModule.Title} - /{page.Path}<br />";
}
}
catch (Exception ex)
{
log += $"Error Processing Module {pageModule.Module.ModuleDefinition.Name} - {ex.Message}<br />";
}
}
}
}
}
// remove global replace setting to prevent reprocessing
settingRepository.DeleteSetting(EntityNames.Site, globalReplaceSettings.First().SettingId);
}
else
{
log += $"No Criteria Provided<br />";
}
}
return log;
}
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Repository;
namespace Oqtane.Infrastructure
{
public class TaskJob : HostedServiceBase
{
public TaskJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "Task Job";
Frequency = "m"; // run every minute
Interval = 1;
IsEnabled = true;
}
// job is executed for each tenant in installation
public override async Task<string> ExecuteJobAsync(IServiceProvider provider)
{
var log = "";
var tenantManager = provider.GetRequiredService<ITenantManager>();
var tenant = tenantManager.GetTenant();
// iterate through sites for current tenant
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var sites = siteRepository.GetSites().ToList();
foreach (var site in sites.Where(item => !item.IsDeleted))
{
log += $"Processing Site: {site.Name}<br />";
// get incomplete tasks for site
var jobTaskRepository = provider.GetRequiredService<IJobTaskRepository>();
var tasks = jobTaskRepository.GetJobTasks(site.SiteId).ToList();
if (tasks != null && tasks.Any())
{
foreach (var task in tasks)
{
log += $"Executing Task: {task.Name}<br />";
Type taskType = Type.GetType(task.Type);
if (taskType != null && taskType.GetInterface(nameof(IJobTask)) != null)
{
try
{
tenantManager.SetAlias(tenant.TenantId, site.SiteId);
var taskObject = ActivatorUtilities.CreateInstance(provider, taskType);
var taskLog = ((IJobTask)taskObject).ExecuteTask(provider, site, task.Parameters);
taskLog += await ((IJobTask)taskObject).ExecuteTaskAsync(provider, site, task.Parameters);
task.Status = taskLog;
}
catch (Exception ex)
{
task.Status = "Error: " + ex.Message;
}
}
else
{
task.Status = $"Error: Task {task.Name} Has An Invalid Type {task.Type}<br />";
}
// update task
task.IsCompleted = true;
jobTaskRepository.UpdateJobTask(task);
log += task.Status + "<br />";
}
}
else
{
log += "No Tasks To Execute<br />";
}
}
return log;
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
namespace Oqtane.Infrastructure
{
public class GlobalReplaceTask : JobTaskBase
{
public override string ExecuteTask(IServiceProvider provider, Site site, string parameters)
{
string log = "";
// get services
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var pageRepository = provider.GetRequiredService<IPageRepository>();
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
if (!string.IsNullOrEmpty(parameters))
{
// get parameters
var globalReplace = JsonSerializer.Deserialize<GlobalReplace>(parameters);
var find = globalReplace.Find;
var replace = globalReplace.Replace;
var comparisonType = (globalReplace.CaseSensitive) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
log += $"Replacing: '{find}' With: '{replace}' Case Sensitive: {globalReplace.CaseSensitive}<br />";
// site properties
site = siteRepository.GetSite(site.SiteId);
var changed = false;
if (site.Name != null && site.Name.Contains(find, comparisonType))
{
site.Name = site.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (site.HeadContent != null && site.HeadContent.Contains(find, comparisonType))
{
site.HeadContent = site.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (site.BodyContent != null && site.BodyContent.Contains(find, comparisonType))
{
site.BodyContent = site.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Site)
{
siteRepository.UpdateSite(site);
log += $"Site Updated<br />";
}
var pages = pageRepository.GetPages(site.SiteId).ToList();
var pageModules = pageModuleRepository.GetPageModules(site.SiteId).ToList();
// iterate pages
foreach (var page in pages)
{
// page properties
changed = false;
if (page.Name != null && page.Name.Contains(find, comparisonType))
{
page.Name = page.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (page.Title != null && page.Title.Contains(find, comparisonType))
{
page.Title = page.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (page.HeadContent != null && page.HeadContent.Contains(find, comparisonType))
{
page.HeadContent = page.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (page.BodyContent != null && page.BodyContent.Contains(find, comparisonType))
{
page.BodyContent = page.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Pages)
{
pageRepository.UpdatePage(page);
log += $"Page Updated: /{page.Path}<br />";
}
foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId))
{
// module properties
changed = false;
if (pageModule.Title != null && pageModule.Title.Contains(find, comparisonType))
{
pageModule.Title = pageModule.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Header != null && pageModule.Header.Contains(find, comparisonType))
{
pageModule.Header = pageModule.Header.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Footer != null && pageModule.Footer.Contains(find, comparisonType))
{
pageModule.Footer = pageModule.Footer.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Modules)
{
pageModuleRepository.UpdatePageModule(pageModule);
log += $"Module Updated: {pageModule.Title} - /{page.Path}<br />";
}
// module content
if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "")
{
Type moduleType = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType);
if (moduleType != null && moduleType.GetInterface(nameof(IPortable)) != null)
{
try
{
var moduleObject = ActivatorUtilities.CreateInstance(provider, moduleType);
var moduleContent = ((IPortable)moduleObject).ExportModule(pageModule.Module);
if (!string.IsNullOrEmpty(moduleContent) && moduleContent.Contains(find, comparisonType) && globalReplace.Content)
{
moduleContent = moduleContent.Replace(find, replace, comparisonType);
((IPortable)moduleObject).ImportModule(pageModule.Module, moduleContent, pageModule.Module.ModuleDefinition.Version);
log += $"Module Content Updated: {pageModule.Title} - /{page.Path}<br />";
}
}
catch (Exception ex)
{
log += $"Error Processing Module {pageModule.Module.ModuleDefinition.Name} - {ex.Message}<br />";
}
}
}
}
}
}
else
{
log += $"No Criteria Provided<br />";
}
return log;
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Managers;
using Oqtane.Models;
using Oqtane.Repository;
namespace Oqtane.Infrastructure
{
public class ImportUsersTask : JobTaskBase
{
public override async Task<string> ExecuteTaskAsync(IServiceProvider provider, Site site, string parameters)
{
string log = "";
if (!string.IsNullOrEmpty(parameters) && parameters.Contains(":"))
{
var fileId = int.Parse(parameters.Split(':')[0]);
var notify = bool.Parse(parameters.Split(':')[1]);
var fileRepository = provider.GetRequiredService<IFileRepository>();
var userManager = provider.GetRequiredService<IUserManager>();
var file = fileRepository.GetFile(fileId);
if (file != null)
{
var filePath = fileRepository.GetFilePath(file);
log += $"Importing Users From {filePath}<br />";
var result = await userManager.ImportUsers(site.SiteId, filePath, notify);
if (result["Success"] == "True")
{
log += $"{result["Users"]} Users Imported<br />";
}
else
{
log += $"User Import Failed<br />";
}
}
else
{
log += $"Import Users FileId {fileId} Does Not Exist<br />";
}
}
return log;
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Threading.Tasks;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public class JobTaskBase : IJobTask
{
public virtual string ExecuteTask(IServiceProvider provider, Site site, string parameters)
{
return "";
}
public virtual Task<string> ExecuteTaskAsync(IServiceProvider provider, Site site, string parameters)
{
return Task.FromResult(string.Empty);
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
using Oqtane.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class JobTaskEntityBuilder : AuditableBaseEntityBuilder<JobTaskEntityBuilder>
{
private const string _entityTableName = "JobTask";
private readonly PrimaryKey<JobTaskEntityBuilder> _primaryKey = new("PK_JobTask", x => x.JobTaskId);
private readonly ForeignKey<JobTaskEntityBuilder> _siteForeignKey = new("FK_JobTask_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
public JobTaskEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_siteForeignKey);
}
protected override JobTaskEntityBuilder BuildTable(ColumnsBuilder table)
{
JobTaskId = AddAutoIncrementColumn(table,"JobTaskId");
SiteId = AddIntegerColumn(table,"SiteId");
Name = AddStringColumn(table, "Name", 200);
Type = AddStringColumn(table, "Type", 200);
Parameters = AddMaxStringColumn(table, "Parameters", true);
IsCompleted = AddBooleanColumn(table, "IsCompleted", true);
Status = AddMaxStringColumn(table, "Status", true);
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> JobTaskId { get; private set; }
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
public OperationBuilder<AddColumnOperation> Name { get; private set; }
public OperationBuilder<AddColumnOperation> Type { get; private set; }
public OperationBuilder<AddColumnOperation> Parameters { get; private set; }
public OperationBuilder<AddColumnOperation> IsCompleted { get; private set; }
public OperationBuilder<AddColumnOperation> Status { get; private set; }
}
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations.Tenant
{
[DbContext(typeof(TenantDBContext))]
[Migration("Tenant.10.01.00.04")]
public class AddJobTasks : MultiDatabaseMigration
{
public AddJobTasks(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var jobTaskEntityBuilder = new JobTaskEntityBuilder(migrationBuilder, ActiveDatabase);
jobTaskEntityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@@ -136,5 +136,6 @@ namespace Oqtane.Repository
public virtual DbSet<MigrationHistory> MigrationHistory { get; set; }
public virtual DbSet<SiteGroup> SiteGroup { get; set; }
public virtual DbSet<SiteGroupMember> SiteGroupMember { get; set; }
public virtual DbSet<JobTask> JobTask { get; set; }
}
}

View File

@@ -61,8 +61,8 @@ namespace Oqtane.Repository
public void DeleteJobLog(int jobLogId)
{
JobLog joblog = _db.JobLog.Find(jobLogId);
_db.JobLog.Remove(joblog);
JobLog jobLog = _db.JobLog.Find(jobLogId);
_db.JobLog.Remove(jobLog);
_db.SaveChanges();
}
}

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface IJobTaskRepository
{
IEnumerable<JobTask> GetJobTasks(int siteId);
JobTask GetJobTask(int jobTaskId);
JobTask AddJobTask(JobTask jobTask);
JobTask UpdateJobTask(JobTask jobTask);
void DeleteJobTask(int jobTaskId);
}
public class JobTaskRepository : IJobTaskRepository
{
private TenantDBContext _db;
public JobTaskRepository(TenantDBContext context)
{
_db = context;
}
public IEnumerable<JobTask> GetJobTasks(int siteId)
{
return _db.JobTask.Where(item => item.SiteId == siteId && !item.IsCompleted).OrderBy(item => item.CreatedOn);
}
public JobTask GetJobTask(int jobTaskId)
{
return _db.JobTask.SingleOrDefault(item => item.JobTaskId == jobTaskId);
}
public JobTask AddJobTask(JobTask jobTask)
{
_db.JobTask.Add(jobTask);
_db.SaveChanges();
return jobTask;
}
public JobTask UpdateJobTask(JobTask jobTask)
{
_db.Entry(jobTask).State = EntityState.Modified;
_db.SaveChanges();
return jobTask;
}
public void DeleteJobTask(int jobTaskId)
{
JobTask jobTask = _db.JobTask.Find(jobTaskId);
_db.JobTask.Remove(jobTask);
_db.SaveChanges();
}
}
}

View File

@@ -3,7 +3,7 @@ using System;
namespace Oqtane.Models
{
/// <summary>
/// Definition of a Job / Task which is run on the server.
/// Definition of a Job which is run on the server
/// When Jobs run, they create a <see cref="JobLog"/>
/// </summary>
public class Job : ModelBase

View File

@@ -0,0 +1,56 @@
using System;
namespace Oqtane.Models
{
/// <summary>
/// An instance of a Task which is executed by the TaskJob
/// </summary>
public class JobTask : ModelBase
{
/// <summary>
/// Internal ID
/// </summary>
public int JobTaskId { get; set; }
/// <summary>
/// Site where the Task should execute
/// </summary>
public int SiteId { get; set; }
/// <summary>
/// Name for simple identification
/// </summary>
public string Name { get; set; }
/// <summary>
/// Fully qualified type name of the Task
/// </summary>
public string Type { get; set; }
/// <summary>
/// Any parameters related to the Task
/// </summary>
public string Parameters { get; set; }
/// <summary>
/// Indicates if the Task is completed
/// </summary>
public bool IsCompleted { get; set; }
/// <summary>
/// Any status information provided by the Task
/// </summary>
public string Status { get; set; }
// constructors
public JobTask() { }
public JobTask(int siteId, string name, string type, string parameters)
{
SiteId = siteId;
Name = name;
Type = type;
Parameters = parameters;
}
}
}