Skip to content

Commit

Permalink
move sync logic to middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
mirkoSekulic committed Nov 7, 2024
1 parent c68946b commit e012bf2
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,18 @@ private static void ConfigureSettingsTypeBySection<TOption>(this IServiceCollect
services.TryAddScoped(typeof(TOption), svc => ((IOptionsSnapshot<object>)svc.GetService(typeof(IOptionsSnapshot<TOption>)))!.Value);
}

public static IServiceCollection RegisterTransientServicesByBaseType<TMarker>(this IServiceCollection services)
{
var typesToRegister = AltinnAssembliesScanner.GetTypesAssignedFrom<TMarker>()
.Where(type => !type.IsInterface && !type.IsAbstract);

foreach (var serviceType in typesToRegister)
{
services.AddTransient(typeof(TMarker), serviceType);
}

return services;
}

}
}
120 changes: 40 additions & 80 deletions backend/src/Designer/Controllers/RepositoryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
using Altinn.Studio.Designer.Configuration;
using Altinn.Studio.Designer.Enums;
using Altinn.Studio.Designer.Helpers;
using Altinn.Studio.Designer.Helpers.Extensions;
using Altinn.Studio.Designer.Hubs.SyncHub;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.RepositoryClient.Model;
using Altinn.Studio.Designer.Services.Interfaces;
using Medallion.Threading;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -37,7 +35,6 @@ public class RepositoryController : ControllerBase
private readonly ISourceControl _sourceControl;
private readonly IRepository _repository;
private readonly IHubContext<SyncHub, ISyncClient> _syncHub;
private readonly IDistributedLockProvider _synchronizationProvider;

/// <summary>
/// This is the API controller for functionality related to repositories.
Expand All @@ -49,14 +46,12 @@ public class RepositoryController : ControllerBase
/// <param name="sourceControl">the source control</param>
/// <param name="repository">the repository control</param>
/// <param name="syncHub">websocket syncHub</param>
/// <param name="synchronizationProvider">Provides distributed locks.</param>
public RepositoryController(IGitea giteaWrapper, ISourceControl sourceControl, IRepository repository, IHubContext<SyncHub, ISyncClient> syncHub, IDistributedLockProvider synchronizationProvider)
public RepositoryController(IGitea giteaWrapper, ISourceControl sourceControl, IRepository repository, IHubContext<SyncHub, ISyncClient> syncHub)
{
_giteaApi = giteaWrapper;
_sourceControl = sourceControl;
_repository = repository;
_syncHub = syncHub;
_synchronizationProvider = synchronizationProvider;
}

/// <summary>
Expand Down Expand Up @@ -220,13 +215,8 @@ public async Task<RepositoryModel> GetRepository(string org, string repository)
[Route("repo/{org}/{repository:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/status")]
public async Task<RepoStatus> RepoStatus(string org, string repository)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
await using (await _synchronizationProvider.AcquireLockAsync(
AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer)))
{
await _sourceControl.FetchRemoteChanges(org, repository);
return _sourceControl.RepositoryStatus(org, repository);
}
await _sourceControl.FetchRemoteChanges(org, repository);
return _sourceControl.RepositoryStatus(org, repository);
}

/// <summary>
Expand All @@ -239,13 +229,8 @@ public async Task<RepoStatus> RepoStatus(string org, string repository)
[Route("repo/{org}/{repository:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/diff")]
public async Task<Dictionary<string, string>> RepoDiff(string org, string repository)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
await using (await _synchronizationProvider.AcquireLockAsync(
AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer)))
{
await _sourceControl.FetchRemoteChanges(org, repository);
return await _sourceControl.GetChangedContent(org, repository);
}
await _sourceControl.FetchRemoteChanges(org, repository);
return await _sourceControl.GetChangedContent(org, repository);
}

/// <summary>
Expand All @@ -258,22 +243,16 @@ public async Task<Dictionary<string, string>> RepoDiff(string org, string reposi
[Route("repo/{org}/{repository:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/pull")]
public async Task<RepoStatus> Pull(string org, string repository)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);

