feat(files): add file storage subsystem with scenario and run artifact support
- add FileEntity, ScenarioFileEntity, ScenarioRunFileEntity with UUID-sharded disk tree - FileStorageService: write/sha256/expiry, paginated listing, dynamic cleanup cron via SchedulerRegistry - expose getScenarioFiles() and downloadFile() on ScriptContext for use in exec code - wire scenarioId, runId, fileService into ExecContextBuilder and scenario scheduler - add file upload and listing endpoints to ScenarioController - add FILES_DIR, FILE_RUN_EXPIRATION_DAYS, FILE_CLEANUP_CRON to AppConfig
This commit is contained in:
@@ -6,6 +6,7 @@ import { Effect } from "effect";
|
||||
import type { Browser, BrowserContext, Page } from "playwright";
|
||||
import { chromium } from "playwright";
|
||||
import { Repository } from "typeorm";
|
||||
import { FileStorageService } from "../file/file-storage.service";
|
||||
import type { ScriptLogger } from "../code-executor/code-executor.service";
|
||||
import { CodeExecutorService } from "../code-executor/code-executor.service";
|
||||
import { ExecContextBuilder } from "../code-executor/exec-context.builder";
|
||||
@@ -43,6 +44,8 @@ export class ScenarioSchedulerService implements OnModuleInit {
|
||||
private readonly runEnvironments = new Map<string, EnvironmentData>();
|
||||
// Cache scenario-level timeout (seconds) per run
|
||||
private readonly runScenarioTimeouts = new Map<string, number | null>();
|
||||
// Cache scenarioId per run
|
||||
private readonly runScenarioIds = new Map<string, string>();
|
||||
|
||||
private static readonly DEFAULT_SCENARIO_TIMEOUT_SEC = 600;
|
||||
private static readonly DEFAULT_STEP_TIMEOUT_SEC = 60;
|
||||
@@ -62,6 +65,7 @@ export class ScenarioSchedulerService implements OnModuleInit {
|
||||
private readonly snippetService: SnippetService,
|
||||
private readonly sessionService: SessionService,
|
||||
private readonly sessionContextService: SessionContextService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
) {}
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
@@ -198,6 +202,7 @@ export class ScenarioSchedulerService implements OnModuleInit {
|
||||
.findOne(run.scenarioId)
|
||||
.catch(() => null);
|
||||
this.runScenarioTimeouts.set(run.id, scenario?.timeoutSeconds ?? null);
|
||||
this.runScenarioIds.set(run.id, run.scenarioId);
|
||||
const traceId = crypto.randomUUID();
|
||||
void traceStorage.run({ traceId }, () =>
|
||||
this.processRunToCompletion(run.id),
|
||||
@@ -210,6 +215,7 @@ export class ScenarioSchedulerService implements OnModuleInit {
|
||||
this.runSnippets.delete(run.id);
|
||||
this.runEnvironments.delete(run.id);
|
||||
this.runScenarioTimeouts.delete(run.id);
|
||||
this.runScenarioIds.delete(run.id);
|
||||
this.logger.error(
|
||||
`Run #${run.id}: setup failed — ${String(err)}`,
|
||||
);
|
||||
@@ -272,6 +278,7 @@ export class ScenarioSchedulerService implements OnModuleInit {
|
||||
this.runSnippets.delete(runId);
|
||||
this.runEnvironments.delete(runId);
|
||||
this.runScenarioTimeouts.delete(runId);
|
||||
this.runScenarioIds.delete(runId);
|
||||
await this.maybePreserveSession(runId);
|
||||
}
|
||||
}
|
||||
@@ -349,6 +356,7 @@ export class ScenarioSchedulerService implements OnModuleInit {
|
||||
const creds = this.runCredentials.get(stepRun.runId);
|
||||
const snips = this.runSnippets.get(stepRun.runId);
|
||||
const env = this.runEnvironments.get(stepRun.runId);
|
||||
const scenarioId = this.runScenarioIds.get(stepRun.runId);
|
||||
const execCtx = new ExecContextBuilder()
|
||||
.page(page)
|
||||
.browser(context)
|
||||
@@ -358,6 +366,9 @@ export class ScenarioSchedulerService implements OnModuleInit {
|
||||
.credentials(creds)
|
||||
.environment(env)
|
||||
.snippets(snips)
|
||||
.scenarioId(scenarioId)
|
||||
.runId(stepRun.runId)
|
||||
.fileService(this.fileStorageService)
|
||||
.build();
|
||||
|
||||
const scenarioTimeoutSec = this.runScenarioTimeouts.get(stepRun.runId) ?? null;
|
||||
|
||||
Reference in New Issue
Block a user