Add video management functionality for processors

- Created ProcessorVideos.razor and ProcessorVideos.razor.cs for managing videos associated with processors.
- Implemented video linking, title updating, and deletion features.
- Added UI components for video management, including input fields for YouTube URLs and video IDs.
- Integrated video display in the processor view with support for YouTube embeds.
- Updated localization files for new video management strings in multiple languages.
- Enhanced ProcessorsService to handle video-related API calls.
This commit is contained in:
2026-05-08 09:00:58 +01:00
parent d8c50a8ddc
commit 459880161d
30 changed files with 13094 additions and 1 deletions

View File

@@ -0,0 +1,85 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System;
namespace Marechai.ApiClient.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class CreateProcessorVideoRequest : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The provider property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Provider { get; set; }
#nullable restore
#else
public string Provider { get; set; }
#endif
/// <summary>The title property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Title { get; set; }
#nullable restore
#else
public string Title { get; set; }
#endif
/// <summary>The video_id property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? VideoId { get; set; }
#nullable restore
#else
public string VideoId { get; set; }
#endif
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Models.CreateProcessorVideoRequest"/> and sets the default values.
/// </summary>
public CreateProcessorVideoRequest()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::Marechai.ApiClient.Models.CreateProcessorVideoRequest"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::Marechai.ApiClient.Models.CreateProcessorVideoRequest CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::Marechai.ApiClient.Models.CreateProcessorVideoRequest();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "provider", n => { Provider = n.GetStringValue(); } },
{ "title", n => { Title = n.GetStringValue(); } },
{ "video_id", n => { VideoId = n.GetStringValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteStringValue("provider", Provider);
writer.WriteStringValue("title", Title);
writer.WriteStringValue("video_id", VideoId);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,103 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System;
namespace Marechai.ApiClient.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class ProcessorVideoDto : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The id property</summary>
public long? Id { get; set; }
/// <summary>The processor_id property</summary>
public int? ProcessorId { get; set; }
/// <summary>The processor_name property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? ProcessorName { get; set; }
#nullable restore
#else
public string ProcessorName { get; set; }
#endif
/// <summary>The provider property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Provider { get; set; }
#nullable restore
#else
public string Provider { get; set; }
#endif
/// <summary>The title property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Title { get; set; }
#nullable restore
#else
public string Title { get; set; }
#endif
/// <summary>The video_id property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? VideoId { get; set; }
#nullable restore
#else
public string VideoId { get; set; }
#endif
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Models.ProcessorVideoDto"/> and sets the default values.
/// </summary>
public ProcessorVideoDto()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::Marechai.ApiClient.Models.ProcessorVideoDto"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::Marechai.ApiClient.Models.ProcessorVideoDto CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::Marechai.ApiClient.Models.ProcessorVideoDto();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "id", n => { Id = n.GetLongValue(); } },
{ "processor_id", n => { ProcessorId = n.GetIntValue(); } },
{ "processor_name", n => { ProcessorName = n.GetStringValue(); } },
{ "provider", n => { Provider = n.GetStringValue(); } },
{ "title", n => { Title = n.GetStringValue(); } },
{ "video_id", n => { VideoId = n.GetStringValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteLongValue("id", Id);
writer.WriteIntValue("processor_id", ProcessorId);
writer.WriteStringValue("processor_name", ProcessorName);
writer.WriteStringValue("provider", Provider);
writer.WriteStringValue("title", Title);
writer.WriteStringValue("video_id", VideoId);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,65 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System;
namespace Marechai.ApiClient.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class UpdateProcessorVideoRequest : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The title property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public string? Title { get; set; }
#nullable restore
#else
public string Title { get; set; }
#endif
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest"/> and sets the default values.
/// </summary>
public UpdateProcessorVideoRequest()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "title", n => { Title = n.GetStringValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteStringValue("title", Title);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618

View File

@@ -5,6 +5,7 @@ using Marechai.ApiClient.Processors.Item.Description;
using Marechai.ApiClient.Processors.Item.Descriptions;
using Marechai.ApiClient.Processors.Item.Machines;
using Marechai.ApiClient.Processors.Item.Photos;
using Marechai.ApiClient.Processors.Item.Videos;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
@@ -41,6 +42,11 @@ namespace Marechai.ApiClient.Processors.Item
{
get => new global::Marechai.ApiClient.Processors.Item.Photos.PhotosRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The videos property</summary>
public global::Marechai.ApiClient.Processors.Item.Videos.VideosRequestBuilder Videos
{
get => new global::Marechai.ApiClient.Processors.Item.Videos.VideosRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Processors.Item.ItemRequestBuilder"/> and sets the default values.
/// </summary>

View File

@@ -0,0 +1,149 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.ApiClient.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.ApiClient.Processors.Item.Videos
{
/// <summary>
/// Builds and executes requests for operations under \processors\{-id}\videos
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Processors.Item.Videos.VideosRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public VideosRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/processors/{%2Did}/videos", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Processors.Item.Videos.VideosRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public VideosRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/processors/{%2Did}/videos", rawUrl)
{
}
/// <returns>A List&lt;global::Marechai.ApiClient.Models.ProcessorVideoDto&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<global::Marechai.ApiClient.Models.ProcessorVideoDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<global::Marechai.ApiClient.Models.ProcessorVideoDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.ApiClient.Models.ProcessorVideoDto>(requestInfo, global::Marechai.ApiClient.Models.ProcessorVideoDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="global::Marechai.ApiClient.Models.ProcessorVideoDto"/></returns>
/// <param name="body">The request body</param>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 400 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 401 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 403 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 404 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 409 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<global::Marechai.ApiClient.Models.ProcessorVideoDto?> PostAsync(global::Marechai.ApiClient.Models.CreateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<global::Marechai.ApiClient.Models.ProcessorVideoDto> PostAsync(global::Marechai.ApiClient.Models.CreateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
var requestInfo = ToPostRequestInformation(body, requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "401", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "403", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "404", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "409", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<global::Marechai.ApiClient.Models.ProcessorVideoDto>(requestInfo, global::Marechai.ApiClient.Models.ProcessorVideoDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="body">The request body</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToPostRequestInformation(global::Marechai.ApiClient.Models.CreateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToPostRequestInformation(global::Marechai.ApiClient.Models.CreateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body);
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.ApiClient.Processors.Item.Videos.VideosRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.ApiClient.Processors.Item.Videos.VideosRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.ApiClient.Processors.Item.Videos.VideosRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosRequestBuilderPostRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -3,6 +3,7 @@
using Marechai.ApiClient.Models;
using Marechai.ApiClient.Processors.Item;
using Marechai.ApiClient.Processors.Photos;
using Marechai.ApiClient.Processors.Videos;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
@@ -24,6 +25,11 @@ namespace Marechai.ApiClient.Processors
{
get => new global::Marechai.ApiClient.Processors.Photos.PhotosRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The videos property</summary>
public global::Marechai.ApiClient.Processors.Videos.VideosRequestBuilder Videos
{
get => new global::Marechai.ApiClient.Processors.Videos.VideosRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>Gets an item from the Marechai.ApiClient.processors.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.ApiClient.Processors.Item.ItemRequestBuilder"/></returns>

View File

@@ -0,0 +1,192 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.ApiClient.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.ApiClient.Processors.Videos.Item
{
/// <summary>
/// Builds and executes requests for operations under \processors\videos\{id}
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosItemRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public VideosItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/processors/videos/{id}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public VideosItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/processors/videos/{id}", rawUrl)
{
}
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 401 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 403 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 404 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task DeleteAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task DeleteAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToDeleteRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "401", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "403", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "404", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="global::Marechai.ApiClient.Models.ProcessorVideoDto"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 404 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<global::Marechai.ApiClient.Models.ProcessorVideoDto?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<global::Marechai.ApiClient.Models.ProcessorVideoDto> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "404", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<global::Marechai.ApiClient.Models.ProcessorVideoDto>(requestInfo, global::Marechai.ApiClient.Models.ProcessorVideoDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <param name="body">The request body</param>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 400 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 401 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 403 status code</exception>
/// <exception cref="global::Marechai.ApiClient.Models.ProblemDetails">When receiving a 404 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task PutAsync(global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task PutAsync(global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
var requestInfo = ToPutRequestInformation(body, requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "401", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "403", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
{ "404", global::Marechai.ApiClient.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
await RequestAdapter.SendNoContentAsync(requestInfo, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToDeleteRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToDeleteRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.DELETE, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
return requestInfo;
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
return requestInfo;
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="body">The request body</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToPutRequestInformation(global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToPutRequestInformation(global::Marechai.ApiClient.Models.UpdateProcessorVideoRequest body, Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body));
var requestInfo = new RequestInformation(Method.PUT, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9");
requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body);
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosItemRequestBuilderDeleteRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosItemRequestBuilderPutRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,61 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.ApiClient.Processors.Videos.Item;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace Marechai.ApiClient.Processors.Videos
{
/// <summary>
/// Builds and executes requests for operations under \processors\videos
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class VideosRequestBuilder : BaseRequestBuilder
{
/// <summary>Gets an item from the Marechai.ApiClient.processors.videos.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder"/></returns>
public global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder this[long position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
urlTplParams.Add("id", position);
return new global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>Gets an item from the Marechai.ApiClient.processors.videos.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder"/></returns>
[Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")]
public global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder this[string position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("id", position);
return new global::Marechai.ApiClient.Processors.Videos.Item.VideosItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Processors.Videos.VideosRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public VideosRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/processors/videos", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.ApiClient.Processors.Videos.VideosRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public VideosRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/processors/videos", rawUrl)
{
}
}
}
#pragma warning restore CS0618

View File

@@ -1,5 +1,5 @@
{
"descriptionHash": "0A0B24472424F5725FAC0B461835F2846FCFBE4D6266D5CA9C8A47BFE69245671601318F589BAFDE4990008ECD148038CCBFAF956E676DC1144EA697B31E6284",
"descriptionHash": "D841F4013F51B97831F05CE9468D15A4446F4DE601CD9D5CBAE3EA4DF8DDB85FB9B0B3C951BCC27AF2EE2066DC8ACE94EA0A3A5CB2C9B4D5063BD33CB03C8221",
"descriptionLocation": "../../../../../tmp/openapi.json",
"lockFileVersion": "1.0.0",
"kiotaVersion": "1.31.1",

View File

@@ -0,0 +1,46 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Marechai.Data.Dtos;
public class CreateProcessorVideoRequest
{
[Required]
[StringLength(32)]
[JsonPropertyName("provider")]
public string Provider { get; set; } = null!;
[Required]
[StringLength(64)]
[JsonPropertyName("video_id")]
public string VideoId { get; set; } = null!;
[StringLength(512)]
[JsonPropertyName("title")]
public string? Title { get; set; }
}

View File

@@ -0,0 +1,50 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Marechai.Data.Dtos;
public class ProcessorVideoDto : BaseDto<long>
{
[JsonPropertyName("processor_id")]
[Required]
public int ProcessorId { get; set; }
[JsonPropertyName("processor_name")]
public string? ProcessorName { get; set; }
[JsonPropertyName("provider")]
[Required]
public string Provider { get; set; }
[JsonPropertyName("video_id")]
[Required]
public string VideoId { get; set; }
[JsonPropertyName("title")]
public string? Title { get; set; }
}

View File

@@ -0,0 +1,36 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Marechai.Data.Dtos;
public class UpdateProcessorVideoRequest
{
[StringLength(512)]
[JsonPropertyName("title")]
public string? Title { get; set; }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marechai.Database.Migrations
{
/// <inheritdoc />
public partial class AddProcessorVideos : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ProcessorVideos",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
ProcessorId = table.Column<int>(type: "int(11)", nullable: false),
Provider = table.Column<string>(type: "varchar(32)", maxLength: 32, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
VideoId = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Title = table.Column<string>(type: "varchar(512)", maxLength: 512, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedOn = table.Column<DateTime>(type: "datetime(6)", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UpdatedOn = table.Column<DateTime>(type: "datetime(6)", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.ComputedColumn)
},
constraints: table =>
{
table.PrimaryKey("PK_ProcessorVideos", x => x.Id);
table.ForeignKey(
name: "FK_ProcessorVideos_processors_ProcessorId",
column: x => x.ProcessorId,
principalTable: "processors",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_ProcessorVideos_ProcessorId",
table: "ProcessorVideos",
column: "ProcessorId");
migrationBuilder.CreateIndex(
name: "IX_ProcessorVideos_ProcessorId_Provider_VideoId",
table: "ProcessorVideos",
columns: new[] { "ProcessorId", "Provider", "VideoId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProcessorVideos");
}
}
}

View File

@@ -6251,6 +6251,53 @@ namespace Marechai.Database.Migrations
b.ToTable("ProcessorPhotos");
});
modelBuilder.Entity("Marechai.Database.Models.ProcessorVideo", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreatedOn")
.ValueGeneratedOnAdd()
.HasColumnType("datetime(6)");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<DateTime>("CreatedOn"));
b.Property<int>("ProcessorId")
.HasColumnType("int(11)");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("varchar(32)");
b.Property<string>("Title")
.HasMaxLength(512)
.HasColumnType("varchar(512)");
b.Property<DateTime>("UpdatedOn")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("datetime(6)");
MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property<DateTime>("UpdatedOn"));
b.Property<string>("VideoId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("varchar(64)");
b.HasKey("Id");
b.HasIndex("ProcessorId");
b.HasIndex("ProcessorId", "Provider", "VideoId")
.IsUnique();
b.ToTable("ProcessorVideos");
});
modelBuilder.Entity("Marechai.Database.Models.ProcessorsByMachine", b =>
{
b.Property<long>("Id")
@@ -9710,6 +9757,17 @@ namespace Marechai.Database.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Marechai.Database.Models.ProcessorVideo", b =>
{
b.HasOne("Marechai.Database.Models.Processor", "Processor")
.WithMany("Videos")
.HasForeignKey("ProcessorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Processor");
});
modelBuilder.Entity("Marechai.Database.Models.ProcessorsByMachine", b =>
{
b.HasOne("Marechai.Database.Models.Machine", "Machine")
@@ -10760,6 +10818,8 @@ namespace Marechai.Database.Migrations
b.Navigation("Photos");
b.Navigation("ProcessorsByMachine");
b.Navigation("Videos");
});
modelBuilder.Entity("Marechai.Database.Models.Resolution", b =>

View File

@@ -181,6 +181,7 @@ public class MarechaiContext : IdentityDbContext<ApplicationUser, ApplicationRol
public virtual DbSet<SoftwareVideo> SoftwareVideos { get; set; }
public virtual DbSet<MachineVideo> MachineVideos { get; set; }
public virtual DbSet<GpuVideo> GpuVideos { get; set; }
public virtual DbSet<ProcessorVideo> ProcessorVideos { get; set; }
public virtual DbSet<SoftwareCriticReview> SoftwareCriticReviews { get; set; }
public virtual DbSet<MobyGamesReviewImportState> MobyGamesReviewImportStates { get; set; }
public virtual DbSet<SoftwareUserRating> SoftwareUserRatings { get; set; }
@@ -2909,6 +2910,17 @@ public class MarechaiContext : IdentityDbContext<ApplicationUser, ApplicationRol
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<ProcessorVideo>(entity =>
{
entity.HasIndex(e => e.ProcessorId);
entity.HasIndex(e => new { e.ProcessorId, e.Provider, e.VideoId }).IsUnique();
entity.HasOne(e => e.Processor)
.WithMany(p => p.Videos)
.HasForeignKey(e => e.ProcessorId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<MobyGamesVideoImportState>(entity =>
{
entity.HasIndex(e => e.VideoUrl);

View File

@@ -102,4 +102,5 @@ public class Processor : BaseModel<int>
public virtual ICollection<ProcessorDescription> Descriptions { get; set; }
public virtual ICollection<ProcessorPhoto> Photos { get; set; }
public virtual ICollection<ProcessorsByMachine> ProcessorsByMachine { get; set; }
public virtual ICollection<ProcessorVideo> Videos { get; set; }
}

View File

@@ -0,0 +1,46 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
using System.ComponentModel.DataAnnotations;
namespace Marechai.Database.Models;
public class ProcessorVideo : BaseModel<long>
{
[Required]
public int ProcessorId { get; set; }
public virtual Processor Processor { get; set; }
[Required]
[StringLength(32)]
public string Provider { get; set; }
[Required]
[StringLength(64)]
public string VideoId { get; set; }
[StringLength(512)]
public string Title { get; set; }
}

View File

@@ -0,0 +1,190 @@
/*******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ---------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] -----------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ---------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Marechai.Data.Dtos;
using Marechai.Database.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Marechai.Server.Controllers;
[Route("/processors/videos")]
[ApiController]
public class ProcessorVideosController(MarechaiContext context) : ControllerBase
{
[HttpGet("/processors/{processorId:int}/videos")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public Task<List<ProcessorVideoDto>> GetVideosByProcessorAsync(int processorId) => context.ProcessorVideos
.Where(v => v.ProcessorId == processorId)
.OrderBy(v => v.Title)
.Select(v => new ProcessorVideoDto
{
Id = v.Id,
ProcessorId = v.ProcessorId,
ProcessorName = v.Processor.Name,
Provider = v.Provider,
VideoId = v.VideoId,
Title = v.Title
})
.ToListAsync();
[HttpGet("{id:long}")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<ProcessorVideoDto>> GetAsync(long id)
{
ProcessorVideoDto dto = await context.ProcessorVideos
.Where(v => v.Id == id)
.Select(v => new ProcessorVideoDto
{
Id = v.Id,
ProcessorId = v.ProcessorId,
ProcessorName = v.Processor.Name,
Provider = v.Provider,
VideoId = v.VideoId,
Title = v.Title
})
.FirstOrDefaultAsync();
if(dto is null) return NotFound();
return dto;
}
[HttpPost("/processors/{processorId:int}/videos")]
[Authorize(Roles = "Admin,UberAdmin")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<ProcessorVideoDto>> CreateAsync(int processorId,
[FromBody] CreateProcessorVideoRequest request)
{
string userId = User.FindFirstValue(ClaimTypes.Sid);
if(userId is null) return Unauthorized();
if(string.IsNullOrWhiteSpace(request.Provider) || string.IsNullOrWhiteSpace(request.VideoId))
return BadRequest("Provider and Video ID are required.");
string provider = request.Provider.Trim();
string videoId = request.VideoId.Trim();
string title = string.IsNullOrWhiteSpace(request.Title) ? null : request.Title.Trim();
bool processorExists = await context.Processors.AnyAsync(p => p.Id == processorId);
if(!processorExists) return NotFound();
bool exists = await context.ProcessorVideos.AnyAsync(v => v.ProcessorId == processorId &&
v.Provider == provider &&
v.VideoId == videoId);
if(exists) return Conflict("This video is already linked to this processor.");
var model = new ProcessorVideo
{
ProcessorId = processorId,
Provider = provider,
VideoId = videoId,
Title = title
};
context.ProcessorVideos.Add(model);
await context.SaveChangesAsync();
string processorName = await context.Processors.Where(p => p.Id == processorId)
.Select(p => p.Name)
.FirstOrDefaultAsync();
var dto = new ProcessorVideoDto
{
Id = model.Id,
ProcessorId = processorId,
ProcessorName = processorName,
Provider = provider,
VideoId = videoId,
Title = title
};
return CreatedAtAction(nameof(GetAsync), new { id = model.Id }, dto);
}
[HttpPut("{id:long}")]
[Authorize(Roles = "Admin,UberAdmin")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateAsync(long id, [FromBody] UpdateProcessorVideoRequest request)
{
string userId = User.FindFirstValue(ClaimTypes.Sid);
if(userId is null) return Unauthorized();
ProcessorVideo video = await context.ProcessorVideos.FindAsync(id);
if(video is null) return NotFound();
video.Title = string.IsNullOrWhiteSpace(request.Title) ? null : request.Title.Trim();
await context.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id:long}")]
[Authorize(Roles = "Admin,UberAdmin")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteAsync(long id)
{
string userId = User.FindFirstValue(ClaimTypes.Sid);
if(userId is null) return Unauthorized();
ProcessorVideo video = await context.ProcessorVideos.FindAsync(id);
if(video is null) return NotFound();
context.ProcessorVideos.Remove(video);
await context.SaveChangesAsync();
return NoContent();
}
}

View File

@@ -0,0 +1,164 @@
@page "/admin/processors/{Id:int}/videos"
@attribute [Authorize(Roles = "Admin, UberAdmin")]
@using Marechai.ApiClient.Models
@using Marechai.Services
@inject ProcessorsService ProcessorsService
@inject IStringLocalizer<ProcessorsService> L
@inject IDialogService DialogService
@inject NavigationManager NavigationManager
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" OnClick="@(() => NavigationManager.NavigateTo("/admin/processors"))" Class="mb-4">@L["Back to Processors"]</MudButton>
<MudPaper Class="pa-6 mb-6" Elevation="0" Style="background: linear-gradient(135deg, #b71c1c 0%, #e53935 100%); border-radius: 16px;">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
<MudIcon Icon="@Icons.Material.Filled.VideoLibrary" Size="Size.Large" Style="color: white;"/>
<MudText Typo="Typo.h4" Style="color: white;">@L["Video Management"]</MudText>
</MudStack>
<MudText Typo="Typo.subtitle1" Style="color: rgba(255,255,255,0.85);" Class="mt-2">@(_processorName ?? L["Manage processor videos."])</MudText>
</MudPaper>
@if(!string.IsNullOrWhiteSpace(_errorMessage))
{
<MudAlert Severity="Severity.Error" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="() => _errorMessage = null">@_errorMessage</MudAlert>
}
@if(!string.IsNullOrWhiteSpace(_successMessage))
{
<MudAlert Severity="Severity.Success" Class="mb-4" ShowCloseIcon="true" CloseIconClicked="() => _successMessage = null">@_successMessage</MudAlert>
}
@if(_isLoading)
{
<div class="d-flex justify-center pa-8">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large"/>
</div>
return;
}
@* ── Add Video Section ── *@
<MudPaper Elevation="2" Class="pa-4 mb-6 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">@L["Link Video"]</MudText>
<MudStack Spacing="3">
<MudTextField @bind-Value="_urlInput"
@bind-Value:after="OnUrlInputChanged"
Label="@L["YouTube URL or Video ID"]"
Variant="Variant.Outlined"
Placeholder="https://www.youtube.com/watch?v=..."
Immediate="true"
DebounceInterval="200"/>
@if(!string.IsNullOrWhiteSpace(_detectedVideoId))
{
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success"/>
<MudText Typo="Typo.body2">@L["Detected Video ID"]:</MudText>
<MudChip T="string" Size="Size.Small" Color="Color.Success">@_detectedVideoId</MudChip>
</MudStack>
}
<MudTextField @bind-Value="_title"
Label="@L["Video Title"]"
Variant="Variant.Outlined"
MaxLength="512"/>
<MudExpansionPanels Elevation="0">
<MudExpansionPanel Text="@L["Advanced"]">
<MudStack Spacing="3">
<MudTextField @bind-Value="_provider"
Label="@L["Provider"]"
Variant="Variant.Outlined"
HelperText="@L["Provider name (e.g. YouTube)."]"
MaxLength="32"/>
<MudTextField @bind-Value="_manualVideoId"
Label="@L["Video ID"]"
Variant="Variant.Outlined"
HelperText="@L["Use this when the URL above cannot be parsed (non-YouTube providers)."]"
MaxLength="64"/>
</MudStack>
</MudExpansionPanel>
</MudExpansionPanels>
<MudButton Variant="Variant.Filled" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Link"
OnClick="LinkVideoAsync"
Disabled="_isSaving || !CanLink">
@if(_isSaving)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2"/>
@L["Saving..."]
}
else
{
@L["Link Video"]
}
</MudButton>
</MudStack>
</MudPaper>
@* ── Videos Grid ── *@
<MudPaper Elevation="2" Class="rounded-lg">
<MudToolBar Dense="true">
<MudText Typo="Typo.h6">@L["Videos"]</MudText>
@if(_videos is not null)
{
<MudChip T="string" Size="Size.Small" Color="Color.Secondary" Class="ml-2">@_videos.Count</MudChip>
}
<MudSpacer/>
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="LoadDataAsync"/>
</MudToolBar>
@if(_videos is null || _videos.Count == 0)
{
<MudAlert Severity="Severity.Info" Class="ma-4">@L["No videos found for this processor."]</MudAlert>
}
else
{
<MudGrid Class="pa-4">
@foreach(ProcessorVideoDto video in _videos)
{
<MudItem xs="12" sm="6" md="4">
<MudCard Elevation="1" Class="rounded-lg">
<MudCardContent Class="pa-2">
@if(string.Equals(video.Provider, "YouTube", StringComparison.OrdinalIgnoreCase))
{
<div style="position: relative; width: 100%; padding-bottom: 56.25%; border-radius: 8px; overflow: hidden;">
<iframe src="@($"https://www.youtube-nocookie.com/embed/{video.VideoId}")"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"></iframe>
</div>
}
else
{
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
@video.Provider: @video.VideoId
</MudAlert>
}
</MudCardContent>
<MudCardContent Class="pa-2">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudChip T="string" Size="Size.Small" Color="Color.Primary">@video.Provider</MudChip>
<MudText Typo="Typo.caption" Class="text-truncate" Style="font-family: monospace;">@video.VideoId</MudText>
</MudStack>
<MudTextField T="string"
Value="@video.Title"
ValueChanged="@(t => OnTitleChangedAsync(video, t))"
Label="@L["Title"]"
Variant="Variant.Outlined"
DebounceInterval="500"
MaxLength="512"
Class="mt-2"/>
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Text" Color="Color.Error" Size="Size.Small"
StartIcon="@Icons.Material.Filled.Delete"
OnClick="() => ConfirmDeleteAsync(video)">@L["Delete"]</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
}
</MudGrid>
}
</MudPaper>

View File

@@ -0,0 +1,168 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
using System.Collections.Generic;
using System.Threading.Tasks;
using Marechai.ApiClient.Models;
using Marechai.Helpers;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Marechai.Pages.Admin;
public partial class ProcessorVideos
{
string _detectedVideoId;
string _errorMessage;
bool _isLoading = true;
bool _isSaving;
string _manualVideoId;
string _processorName;
string _provider = "YouTube";
string _successMessage;
string _title;
string _urlInput;
List<ProcessorVideoDto> _videos;
[Parameter] public int Id { get; set; }
bool CanLink => !string.IsNullOrWhiteSpace(EffectiveProvider) && !string.IsNullOrWhiteSpace(EffectiveVideoId);
string EffectiveProvider => string.IsNullOrWhiteSpace(_manualVideoId)
? "YouTube"
: (_provider ?? string.Empty).Trim();
string EffectiveVideoId => !string.IsNullOrWhiteSpace(_manualVideoId)
? _manualVideoId.Trim()
: _detectedVideoId;
protected override async Task OnInitializedAsync()
{
ProcessorDto processor = await ProcessorsService.GetByIdAsync(Id);
_processorName = processor?.Name;
await LoadDataAsync();
}
async Task LoadDataAsync()
{
_isLoading = true;
_videos = await ProcessorsService.GetVideosByProcessorAsync(Id);
_isLoading = false;
}
void OnUrlInputChanged()
{
_detectedVideoId = YouTubeUrlParser.TryExtract(_urlInput, out string id) ? id : null;
}
async Task LinkVideoAsync()
{
_errorMessage = null;
_successMessage = null;
if(!CanLink) return;
_isSaving = true;
try
{
(ProcessorVideoDto dto, string error) = await ProcessorsService.CreateVideoAsync(Id,
EffectiveProvider, EffectiveVideoId, string.IsNullOrWhiteSpace(_title) ? null : _title.Trim());
if(dto is not null)
{
_successMessage = L["Video linked successfully."];
_urlInput = null;
_detectedVideoId = null;
_manualVideoId = null;
_title = null;
_provider = "YouTube";
await LoadDataAsync();
}
else
{
_errorMessage = string.IsNullOrWhiteSpace(error) ? L["Failed to link video."] : error;
}
}
finally
{
_isSaving = false;
}
}
async Task OnTitleChangedAsync(ProcessorVideoDto video, string newTitle)
{
if(video.Title == newTitle) return;
(bool succeeded, string error) =
await ProcessorsService.UpdateVideoTitleAsync(video.Id!.Value, newTitle);
if(succeeded)
{
video.Title = newTitle;
_successMessage = L["Video updated successfully."];
}
else
{
_errorMessage = string.IsNullOrWhiteSpace(error) ? L["Failed to update video."] : error;
}
}
async Task ConfirmDeleteAsync(ProcessorVideoDto video)
{
DialogParameters<DeleteConfirmDialog> parameters = new()
{
{
x => x.ContentText,
L["Are you sure you want to delete this video? This action cannot be undone."]
}
};
IDialogReference dialog =
await DialogService.ShowAsync<DeleteConfirmDialog>(L["Delete Video"], parameters,
new DialogOptions
{
MaxWidth = MaxWidth.ExtraSmall,
FullWidth = true
});
DialogResult result = await dialog.Result;
if(result is not { Canceled: false }) return;
(bool succeeded, string errorMessage) =
await ProcessorsService.DeleteVideoAsync(video.Id ?? 0);
if(succeeded)
{
_successMessage = L["Video deleted successfully."];
await LoadDataAsync();
}
else
{
_errorMessage = string.IsNullOrWhiteSpace(errorMessage) ? L["Failed to delete video."] : errorMessage;
}
}
}

View File

@@ -64,6 +64,7 @@
<MudTooltip Text="@L["Descriptions"]"><MudIconButton Icon="@Icons.Material.Filled.Description" Size="Size.Small" OnClick="() => OpenDescriptionsDialog(context.Item)"/></MudTooltip>
<MudTooltip Text="@L["Photos"]"><MudIconButton Icon="@Icons.Material.Filled.PhotoLibrary" Size="Size.Small" Href="@($"/admin/processors/{context.Item.Id}/photos")"/></MudTooltip>
<MudTooltip Text="@L["Videos"]"><MudIconButton Icon="@Icons.Material.Filled.VideoLibrary" Size="Size.Small" Href="@($"/admin/processors/{context.Item.Id}/videos")"/></MudTooltip>
<MudTooltip Text="@L["Delete Processor"]"><MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Small" Color="Color.Error" OnClick="() => ConfirmDeleteProcessor(context.Item)"/></MudTooltip>
</MudStack>

View File

@@ -298,6 +298,47 @@
<PhotoLightbox @ref="_lightbox" BaseUrl="@AssetUrls.BaseUrl" Category="processors"/>
@* ── Videos Card ── *@
@if(_videos is { Count: > 0 })
{
<MudCard Elevation="2" Class="mb-4 rounded-lg">
<MudCardContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-3">
<MudIcon Icon="@Icons.Material.Filled.VideoLibrary" Color="Color.Primary"/>
<MudText Typo="Typo.h6">@L["Videos"]</MudText>
<MudChip T="string" Size="Size.Small" Color="Color.Secondary">@_videos.Count</MudChip>
</MudStack>
<MudGrid Spacing="3">
@foreach(ProcessorVideoDto video in _videos)
{
<MudItem xs="12" sm="6">
<MudCard Elevation="1" Class="rounded-lg">
@if(string.Equals(video.Provider, "YouTube", StringComparison.OrdinalIgnoreCase))
{
<div style="position: relative; width: 100%; padding-bottom: 56.25%; border-radius: 8px 8px 0 0; overflow: hidden;">
<iframe src="@($"https://www.youtube-nocookie.com/embed/{video.VideoId}")"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
title="@(video.Title ?? "Video")"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"></iframe>
</div>
}
@if(!string.IsNullOrWhiteSpace(video.Title))
{
<MudCardContent>
<MudText Typo="Typo.body2">@video.Title</MudText>
</MudCardContent>
}
</MudCard>
</MudItem>
}
</MudGrid>
</MudCardContent>
</MudCard>
}
@* ── Description Card ── *@
@if(!string.IsNullOrWhiteSpace(_description))
{

View File

@@ -45,6 +45,7 @@ public partial class View
PhotoLightbox _lightbox;
List<Guid> _photos = [];
ProcessorDto _processor;
List<ProcessorVideoDto> _videos = [];
[Parameter]
public int Id { get; set; }
@@ -87,6 +88,8 @@ public partial class View
_photos = await ProcessorPhotosService.GetGuidsByProcessorAsync(Id);
_videos = await Service.GetVideosByProcessorAsync(Id);
_loaded = true;
StateHasChanged();
}

View File

@@ -526,4 +526,104 @@
<value>Nur Jahr</value>
<comment>Year only</comment>
</data>
<data name="Videos" xml:space="preserve">
<value>Videos</value>
<comment>Videos</comment>
</data>
<data name="Video Management" xml:space="preserve">
<value>Videoverwaltung</value>
<comment>Video Management</comment>
</data>
<data name="Manage processor videos." xml:space="preserve">
<value>Prozessor-Videos verwalten.</value>
<comment>Manage processor videos.</comment>
</data>
<data name="Back to Processors" xml:space="preserve">
<value>Zurück zu Prozessoren</value>
<comment>Back to Processors</comment>
</data>
<data name="Link Video" xml:space="preserve">
<value>Video verknüpfen</value>
<comment>Link Video</comment>
</data>
<data name="YouTube URL or Video ID" xml:space="preserve">
<value>YouTube-URL oder Video-ID</value>
<comment>YouTube URL or Video ID</comment>
</data>
<data name="Detected Video ID" xml:space="preserve">
<value>Erkannte Video-ID</value>
<comment>Detected Video ID</comment>
</data>
<data name="Video Title" xml:space="preserve">
<value>Videotitel</value>
<comment>Video Title</comment>
</data>
<data name="Advanced" xml:space="preserve">
<value>Erweitert</value>
<comment>Advanced</comment>
</data>
<data name="Provider" xml:space="preserve">
<value>Anbieter</value>
<comment>Provider</comment>
</data>
<data name="Provider name (e.g. YouTube)." xml:space="preserve">
<value>Anbietername (z. B. YouTube).</value>
<comment>Provider name (e.g. YouTube).</comment>
</data>
<data name="Video ID" xml:space="preserve">
<value>Video-ID</value>
<comment>Video ID</comment>
</data>
<data name="Use this when the URL above cannot be parsed (non-YouTube providers)." xml:space="preserve">
<value>Verwenden Sie dies, wenn die obige URL nicht analysiert werden kann (Nicht-YouTube-Anbieter).</value>
<comment>Use this when the URL above cannot be parsed (non-YouTube providers).</comment>
</data>
<data name="Saving..." xml:space="preserve">
<value>Speichern…</value>
<comment>Saving...</comment>
</data>
<data name="No videos found for this processor." xml:space="preserve">
<value>Für diesen Prozessor wurden keine Videos gefunden.</value>
<comment>No videos found for this processor.</comment>
</data>
<data name="Title" xml:space="preserve">
<value>Titel</value>
<comment>Title</comment>
</data>
<data name="Delete" xml:space="preserve">
<value>Löschen</value>
<comment>Delete</comment>
</data>
<data name="Video linked successfully." xml:space="preserve">
<value>Video erfolgreich verknüpft.</value>
<comment>Video linked successfully.</comment>
</data>
<data name="Failed to link video." xml:space="preserve">
<value>Video konnte nicht verknüpft werden.</value>
<comment>Failed to link video.</comment>
</data>
<data name="Video updated successfully." xml:space="preserve">
<value>Video erfolgreich aktualisiert.</value>
<comment>Video updated successfully.</comment>
</data>
<data name="Failed to update video." xml:space="preserve">
<value>Video konnte nicht aktualisiert werden.</value>
<comment>Failed to update video.</comment>
</data>
<data name="Are you sure you want to delete this video? This action cannot be undone." xml:space="preserve">
<value>Möchten Sie dieses Video wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.</value>
<comment>Are you sure you want to delete this video? This action cannot be undone.</comment>
</data>
<data name="Delete Video" xml:space="preserve">
<value>Video löschen</value>
<comment>Delete Video</comment>
</data>
<data name="Video deleted successfully." xml:space="preserve">
<value>Video erfolgreich gelöscht.</value>
<comment>Video deleted successfully.</comment>
</data>
<data name="Failed to delete video." xml:space="preserve">
<value>Video konnte nicht gelöscht werden.</value>
<comment>Failed to delete video.</comment>
</data>
</root>

View File

@@ -655,4 +655,79 @@
<value>Status</value>
<comment>Column header for import status</comment>
</data>
<data name="Videos" xml:space="preserve">
<value>Videos</value>
</data>
<data name="Video Management" xml:space="preserve">
<value>Video Management</value>
</data>
<data name="Manage processor videos." xml:space="preserve">
<value>Manage processor videos.</value>
</data>
<data name="Back to Processors" xml:space="preserve">
<value>Back to Processors</value>
</data>
<data name="Link Video" xml:space="preserve">
<value>Link Video</value>
</data>
<data name="YouTube URL or Video ID" xml:space="preserve">
<value>YouTube URL or Video ID</value>
</data>
<data name="Detected Video ID" xml:space="preserve">
<value>Detected Video ID</value>
</data>
<data name="Video Title" xml:space="preserve">
<value>Video Title</value>
</data>
<data name="Advanced" xml:space="preserve">
<value>Advanced</value>
</data>
<data name="Provider" xml:space="preserve">
<value>Provider</value>
</data>
<data name="Provider name (e.g. YouTube)." xml:space="preserve">
<value>Provider name (e.g. YouTube).</value>
</data>
<data name="Video ID" xml:space="preserve">
<value>Video ID</value>
</data>
<data name="Use this when the URL above cannot be parsed (non-YouTube providers)." xml:space="preserve">
<value>Use this when the URL above cannot be parsed (non-YouTube providers).</value>
</data>
<data name="Saving..." xml:space="preserve">
<value>Saving...</value>
</data>
<data name="No videos found for this processor." xml:space="preserve">
<value>No videos found for this processor.</value>
</data>
<data name="Title" xml:space="preserve">
<value>Title</value>
</data>
<data name="Delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Video linked successfully." xml:space="preserve">
<value>Video linked successfully.</value>
</data>
<data name="Failed to link video." xml:space="preserve">
<value>Failed to link video.</value>
</data>
<data name="Video updated successfully." xml:space="preserve">
<value>Video updated successfully.</value>
</data>
<data name="Failed to update video." xml:space="preserve">
<value>Failed to update video.</value>
</data>
<data name="Are you sure you want to delete this video? This action cannot be undone." xml:space="preserve">
<value>Are you sure you want to delete this video? This action cannot be undone.</value>
</data>
<data name="Delete Video" xml:space="preserve">
<value>Delete Video</value>
</data>
<data name="Video deleted successfully." xml:space="preserve">
<value>Video deleted successfully.</value>
</data>
<data name="Failed to delete video." xml:space="preserve">
<value>Failed to delete video.</value>
</data>
</root>

View File

@@ -607,4 +607,104 @@
<value>Estado</value>
<comment>Column header for import status</comment>
</data>
<data name="Videos" xml:space="preserve">
<value>Vídeos</value>
<comment>Videos</comment>
</data>
<data name="Video Management" xml:space="preserve">
<value>Gestión de vídeos</value>
<comment>Video Management</comment>
</data>
<data name="Manage processor videos." xml:space="preserve">
<value>Gestionar vídeos del procesador.</value>
<comment>Manage processor videos.</comment>
</data>
<data name="Back to Processors" xml:space="preserve">
<value>Volver a procesadores</value>
<comment>Back to Processors</comment>
</data>
<data name="Link Video" xml:space="preserve">
<value>Vincular vídeo</value>
<comment>Link Video</comment>
</data>
<data name="YouTube URL or Video ID" xml:space="preserve">
<value>URL de YouTube o ID del vídeo</value>
<comment>YouTube URL or Video ID</comment>
</data>
<data name="Detected Video ID" xml:space="preserve">
<value>ID de vídeo detectado</value>
<comment>Detected Video ID</comment>
</data>
<data name="Video Title" xml:space="preserve">
<value>Título del vídeo</value>
<comment>Video Title</comment>
</data>
<data name="Advanced" xml:space="preserve">
<value>Avanzado</value>
<comment>Advanced</comment>
</data>
<data name="Provider" xml:space="preserve">
<value>Proveedor</value>
<comment>Provider</comment>
</data>
<data name="Provider name (e.g. YouTube)." xml:space="preserve">
<value>Nombre del proveedor (p. ej., YouTube).</value>
<comment>Provider name (e.g. YouTube).</comment>
</data>
<data name="Video ID" xml:space="preserve">
<value>ID del vídeo</value>
<comment>Video ID</comment>
</data>
<data name="Use this when the URL above cannot be parsed (non-YouTube providers)." xml:space="preserve">
<value>Úselo cuando la URL anterior no pueda analizarse (proveedores distintos de YouTube).</value>
<comment>Use this when the URL above cannot be parsed (non-YouTube providers).</comment>
</data>
<data name="Saving..." xml:space="preserve">
<value>Guardando…</value>
<comment>Saving...</comment>
</data>
<data name="No videos found for this processor." xml:space="preserve">
<value>No se encontraron vídeos para este procesador.</value>
<comment>No videos found for this processor.</comment>
</data>
<data name="Title" xml:space="preserve">
<value>Título</value>
<comment>Title</comment>
</data>
<data name="Delete" xml:space="preserve">
<value>Eliminar</value>
<comment>Delete</comment>
</data>
<data name="Video linked successfully." xml:space="preserve">
<value>Vídeo vinculado correctamente.</value>
<comment>Video linked successfully.</comment>
</data>
<data name="Failed to link video." xml:space="preserve">
<value>No se pudo vincular el vídeo.</value>
<comment>Failed to link video.</comment>
</data>
<data name="Video updated successfully." xml:space="preserve">
<value>Vídeo actualizado correctamente.</value>
<comment>Video updated successfully.</comment>
</data>
<data name="Failed to update video." xml:space="preserve">
<value>No se pudo actualizar el vídeo.</value>
<comment>Failed to update video.</comment>
</data>
<data name="Are you sure you want to delete this video? This action cannot be undone." xml:space="preserve">
<value>¿Está seguro de que desea eliminar este vídeo? Esta acción no se puede deshacer.</value>
<comment>Are you sure you want to delete this video? This action cannot be undone.</comment>
</data>
<data name="Delete Video" xml:space="preserve">
<value>Eliminar vídeo</value>
<comment>Delete Video</comment>
</data>
<data name="Video deleted successfully." xml:space="preserve">
<value>Vídeo eliminado correctamente.</value>
<comment>Video deleted successfully.</comment>
</data>
<data name="Failed to delete video." xml:space="preserve">
<value>No se pudo eliminar el vídeo.</value>
<comment>Failed to delete video.</comment>
</data>
</root>

View File

@@ -526,4 +526,104 @@
<value>Année uniquement</value>
<comment>Year only</comment>
</data>
<data name="Videos" xml:space="preserve">
<value>Vidéos</value>
<comment>Videos</comment>
</data>
<data name="Video Management" xml:space="preserve">
<value>Gestion des vidéos</value>
<comment>Video Management</comment>
</data>
<data name="Manage processor videos." xml:space="preserve">
<value>Gérer les vidéos du processeur.</value>
<comment>Manage processor videos.</comment>
</data>
<data name="Back to Processors" xml:space="preserve">
<value>Retour aux processeurs</value>
<comment>Back to Processors</comment>
</data>
<data name="Link Video" xml:space="preserve">
<value>Associer une vidéo</value>
<comment>Link Video</comment>
</data>
<data name="YouTube URL or Video ID" xml:space="preserve">
<value>URL YouTube ou identifiant de la vidéo</value>
<comment>YouTube URL or Video ID</comment>
</data>
<data name="Detected Video ID" xml:space="preserve">
<value>Identifiant de vidéo détecté</value>
<comment>Detected Video ID</comment>
</data>
<data name="Video Title" xml:space="preserve">
<value>Titre de la vidéo</value>
<comment>Video Title</comment>
</data>
<data name="Advanced" xml:space="preserve">
<value>Avancé</value>
<comment>Advanced</comment>
</data>
<data name="Provider" xml:space="preserve">
<value>Fournisseur</value>
<comment>Provider</comment>
</data>
<data name="Provider name (e.g. YouTube)." xml:space="preserve">
<value>Nom du fournisseur (par ex. YouTube).</value>
<comment>Provider name (e.g. YouTube).</comment>
</data>
<data name="Video ID" xml:space="preserve">
<value>Identifiant de la vidéo</value>
<comment>Video ID</comment>
</data>
<data name="Use this when the URL above cannot be parsed (non-YouTube providers)." xml:space="preserve">
<value>Utilisez ceci lorsque l'URL ci-dessus ne peut pas être analysée (fournisseurs autres que YouTube).</value>
<comment>Use this when the URL above cannot be parsed (non-YouTube providers).</comment>
</data>
<data name="Saving..." xml:space="preserve">
<value>Enregistrement…</value>
<comment>Saving...</comment>
</data>
<data name="No videos found for this processor." xml:space="preserve">
<value>Aucune vidéo trouvée pour ce processeur.</value>
<comment>No videos found for this processor.</comment>
</data>
<data name="Title" xml:space="preserve">
<value>Titre</value>
<comment>Title</comment>
</data>
<data name="Delete" xml:space="preserve">
<value>Supprimer</value>
<comment>Delete</comment>
</data>
<data name="Video linked successfully." xml:space="preserve">
<value>Vidéo associée avec succès.</value>
<comment>Video linked successfully.</comment>
</data>
<data name="Failed to link video." xml:space="preserve">
<value>Échec de l'association de la vidéo.</value>
<comment>Failed to link video.</comment>
</data>
<data name="Video updated successfully." xml:space="preserve">
<value>Vidéo mise à jour avec succès.</value>
<comment>Video updated successfully.</comment>
</data>
<data name="Failed to update video." xml:space="preserve">
<value>Échec de la mise à jour de la vidéo.</value>
<comment>Failed to update video.</comment>
</data>
<data name="Are you sure you want to delete this video? This action cannot be undone." xml:space="preserve">
<value>Êtes-vous sûr de vouloir supprimer cette vidéo ? Cette action ne peut pas être annulée.</value>
<comment>Are you sure you want to delete this video? This action cannot be undone.</comment>
</data>
<data name="Delete Video" xml:space="preserve">
<value>Supprimer la vidéo</value>
<comment>Delete Video</comment>
</data>
<data name="Video deleted successfully." xml:space="preserve">
<value>Vidéo supprimée avec succès.</value>
<comment>Video deleted successfully.</comment>
</data>
<data name="Failed to delete video." xml:space="preserve">
<value>Échec de la suppression de la vidéo.</value>
<comment>Failed to delete video.</comment>
</data>
</root>

View File

@@ -526,4 +526,104 @@
<value>Solo anno</value>
<comment>Year only</comment>
</data>
<data name="Videos" xml:space="preserve">
<value>Video</value>
<comment>Videos</comment>
</data>
<data name="Video Management" xml:space="preserve">
<value>Gestione video</value>
<comment>Video Management</comment>
</data>
<data name="Manage processor videos." xml:space="preserve">
<value>Gestisci i video del processore.</value>
<comment>Manage processor videos.</comment>
</data>
<data name="Back to Processors" xml:space="preserve">
<value>Torna ai processori</value>
<comment>Back to Processors</comment>
</data>
<data name="Link Video" xml:space="preserve">
<value>Collega video</value>
<comment>Link Video</comment>
</data>
<data name="YouTube URL or Video ID" xml:space="preserve">
<value>URL di YouTube o ID del video</value>
<comment>YouTube URL or Video ID</comment>
</data>
<data name="Detected Video ID" xml:space="preserve">
<value>ID del video rilevato</value>
<comment>Detected Video ID</comment>
</data>
<data name="Video Title" xml:space="preserve">
<value>Titolo del video</value>
<comment>Video Title</comment>
</data>
<data name="Advanced" xml:space="preserve">
<value>Avanzate</value>
<comment>Advanced</comment>
</data>
<data name="Provider" xml:space="preserve">
<value>Fornitore</value>
<comment>Provider</comment>
</data>
<data name="Provider name (e.g. YouTube)." xml:space="preserve">
<value>Nome del fornitore (es. YouTube).</value>
<comment>Provider name (e.g. YouTube).</comment>
</data>
<data name="Video ID" xml:space="preserve">
<value>ID del video</value>
<comment>Video ID</comment>
</data>
<data name="Use this when the URL above cannot be parsed (non-YouTube providers)." xml:space="preserve">
<value>Usalo quando l'URL sopra non può essere analizzato (fornitori diversi da YouTube).</value>
<comment>Use this when the URL above cannot be parsed (non-YouTube providers).</comment>
</data>
<data name="Saving..." xml:space="preserve">
<value>Salvataggio…</value>
<comment>Saving...</comment>
</data>
<data name="No videos found for this processor." xml:space="preserve">
<value>Nessun video trovato per questo processore.</value>
<comment>No videos found for this processor.</comment>
</data>
<data name="Title" xml:space="preserve">
<value>Titolo</value>
<comment>Title</comment>
</data>
<data name="Delete" xml:space="preserve">
<value>Elimina</value>
<comment>Delete</comment>
</data>
<data name="Video linked successfully." xml:space="preserve">
<value>Video collegato con successo.</value>
<comment>Video linked successfully.</comment>
</data>
<data name="Failed to link video." xml:space="preserve">
<value>Impossibile collegare il video.</value>
<comment>Failed to link video.</comment>
</data>
<data name="Video updated successfully." xml:space="preserve">
<value>Video aggiornato con successo.</value>
<comment>Video updated successfully.</comment>
</data>
<data name="Failed to update video." xml:space="preserve">
<value>Impossibile aggiornare il video.</value>
<comment>Failed to update video.</comment>
</data>
<data name="Are you sure you want to delete this video? This action cannot be undone." xml:space="preserve">
<value>Sei sicuro di voler eliminare questo video? Questa azione non può essere annullata.</value>
<comment>Are you sure you want to delete this video? This action cannot be undone.</comment>
</data>
<data name="Delete Video" xml:space="preserve">
<value>Elimina video</value>
<comment>Delete Video</comment>
</data>
<data name="Video deleted successfully." xml:space="preserve">
<value>Video eliminato con successo.</value>
<comment>Video deleted successfully.</comment>
</data>
<data name="Failed to delete video." xml:space="preserve">
<value>Impossibile eliminare il video.</value>
<comment>Failed to delete video.</comment>
</data>
</root>

View File

@@ -258,4 +258,92 @@ public class ProcessorsService(Marechai.ApiClient.Client client)
return (false, ex.Message);
}
}
// ── Videos ──
static string ExtractErrorMessage(ApiException ex)
{
if(ex is ProblemDetails pd) return pd.Detail ?? pd.Title ?? ex.Message;
return ex.Message;
}
public async Task<List<ProcessorVideoDto>> GetVideosByProcessorAsync(int processorId)
{
try
{
var result = await client.Processors[processorId].Videos.GetAsync();
return result ?? [];
}
catch
{
return [];
}
}
public async Task<(ProcessorVideoDto dto, string error)> CreateVideoAsync(int processorId, string provider,
string videoId,
string title)
{
try
{
var dto = await client.Processors[processorId]
.Videos.PostAsync(new CreateProcessorVideoRequest
{
Provider = provider,
VideoId = videoId,
Title = title
});
return (dto, null);
}
catch(ApiException ex)
{
return (null, ExtractErrorMessage(ex));
}
catch(Exception ex)
{
return (null, ex.Message);
}
}
public async Task<(bool succeeded, string error)> UpdateVideoTitleAsync(long id, string title)
{
try
{
await client.Processors.Videos[id].PutAsync(new UpdateProcessorVideoRequest
{
Title = title
});
return (true, null);
}
catch(ApiException ex)
{
return (false, ExtractErrorMessage(ex));
}
catch(Exception ex)
{
return (false, ex.Message);
}
}
public async Task<(bool succeeded, string error)> DeleteVideoAsync(long id)
{
try
{
await client.Processors.Videos[id].DeleteAsync();
return (true, null);
}
catch(ApiException ex)
{
return (false, ExtractErrorMessage(ex));
}
catch(Exception ex)
{
return (false, ex.Message);
}
}
}