chore: moving towards agentic development
This commit is contained in:
@@ -0,0 +1,633 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user