diff --git a/Trax.Samples.slnx b/Trax.Samples.slnx index db56303..8f0ce54 100644 --- a/Trax.Samples.slnx +++ b/Trax.Samples.slnx @@ -13,6 +13,11 @@ + + + + + diff --git a/docker-compose.yml b/docker-compose.yml index 05a5668..fafd046 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,3 +16,16 @@ services: - default volumes: - ./init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh + + rabbitmq: + container_name: trax_rabbitmq + image: rabbitmq:4-management + restart: always + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + ports: + - "5672:5672" + - "15672:15672" + networks: + - default diff --git a/samples/DataPipeline/Trax.Samples.Flowthru.Spaceflights.Scheduler/Program.cs b/samples/DataPipeline/Trax.Samples.Flowthru.Spaceflights.Scheduler/Program.cs index e1646ff..179cad3 100644 --- a/samples/DataPipeline/Trax.Samples.Flowthru.Spaceflights.Scheduler/Program.cs +++ b/samples/DataPipeline/Trax.Samples.Flowthru.Spaceflights.Scheduler/Program.cs @@ -33,7 +33,6 @@ using Trax.Scheduler.Configuration; using Trax.Scheduler.Extensions; using Trax.Scheduler.Services.Scheduling; -using Trax.Scheduler.Trains.ManifestManager; var builder = WebApplication.CreateBuilder(args); @@ -81,7 +80,7 @@ .SaveTrainParameters() .AddStepProgress() ) - .AddMediator(typeof(ManifestNames).Assembly, typeof(ManifestManagerTrain).Assembly) + .AddMediator(typeof(ManifestNames).Assembly) .AddScheduler(scheduler => { scheduler diff --git a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Program.cs b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Program.cs index fe7170c..525d8ea 100644 --- a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Program.cs +++ b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Program.cs @@ -49,6 +49,7 @@ using Trax.Api.Extensions; using Trax.Api.GraphQL.Extensions; using Trax.Dashboard.Extensions; +using Trax.Effect.Broadcaster.RabbitMQ.Extensions; using Trax.Effect.Data.Extensions; using Trax.Effect.Data.Postgres.Extensions; using Trax.Effect.Extensions; @@ -67,7 +68,6 @@ using Trax.Scheduler.Configuration; using Trax.Scheduler.Extensions; using Trax.Scheduler.Services.Scheduling; -using Trax.Scheduler.Trains.ManifestManager; var builder = WebApplication.CreateBuilder(args); @@ -75,6 +75,10 @@ builder.Configuration.GetConnectionString("TraxDatabase") ?? throw new InvalidOperationException("Connection string 'TraxDatabase' not found."); +var rabbitMqConnectionString = + builder.Configuration.GetConnectionString("RabbitMQ") + ?? throw new InvalidOperationException("Connection string 'RabbitMQ' not found."); + builder.Services.AddLogging(logging => logging.AddConsole()); builder.Services.AddTrax(trax => @@ -85,24 +89,23 @@ .AddJson() .SaveTrainParameters() .AddStepProgress() + .UseBroadcaster(b => b.UseRabbitMq(rabbitMqConnectionString)) ) - .AddMediator(typeof(ManifestNames).Assembly, typeof(ManifestManagerTrain).Assembly) + .AddMediator(typeof(ManifestNames).Assembly) .AddScheduler(scheduler => { // ── Key: scheduling only, no local execution ────────────────── // PostgresJobSubmitter is the default — omit UseLocalWorkers() to schedule without executing locally // Jobs accumulate until the Worker process picks them up. - scheduler - .AddMetadataCleanup(cleanup => - { - cleanup.AddTrainType(); - cleanup.AddTrainType(); - cleanup.AddTrainType(); - cleanup.AddTrainType(); - cleanup.AddTrainType(); - cleanup.AddTrainType(); - }) - .JobDispatcherPollingInterval(TimeSpan.FromSeconds(2)); + scheduler.AddMetadataCleanup(cleanup => + { + cleanup.AddTrainType(); + cleanup.AddTrainType(); + cleanup.AddTrainType(); + cleanup.AddTrainType(); + cleanup.AddTrainType(); + cleanup.AddTrainType(); + }); // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 1. INTERVAL + DEPENDENCY CHAIN diff --git a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Trax.Samples.EnergyHub.Hub.csproj b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Trax.Samples.EnergyHub.Hub.csproj index e1d511c..0d5a76a 100644 --- a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Trax.Samples.EnergyHub.Hub.csproj +++ b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/Trax.Samples.EnergyHub.Hub.csproj @@ -19,5 +19,6 @@ + diff --git a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/appsettings.json b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/appsettings.json index 6461d1e..8905b26 100644 --- a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/appsettings.json +++ b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Hub/appsettings.json @@ -1,6 +1,7 @@ { "ConnectionStrings": { - "TraxDatabase": "Host=localhost;Port=5432;Database=trax;Username=trax;Password=trax123" + "TraxDatabase": "Host=localhost;Port=5432;Database=trax;Username=trax;Password=trax123", + "RabbitMQ": "amqp://guest:guest@localhost:5672" }, "Kestrel": { "Endpoints": { diff --git a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Program.cs b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Program.cs index 133c4f5..ce6fafc 100644 --- a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Program.cs +++ b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Program.cs @@ -23,6 +23,7 @@ // so multiple worker instances can run safely without duplicate execution. // ───────────────────────────────────────────────────────────────────────────── +using Trax.Effect.Broadcaster.RabbitMQ.Extensions; using Trax.Effect.Data.Postgres.Extensions; using Trax.Effect.Extensions; using Trax.Effect.Provider.Json.Extensions; @@ -31,7 +32,6 @@ using Trax.Mediator.Extensions; using Trax.Samples.EnergyHub; using Trax.Scheduler.Extensions; -using Trax.Scheduler.Trains.ManifestManager; var builder = WebApplication.CreateBuilder(args); @@ -39,16 +39,27 @@ builder.Configuration.GetConnectionString("TraxDatabase") ?? throw new InvalidOperationException("Connection string 'TraxDatabase' not found."); +var rabbitMqConnectionString = + builder.Configuration.GetConnectionString("RabbitMQ") + ?? throw new InvalidOperationException("Connection string 'RabbitMQ' not found."); + builder.Services.AddLogging(logging => logging.AddConsole()); // ── Register Trax Effect + Mediator (trains, bus, discovery, execution) ── // The worker must reference the same train assemblies as the scheduler so it // can resolve and execute any train type that gets dispatched. +// UseBroadcaster() publishes lifecycle events to RabbitMQ so the Hub's +// GraphQL subscriptions are notified when queued trains complete. builder.Services.AddTrax(trax => trax.AddEffects(effects => - effects.UsePostgres(connectionString).AddJson().SaveTrainParameters().AddStepProgress() + effects + .UsePostgres(connectionString) + .AddJson() + .SaveTrainParameters() + .AddStepProgress() + .UseBroadcaster(b => b.UseRabbitMq(rabbitMqConnectionString)) ) - .AddMediator(typeof(ManifestNames).Assembly, typeof(ManifestManagerTrain).Assembly) + .AddMediator(typeof(ManifestNames).Assembly) ); // ── Register standalone worker ─────────────────────────────────────────── diff --git a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Trax.Samples.EnergyHub.Worker.csproj b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Trax.Samples.EnergyHub.Worker.csproj index cd3ef4d..158da86 100644 --- a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Trax.Samples.EnergyHub.Worker.csproj +++ b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/Trax.Samples.EnergyHub.Worker.csproj @@ -15,5 +15,6 @@ + diff --git a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/appsettings.json b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/appsettings.json index 8b2ff47..19ed6f3 100644 --- a/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/appsettings.json +++ b/samples/DistributedWorkers/Trax.Samples.EnergyHub.Worker/appsettings.json @@ -1,6 +1,7 @@ { "ConnectionStrings": { - "TraxDatabase": "Host=localhost;Port=5432;Database=trax;Username=trax;Password=trax123" + "TraxDatabase": "Host=localhost;Port=5432;Database=trax;Username=trax;Password=trax123", + "RabbitMQ": "amqp://guest:guest@localhost:5672" }, "Kestrel": { "Endpoints": { diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/Program.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/Program.cs new file mode 100644 index 0000000..e59c3e3 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/Program.cs @@ -0,0 +1,121 @@ +// ───────────────────────────────────────────────────────────────────────────── +// ContentShield — API (GraphQL + Dashboard, ephemeral HTTP dispatch) +// +// A single process that serves the GraphQL API and hosts the Trax dashboard. +// There are NO scheduled jobs — all work is triggered by GraphQL mutations. +// Queued mutations are dispatched via HTTP to the ephemeral Runner process +// using UseRemoteWorkers(). The Runner simulates a Lambda/serverless function. +// +// This demonstrates the Ephemeral Workers pattern: +// - Query trains (LookupModerationResult) run synchronously on this process +// - Queued trains (ReviewContent, SendViolationNotice, GenerateModerationReport) +// are POSTed to the Runner via HTTP — no background_job table, no DB polling +// - Run mutations (GenerateModerationReport) are also offloaded to the Runner +// via UseRemoteRun() — the API blocks until the Runner returns the output +// +// GraphQL schema (auto-generated from train attributes): +// Queries: lookupModerationResult — [TraxQuery] +// Mutations: queueReviewContent — [TraxMutation(Queue)] +// queueSendViolationNotice — [TraxMutation(Queue)] +// runGenerateModerationReport — [TraxMutation(RunAndQueue)] +// queueGenerateModerationReport — [TraxMutation(RunAndQueue)] +// Subscriptions: onTrainStarted, onTrainCompleted, onTrainFailed +// +// Prerequisites: +// 1. Start Postgres: cd Trax.Samples && docker compose up -d +// 2. Pack local: ./pack-local.sh +// 3. Start runner: dotnet run --project samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner +// 4. Start API: dotnet run --project samples/EphemeralWorkers/Trax.Samples.ContentShield.Api +// +// Endpoints: +// Dashboard: http://localhost:5204/trax +// GraphQL IDE: http://localhost:5204/trax/graphql (Banana Cake Pop) +// +// Try it: +// # Look up a moderation result (runs on API) +// curl -X POST http://localhost:5204/trax/graphql \ +// -H "Content-Type: application/json" \ +// -d '{"query":"{ discover { lookupModerationResult(input: {contentId: \"test-001\"}) { contentId moderationStatus classification threatScore } } }"}' +// +// # Queue a content review (dispatched to Runner via HTTP) +// curl -X POST http://localhost:5204/trax/graphql \ +// -H "Content-Type: application/json" \ +// -d '{"query":"mutation { dispatch { queueReviewContent(input: {contentId: \"test-002\", contentType: \"video\", contentBody: \"suspicious video content\"}) { workQueueId externalId } } }"}' +// +// # Generate a moderation report (offloaded to Runner via UseRemoteRun, blocks until done) +// curl -X POST http://localhost:5204/trax/graphql \ +// -H "Content-Type: application/json" \ +// -d '{"query":"mutation { dispatch { runGenerateModerationReport(input: {reportPeriod: \"Daily\"}) { totalReviewed totalFlagged topViolationTypes falsePositiveRate } } }"}' +// ───────────────────────────────────────────────────────────────────────────── + +using Trax.Api.Extensions; +using Trax.Api.GraphQL.Extensions; +using Trax.Dashboard.Extensions; +using Trax.Effect.Broadcaster.RabbitMQ.Extensions; +using Trax.Effect.Data.Extensions; +using Trax.Effect.Data.Postgres.Extensions; +using Trax.Effect.Extensions; +using Trax.Effect.Provider.Json.Extensions; +using Trax.Effect.Provider.Parameter.Extensions; +using Trax.Effect.StepProvider.Progress.Extensions; +using Trax.Mediator.Extensions; +using Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent; +using Trax.Scheduler.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +var connectionString = + builder.Configuration.GetConnectionString("TraxDatabase") + ?? throw new InvalidOperationException("Connection string 'TraxDatabase' not found."); + +var rabbitMqConnectionString = + builder.Configuration.GetConnectionString("RabbitMQ") + ?? throw new InvalidOperationException("Connection string 'RabbitMQ' not found."); + +builder.Services.AddLogging(logging => logging.AddConsole()); + +builder.Services.AddTrax(trax => + trax.AddEffects(effects => + effects + .UsePostgres(connectionString) + .AddDataContextLogging() + .AddJson() + .SaveTrainParameters() + .AddStepProgress() + .UseBroadcaster(b => b.UseRabbitMq(rabbitMqConnectionString)) + ) + .AddMediator(typeof(ReviewContentTrain).Assembly) + .AddScheduler(scheduler => + { + // ── Ephemeral dispatch only — no scheduled jobs ────────────────── + // UseRemoteWorkers replaces the default PostgresJobSubmitter with + // HttpJobSubmitter. When a GraphQL queue* mutation is called, the + // JobDispatcher POSTs the job directly to the Runner via HTTP. + // No cron schedules, no intervals, no manifests — purely on-demand. + scheduler.UseRemoteWorkers(remote => + remote.BaseUrl = "http://localhost:5205/trax/execute" + ); + + // ── Remote run offloading ──────────────────────────────────────── + // UseRemoteRun replaces the default LocalRunExecutor with + // HttpRunExecutor. When a GraphQL run* mutation is called, the + // request is POSTed to the Runner and blocks until the train + // completes. Without this, runs execute in-process on this API. + scheduler.UseRemoteRun(remote => remote.BaseUrl = "http://localhost:5205/trax/run"); + }) +); + +// ── Register GraphQL API ──────────────────────────────────────────────── +// Trains annotated with [TraxQuery] or [TraxMutation] get typed GraphQL +// fields auto-generated. [TraxBroadcast] trains emit subscription events. +builder.AddTraxDashboard(); +builder.Services.AddTraxGraphQL(); +builder.Services.AddHealthChecks().AddTraxHealthCheck(); + +var app = builder.Build(); + +app.UseTraxDashboard(); +app.UseTraxGraphQL(); +app.MapHealthChecks("/trax/health"); + +app.Run(); diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/Trax.Samples.ContentShield.Api.csproj b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/Trax.Samples.ContentShield.Api.csproj new file mode 100644 index 0000000..08e1d37 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/Trax.Samples.ContentShield.Api.csproj @@ -0,0 +1,24 @@ + + + Exe + net10.0 + enable + enable + true + true + + + + + + + + + + + + + + + + diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/appsettings.json b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/appsettings.json new file mode 100644 index 0000000..a0f4aa5 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Api/appsettings.json @@ -0,0 +1,19 @@ +{ + "ConnectionStrings": { + "TraxDatabase": "Host=localhost;Port=5432;Database=trax;Username=trax;Password=trax123", + "RabbitMQ": "amqp://guest:guest@localhost:5672" + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5204" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/Program.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/Program.cs new file mode 100644 index 0000000..c4f8289 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/Program.cs @@ -0,0 +1,80 @@ +// ───────────────────────────────────────────────────────────────────────────── +// ContentShield — Ephemeral Runner (simulates Lambda/serverless execution) +// +// A minimal HTTP endpoint that receives job requests from the API via HTTP POST +// and executes trains to completion. This process has no scheduler, no polling, +// no dashboard — it only runs trains that are dispatched to it. +// +// This demonstrates the ephemeral/serverless worker pattern: +// 1. The API dispatches jobs via UseRemoteWorkers() +// 2. HttpJobSubmitter POSTs a RemoteJobRequest to this endpoint +// 3. This runner deserializes the input, runs JobRunnerTrain, and returns +// 4. No background_job table — jobs arrive directly over HTTP +// +// In production, this would be an AWS Lambda, Azure Function, or Cloud Run +// service that spins up on demand to handle each job request. +// +// Prerequisites: +// 1. Start Postgres: cd Trax.Samples && docker compose up -d +// 2. Pack local: ./pack-local.sh +// 3. Start runner: dotnet run --project samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner +// 4. Start API: dotnet run --project samples/EphemeralWorkers/Trax.Samples.ContentShield.Api +// +// Endpoints: +// POST http://localhost:5205/trax/execute (receives RemoteJobRequest JSON — queue path) +// POST http://localhost:5205/trax/run (receives RemoteRunRequest JSON — synchronous run path) +// ───────────────────────────────────────────────────────────────────────────── + +using Trax.Effect.Broadcaster.RabbitMQ.Extensions; +using Trax.Effect.Data.Postgres.Extensions; +using Trax.Effect.Extensions; +using Trax.Effect.Provider.Json.Extensions; +using Trax.Effect.Provider.Parameter.Extensions; +using Trax.Effect.StepProvider.Progress.Extensions; +using Trax.Mediator.Extensions; +using Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent; +using Trax.Scheduler.Extensions; + +var builder = WebApplication.CreateBuilder(args); + +var connectionString = + builder.Configuration.GetConnectionString("TraxDatabase") + ?? throw new InvalidOperationException("Connection string 'TraxDatabase' not found."); + +var rabbitMqConnectionString = + builder.Configuration.GetConnectionString("RabbitMQ") + ?? throw new InvalidOperationException("Connection string 'RabbitMQ' not found."); + +builder.Services.AddLogging(logging => logging.AddConsole()); + +// ── Register Trax Effect + Mediator (trains, bus, discovery, execution) ── +// The runner must reference the same train assemblies as the API so it can +// resolve and execute any train type that gets dispatched. +// UseBroadcaster publishes lifecycle events to RabbitMQ so the API's +// GraphQL subscriptions are notified when queued trains complete. +builder.Services.AddTrax(trax => + trax.AddEffects(effects => + effects + .UsePostgres(connectionString) + .AddJson() + .SaveTrainParameters() + .AddStepProgress() + .UseBroadcaster(b => b.UseRabbitMq(rabbitMqConnectionString)) + ) + .AddMediator(typeof(ReviewContentTrain).Assembly) +); + +// ── Register job runner endpoint ────────────────────────────────────────── +// AddTraxJobRunner() registers JobRunnerTrain and minimal supporting services. +// No scheduler, no polling, no dashboard — just the execution pipeline. +builder.Services.AddTraxJobRunner(); + +var app = builder.Build(); + +// Maps POST /trax/execute — receives RemoteJobRequest, runs JobRunnerTrain (queue path) +app.UseTraxJobRunner(); + +// Maps POST /trax/run — receives RemoteRunRequest, runs train synchronously and returns output +app.UseTraxRunEndpoint(); + +app.Run(); diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/Trax.Samples.ContentShield.Runner.csproj b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/Trax.Samples.ContentShield.Runner.csproj new file mode 100644 index 0000000..2b0c11b --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/Trax.Samples.ContentShield.Runner.csproj @@ -0,0 +1,21 @@ + + + Exe + net10.0 + enable + enable + true + + + + + + + + + + + + + + diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/appsettings.json b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/appsettings.json new file mode 100644 index 0000000..2547367 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield.Runner/appsettings.json @@ -0,0 +1,19 @@ +{ + "ConnectionStrings": { + "TraxDatabase": "Host=localhost;Port=5432;Database=trax;Username=trax;Password=trax123", + "RabbitMQ": "amqp://guest:guest@localhost:5672" + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5205" + } + } + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/ILookupModerationResultTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/ILookupModerationResultTrain.cs new file mode 100644 index 0000000..b8ed92c --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/ILookupModerationResultTrain.cs @@ -0,0 +1,6 @@ +using Trax.Effect.Services.ServiceTrain; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.LookupModerationResult; + +public interface ILookupModerationResultTrain + : IServiceTrain; diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/LookupModerationResultInput.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/LookupModerationResultInput.cs new file mode 100644 index 0000000..f81c53d --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/LookupModerationResultInput.cs @@ -0,0 +1,6 @@ +namespace Trax.Samples.ContentShield.Trains.ContentReview.LookupModerationResult; + +public record LookupModerationResultInput +{ + public required string ContentId { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/LookupModerationResultTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/LookupModerationResultTrain.cs new file mode 100644 index 0000000..f5b0d7f --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/LookupModerationResultTrain.cs @@ -0,0 +1,21 @@ +using LanguageExt; +using Trax.Effect.Attributes; +using Trax.Effect.Services.ServiceTrain; +using Trax.Samples.ContentShield.Trains.ContentReview.LookupModerationResult.Steps; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.LookupModerationResult; + +/// +/// Lightweight lookup of a content moderation result. Runs synchronously on the +/// API process — does not go through the scheduler or ephemeral Runner. +/// +[TraxQuery(Description = "Looks up a content moderation result")] +[TraxBroadcast] +public class LookupModerationResultTrain + : ServiceTrain, + ILookupModerationResultTrain +{ + protected override async Task> RunInternal( + LookupModerationResultInput input + ) => Activate(input).Chain().Resolve(); +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/ModerationResult.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/ModerationResult.cs new file mode 100644 index 0000000..d389745 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/ModerationResult.cs @@ -0,0 +1,10 @@ +namespace Trax.Samples.ContentShield.Trains.ContentReview.LookupModerationResult; + +public record ModerationResult +{ + public required string ContentId { get; init; } + public required string ModerationStatus { get; init; } + public required string Classification { get; init; } + public double ThreatScore { get; init; } + public DateTimeOffset ReviewedAt { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/Steps/FetchModerationResultStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/Steps/FetchModerationResultStep.cs new file mode 100644 index 0000000..f881296 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/LookupModerationResult/Steps/FetchModerationResultStep.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.LookupModerationResult.Steps; + +/// +/// Fetches a moderation result from the database. Returns a simulated result +/// for demonstration purposes. +/// +public class FetchModerationResultStep(ILogger logger) + : Step +{ + public override async Task Run(LookupModerationResultInput input) + { + logger.LogInformation( + "Fetching moderation result for content {ContentId}", + input.ContentId + ); + + await Task.Delay(50); + + return new ModerationResult + { + ContentId = input.ContentId, + ModerationStatus = "Reviewed", + Classification = "safe", + ThreatScore = 0.12, + ReviewedAt = DateTimeOffset.UtcNow.AddMinutes(-15), + }; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/IReviewContentTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/IReviewContentTrain.cs new file mode 100644 index 0000000..24841b4 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/IReviewContentTrain.cs @@ -0,0 +1,5 @@ +using Trax.Effect.Services.ServiceTrain; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent; + +public interface IReviewContentTrain : IServiceTrain; diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentInput.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentInput.cs new file mode 100644 index 0000000..756b6ce --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentInput.cs @@ -0,0 +1,8 @@ +namespace Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent; + +public record ReviewContentInput +{ + public required string ContentId { get; init; } + public required string ContentType { get; init; } + public required string ContentBody { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentOutput.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentOutput.cs new file mode 100644 index 0000000..6dc9769 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentOutput.cs @@ -0,0 +1,10 @@ +namespace Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent; + +public record ReviewContentOutput +{ + public required string ContentId { get; init; } + public required string Classification { get; init; } + public double ThreatScore { get; init; } + public bool IsFlagged { get; init; } + public string? FlagReason { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentTrain.cs new file mode 100644 index 0000000..122b5b5 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/ReviewContentTrain.cs @@ -0,0 +1,32 @@ +using LanguageExt; +using Trax.Effect.Attributes; +using Trax.Effect.Services.ServiceTrain; +using Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent.Steps; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent; + +/// +/// Reviews user-submitted content for policy violations. Classifies the content, +/// scores its threat level, and flags violations. When flagged, activates the +/// dormant SendViolationNotice train to notify the content owner. +/// +/// Dispatched to the ephemeral Runner via HTTP (UseRemoteWorkers). +/// +[TraxMutation( + Operations = GraphQLOperation.Queue, + Description = "Reviews content for policy violations" +)] +[TraxBroadcast] +public class ReviewContentTrain + : ServiceTrain, + IReviewContentTrain +{ + protected override async Task> RunInternal( + ReviewContentInput input + ) => + Activate(input) + .Chain() + .Chain() + .Chain() + .Resolve(); +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/ClassifyContentStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/ClassifyContentStep.cs new file mode 100644 index 0000000..b41a458 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/ClassifyContentStep.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent.Steps; + +/// +/// Classifies content into a category (safe, spam, hate-speech, violence, etc.) +/// using a simulated ML model. +/// +public class ClassifyContentStep(ILogger logger) + : Step +{ + public override async Task Run(ReviewContentInput input) + { + logger.LogInformation( + "Classifying {ContentType} content {ContentId}", + input.ContentType, + input.ContentId + ); + + await Task.Delay(300); + + var category = input.ContentType == "video" ? "violence" : "safe"; + logger.LogInformation( + "Content {ContentId} classified as: {Category}", + input.ContentId, + category + ); + + return input; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/FlagContentStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/FlagContentStep.cs new file mode 100644 index 0000000..069e412 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/FlagContentStep.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent.Steps; + +/// +/// Evaluates the threat score and flags content that exceeds the threshold. +/// +public class FlagContentStep(ILogger logger) + : Step +{ + public override async Task Run(ReviewContentInput input) + { + // Simulate threat scoring — video content scores high, text scores low + var threatScore = input.ContentType == "video" ? 0.85 : 0.25; + var classification = threatScore > 0.7 ? "violence" : "safe"; + var isFlagged = threatScore > 0.7; + string? flagReason = isFlagged ? "Threat score exceeds moderation threshold" : null; + + if (isFlagged) + { + logger.LogWarning( + "Content {ContentId} FLAGGED — score {ThreatScore:F2}, classification: {Classification}", + input.ContentId, + threatScore, + classification + ); + } + else + { + logger.LogInformation( + "Content {ContentId} passed moderation — score {ThreatScore:F2}", + input.ContentId, + threatScore + ); + } + + return new ReviewContentOutput + { + ContentId = input.ContentId, + Classification = classification, + ThreatScore = threatScore, + IsFlagged = isFlagged, + FlagReason = flagReason, + }; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/ScoreContentStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/ScoreContentStep.cs new file mode 100644 index 0000000..20f81d0 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/ContentReview/ReviewContent/Steps/ScoreContentStep.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.ContentReview.ReviewContent.Steps; + +/// +/// Assigns a threat score (0.0–1.0) to the content based on classification signals. +/// Scores above 0.7 trigger flagging in the next step. +/// +public class ScoreContentStep(ILogger logger) + : Step +{ + public override async Task Run(ReviewContentInput input) + { + logger.LogInformation("Scoring threat level for content {ContentId}", input.ContentId); + + await Task.Delay(200); + + var score = input.ContentType == "video" ? 0.85 : 0.25; + logger.LogInformation( + "Content {ContentId} threat score: {ThreatScore:F2}", + input.ContentId, + score + ); + + return input; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/ISendViolationNoticeTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/ISendViolationNoticeTrain.cs new file mode 100644 index 0000000..271972a --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/ISendViolationNoticeTrain.cs @@ -0,0 +1,6 @@ +using Trax.Effect.Services.ServiceTrain; + +namespace Trax.Samples.ContentShield.Trains.Notices.SendViolationNotice; + +public interface ISendViolationNoticeTrain + : IServiceTrain; diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeInput.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeInput.cs new file mode 100644 index 0000000..b2299d5 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeInput.cs @@ -0,0 +1,8 @@ +namespace Trax.Samples.ContentShield.Trains.Notices.SendViolationNotice; + +public record SendViolationNoticeInput +{ + public required string ContentId { get; init; } + public required string ViolationType { get; init; } + public required string UserId { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeOutput.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeOutput.cs new file mode 100644 index 0000000..731de29 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeOutput.cs @@ -0,0 +1,7 @@ +namespace Trax.Samples.ContentShield.Trains.Notices.SendViolationNotice; + +public record SendViolationNoticeOutput +{ + public required string NoticeId { get; init; } + public DateTimeOffset DeliveredAt { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeTrain.cs new file mode 100644 index 0000000..ec4f3e2 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/SendViolationNoticeTrain.cs @@ -0,0 +1,27 @@ +using LanguageExt; +using Trax.Effect.Attributes; +using Trax.Effect.Services.ServiceTrain; +using Trax.Samples.ContentShield.Trains.Notices.SendViolationNotice.Steps; + +namespace Trax.Samples.ContentShield.Trains.Notices.SendViolationNotice; + +/// +/// Sends a violation notice to the content owner. Dormant dependent — activated +/// by FlagContentStep when content is flagged. Composes the notice from a template +/// and delivers it via email/push notification. +/// +/// Dispatched to the ephemeral Runner via HTTP (UseRemoteWorkers). +/// +[TraxMutation( + Operations = GraphQLOperation.Queue, + Description = "Sends a violation notice to the content owner" +)] +[TraxBroadcast] +public class SendViolationNoticeTrain + : ServiceTrain, + ISendViolationNoticeTrain +{ + protected override async Task> RunInternal( + SendViolationNoticeInput input + ) => Activate(input).Chain().Chain().Resolve(); +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/Steps/ComposeNoticeStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/Steps/ComposeNoticeStep.cs new file mode 100644 index 0000000..caaa71f --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/Steps/ComposeNoticeStep.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.Notices.SendViolationNotice.Steps; + +/// +/// Composes a violation notice from a template based on the violation type. +/// +public class ComposeNoticeStep(ILogger logger) + : Step +{ + public override async Task Run(SendViolationNoticeInput input) + { + logger.LogInformation( + "Composing {ViolationType} violation notice for content {ContentId}, user {UserId}", + input.ViolationType, + input.ContentId, + input.UserId + ); + + await Task.Delay(100); + + logger.LogInformation( + "Notice composed from template: violation-{ViolationType}", + input.ViolationType + ); + + return input; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/Steps/DeliverNoticeStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/Steps/DeliverNoticeStep.cs new file mode 100644 index 0000000..57574aa --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Notices/SendViolationNotice/Steps/DeliverNoticeStep.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.Notices.SendViolationNotice.Steps; + +/// +/// Delivers the composed violation notice via email and push notification. +/// +public class DeliverNoticeStep(ILogger logger) + : Step +{ + public override async Task Run(SendViolationNoticeInput input) + { + logger.LogInformation( + "Delivering violation notice to user {UserId} for content {ContentId}", + input.UserId, + input.ContentId + ); + + await Task.Delay(150); + + var noticeId = $"notice-{Guid.NewGuid():N}"; + logger.LogInformation("Notice {NoticeId} delivered successfully", noticeId); + + return new SendViolationNoticeOutput + { + NoticeId = noticeId, + DeliveredAt = DateTimeOffset.UtcNow, + }; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportInput.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportInput.cs new file mode 100644 index 0000000..76c88d2 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportInput.cs @@ -0,0 +1,6 @@ +namespace Trax.Samples.ContentShield.Trains.Reports.GenerateModerationReport; + +public record GenerateModerationReportInput +{ + public required string ReportPeriod { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportOutput.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportOutput.cs new file mode 100644 index 0000000..131f29d --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportOutput.cs @@ -0,0 +1,9 @@ +namespace Trax.Samples.ContentShield.Trains.Reports.GenerateModerationReport; + +public record GenerateModerationReportOutput +{ + public int TotalReviewed { get; init; } + public int TotalFlagged { get; init; } + public required string[] TopViolationTypes { get; init; } + public double FalsePositiveRate { get; init; } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportTrain.cs new file mode 100644 index 0000000..6d6371c --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/GenerateModerationReportTrain.cs @@ -0,0 +1,24 @@ +using LanguageExt; +using Trax.Effect.Attributes; +using Trax.Effect.Services.ServiceTrain; +using Trax.Samples.ContentShield.Trains.Reports.GenerateModerationReport.Steps; + +namespace Trax.Samples.ContentShield.Trains.Reports.GenerateModerationReport; + +/// +/// Generates a summary report of moderation activity for the specified period. +/// Scheduled daily at midnight. Can also be run on-demand via GraphQL. +/// +[TraxMutation( + Operations = GraphQLOperation.RunAndQueue, + Description = "Generates a moderation activity report" +)] +[TraxBroadcast] +public class GenerateModerationReportTrain + : ServiceTrain, + IGenerateModerationReportTrain +{ + protected override async Task> RunInternal( + GenerateModerationReportInput input + ) => Activate(input).Chain().Chain().Resolve(); +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/IGenerateModerationReportTrain.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/IGenerateModerationReportTrain.cs new file mode 100644 index 0000000..1e907df --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/IGenerateModerationReportTrain.cs @@ -0,0 +1,6 @@ +using Trax.Effect.Services.ServiceTrain; + +namespace Trax.Samples.ContentShield.Trains.Reports.GenerateModerationReport; + +public interface IGenerateModerationReportTrain + : IServiceTrain; diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/Steps/AggregateMetricsStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/Steps/AggregateMetricsStep.cs new file mode 100644 index 0000000..97981ff --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/Steps/AggregateMetricsStep.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.Reports.GenerateModerationReport.Steps; + +/// +/// Aggregates moderation metrics from the database for the requested period. +/// +public class AggregateMetricsStep(ILogger logger) + : Step +{ + public override async Task Run( + GenerateModerationReportInput input + ) + { + logger.LogInformation( + "Aggregating moderation metrics for period: {ReportPeriod}", + input.ReportPeriod + ); + + await Task.Delay(250); + + logger.LogInformation("Metrics aggregated: 1,247 items reviewed, 83 flagged"); + + return input; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/Steps/FormatReportStep.cs b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/Steps/FormatReportStep.cs new file mode 100644 index 0000000..03fe3f8 --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trains/Reports/GenerateModerationReport/Steps/FormatReportStep.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Logging; +using Trax.Core.Step; + +namespace Trax.Samples.ContentShield.Trains.Reports.GenerateModerationReport.Steps; + +/// +/// Formats the aggregated metrics into a structured report output. +/// +public class FormatReportStep(ILogger logger) + : Step +{ + public override async Task Run( + GenerateModerationReportInput input + ) + { + logger.LogInformation("Formatting {ReportPeriod} moderation report", input.ReportPeriod); + + await Task.Delay(100); + + return new GenerateModerationReportOutput + { + TotalReviewed = 1247, + TotalFlagged = 83, + TopViolationTypes = ["violence", "spam", "hate-speech"], + FalsePositiveRate = 0.042, + }; + } +} diff --git a/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trax.Samples.ContentShield.csproj b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trax.Samples.ContentShield.csproj new file mode 100644 index 0000000..275b4bc --- /dev/null +++ b/samples/EphemeralWorkers/Trax.Samples.ContentShield/Trax.Samples.ContentShield.csproj @@ -0,0 +1,18 @@ + + + net10.0 + enable + enable + true + + + + + + + + + + + + diff --git a/samples/LocalWorkers/Trax.Samples.GameServer.Scheduler/Program.cs b/samples/LocalWorkers/Trax.Samples.GameServer.Scheduler/Program.cs index ab6be24..770103c 100644 --- a/samples/LocalWorkers/Trax.Samples.GameServer.Scheduler/Program.cs +++ b/samples/LocalWorkers/Trax.Samples.GameServer.Scheduler/Program.cs @@ -39,7 +39,6 @@ using Trax.Scheduler.Configuration; using Trax.Scheduler.Extensions; using Trax.Scheduler.Services.Scheduling; -using Trax.Scheduler.Trains.ManifestManager; var builder = WebApplication.CreateBuilder(args); @@ -58,7 +57,7 @@ .SaveTrainParameters() .AddStepProgress() ) - .AddMediator(typeof(ManifestNames).Assembly, typeof(ManifestManagerTrain).Assembly) + .AddMediator(typeof(ManifestNames).Assembly) .AddScheduler(scheduler => { // ── Global Configuration ──────────────────────────────────────── @@ -75,7 +74,6 @@ cleanup.AddTrainType(); cleanup.AddTrainType(); }) - .JobDispatcherPollingInterval(TimeSpan.FromSeconds(2)) .UseLocalWorkers(); // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/templates/content/Trax.Samples.Scheduler/Program.cs b/templates/content/Trax.Samples.Scheduler/Program.cs index 02f8c46..9409064 100644 --- a/templates/content/Trax.Samples.Scheduler/Program.cs +++ b/templates/content/Trax.Samples.Scheduler/Program.cs @@ -22,7 +22,6 @@ using Trax.Samples.Scheduler.Trains.HelloWorld; using Trax.Scheduler.Extensions; using Trax.Scheduler.Services.Scheduling; -using Trax.Scheduler.Trains.ManifestManager; var builder = WebApplication.CreateBuilder(args); @@ -40,7 +39,7 @@ trax.AddEffects(effects => effects.UsePostgres(connectionString).AddJson().SaveTrainParameters().AddStepProgress() ) - .AddMediator(typeof(Program).Assembly, typeof(ManifestManagerTrain).Assembly) + .AddMediator(typeof(Program).Assembly) .AddScheduler(scheduler => { scheduler.UseLocalWorkers();