Skip to content

Commit

Permalink
#198 migration of media file refactor; fails for member users resoved
Browse files Browse the repository at this point in the history
  • Loading branch information
tkrch committed Jul 14, 2024
1 parent d69d071 commit 73ba959
Show file tree
Hide file tree
Showing 31 changed files with 531 additions and 1,242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Reflection;
using Microsoft.Extensions.Logging;
using Migration.Toolkit.Common;
using Migration.Toolkit.Common.Services;
using Migration.Toolkit.Source.Services;

public class PrimaryKeyMappingContext(
Expand All @@ -23,7 +22,7 @@ public class PrimaryKeyMappingContext(
var mappings = toolkitConfiguration.EntityConfigurations?.GetEntityConfiguration<T>().ExplicitPrimaryKeyMapping;
if (mappings?.TryGetValue(memberName, out var memberMappings) ?? false)
{
return memberMappings.TryGetValue($"{sourceId}", out var mappedId) ? mappedId : null;
return memberMappings.GetValueOrDefault($"{sourceId}");
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ AttachmentMigrator attachmentMigrator
{
public async Task<CommandResult> Handle(MigrateAttachmentsCommand request, CancellationToken cancellationToken)
{
var kx13CmsAttachments = modelFacade.SelectAll<ICmsAttachment>();
var ksCmsAttachments = modelFacade.SelectAll<ICmsAttachment>();

foreach (var kx13CmsAttachment in kx13CmsAttachments)
foreach (var ksCmsAttachment in ksCmsAttachments)
{
if (kx13CmsAttachment.AttachmentIsUnsorted != true || kx13CmsAttachment.AttachmentGroupGUID != null)
if (ksCmsAttachment.AttachmentIsUnsorted != true || ksCmsAttachment.AttachmentGroupGUID != null)
{
// those must be migrated with pages
continue;
}

var (_, canContinue, _, _) = attachmentMigrator.MigrateAttachment(kx13CmsAttachment);
var (_, canContinue, _, _) = attachmentMigrator.MigrateAttachment(ksCmsAttachment);
if (!canContinue)
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
namespace Migration.Toolkit.Core.K11.Handlers;
namespace Migration.Toolkit.Source.Handlers;

using CMS.Base;
using CMS.MediaLibrary;
using MediatR;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Migration.Toolkit.Common;
using Migration.Toolkit.Common.Abstractions;
using Migration.Toolkit.Common.MigrationProtocol;
using Migration.Toolkit.Core.K11.Contexts;
using Migration.Toolkit.Core.K11.Mappers;
using Migration.Toolkit.K11;
using Migration.Toolkit.K11.Models;
using Migration.Toolkit.KXP.Api;
using Migration.Toolkit.KXP.Api.Auxiliary;
using Migration.Toolkit.KXP.Context;

public class MigrateMediaLibrariesCommandHandler(ILogger<MigrateMediaLibrariesCommandHandler> logger,
IDbContextFactory<KxpContext> kxpContextFactory,
IDbContextFactory<K11Context> k11ContextFactory,
IEntityMapper<MediaLibrary, MediaLibraryInfo> mediaLibraryInfoMapper,
KxpMediaFileFacade mediaFileFacade,
IEntityMapper<MediaFileInfoMapperSource, MediaFileInfo> mediaFileInfoMapper,
ToolkitConfiguration toolkitConfiguration,
PrimaryKeyMappingContext primaryKeyMappingContext,
IProtocol protocol)
using Migration.Toolkit.KXP.Models;
using Migration.Toolkit.Source.Contexts;
using Migration.Toolkit.Source.Mappers;
using Migration.Toolkit.Source.Model;

public class MigrateMediaLibrariesCommandHandler(
ILogger<MigrateMediaLibrariesCommandHandler> logger,
IDbContextFactory<KxpContext> kxpContextFactory,
ModelFacade modelFacade,
KxpMediaFileFacade mediaFileFacade,
IEntityMapper<MediaFileInfoMapperSource, MediaFileInfo> mediaFileInfoMapper,
IEntityMapper<MediaLibraryInfoMapperSource, MediaLibraryInfo> mediaLibraryInfoMapper,
ToolkitConfiguration toolkitConfiguration,
PrimaryKeyMappingContext primaryKeyMappingContext,
IProtocol protocol)
: IRequestHandler<MigrateMediaLibrariesCommand, CommandResult>, IDisposable
{
private const string DIR_MEDIA = "media";
Expand All @@ -33,66 +35,39 @@ public class MigrateMediaLibrariesCommandHandler(ILogger<MigrateMediaLibrariesCo

public async Task<CommandResult> Handle(MigrateMediaLibrariesCommand request, CancellationToken cancellationToken)
{
await using var k11Context = await k11ContextFactory.CreateDbContextAsync(cancellationToken);

var skippedMediaLibraries = new HashSet<Guid>();
var unsuitableMediaLibraries = k11Context.MediaLibraries
.Include(ml => ml.LibrarySite)
.GroupBy(l => l.LibraryName);
var ksMediaLibraries = modelFacade.SelectAll<IMediaLibrary>(" ORDER BY LibraryID");

foreach (var mlg in unsuitableMediaLibraries)
var migratedMediaLibraries = new List<(IMediaLibrary sourceLibrary, ICmsSite sourceSite, MediaLibraryInfo targetLibrary)>();
foreach (var ksMediaLibrary in ksMediaLibraries)
{
if (mlg.Count() <= 1) continue;

logger.LogError("""
Media libraries with LibraryGuid ({LibraryGuids}) have same LibraryName '{LibraryName}', due to removal of sites and media library globalization it is required to set unique LibraryName and LibraryFolder
""", string.Join(",", mlg.Select(l => l.LibraryGuid)), mlg.Key);
protocol.FetchedSource(ksMediaLibrary);

foreach (var ml in mlg)
if (ksMediaLibrary.LibraryGUID is not { } mediaLibraryGuid)
{
if (ml.LibraryGuid is { } libraryGuid)
{
skippedMediaLibraries.Add(libraryGuid);
}

protocol.Append(HandbookReferences.NotCurrentlySupportedSkip()
.WithMessage($"Media library '{ml.LibraryName}' with LibraryGuid '{ml.LibraryGuid}' doesn't satisfy unique LibraryName and LibraryFolder condition for migration")
.WithIdentityPrint(ml)
.WithData(new { ml.LibraryGuid, ml.LibraryName, ml.LibrarySiteId, ml.LibraryFolder})
protocol.Append(HandbookReferences
.InvalidSourceData<MediaLibrary>()
.WithId(nameof(MediaLibrary.LibraryId), ksMediaLibrary.LibraryID)
.WithMessage("Media library has missing MediaLibraryGUID")
);
}
}

var k11MediaLibraries = k11Context.MediaLibraries
.Include(ml => ml.LibrarySite)
.OrderBy(t => t.LibraryId)
;

var migratedMediaLibraries = new List<(MediaLibrary sourceLibrary, MediaLibraryInfo targetLibrary)>();
foreach (var k11MediaLibrary in k11MediaLibraries)
{
if (k11MediaLibrary.LibraryGuid is {} libraryGuid && skippedMediaLibraries.Contains(libraryGuid))
{
continue;
}

protocol.FetchedSource(k11MediaLibrary);
var mediaLibraryInfo = mediaFileFacade.GetMediaLibraryInfo(mediaLibraryGuid);

protocol.FetchedTarget(mediaLibraryInfo);

if (k11MediaLibrary.LibraryGuid is not { } mediaLibraryGuid)
if (modelFacade.SelectById<ICmsSite>(ksMediaLibrary.LibrarySiteID) is not { } ksSite)
{
protocol.Append(HandbookReferences
.InvalidSourceData<MediaLibrary>()
.WithId(nameof(MediaLibrary.LibraryId), k11MediaLibrary.LibraryId)
.WithMessage("Media library has missing MediaLibraryGUID")
.WithId(nameof(MediaLibrary.LibraryId), ksMediaLibrary.LibraryID)
.WithMessage("Media library has missing site assigned")
);
logger.LogError("Missing site, SiteID=={SiteId}", ksMediaLibrary.LibrarySiteID);
continue;
}

var mediaLibraryInfo = mediaFileFacade.GetMediaLibraryInfo(mediaLibraryGuid);

protocol.FetchedTarget(mediaLibraryInfo);

var mapped = mediaLibraryInfoMapper.Map(k11MediaLibrary, mediaLibraryInfo);
var mapped = mediaLibraryInfoMapper.Map(new (ksMediaLibrary, ksSite), mediaLibraryInfo);
protocol.MappedTarget(mapped);

if (mapped is { Success : true } result)
Expand All @@ -104,7 +79,7 @@ Media libraries with LibraryGuid ({LibraryGuids}) have same LibraryName '{Librar
{
mediaFileFacade.SetMediaLibrary(mfi);

protocol.Success(k11MediaLibrary, mfi, mapped);
protocol.Success(ksMediaLibrary, mfi, mapped);
logger.LogEntitySetAction(newInstance, mfi);
}
catch (Exception ex)
Expand All @@ -123,15 +98,15 @@ Media libraries with LibraryGuid ({LibraryGuids}) have same LibraryName '{Librar

primaryKeyMappingContext.SetMapping<MediaLibrary>(
r => r.LibraryId,
k11MediaLibrary.LibraryId,
ksMediaLibrary.LibraryID,
mfi.LibraryID
);

migratedMediaLibraries.Add((k11MediaLibrary, mfi));
migratedMediaLibraries.Add((ksMediaLibrary, ksSite, mfi));
}
}

await RequireMigratedMediaFiles(migratedMediaLibraries, k11Context, cancellationToken);
await RequireMigratedMediaFiles(migratedMediaLibraries, cancellationToken);

return new GenericCommandResult();
}
Expand All @@ -155,49 +130,46 @@ private LoadMediaFileResult LoadMediaFileBinary(string? sourceMediaLibraryPath,
return new LoadMediaFileResult(false, null);
}

private async Task RequireMigratedMediaFiles(
List<(MediaLibrary sourceLibrary, MediaLibraryInfo targetLibrary)> migratedMediaLibraries,
K11Context k11Context, CancellationToken cancellationToken)
private async Task RequireMigratedMediaFiles(List<(IMediaLibrary sourceLibrary, ICmsSite sourceSite, MediaLibraryInfo targetLibrary)> migratedMediaLibraries, CancellationToken cancellationToken)
{
var kxoDbContext = await kxpContextFactory.CreateDbContextAsync(cancellationToken);
try
{
foreach (var (sourceMediaLibrary, targetMediaLibrary) in migratedMediaLibraries)
foreach (var (ksMediaLibrary, ksSite, targetMediaLibrary) in migratedMediaLibraries)
{
string? sourceMediaLibraryPath = null;
var loadMediaFileData = false;
if (!toolkitConfiguration.MigrateOnlyMediaFileInfo.GetValueOrDefault(true) &&
!string.IsNullOrWhiteSpace(toolkitConfiguration.KxCmsDirPath))
{
sourceMediaLibraryPath = Path.Combine(toolkitConfiguration.KxCmsDirPath, sourceMediaLibrary.LibrarySite.SiteName, DIR_MEDIA, sourceMediaLibrary.LibraryFolder);
sourceMediaLibraryPath = Path.Combine(toolkitConfiguration.KxCmsDirPath, ksSite.SiteName, DIR_MEDIA, ksMediaLibrary.LibraryFolder);
loadMediaFileData = true;
}

var k11MediaFiles = k11Context.MediaFiles
.Where(x => x.FileLibraryId == sourceMediaLibrary.LibraryId);
var ksMediaFiles = modelFacade.SelectWhere<IMediaFile>("FileLibraryID = @FileLibraryId", new SqlParameter("FileLibraryId", ksMediaLibrary.LibraryID));

foreach (var k11MediaFile in k11MediaFiles)
foreach (var ksMediaFile in ksMediaFiles)
{
protocol.FetchedSource(k11MediaFile);
protocol.FetchedSource(ksMediaFile);

bool found = false;
IUploadedFile? uploadedFile = null;
if (loadMediaFileData)
{
(found, uploadedFile) = LoadMediaFileBinary(sourceMediaLibraryPath, k11MediaFile.FilePath, k11MediaFile.FileMimeType);
(found, uploadedFile) = LoadMediaFileBinary(sourceMediaLibraryPath, ksMediaFile.FilePath, ksMediaFile.FileMimeType);
if (!found)
{
// TODO tk: 2022-07-07 report missing file (currently reported in mapper)
}
}

var librarySubfolder = Path.GetDirectoryName(k11MediaFile.FilePath);
var librarySubfolder = Path.GetDirectoryName(ksMediaFile.FilePath);

var kxoMediaFile = mediaFileFacade.GetMediaFile(k11MediaFile.FileGuid);
var kxoMediaFile = mediaFileFacade.GetMediaFile(ksMediaFile.FileGUID);

protocol.FetchedTarget(kxoMediaFile);

var source = new MediaFileInfoMapperSource(k11MediaFile, targetMediaLibrary.LibraryID, found ? uploadedFile : null,
var source = new MediaFileInfoMapperSource(ksMediaFile, targetMediaLibrary.LibraryID, found ? uploadedFile : null,
librarySubfolder, toolkitConfiguration.MigrateOnlyMediaFileInfo.GetValueOrDefault(false));
var mapped = mediaFileInfoMapper.Map(source, kxoMediaFile);
protocol.MappedTarget(mapped);
Expand All @@ -217,10 +189,10 @@ private async Task RequireMigratedMediaFiles(
mediaFileFacade.SetMediaFile(mf, newInstance);
await _kxpContext.SaveChangesAsync(cancellationToken);

protocol.Success(k11MediaFile, mf, mapped);
protocol.Success(ksMediaFile, mf, mapped);
logger.LogEntitySetAction(newInstance, mf);
}
catch (Exception ex) // TODO tk: 2022-05-18 handle exceptions
catch (Exception ex)
{
await kxoDbContext.DisposeAsync(); // reset context errors
kxoDbContext = await kxpContextFactory.CreateDbContextAsync(cancellationToken);
Expand All @@ -236,7 +208,7 @@ private async Task RequireMigratedMediaFiles(

primaryKeyMappingContext.SetMapping<MediaFile>(
r => r.FileId,
k11MediaFile.FileId,
ksMediaFile.FileID,
mf.FileID
);
}
Expand Down
45 changes: 45 additions & 0 deletions KVA/Migration.Toolkit.Source/Helpers/AdminUserHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Migration.Toolkit.Source.Helpers;

using CMS.Base;
using Microsoft.Extensions.DependencyInjection;
using Migration.Toolkit.Common;
using Migration.Toolkit.KXP.Api.Auxiliary;
using Migration.Toolkit.Source.Model;

public class AdminUserHelper
{
/// <summary>
/// maps source user id to target admin user
/// </summary>
/// <param name="sourceUserId">source user id</param>
/// <param name="memberFallbackTargetUserId">in case user was migrated to member, replacement admin user shall be returned</param>
/// <param name="onAdminNotFound">called when no admin user exists in target instance, but is expected to exist</param>
/// <returns>Target admin user ID</returns>
public static int? MapTargetAdminUser(int? sourceUserId, int memberFallbackTargetUserId, Action? onAdminNotFound = null)
{
if (sourceUserId is null) return null;

var modelFacade = KsCoreDiExtensions.ServiceProvider.GetRequiredService<ModelFacade>();
if (modelFacade.SelectById<ICmsUser>(sourceUserId) is { } modifiedByUser)
{
if (UserHelper.PrivilegeLevelsMigratedAsAdminUser.Contains(modifiedByUser.UserPrivilegeLevel))
{
var primaryKeyMappingContext = KsCoreDiExtensions.ServiceProvider.GetRequiredService<IPrimaryKeyMappingContext>();
switch (primaryKeyMappingContext.MapSourceId<ICmsUser>(u => u.UserID, sourceUserId))
{
case { Success: true, MappedId: { } targetUserId }:
return targetUserId;
default:
{
onAdminNotFound?.Invoke();
return null;
}
}
}
return CMSActionContext.CurrentUser.UserID;
}

// user not found, setting fallback
return CMSActionContext.CurrentUser.UserID;
}
}
11 changes: 11 additions & 0 deletions KVA/Migration.Toolkit.Source/KsCoreDiExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ namespace Migration.Toolkit.Source;

public static class KsCoreDiExtensions
{
public static IServiceProvider ServiceProvider { get; private set; } = null!;
public static void InitServiceProvider(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}

public static IServiceCollection UseKsToolkitCore(this IServiceCollection services)
{
var printService = new PrintService();
Expand Down Expand Up @@ -58,6 +64,7 @@ public static IServiceCollection UseKsToolkitCore(this IServiceCollection servic
services.AddTransient<ReusableSchemaService>();

services.AddScoped<PrimaryKeyMappingContext>();
services.AddScoped<IPrimaryKeyMappingContext, PrimaryKeyMappingContext>(s => s.GetRequiredService<PrimaryKeyMappingContext>());
services.AddScoped<IPrimaryKeyLocatorService, PrimaryKeyLocatorService>();

// commands
Expand All @@ -79,6 +86,10 @@ public static IServiceCollection UseKsToolkitCore(this IServiceCollection servic
services.AddTransient<IEntityMapper<AlternativeFormMapperSource, AlternativeFormInfo>, AlternativeFormMapper>();
services.AddTransient<IEntityMapper<MemberInfoMapperSource, MemberInfo>, MemberInfoMapper>();
services.AddTransient<IEntityMapper<ICmsPageTemplateConfiguration, PageTemplateConfigurationInfo>, PageTemplateConfigurationMapper>();
services.AddTransient<IEntityMapper<MediaLibraryInfoMapperSource, MediaLibraryInfo>, MediaLibraryInfoMapper>();
services.AddTransient<IEntityMapper<MediaFileInfoMapperSource, MediaFileInfo>, MediaFileInfoMapper>();



services.AddUniversalMigrationToolkit();

Expand Down
Loading

0 comments on commit 73ba959

Please sign in to comment.