From e274805915f874509a79624146db532b1b9f3d27 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 17 Mar 2026 14:28:44 +0000 Subject: [PATCH] Fix S3 bucket not found error by auto-creating bucket on first use The MinIO bucket was never created - docker-compose starts MinIO but nothing ensures the SOURCES_BUCKET exists. Added lazy ensureBucket() that checks/creates the bucket before any S3 operation. https://claude.ai/code/session_014AmzJVb4WYP42hW3LDiXtm --- src/lib/sources.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/sources.ts b/src/lib/sources.ts index f5a1c0f..a29e868 100644 --- a/src/lib/sources.ts +++ b/src/lib/sources.ts @@ -48,6 +48,8 @@ export interface SourceCreateInput { * Service for managing sources and raw payload storage. */ export class SourceService { + private bucketReady: Promise | null = null; + constructor( private db: DrizzleDB, private minioClient: MinioClient, @@ -55,6 +57,25 @@ export class SourceService { private inlineThreshold = 1024, // bytes ) {} + /** Ensure the S3/MinIO bucket exists, creating it if necessary */ + private ensureBucket(): Promise { + if (!this.bucketReady) { + this.bucketReady = this.minioClient + .bucketExists(this.bucket) + .then((exists) => { + if (!exists) { + return this.minioClient.makeBucket(this.bucket); + } + }) + .catch((err) => { + // Reset so next call retries + this.bucketReady = null; + throw err; + }); + } + return this.bucketReady; + } + /** Insert multiple sources with optional inline or blob payloads */ async insertMany(inputs: SourceCreateInput[]): Promise<{ successes: TypeId<"source">[]; @@ -98,6 +119,7 @@ export class SourceService { .returning(); // 2. Handle payloads + await this.ensureBucket(); for (const row of inserted) { const lookupKey = makeLookupKey(row.userId, row.type, row.externalId); const input = inputLookup.get(lookupKey); @@ -177,6 +199,7 @@ export class SourceService { /** Hard delete a source: remove blob then drop the DB row */ async deleteHard(userId: string, sourceId: TypeId<"source">): Promise { + await this.ensureBucket(); const key = `${userId}/${sourceId}`; // delete blob, ignore errors try { @@ -203,6 +226,7 @@ export class SourceService { and(eq(src.userId, userId), inArray(src.id, sourceIds)), }); const results: RawResult[] = []; + await this.ensureBucket(); for (const row of rows) { const meta = metadataSchema.parse(row.metadata ?? {});