await using (await _synchronizationProvider.AcquireLockAsync(
AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer)))
{
RepoStatus pullStatus = await _sourceControl.PullRemoteChanges(org, repository);
RepoStatus pullStatus = await _sourceControl.PullRemoteChanges(org, repository);

RepoStatus status = _sourceControl.RepositoryStatus(org, repository);

if (pullStatus.RepositoryStatus != Enums.RepositoryStatus.Ok)
{
status.RepositoryStatus = pullStatus.RepositoryStatus;
}
RepoStatus status = _sourceControl.RepositoryStatus(org, repository);

return status;
if (pullStatus.RepositoryStatus != Enums.RepositoryStatus.Ok)
{
status.RepositoryStatus = pullStatus.RepositoryStatus;
}

return status;
}

/// <summary>
Expand All @@ -289,18 +268,14 @@ public async Task<ActionResult> ResetLocalRepository(string org, string reposito
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
AltinnRepoEditingContext editingContext = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer);

await using (await _synchronizationProvider.AcquireLockAsync(
AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer)))
try
{
try
{
await _repository.ResetLocalRepository(editingContext);
return Ok();
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError);
}
await _repository.ResetLocalRepository(editingContext);
return Ok();
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError);
}
}

Expand All @@ -315,23 +290,19 @@ public async Task<ActionResult> ResetLocalRepository(string org, string reposito
public async Task CommitAndPushRepo(string org, string repository, [FromBody] CommitInfo commitInfo)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
await using (await _synchronizationProvider.AcquireLockAsync(
AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer)))
try
{
try
{
await _sourceControl.PushChangesForRepository(commitInfo);
}
catch (LibGit2Sharp.NonFastForwardException)
await _sourceControl.PushChangesForRepository(commitInfo);
}
catch (LibGit2Sharp.NonFastForwardException)
{
RepoStatus repoStatus = await _sourceControl.PullRemoteChanges(commitInfo.Org, commitInfo.Repository);
await _sourceControl.Push(commitInfo.Org, commitInfo.Repository);
foreach (RepositoryContent repoContent in repoStatus?.ContentStatus)
{
RepoStatus repoStatus = await _sourceControl.PullRemoteChanges(commitInfo.Org, commitInfo.Repository);
await _sourceControl.Push(commitInfo.Org, commitInfo.Repository);
foreach (RepositoryContent repoContent in repoStatus?.ContentStatus)
{
Source source = new(Path.GetFileName(repoContent.FilePath), repoContent.FilePath);
SyncSuccess syncSuccess = new(source);
await _syncHub.Clients.Group(developer).FileSyncSuccess(syncSuccess);
}
Source source = new(Path.GetFileName(repoContent.FilePath), repoContent.FilePath);
SyncSuccess syncSuccess = new(source);
await _syncHub.Clients.Group(developer).FileSyncSuccess(syncSuccess);
}
}
}
Expand All @@ -347,20 +318,15 @@ public async Task CommitAndPushRepo(string org, string repository, [FromBody] Co
[Route("repo/{org}/{repository:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/commit")]
public async Task<ActionResult> Commit(string org, string repository, [FromBody] CommitInfo commitInfo)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);

await using (await _synchronizationProvider.AcquireLockAsync(
AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer)))
await Task.CompletedTask;
try
{
try
{
_sourceControl.Commit(commitInfo);
return Ok();
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError);
}
_sourceControl.Commit(commitInfo);
return Ok();
}
catch (Exception)
{
return StatusCode(StatusCodes.Status500InternalServerError);
}
}

Expand All @@ -373,14 +339,8 @@ public async Task<ActionResult> Commit(string org, string repository, [FromBody]
[Route("repo/{org}/{repository:regex(^(?!datamodels$)[[a-z]][[a-z0-9-]]{{1,28}}[[a-z0-9]]$)}/push")]
public async Task<ActionResult> Push(string org, string repository)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);

await using (await _synchronizationProvider.AcquireLockAsync(
AltinnRepoEditingContext.FromOrgRepoDeveloper(org, repository, developer)))
{
bool pushSuccess = await _sourceControl.Push(org, repository);
return pushSuccess ? Ok() : StatusCode(StatusCodes.Status500InternalServerError);
}
bool pushSuccess = await _sourceControl.Push(org, repository);
return pushSuccess ? Ok() : StatusCode(StatusCodes.Status500InternalServerError);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Altinn.Studio.Designer.Helpers;
using Altinn.Studio.Designer.Models;
using Microsoft.AspNetCore.Http;

namespace Altinn.Studio.Designer.Middleware.UserRequestSynchronization;

