Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions MORYX-Framework.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
<Project Path="src/Moryx.Resources.Benchmarking/Moryx.Resources.Benchmarking.csproj" />
<Project Path="src/Moryx.Resources.BenchmarkSetup/Moryx.Benchmarking.Setups.csproj" />
</Folder>
<Folder Name="/Maintenance/">
<Project Path="src/Moryx.Maintenance/Moryx.Maintenance.csproj" />
<Project Path="src/Moryx.Maintenance.Endpoints/Moryx.Maintenance.Endpoints.csproj" />
<Project Path="src/Moryx.Maintenance.Management/Moryx.Maintenance.Management.csproj" />
<Project Path="src/Moryx.Maintenance.Model/Moryx.Maintenance.Model.csproj" />
<Project Path="src/Moryx.Maintenance.Web/Moryx.Maintenance.Web.csproj" />
</Folder>
<Folder Name="/CommandCenter/">
<Project Path="src/Moryx.CommandCenter.Web/Moryx.CommandCenter.Web.csproj" />
</Folder>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2025, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
using Moryx.Maintenance.Management.Models;

namespace Moryx.Maintenance.Endpoints.Dtos;
public record GetAllMaintenanceOrderResponse(List<MaintenanceOrderModel> Data);
70 changes: 70 additions & 0 deletions src/Moryx.Maintenance.Endpoints/Dtos/MaintenanceOrderResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2025, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Moryx.ControlSystem.VisualInstructions;
using Moryx.Maintenance.Attributes;
using Moryx.Maintenance.IntervalTypes;
using Moryx.Maintenance.Localizations;
using Moryx.Serialization;

namespace Moryx.Maintenance.Endpoints.Dtos;

