Files
Umbraco-CMS/src/Umbraco.Core/Services/ScriptService.cs

189 lines
7.0 KiB
C#
Raw Normal View History

New backoffice: Add new codefile controllers and services (#14157) * Add scaffolding * Entities not Entitys * Remove unececary obsoleted constructor * Implement create script * Return a simplified ScriptFile instead of IScript That file abstraction seems way too bloated, containing a lot of stuff that's not relevant for files, such as IDs and keys. * Use IScript instead of custom return model * Add validation when creating script * Add Get script endpoint * Add response types * Add Delete * Throw if user key not found * Remove unused maapper * Add update endpoint * Add Get by path * Add create folder endpoint * Don't pass performingUserId to folder creation * Remove update folder * Add delete folder endpoint * Use specific ScriptFolderOperationStatus instead of ScriptOperationStatus * Add OperationStatusResult * Check folder for invalid name * Check name for invalid characters * Add partial view snippet endpoint * Start working on CreatePartialView * Add create partial view endpoint * Retrieve key from audit method * Add operation status results * Add Get endpoint * Return 201 when creating * Add update partial view endpoint * Add delete endpoint * Add response types * Add folder base implementation * Add folder endpoints * User property for allowed file extensions * Rename async method to async * Break snippet into endpoint in two * Make content non-nullable * Remove IService * Add get by path * Add viewmodels * Add create and update models * Add create stylesheet * Add update endpoint * Rename StylesheetControllerBase to StylesheetControllerBase * Add stylesheet delete * Rename controller bases * Add stylesheet folders * Add status results * Add response types to folders * Add richtext rules endpoints * Add Get all endpoint * Add get rules by path endpoint * Aling validates so they're not async These are private methods, so there's no reason to make them preemptively async * Add template obsoletions to interface * Add stream methods This is evidently used by deploy 🤷 * Obsolete stylesheet operations * Add get and getall across all services * Obsolete script operations * Obsolete old partial view methods * Add some method docs * Add compatibility suppression * Update OpenApi.json * Rename action * formatting * Fix import * add expression body * Invert if * Move base on own line * Rename file * Rename to all * Change to stylesheet instead of script * Add Umbraco.Code.MapAll to map definitions * Add comment about auditing * use publish cancelable async * use expression body * formatting * fix to use pattern matching --------- Co-authored-by: Zeegaan <nge@umbraco.dk>
2023-04-26 13:47:47 +02:00
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
public class ScriptService : FileServiceBase<IScriptRepository, IScript>, IScriptService
{
private readonly IAuditRepository _auditRepository;
private readonly IUserIdKeyResolver _userIdKeyResolver;
private readonly ILogger<ScriptService> _logger;
protected override string[] AllowedFileExtensions { get; } = { ".js" };
public ScriptService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IScriptRepository scriptRepository,
IAuditRepository auditRepository,
IUserIdKeyResolver userIdKeyResolver,
ILogger<ScriptService> logger)
: base(provider, loggerFactory, eventMessagesFactory, scriptRepository)
{
_auditRepository = auditRepository;
_userIdKeyResolver = userIdKeyResolver;
_logger = logger;
}
/// <inheritdoc />
public async Task<ScriptOperationStatus> DeleteAsync(string path, Guid performingUserKey)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope();
IScript? script = Repository.Get(path);
if (script is null)
{
return ScriptOperationStatus.NotFound;
}
EventMessages eventMessages = EventMessagesFactory.Get();
var deletingNotification = new ScriptDeletingNotification(script, eventMessages);
if (await scope.Notifications.PublishCancelableAsync(deletingNotification))
{
return ScriptOperationStatus.CancelledByNotification;
}
Repository.Delete(script);
scope.Notifications.Publish(
new ScriptDeletedNotification(script, eventMessages).WithStateFrom(deletingNotification));
await AuditAsync(AuditType.Delete, performingUserKey);
scope.Complete();
return ScriptOperationStatus.Success;
}
/// <inheritdoc />
public async Task<Attempt<IScript?, ScriptOperationStatus>> CreateAsync(ScriptCreateModel createModel, Guid performingUserKey)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope();
try
{
ScriptOperationStatus validationResult = await ValidateCreateAsync(createModel);
if (validationResult is not ScriptOperationStatus.Success)
{
return Attempt.FailWithStatus<IScript?, ScriptOperationStatus>(validationResult, null);
}
}
catch (PathTooLongException exception)
{
_logger.LogError(exception, "The script path was too long");
return Attempt.FailWithStatus<IScript?, ScriptOperationStatus>(ScriptOperationStatus.PathTooLong, null);
}
var script = new Script(createModel.FilePath) { Content = createModel.Content };
EventMessages eventMessages = EventMessagesFactory.Get();
var savingNotification = new ScriptSavingNotification(script, eventMessages);
if (await scope.Notifications.PublishCancelableAsync(savingNotification))
{
return Attempt.FailWithStatus<IScript?, ScriptOperationStatus>(ScriptOperationStatus.CancelledByNotification, null);
}
Repository.Save(script);
scope.Notifications.Publish(new ScriptSavedNotification(script, eventMessages).WithStateFrom(savingNotification));
await AuditAsync(AuditType.Save, performingUserKey);
scope.Complete();
return Attempt.SucceedWithStatus<IScript?, ScriptOperationStatus>(ScriptOperationStatus.Success, script);
}
private Task<ScriptOperationStatus> ValidateCreateAsync(ScriptCreateModel createModel)
{
if (Repository.Exists(createModel.FilePath))
{
return Task.FromResult(ScriptOperationStatus.AlreadyExists);
}
if (string.IsNullOrWhiteSpace(createModel.ParentPath) is false &&
Repository.FolderExists(createModel.ParentPath) is false)
{
return Task.FromResult(ScriptOperationStatus.ParentNotFound);
}
if(HasValidFileName(createModel.Name) is false)
{
return Task.FromResult(ScriptOperationStatus.InvalidName);
}
if (HasValidFileExtension(createModel.FilePath) is false)
{
return Task.FromResult(ScriptOperationStatus.InvalidFileExtension);
}
return Task.FromResult(ScriptOperationStatus.Success);
}
/// <inheritdoc />
public async Task<Attempt<IScript?, ScriptOperationStatus>> UpdateAsync(ScriptUpdateModel updateModel, Guid performingUserKey)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope();
IScript? script = Repository.Get(updateModel.ExistingPath);
if (script is null)
{
return Attempt.FailWithStatus<IScript?, ScriptOperationStatus>(ScriptOperationStatus.NotFound, null);
}
ScriptOperationStatus validationResult = ValidateUpdate(updateModel);
if (validationResult is not ScriptOperationStatus.Success)
{
return Attempt.FailWithStatus<IScript?, ScriptOperationStatus>(validationResult, null);
}
script.Content = updateModel.Content;
if (script.Name != updateModel.Name)
{
// Name has been updated, so we need to update the path as well
var newPath = script.Path.Replace(script.Name!, updateModel.Name);
script.Path = newPath;
}
EventMessages eventMessages = EventMessagesFactory.Get();
var savingNotification = new ScriptSavingNotification(script, eventMessages);
if (await scope.Notifications.PublishCancelableAsync(savingNotification))
{
return Attempt.FailWithStatus<IScript?, ScriptOperationStatus>(ScriptOperationStatus.CancelledByNotification, null);
}
Repository.Save(script);
scope.Notifications.Publish(new ScriptSavedNotification(script, eventMessages).WithStateFrom(savingNotification));
await AuditAsync(AuditType.Save, performingUserKey);
scope.Complete();
return Attempt.SucceedWithStatus<IScript?, ScriptOperationStatus>(ScriptOperationStatus.Success, script);
}
private ScriptOperationStatus ValidateUpdate(ScriptUpdateModel updateModel)
{
if (HasValidFileExtension(updateModel.Name) is false)
{
return ScriptOperationStatus.InvalidFileExtension;
}
if (HasValidFileName(updateModel.Name) is false)
{
return ScriptOperationStatus.InvalidName;
}
return ScriptOperationStatus.Success;
}
private async Task AuditAsync(AuditType type, Guid performingUserKey)
{
int userId = await _userIdKeyResolver.GetAsync(performingUserKey);
_auditRepository.Save(new AuditItem(-1, type, userId, "Script"));
}
}