diff --git a/apps/api/src/lib/import/adapters/mock.adapter.ts b/apps/api/src/lib/import/adapters/mock.adapter.ts index 195e47d..3b225c2 100644 --- a/apps/api/src/lib/import/adapters/mock.adapter.ts +++ b/apps/api/src/lib/import/adapters/mock.adapter.ts @@ -1,4 +1,3 @@ -import { Buffer } from 'node:buffer'; import type { IPageScraperAdapter, ScrapedPageData } from '@minimalblock/core'; function normalizeDomain(url: URL): string { @@ -7,7 +6,7 @@ function normalizeDomain(url: URL): string { function buildMockDataUrl(label: string, color: string): string { const svg = `${label}`; - return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`; + return `data:image/svg+xml;base64,${btoa(svg)}`; } export class MockAdapter implements IPageScraperAdapter { diff --git a/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts b/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts index 39cc00e..3a6635c 100644 --- a/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts +++ b/apps/api/src/lib/import/pipeline/image-intelligence.pipeline.ts @@ -1,4 +1,3 @@ -import { Buffer } from 'node:buffer'; import type { ImportedImageCandidate, ProductImportData } from '@minimalblock/core'; import { GeminiImageClassifier, ImageDeduplicationService } from '@minimalblock/ai'; @@ -33,7 +32,7 @@ export class ImageIntelligencePipeline { headers: { 'user-agent': 'MinimalBlockBot/1.0', accept: 'image/*' }, }); if (!res.ok) return null; - return Buffer.from(await res.arrayBuffer()); + return new Uint8Array(await res.arrayBuffer()); } catch { return null; } @@ -41,7 +40,7 @@ export class ImageIntelligencePipeline { ); // Perceptual deduplication - const hashes = buffers.map((buf) => buf ? this.deduplicator.computeHash(new Uint8Array(buf)) : '0000000000000000'); + const hashes = buffers.map((buf) => buf ? this.deduplicator.computeHash(buf) : '0000000000000000'); const duplicateIndexes = new Set(this.deduplicator.findDuplicates(hashes)); // Build base64 images for Gemini classification (only non-failed uploads) @@ -50,7 +49,7 @@ export class ImageIntelligencePipeline { const buf = buffers[i]; if (buf && candidates[i].mimeType && candidates[i].mimeType !== 'image/svg+xml') { geminiImages.push({ - base64: buf.toString('base64'), + base64: btoa(String.fromCharCode(...buf)), mimeType: candidates[i].mimeType!, originalIndex: i, }); diff --git a/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts b/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts index fb60262..d39e954 100644 --- a/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts +++ b/apps/api/src/lib/import/pipeline/image-upload.pipeline.ts @@ -1,4 +1,3 @@ -import { Buffer } from 'node:buffer'; import { generateId, type ImportedImageCandidate, type MediaAssetType } from '@minimalblock/core'; import type { ScrapedImageCandidate } from '@minimalblock/core'; import type { SupabaseClient } from '@supabase/supabase-js'; @@ -17,11 +16,14 @@ function mimeExtension(mimeType: string): string { } } -function decodeDataUrl(raw: string): { mimeType: string; buffer: Buffer } | null { +function decodeDataUrl(raw: string): { mimeType: string; buffer: Uint8Array } | null { if (!raw.startsWith('data:')) return null; const match = raw.match(/^data:([^;]+);base64,(.+)$/); if (!match) return null; - return { mimeType: match[1], buffer: Buffer.from(match[2], 'base64') }; + const binaryStr = atob(match[2]); + const bytes = new Uint8Array(binaryStr.length); + for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i); + return { mimeType: match[1], buffer: bytes }; } export type UploadedImportImage = ImportedImageCandidate; @@ -76,7 +78,7 @@ export class ImageUploadPipeline { ? mimeType : 'image/jpeg'; - const bytes = decoded?.buffer ?? Buffer.from(await response!.arrayBuffer()); + const bytes = decoded?.buffer ?? new Uint8Array(await response!.arrayBuffer()); const fileName = `${Date.now()}-${slugify(image.title ?? image.alt ?? `import-${image.ordinal}`)}.${mimeExtension(normalizedMimeType)}`; const storageKey = `${this.ownerId}/imports/${fileName}`; diff --git a/apps/api/src/worker.ts b/apps/api/src/worker.ts index 4f75437..bdefaef 100644 --- a/apps/api/src/worker.ts +++ b/apps/api/src/worker.ts @@ -4,4 +4,4 @@ export default { async fetch(request: Request, env: ApiEnv): Promise { return handleRequest(request, env); }, -} satisfies ExportedHandler; +}; diff --git a/apps/docs/src/.vitepress/config.mts b/apps/docs/src/.vitepress/config.mts index fcdc2bc..513addd 100644 --- a/apps/docs/src/.vitepress/config.mts +++ b/apps/docs/src/.vitepress/config.mts @@ -2,6 +2,10 @@ import { defineConfig } from 'vitepress' export default defineConfig({ srcDir: '../../../docs', + head: [ + ['meta', { name: 'robots', content: 'noindex, nofollow, noarchive, noimageindex' }], + ['meta', { name: 'googlebot', content: 'noindex, nofollow' }], + ], locales: { en: { diff --git a/apps/docs/src/.vitepress/public/robots.txt b/apps/docs/src/.vitepress/public/robots.txt new file mode 100644 index 0000000..0c9922f --- /dev/null +++ b/apps/docs/src/.vitepress/public/robots.txt @@ -0,0 +1,20 @@ +User-agent: * +Disallow: / + +User-agent: GPTBot +Disallow: / + +User-agent: ChatGPT-User +Disallow: / + +User-agent: CCBot +Disallow: / + +User-agent: anthropic-ai +Disallow: / + +User-agent: Claude-Web +Disallow: / + +User-agent: Google-Extended +Disallow: / diff --git a/apps/web/index.html b/apps/web/index.html index 67a4c9d..0e3696d 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -24,6 +24,8 @@ + + diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt new file mode 100644 index 0000000..0c9922f --- /dev/null +++ b/apps/web/public/robots.txt @@ -0,0 +1,20 @@ +User-agent: * +Disallow: / + +User-agent: GPTBot +Disallow: / + +User-agent: ChatGPT-User +Disallow: / + +User-agent: CCBot +Disallow: / + +User-agent: anthropic-ai +Disallow: / + +User-agent: Claude-Web +Disallow: / + +User-agent: Google-Extended +Disallow: /