Require production approval for question media

This commit is contained in:
hz
2026-07-30 04:18:31 -05:00
parent f39bfb42be
commit 0676c7f95e
4 changed files with 70 additions and 6 deletions
@@ -25,8 +25,11 @@ public static class ContentPromotionCommand
await source.RunCommandAsync<BsonDocument>(new BsonDocument("ping", 1));
await target.RunCommandAsync<BsonDocument>(new BsonDocument("ping", 1));
var questions = await source.GetCollection<BsonDocument>("Questions")
var sourceQuestions = await source.GetCollection<BsonDocument>("Questions")
.Find(Builders<BsonDocument>.Filter.Eq("IsPublished", true)).ToListAsync();
var questions = sourceQuestions
.Select(QuestionPromotionCommand.PrepareForProduction)
.ToList();
var mediaIds = CollectMediaIds(questions);
var media = mediaIds.Count == 0
? []
@@ -9,6 +9,8 @@ namespace QuestionsApi.Services;
public static class QuestionPromotionCommand
{
public const string Confirmation = "PROMOTE_TO_PRD";
internal const string ProductionMediaReviewReason = "Contains media - pending production review";
internal const string ProductionMediaReviewActor = "system:production-promotion";
public static async Task<int> RunAsync(string[] args, TextWriter output)
{
@@ -28,9 +30,9 @@ public static class QuestionPromotionCommand
var questionFilter = Builders<BsonDocument>.Filter.Eq("IsPublished", true)
& Builders<BsonDocument>.Filter.Eq("FlaggedForReviewUtc", BsonNull.Value);
var questions = await source.GetCollection<BsonDocument>("Questions")
var sourceQuestions = await source.GetCollection<BsonDocument>("Questions")
.Find(questionFilter).ToListAsync();
var duplicateIds = questions
var duplicateIds = sourceQuestions
.GroupBy(ExternalId, StringComparer.Ordinal)
.Where(group => group.Count() > 1)
.Select(group => group.Key)
@@ -38,6 +40,7 @@ public static class QuestionPromotionCommand
if (duplicateIds.Count > 0)
throw new InvalidOperationException($"duplicate eligible ExternalIds: {string.Join(", ", duplicateIds)}");
var questions = sourceQuestions.Select(PrepareForProduction).ToList();
var mediaIds = CollectMediaIds(questions);
var media = mediaIds.Count == 0
? []
@@ -95,7 +98,7 @@ public static class QuestionPromotionCommand
["SourceDatabase"] = options.SourceDatabase,
["TargetDatabase"] = options.TargetDatabase,
["Scope"] = "questions",
["Eligibility"] = "published_unflagged",
["Eligibility"] = "published_unflagged; media_requires_production_review",
["Questions"] = summary.EligibleQuestions,
["Inserted"] = summary.Inserted,
["Updated"] = summary.Updated,
@@ -118,6 +121,21 @@ public static class QuestionPromotionCommand
question.GetValue("IsPublished", false).ToBoolean()
&& (!question.TryGetValue("FlaggedForReviewUtc", out var flagged) || flagged.IsBsonNull);
internal static BsonDocument PrepareForProduction(BsonDocument source)
{
var prepared = new BsonDocument(source);
if (!HasMedia(prepared))
return prepared;
var reviewUtc = prepared.TryGetValue("UpdatedUtc", out var updated) && updated.IsValidDateTime
? updated
: new BsonDateTime(DateTime.UnixEpoch);
prepared["FlaggedForReviewUtc"] = reviewUtc;
prepared["FlagReason"] = ProductionMediaReviewReason;
prepared["FlaggedByUserId"] = ProductionMediaReviewActor;
return prepared;
}
internal static HashSet<ObjectId> CollectMediaIds(IEnumerable<BsonDocument> questions)
{
var ids = new HashSet<ObjectId>();
@@ -192,6 +210,11 @@ public static class QuestionPromotionCommand
private static string ExternalId(BsonDocument document) =>
document.GetValue("ExternalId", "").AsString;
private static bool HasMedia(BsonDocument question) =>
question.TryGetValue("Media", out var media)
&& media.IsBsonArray
&& media.AsBsonArray.Count > 0;
private static async Task UpsertQuestionsAsync(IMongoDatabase target, IEnumerable<BsonDocument> questions)
{
var collection = target.GetCollection<BsonDocument>("Questions");
@@ -224,7 +247,7 @@ public static class QuestionPromotionCommand
{
mode,
scope = "questions",
eligibility = "published_unflagged",
eligibility = "published_unflagged; media_requires_production_review",
source = options.SourceDatabase,
target = options.TargetDatabase,
eligibleQuestions = summary.EligibleQuestions,