/// <summary>
/// UI specific Dto for a maintenance order
/// </summary>
[DataContract, EntrySerialize]
[Display(ResourceType = typeof(Strings), Name = nameof(Strings.MAINTENANCE_FORM))]
public class MaintenanceOrderResponse
{
[Display(
ResourceType = typeof(Strings),
Name = nameof(Strings.RESOURCE),
Description = nameof(Strings.RESOURCE_DESCRIPTION)
)]
[MaintainableResources, Required, DataMember]
public string Resource { get; set; }

[Display(
ResourceType = typeof(Strings),
Name = nameof(Strings.DESCRIPTION)
)]
[DataMember]
public string? Description { get; set; }

[Display(
ResourceType = typeof(Strings),
Name = nameof(Strings.INTERVAL),
Description = nameof(Strings.INTERVAL_DESCRIPTION)
)]
[EntryPrototypes(typeof(IntervalBase)), Required, DataMember]
public IntervalBase Interval { get; set; } = new Days();

[Display(
ResourceType = typeof(Strings),
Name = nameof(Strings.INSTRUCTIONS),
Description = nameof(Strings.INTERVAL_DESCRIPTION)
)]
[DataMember]
public VisualInstruction[] Instructions { get; set; } = [];

[Display(
ResourceType = typeof(Strings),
Name = nameof(Strings.BLOCK),
Description = nameof(Strings.BLOCK_DESCRIPTION)
)]
[EntrySerialize, DataMember]
public bool Block { get; set; }

[Display(
ResourceType = typeof(Strings),
Name = nameof(Strings.IS_ACTIVE),
Description = nameof(Strings.IS_ACTIVE_DESCRIPTION)
)]
[DefaultValue(true), DataMember]
public bool IsActive { get; set; }

[EntrySerialize(EntrySerializeMode.Never), ReadOnly(true), DataMember]
public DateTime Created { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2025, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
using Moryx.Maintenance.Endpoints.Dtos;
using Moryx.Maintenance.Management.Models;
using Moryx.Serialization;

namespace Moryx.Maintenance.Endpoints.Extensions;

internal static class MaintenanceOrderExtensions
{

public static Entry ToEntry(this MaintenanceOrderResponse dto, ICustomSerialization serialization)
{
return EntryConvert.EncodeObject(dto, serialization);
}

public static MaintenanceOrderResponse ToOrderEntry(this MaintenanceOrderModel dto)
=> new()
{
Resource = dto.Resource.Name,
Description = dto.Description,
Block = dto.Block,
IsActive = dto.IsActive,
Created = dto.Created,
Instructions = [.. dto.Instructions],
Interval = dto.Interval.Interval,
};
}
41 changes: 41 additions & 0 deletions src/Moryx.Maintenance.Endpoints/MaintainableResourcesAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2025, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
using Microsoft.Extensions.DependencyInjection;
using Moryx.AbstractionLayer.Resources;
using Moryx.Container;
using Moryx.Serialization;
using Moryx.Maintenance.Endpoints.Mappers;
using Moryx.Maintenance.Management.Models;

namespace Moryx.Maintenance.Endpoints;

internal class MaintainableResourcesAttribute : PossibleValuesAttribute
{
public override IEnumerable<string> GetValues(IContainer localContainer, IServiceProvider serviceProvider)
{
var values = GetPossibleValues(serviceProvider).Select(x => x.Name);
return values;
}

/// <inheritdoc />
public override bool OverridesConversion => true;

/// <inheritdoc />
public override bool UpdateFromPredecessor => true;

/// <inheritdoc />
public override object Parse(IContainer container, IServiceProvider serviceProvider, string value)
{
var possibleTypes = GetPossibleValues(serviceProvider);
return possibleTypes.First(type => type.Name == value).Name;
}

private static IEnumerable<ResourceModel> GetPossibleValues(IServiceProvider serviceProvider)
{
return serviceProvider?
.GetService<IResourceManagement>()?
.GetResources<IMaintainableResource>()
.Select(x => x.ToDto())
?? [];
}
}
164 changes: 164 additions & 0 deletions src/Moryx.Maintenance.Endpoints/MaintenanceManagementController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) 2025, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Moryx.AbstractionLayer.Resources;
using Moryx.Maintenance.Endpoints.Dtos;
using Moryx.Maintenance.Endpoints.Services;
using Moryx.Maintenance.Exceptions;
using Moryx.Maintenance.Management.Models;
using Moryx.Runtime.Modules;
using Moryx.Serialization;

namespace Moryx.Maintenance.Endpoints;

/// <summary>
/// Definition of a REST API on the <see cref="IMaintenanceManagement"/> facade.
/// </summary>
[ApiController]
[Route("api/moryx/maintenances/")]
[Produces("application/json")]
public class MaintenanceManagementController(
IMaintenanceManagement maintenanceManagement,
IServiceProvider serviceProvider,
IModuleManager moduleManager,
IResourceManagement resourceManagement
) : ControllerBase
{
private readonly MaintenanceService _maintenanceService = new(maintenanceManagement, serviceProvider, moduleManager, resourceManagement);

[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<GetAllMaintenanceOrderResponse> GetAll()
{
return Response(() => new GetAllMaintenanceOrderResponse([.._maintenanceService.GetAll()]));
}

[HttpGet("{id:long}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Entry> Get(long id)
{
return Response(() =>
{
return _maintenanceService.Get(id);
});
}

[HttpGet("prototype")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Entry> Prototype()
{
return Response(_maintenanceService.Prototype);
}

[HttpPost("new")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult Add(Entry entry)
{
return Response(() =>
{
_maintenanceService.Add(entry);
});
}

[HttpPut("{id:long}")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult Update(long id, Entry entry)
{
return Response(() =>
{
_maintenanceService.Update(id, entry);
});
}

[HttpPut("{id:long}/acknowledge")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult Acknowledge(long id, Acknowledgement data)
{
return Response(() =>
{
_maintenanceService.Acknowledge(id, data);
});
}

[HttpPut("{id:long}/start")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult Start(long id)
{
return Response(() =>
{
_maintenanceService.Start(id);
});
}

[HttpDelete("{id:long}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult Delete(long id)
{
return Response(() =>
{
_maintenanceService.Delete(id);
});
}

[HttpGet("stream")]
[ProducesResponseType(typeof(MaintenanceOrderModel), StatusCodes.Status200OK)]
public async Task Stream(CancellationToken cancelToken)
{
await _maintenanceService.Stream(cancelToken, HttpContext);
}

private new ActionResult<TResult> Response<TResult>(Func<TResult> func, Func<TResult, ActionResult<TResult>>? onSuccess = null)
{
try
{
var result = func();
return onSuccess != null
? onSuccess(result)
: (ActionResult<TResult>)Ok(result);
}
catch (Exception ex)
{
return MapToResponse(ex);
}
}

private new ActionResult Response(Action action)
{
try
{
action();
return Ok();
}
catch (Exception ex)
{
return MapToResponse(ex);
}
}

private ObjectResult MapToResponse(Exception ex) => ex switch
{
NotFoundException _ => NotFound(ex.Message),
ArgumentNullException _ => BadRequest(ex.Message),
ArgumentException _ => BadRequest(ex.Message),
KeyNotFoundException _ => StatusCode(500, ex.Message),
HealthStateException _ => StatusCode(500, ex.Message),
_ => StatusCode(500, ex.Message),
};
}
39 changes: 39 additions & 0 deletions src/Moryx.Maintenance.Endpoints/MaintenanceOrderSerialization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2025, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
using System.Reflection;
using Moryx.Configuration;
using Moryx.Container;
using Moryx.Serialization;

namespace Moryx.Maintenance.Endpoints;

internal sealed class MaintenanceOrderSerialization(IContainer container, IServiceProvider serviceProvider)
: PossibleValuesSerialization(container, serviceProvider, new ValueProviderExecutor(new ValueProviderExecutorSettings()))
{
///// <summary>
///// Instance for <see cref="EntrySerializeSerialization"/> we use to filter properties and methods
///// </summary>
private readonly EntrySerializeSerialization _memberFilter = new();

/// <summary>
/// Follow the rules for <see cref="EntrySerializeSerialization"/>
/// </summary>
public override IEnumerable<PropertyInfo> GetProperties(Type sourceType)
{
return _memberFilter.GetProperties(sourceType);
}

/// <summary>
/// Follow the rules for <see cref="EntrySerializeSerialization"/>
/// </summary>
public override IEnumerable<MethodInfo> GetMethods(Type sourceType)
{
return _memberFilter.GetMethods(sourceType);
}

public override IEnumerable<MappedProperty> WriteFilter(Type sourceType, IEnumerable<Entry> encoded)
{
return _memberFilter.WriteFilter(sourceType, encoded);

}
}
10 changes: 10 additions & 0 deletions src/Moryx.Maintenance.Endpoints/Mappers/ResourceMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) 2025, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
using Moryx.Maintenance.Management.Models;

namespace Moryx.Maintenance.Endpoints.Mappers;
internal static class ResourceMapper
{
public static ResourceModel ToDto(this IMaintainableResource resource)
=> new ResourceModel(resource.Id, resource.Name);
}
20 changes: 20 additions & 0 deletions src/Moryx.Maintenance.Endpoints/Moryx.Maintenance.Endpoints.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Description>Moryx.Maintenance.Endpoints</Description>
<IsPackable>true</IsPackable>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Moryx.Maintenance.Management\Moryx.Maintenance.Management.csproj" />
</ItemGroup>

</Project>
Loading
Loading