Activos now supports multi-file/folder upload to private Storage (PDF, Excel, CSV, images, ZIP, etc.) with per-tenant folders. Co-authored-by: Cursor <cursoragent@cursor.com>
69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
import { createClient } from '@supabase/supabase-js';
|
|
import {
|
|
serviceHeaders,
|
|
supabaseUrl,
|
|
} from '@/lib/agency';
|
|
|
|
export const ASSETS_BUCKET = 'tenant-assets';
|
|
export const MAX_ASSET_BYTES = 80 * 1024 * 1024; // 80 MB
|
|
|
|
export function serviceSupabase() {
|
|
const key = process.env.SUPABASE_SERVICE_ROLE_KEY?.trim();
|
|
if (!key) return null;
|
|
return createClient(supabaseUrl(), key, {
|
|
auth: { persistSession: false, autoRefreshToken: false },
|
|
});
|
|
}
|
|
|
|
export async function ensureAssetsBucket(): Promise<{ ok: boolean; error?: string }> {
|
|
const headers = serviceHeaders();
|
|
if (!headers) return { ok: false, error: 'no service role' };
|
|
|
|
const list = await fetch(`${supabaseUrl()}/storage/v1/bucket`, {
|
|
headers,
|
|
cache: 'no-store',
|
|
});
|
|
if (list.ok) {
|
|
const buckets = (await list.json()) as { id: string }[];
|
|
if (buckets.some((b) => b.id === ASSETS_BUCKET)) {
|
|
return { ok: true };
|
|
}
|
|
}
|
|
|
|
const create = await fetch(`${supabaseUrl()}/storage/v1/bucket`, {
|
|
method: 'POST',
|
|
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
id: ASSETS_BUCKET,
|
|
name: ASSETS_BUCKET,
|
|
public: false,
|
|
file_size_limit: MAX_ASSET_BYTES,
|
|
allowed_mime_types: null,
|
|
}),
|
|
});
|
|
if (!create.ok) {
|
|
const text = await create.text();
|
|
// race: already exists
|
|
if (text.toLowerCase().includes('already') || create.status === 409) {
|
|
return { ok: true };
|
|
}
|
|
return { ok: false, error: text.slice(0, 300) };
|
|
}
|
|
return { ok: true };
|
|
}
|
|
|
|
export function sanitizeFolder(raw: string): string {
|
|
return raw
|
|
.trim()
|
|
.replace(/\\/g, '/')
|
|
.replace(/\.\./g, '')
|
|
.replace(/^\/+|\/+$/g, '')
|
|
.replace(/[^a-zA-Z0-9/_ .-]/g, '-')
|
|
.slice(0, 180);
|
|
}
|
|
|
|
export function sanitizeFileName(name: string): string {
|
|
const base = name.split(/[/\\]/).pop() || 'file';
|
|
return base.replace(/[^\w.\- ()áéíóúÁÉÍÓÚñÑ]+/gi, '_').slice(0, 180);
|
|
}
|