634 lines
26 KiB
C#
634 lines
26 KiB
C#
using System.Security.Claims;
|
|
using Socialize.Infrastructure.Security;
|
|
using Socialize.Modules.Identity.Contracts;
|
|
using Socialize.Modules.Identity.Data;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Socialize.Infrastructure.Development;
|
|
|
|
public static class DevelopmentSeedExtensions
|
|
{
|
|
private static readonly Guid WorkspaceId = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
|
private static readonly Guid ScopedClientId = Guid.Parse("22222222-2222-2222-2222-222222222222");
|
|
private static readonly Guid HiddenClientId = Guid.Parse("22222222-2222-2222-2222-333333333333");
|
|
private static readonly Guid ScopedProjectId = Guid.Parse("33333333-3333-3333-3333-333333333333");
|
|
private static readonly Guid HiddenProjectId = Guid.Parse("33333333-3333-3333-3333-444444444444");
|
|
private static readonly Guid ScopedContentItemId = Guid.Parse("44444444-4444-4444-4444-444444444444");
|
|
private static readonly Guid HiddenContentItemId = Guid.Parse("44444444-4444-4444-4444-555555555555");
|
|
private static readonly Guid ScopedAssetId = Guid.Parse("55555555-5555-5555-5555-555555555555");
|
|
private static readonly Guid ScopedApprovalRequestId = Guid.Parse("66666666-6666-6666-6666-666666666666");
|
|
private static readonly Guid ClientCommentId = Guid.Parse("77777777-7777-7777-7777-777777777777");
|
|
private static readonly Guid NotificationId = Guid.Parse("88888888-8888-8888-8888-888888888888");
|
|
|
|
public static async Task<IApplicationBuilder> UseDevelopmentSeedAsync(
|
|
this IApplicationBuilder app,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
IHostEnvironment environment = app.ApplicationServices.GetRequiredService<IHostEnvironment>();
|
|
if (!environment.IsDevelopment())
|
|
{
|
|
return app;
|
|
}
|
|
|
|
using IServiceScope scope = app.ApplicationServices.CreateScope();
|
|
IOptions<DevelopmentSeedOptions> options = scope.ServiceProvider.GetRequiredService<IOptions<DevelopmentSeedOptions>>();
|
|
if (!options.Value.Enabled)
|
|
{
|
|
return app;
|
|
}
|
|
|
|
UserManager userManager = scope.ServiceProvider.GetRequiredService<UserManager>();
|
|
AppDbContext dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
|
|
await RemoveLegacyDevUserAsync(userManager);
|
|
|
|
User manager = await EnsureUserAsync(
|
|
userManager,
|
|
id: Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
|
username: "manager",
|
|
email: "manager@socialize.local",
|
|
password: "manager",
|
|
alias: "Northstar Manager",
|
|
firstname: "Morgan",
|
|
lastname: "Reid",
|
|
portraitUrl: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=200&q=80",
|
|
roles: [KnownRoles.Administrator, KnownRoles.Manager, KnownRoles.WorkspaceMember],
|
|
claims:
|
|
[
|
|
new Claim(KnownClaims.WorkspaceScope, WorkspaceId.ToString()),
|
|
]);
|
|
|
|
User clientUser = await EnsureUserAsync(
|
|
userManager,
|
|
id: Guid.Parse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"),
|
|
username: "client",
|
|
email: "client@socialize.local",
|
|
password: "client",
|
|
alias: "Sofia Martin",
|
|
firstname: "Sofia",
|
|
lastname: "Martin",
|
|
portraitUrl: "https://images.unsplash.com/photo-1544723795-3fb6469f5b39?auto=format&fit=crop&w=200&q=80",
|
|
roles: [KnownRoles.Client, KnownRoles.WorkspaceMember],
|
|
claims:
|
|
[
|
|
new Claim(KnownClaims.WorkspaceScope, WorkspaceId.ToString()),
|
|
new Claim(KnownClaims.ClientScope, ScopedClientId.ToString()),
|
|
]);
|
|
|
|
User provider = await EnsureUserAsync(
|
|
userManager,
|
|
id: Guid.Parse("cccccccc-cccc-cccc-cccc-cccccccccccc"),
|
|
username: "provider",
|
|
email: "provider@socialize.local",
|
|
password: "provider",
|
|
alias: "Alex Studio",
|
|
firstname: "Alex",
|
|
lastname: "Studio",
|
|
portraitUrl: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=200&q=80",
|
|
roles: [KnownRoles.Provider, KnownRoles.WorkspaceMember],
|
|
claims:
|
|
[
|
|
new Claim(KnownClaims.WorkspaceScope, WorkspaceId.ToString()),
|
|
new Claim(KnownClaims.ClientScope, ScopedClientId.ToString()),
|
|
new Claim(KnownClaims.ProjectScope, ScopedProjectId.ToString()),
|
|
]);
|
|
|
|
await EnsureWorkspaceDataAsync(
|
|
manager.Id,
|
|
clientUser.Id,
|
|
provider.Id,
|
|
dbContext,
|
|
cancellationToken);
|
|
|
|
return app;
|
|
}
|
|
|
|
private static async Task RemoveLegacyDevUserAsync(UserManager userManager)
|
|
{
|
|
User? legacyUser = await userManager.FindByNameAsync("dev")
|
|
?? await userManager.FindByEmailAsync("dev@socialize.local");
|
|
|
|
if (legacyUser is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await userManager.DeleteAsync(legacyUser);
|
|
}
|
|
|
|
private static async Task<User> EnsureUserAsync(
|
|
UserManager userManager,
|
|
Guid id,
|
|
string username,
|
|
string email,
|
|
string password,
|
|
string alias,
|
|
string firstname,
|
|
string lastname,
|
|
string? portraitUrl,
|
|
IReadOnlyCollection<string> roles,
|
|
IReadOnlyCollection<Claim> claims)
|
|
{
|
|
User? user = await userManager.FindByNameAsync(username)
|
|
?? await userManager.FindByEmailAsync(email);
|
|
|
|
if (user is null)
|
|
{
|
|
user = new User
|
|
{
|
|
Id = id,
|
|
UserName = username,
|
|
Email = email,
|
|
Alias = alias,
|
|
Firstname = firstname,
|
|
Lastname = lastname,
|
|
PortraitUrl = portraitUrl,
|
|
EmailConfirmed = true,
|
|
};
|
|
|
|
IdentityResult createResult = await userManager.CreateAsync(user, password);
|
|
if (!createResult.Succeeded)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Failed to seed development user '{username}': {string.Join(", ", createResult.Errors.Select(error => error.Description))}");
|
|
}
|
|
}
|
|
|
|
user.UserName = username;
|
|
user.Email = email;
|
|
user.Alias = alias;
|
|
user.Firstname = firstname;
|
|
user.Lastname = lastname;
|
|
user.PortraitUrl = portraitUrl;
|
|
user.EmailConfirmed = true;
|
|
await userManager.UpdateAsync(user);
|
|
|
|
if (!await userManager.CheckPasswordAsync(user, password))
|
|
{
|
|
string resetToken = await userManager.GeneratePasswordResetTokenAsync(user);
|
|
IdentityResult passwordResetResult = await userManager.ResetPasswordAsync(user, resetToken, password);
|
|
if (!passwordResetResult.Succeeded)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Failed to set development password for '{username}': {string.Join(", ", passwordResetResult.Errors.Select(error => error.Description))}");
|
|
}
|
|
}
|
|
|
|
IList<string> existingRoles = await userManager.GetRolesAsync(user);
|
|
foreach (string role in roles.Except(existingRoles, StringComparer.Ordinal))
|
|
{
|
|
await userManager.AddToRoleAsync(user, role);
|
|
}
|
|
|
|
foreach (string role in existingRoles
|
|
.Where(role => role is KnownRoles.Manager or KnownRoles.Client or KnownRoles.Provider or KnownRoles.Administrator or KnownRoles.WorkspaceMember)
|
|
.Except(roles, StringComparer.Ordinal))
|
|
{
|
|
await userManager.RemoveFromRoleAsync(user, role);
|
|
}
|
|
|
|
IList<Claim> existingClaims = await userManager.GetClaimsAsync(user);
|
|
List<Claim> managedClaims = existingClaims
|
|
.Where(claim => claim.Type is KnownClaims.WorkspaceScope or KnownClaims.ClientScope or KnownClaims.ProjectScope or KnownClaims.Persona)
|
|
.ToList();
|
|
|
|
foreach (Claim claim in managedClaims)
|
|
{
|
|
await userManager.RemoveClaimAsync(user, claim);
|
|
}
|
|
|
|
string persona = roles.Contains(KnownRoles.Manager, StringComparer.Ordinal)
|
|
? KnownRoles.Manager
|
|
: roles.Contains(KnownRoles.Client, StringComparer.Ordinal)
|
|
? KnownRoles.Client
|
|
: roles.Contains(KnownRoles.Provider, StringComparer.Ordinal)
|
|
? KnownRoles.Provider
|
|
: KnownRoles.WorkspaceMember;
|
|
|
|
foreach (Claim claim in claims.Concat([new Claim(KnownClaims.Persona, persona)]))
|
|
{
|
|
await userManager.AddClaimAsync(user, claim);
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
private static async Task EnsureWorkspaceDataAsync(
|
|
Guid managerUserId,
|
|
Guid clientUserId,
|
|
Guid providerUserId,
|
|
AppDbContext dbContext,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Workspace? workspace = await dbContext.Workspaces
|
|
.SingleOrDefaultAsync(candidate => candidate.Id == WorkspaceId, cancellationToken);
|
|
if (workspace is null)
|
|
{
|
|
workspace = new Workspace
|
|
{
|
|
Id = WorkspaceId,
|
|
Name = string.Empty,
|
|
Slug = string.Empty,
|
|
TimeZone = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
};
|
|
dbContext.Workspaces.Add(workspace);
|
|
}
|
|
|
|
workspace.Name = "Northstar Studio";
|
|
workspace.Slug = "northstar-studio";
|
|
workspace.OwnerUserId = managerUserId;
|
|
workspace.TimeZone = "America/Montreal";
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
|
await UpsertClientAsync(
|
|
dbContext,
|
|
ScopedClientId,
|
|
"Luma Coffee",
|
|
"Active",
|
|
"https://images.unsplash.com/photo-1511920170033-f8396924c348?auto=format&fit=crop&w=200&q=80",
|
|
"Sofia Martin",
|
|
"client@socialize.local",
|
|
WorkspaceId,
|
|
cancellationToken);
|
|
await UpsertClientAsync(
|
|
dbContext,
|
|
HiddenClientId,
|
|
"Atlas Bakery",
|
|
"Active",
|
|
"https://images.unsplash.com/photo-1509440159596-0249088772ff?auto=format&fit=crop&w=200&q=80",
|
|
"Nina Cole",
|
|
"nina@atlasbakery.test",
|
|
WorkspaceId,
|
|
cancellationToken);
|
|
|
|
await UpsertProjectAsync(
|
|
dbContext,
|
|
ScopedProjectId,
|
|
WorkspaceId,
|
|
ScopedClientId,
|
|
"Spring Launch",
|
|
"In progress",
|
|
DateTimeOffset.UtcNow.AddDays(1),
|
|
DateTimeOffset.UtcNow.AddDays(7),
|
|
"Cross-channel launch campaign for the spring offer.",
|
|
"Coordinate creative approvals before the final week.",
|
|
cancellationToken);
|
|
await UpsertProjectAsync(
|
|
dbContext,
|
|
HiddenProjectId,
|
|
WorkspaceId,
|
|
HiddenClientId,
|
|
"Summer Retention",
|
|
"Planned",
|
|
DateTimeOffset.UtcNow.AddDays(10),
|
|
DateTimeOffset.UtcNow.AddDays(16),
|
|
"Retention campaign aimed at existing subscribers.",
|
|
"Sequence email and paid social updates together.",
|
|
cancellationToken);
|
|
|
|
await UpsertContentItemAsync(
|
|
dbContext,
|
|
ScopedContentItemId,
|
|
WorkspaceId,
|
|
ScopedClientId,
|
|
ScopedProjectId,
|
|
"Spring launch hero video",
|
|
"Fresh seasonal menu launch across Instagram and TikTok.",
|
|
"Instagram Reel, TikTok",
|
|
"In client review",
|
|
DateTimeOffset.UtcNow.AddDays(3),
|
|
"v3",
|
|
3,
|
|
cancellationToken);
|
|
await UpsertContentItemAsync(
|
|
dbContext,
|
|
HiddenContentItemId,
|
|
WorkspaceId,
|
|
HiddenClientId,
|
|
HiddenProjectId,
|
|
"Bakery loyalty carousel",
|
|
"Reward regular customers with a four-card retention carousel.",
|
|
"Instagram Carousel",
|
|
"Draft",
|
|
DateTimeOffset.UtcNow.AddDays(10),
|
|
"v1",
|
|
1,
|
|
cancellationToken);
|
|
|
|
await EnsureRevisionAsync(dbContext, Guid.Parse("44444444-4444-4444-4444-000000000001"), ScopedContentItemId, 1, "v1", "Spring launch hero video", "Initial draft for the seasonal menu launch.", "Instagram Reel, TikTok", "Initial concept draft.", providerUserId, DateTimeOffset.UtcNow.AddDays(-5), cancellationToken);
|
|
await EnsureRevisionAsync(dbContext, Guid.Parse("44444444-4444-4444-4444-000000000002"), ScopedContentItemId, 2, "v2", "Spring launch hero video", "Updated hook and transitions after internal review.", "Instagram Reel, TikTok", "Addressed internal pacing feedback.", providerUserId, DateTimeOffset.UtcNow.AddDays(-3), cancellationToken);
|
|
await EnsureRevisionAsync(dbContext, Guid.Parse("44444444-4444-4444-4444-000000000003"), ScopedContentItemId, 3, "v3", "Spring launch hero video", "Fresh seasonal menu launch across Instagram and TikTok.", "Instagram Reel, TikTok", "Client-facing draft after copy cleanup.", providerUserId, DateTimeOffset.UtcNow.AddDays(-1), cancellationToken);
|
|
await EnsureRevisionAsync(dbContext, Guid.Parse("44444444-4444-4444-4444-000000000004"), HiddenContentItemId, 1, "v1", "Bakery loyalty carousel", "Reward regular customers with a four-card retention carousel.", "Instagram Carousel", "First draft.", managerUserId, DateTimeOffset.UtcNow.AddDays(-2), cancellationToken);
|
|
|
|
Asset? asset = await dbContext.Assets.SingleOrDefaultAsync(candidate => candidate.Id == ScopedAssetId, cancellationToken);
|
|
if (asset is null)
|
|
{
|
|
asset = new Asset
|
|
{
|
|
Id = ScopedAssetId,
|
|
AssetType = string.Empty,
|
|
SourceType = string.Empty,
|
|
DisplayName = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow.AddDays(-4),
|
|
};
|
|
dbContext.Assets.Add(asset);
|
|
}
|
|
asset.WorkspaceId = WorkspaceId;
|
|
asset.ContentItemId = ScopedContentItemId;
|
|
asset.AssetType = "Video";
|
|
asset.SourceType = "GoogleDrive";
|
|
asset.DisplayName = "Spring launch cut";
|
|
asset.GoogleDriveFileId = "dev-socialize-demo";
|
|
asset.GoogleDriveLink = "https://drive.google.com/file/d/dev-socialize-demo/view";
|
|
asset.PreviewUrl = "https://drive.google.com/thumbnail?id=dev-socialize-demo";
|
|
asset.CurrentRevisionNumber = 2;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
|
await EnsureAssetRevisionAsync(dbContext, Guid.Parse("55555555-5555-5555-5555-000000000001"), ScopedAssetId, 1, "https://drive.google.com/file/d/dev-socialize-demo-v1/view", "https://drive.google.com/thumbnail?id=dev-socialize-demo-v1", "First uploaded cut from the editor.", providerUserId, DateTimeOffset.UtcNow.AddDays(-4), cancellationToken);
|
|
await EnsureAssetRevisionAsync(dbContext, Guid.Parse("55555555-5555-5555-5555-000000000002"), ScopedAssetId, 2, "https://drive.google.com/file/d/dev-socialize-demo-v2/view", "https://drive.google.com/thumbnail?id=dev-socialize-demo-v2", "Re-export with pacing changes and updated title card.", providerUserId, DateTimeOffset.UtcNow.AddDays(-2), cancellationToken);
|
|
|
|
Comment? comment = await dbContext.Comments.SingleOrDefaultAsync(candidate => candidate.Id == ClientCommentId, cancellationToken);
|
|
if (comment is null)
|
|
{
|
|
comment = new Comment
|
|
{
|
|
Id = ClientCommentId,
|
|
AuthorDisplayName = string.Empty,
|
|
AuthorEmail = string.Empty,
|
|
Body = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow.AddHours(-20),
|
|
};
|
|
dbContext.Comments.Add(comment);
|
|
}
|
|
comment.WorkspaceId = WorkspaceId;
|
|
comment.ContentItemId = ScopedContentItemId;
|
|
comment.AuthorUserId = clientUserId;
|
|
comment.AuthorDisplayName = "Sofia Martin";
|
|
comment.AuthorEmail = "client@socialize.local";
|
|
comment.Body = "Please tighten the opening three seconds and make the launch CTA more explicit.";
|
|
comment.IsResolved = false;
|
|
comment.ResolvedAt = null;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
|
ApprovalRequest? approvalRequest = await dbContext.ApprovalRequests.SingleOrDefaultAsync(candidate => candidate.Id == ScopedApprovalRequestId, cancellationToken);
|
|
if (approvalRequest is null)
|
|
{
|
|
approvalRequest = new ApprovalRequest
|
|
{
|
|
Id = ScopedApprovalRequestId,
|
|
Stage = string.Empty,
|
|
ReviewerName = string.Empty,
|
|
ReviewerEmail = string.Empty,
|
|
State = string.Empty,
|
|
AccessToken = string.Empty,
|
|
SentAt = DateTimeOffset.UtcNow.AddHours(-12),
|
|
};
|
|
dbContext.ApprovalRequests.Add(approvalRequest);
|
|
}
|
|
approvalRequest.WorkspaceId = WorkspaceId;
|
|
approvalRequest.ContentItemId = ScopedContentItemId;
|
|
approvalRequest.Stage = "Client";
|
|
approvalRequest.ReviewerName = "Sofia Martin";
|
|
approvalRequest.ReviewerEmail = "client@socialize.local";
|
|
approvalRequest.RequestedByUserId = managerUserId;
|
|
approvalRequest.DueAt = DateTimeOffset.UtcNow.AddDays(1);
|
|
approvalRequest.State = "Pending";
|
|
approvalRequest.AccessToken = "seed-client-review-token";
|
|
approvalRequest.CompletedAt = null;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
|
NotificationEvent? approvalNotification = await dbContext.NotificationEvents.SingleOrDefaultAsync(candidate => candidate.Id == NotificationId, cancellationToken);
|
|
if (approvalNotification is null)
|
|
{
|
|
approvalNotification = new NotificationEvent
|
|
{
|
|
Id = NotificationId,
|
|
EventType = string.Empty,
|
|
EntityType = string.Empty,
|
|
Message = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow.AddHours(-12),
|
|
};
|
|
dbContext.NotificationEvents.Add(approvalNotification);
|
|
}
|
|
approvalNotification.WorkspaceId = WorkspaceId;
|
|
approvalNotification.ContentItemId = ScopedContentItemId;
|
|
approvalNotification.EventType = "approval.requested";
|
|
approvalNotification.EntityType = "ApprovalRequest";
|
|
approvalNotification.EntityId = ScopedApprovalRequestId;
|
|
approvalNotification.Message = "Approval requested from Sofia Martin for Spring launch hero video.";
|
|
approvalNotification.RecipientEmail = "client@socialize.local";
|
|
approvalNotification.MetadataJson = """{"stage":"Client"}""";
|
|
approvalNotification.ReadAt = null;
|
|
Guid commentNotificationId = Guid.Parse("88888888-8888-8888-8888-000000000002");
|
|
NotificationEvent? commentNotification = await dbContext.NotificationEvents.SingleOrDefaultAsync(candidate => candidate.Id == commentNotificationId, cancellationToken);
|
|
if (commentNotification is null)
|
|
{
|
|
commentNotification = new NotificationEvent
|
|
{
|
|
Id = commentNotificationId,
|
|
EventType = string.Empty,
|
|
EntityType = string.Empty,
|
|
Message = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow.AddHours(-20),
|
|
};
|
|
dbContext.NotificationEvents.Add(commentNotification);
|
|
}
|
|
commentNotification.WorkspaceId = WorkspaceId;
|
|
commentNotification.ContentItemId = ScopedContentItemId;
|
|
commentNotification.EventType = "comment.created";
|
|
commentNotification.EntityType = "Comment";
|
|
commentNotification.EntityId = ClientCommentId;
|
|
commentNotification.Message = "Sofia Martin commented on Spring launch hero video.";
|
|
commentNotification.RecipientUserId = managerUserId;
|
|
commentNotification.RecipientEmail = "manager@socialize.local";
|
|
commentNotification.MetadataJson = null;
|
|
commentNotification.ReadAt = null;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private static async Task UpsertClientAsync(
|
|
AppDbContext dbContext,
|
|
Guid id,
|
|
string name,
|
|
string status,
|
|
string portraitUrl,
|
|
string primaryContactName,
|
|
string primaryContactEmail,
|
|
Guid workspaceId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Client? client = await dbContext.Clients.SingleOrDefaultAsync(candidate => candidate.Id == id, cancellationToken);
|
|
if (client is null)
|
|
{
|
|
client = new Client
|
|
{
|
|
Id = id,
|
|
Name = string.Empty,
|
|
Status = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
};
|
|
dbContext.Clients.Add(client);
|
|
}
|
|
client.WorkspaceId = workspaceId;
|
|
client.Name = name;
|
|
client.Status = status;
|
|
client.PortraitUrl = portraitUrl;
|
|
client.PrimaryContactName = primaryContactName;
|
|
client.PrimaryContactEmail = primaryContactEmail;
|
|
client.PrimaryContactPortraitUrl = null;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private static async Task UpsertProjectAsync(
|
|
AppDbContext dbContext,
|
|
Guid id,
|
|
Guid workspaceId,
|
|
Guid clientId,
|
|
string name,
|
|
string status,
|
|
DateTimeOffset startDate,
|
|
DateTimeOffset endDate,
|
|
string? description,
|
|
string? notes,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Project? project = await dbContext.Projects.SingleOrDefaultAsync(candidate => candidate.Id == id, cancellationToken);
|
|
if (project is null)
|
|
{
|
|
project = new Project
|
|
{
|
|
Id = id,
|
|
Name = string.Empty,
|
|
Status = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
};
|
|
dbContext.Projects.Add(project);
|
|
}
|
|
project.WorkspaceId = workspaceId;
|
|
project.ClientId = clientId;
|
|
project.Name = name;
|
|
project.Description = description;
|
|
project.Notes = notes;
|
|
project.Status = status;
|
|
project.StartDate = startDate;
|
|
project.EndDate = endDate;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private static async Task UpsertContentItemAsync(
|
|
AppDbContext dbContext,
|
|
Guid id,
|
|
Guid workspaceId,
|
|
Guid clientId,
|
|
Guid projectId,
|
|
string title,
|
|
string publicationMessage,
|
|
string publicationTargets,
|
|
string status,
|
|
DateTimeOffset? dueDate,
|
|
string currentRevisionLabel,
|
|
int currentRevisionNumber,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ContentItem? item = await dbContext.ContentItems.SingleOrDefaultAsync(candidate => candidate.Id == id, cancellationToken);
|
|
if (item is null)
|
|
{
|
|
item = new ContentItem
|
|
{
|
|
Id = id,
|
|
Title = string.Empty,
|
|
PublicationMessage = string.Empty,
|
|
PublicationTargets = string.Empty,
|
|
Status = string.Empty,
|
|
CurrentRevisionLabel = string.Empty,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
};
|
|
dbContext.ContentItems.Add(item);
|
|
}
|
|
item.WorkspaceId = workspaceId;
|
|
item.ClientId = clientId;
|
|
item.ProjectId = projectId;
|
|
item.Title = title;
|
|
item.PublicationMessage = publicationMessage;
|
|
item.PublicationTargets = publicationTargets;
|
|
item.Status = status;
|
|
item.DueDate = dueDate;
|
|
item.CurrentRevisionLabel = currentRevisionLabel;
|
|
item.CurrentRevisionNumber = currentRevisionNumber;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private static async Task EnsureRevisionAsync(
|
|
AppDbContext dbContext,
|
|
Guid id,
|
|
Guid contentItemId,
|
|
int revisionNumber,
|
|
string revisionLabel,
|
|
string title,
|
|
string publicationMessage,
|
|
string publicationTargets,
|
|
string changeSummary,
|
|
Guid createdByUserId,
|
|
DateTimeOffset createdAt,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
ContentItemRevision? revision = await dbContext.ContentItemRevisions.SingleOrDefaultAsync(candidate => candidate.Id == id, cancellationToken);
|
|
if (revision is null)
|
|
{
|
|
revision = new ContentItemRevision
|
|
{
|
|
Id = id,
|
|
RevisionLabel = string.Empty,
|
|
Title = string.Empty,
|
|
PublicationMessage = string.Empty,
|
|
PublicationTargets = string.Empty,
|
|
CreatedAt = createdAt,
|
|
};
|
|
dbContext.ContentItemRevisions.Add(revision);
|
|
}
|
|
revision.ContentItemId = contentItemId;
|
|
revision.RevisionNumber = revisionNumber;
|
|
revision.RevisionLabel = revisionLabel;
|
|
revision.Title = title;
|
|
revision.PublicationMessage = publicationMessage;
|
|
revision.PublicationTargets = publicationTargets;
|
|
revision.ChangeSummary = changeSummary;
|
|
revision.CreatedByUserId = createdByUserId;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private static async Task EnsureAssetRevisionAsync(
|
|
AppDbContext dbContext,
|
|
Guid id,
|
|
Guid assetId,
|
|
int revisionNumber,
|
|
string sourceReference,
|
|
string? previewUrl,
|
|
string? notes,
|
|
Guid createdByUserId,
|
|
DateTimeOffset createdAt,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
AssetRevision? revision = await dbContext.AssetRevisions.SingleOrDefaultAsync(candidate => candidate.Id == id, cancellationToken);
|
|
if (revision is null)
|
|
{
|
|
revision = new AssetRevision
|
|
{
|
|
Id = id,
|
|
SourceReference = string.Empty,
|
|
CreatedAt = createdAt,
|
|
};
|
|
dbContext.AssetRevisions.Add(revision);
|
|
}
|
|
revision.AssetId = assetId;
|
|
revision.RevisionNumber = revisionNumber;
|
|
revision.SourceReference = sourceReference;
|
|
revision.PreviewUrl = previewUrl;
|
|
revision.Notes = notes;
|
|
revision.CreatedByUserId = createdByUserId;
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|