public class EditingContextResolver : IEditingContextResolver
{
public bool TryResolveContext(HttpContext httpContext, out AltinnRepoEditingContext context)
{
context = null;
var routeValues = httpContext.Request.RouteValues;

string org = routeValues.TryGetValue("org", out var orgValue) ? orgValue?.ToString() : null;
string app = null;

if (routeValues.TryGetValue("app", out object appValue) || routeValues.TryGetValue("repo", out appValue) ||
routeValues.TryGetValue("repository", out appValue))
{
app = appValue?.ToString();
}

string developer = AuthenticationHelper.GetDeveloperUserName(httpContext);


if (string.IsNullOrEmpty(org) || string.IsNullOrEmpty(app) || string.IsNullOrEmpty(developer))
{
return false;
}

context = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Altinn.Studio.Designer.Models;
using Microsoft.AspNetCore.Http;

namespace Altinn.Studio.Designer.Middleware.UserRequestSynchronization;

public interface IEditingContextResolver
{
bool TryResolveContext(HttpContext httpContext, out AltinnRepoEditingContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Http;

namespace Altinn.Studio.Designer.Middleware.UserRequestSynchronization;

public interface IRequestSyncEvaluator
{
bool EvaluateSyncRequest(HttpContext httpContext);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Altinn.Studio.Designer.Models;
using Microsoft.AspNetCore.Http;

namespace Altinn.Studio.Designer.Middleware.UserRequestSynchronization;

public interface IRequestSyncResolver
{
bool TryResolveSyncRequest(HttpContext httpContext, out AltinnRepoEditingContext editingContext);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using Altinn.Studio.Designer.Controllers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;

namespace Altinn.Studio.Designer.Middleware.UserRequestSynchronization.RequestSyncEvaluatorImplementations;

public class RepositoryEndpointsSyncEvaluator : IRequestSyncEvaluator
{
private readonly string _controllerName = nameof(RepositoryController).Replace("Controller", string.Empty);
private readonly List<string> _actionNamesWhiteList =
[
nameof(RepositoryController.RepoStatus),
nameof(RepositoryController.RepoDiff),
nameof(RepositoryController.Pull),
nameof(RepositoryController.ResetLocalRepository),
nameof(RepositoryController.CommitAndPushRepo),
nameof(RepositoryController.Commit),
nameof(RepositoryController.Push)
];

public bool EvaluateSyncRequest(HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();

var controllerActionDescriptor = endpoint?.Metadata.GetMetadata<ControllerActionDescriptor>();

if (controllerActionDescriptor == null)
{
return false;
}

string controllerName = controllerActionDescriptor.ControllerName;
string actionName = controllerActionDescriptor.ActionName;
if (controllerName.Equals(_controllerName, StringComparison.OrdinalIgnoreCase) && _actionNamesWhiteList.Contains(actionName))
{
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Altinn.Studio.Designer.Configuration.Extensions;
using Microsoft.Extensions.DependencyInjection;

namespace Altinn.Studio.Designer.Middleware.UserRequestSynchronization;

public static class RequestSyncExtensions
{
public static IServiceCollection RegisterSynchronizationServices(this IServiceCollection services)
{
services.AddTransient<IRequestSyncResolver, RequestSyncResolver>();
services.AddTransient<IEditingContextResolver, EditingContextResolver>();
services.RegisterTransientServicesByBaseType<IRequestSyncEvaluator>();
return services;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using Altinn.Studio.Designer.Models;
using Microsoft.AspNetCore.Http;

namespace Altinn.Studio.Designer.Middleware.UserRequestSynchronization;

public class RequestSyncResolver : IRequestSyncResolver
{
IEnumerable<IRequestSyncEvaluator> _requestSyncEvaluators;
IEditingContextResolver _editingContextResolver;

public RequestSyncResolver(IEnumerable<IRequestSyncEvaluator> requestSyncEvaluators, IEditingContextResolver editingContextResolver)
{
_requestSyncEvaluators = requestSyncEvaluators;
_editingContextResolver = editingContextResolver;
}

public bool TryResolveSyncRequest(HttpContext httpContext, out AltinnRepoEditingContext editingContext)
{
if (!_editingContextResolver.TryResolveContext(httpContext, out editingContext))
{
return false;
}

return _requestSyncEvaluators.Any(e => e.EvaluateSyncRequest(httpContext));
}
}
Loading

0 comments on commit e012bf2

Please sign in to comment.