feat: upload any validated file to the asset library
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>
This commit is contained in:
68
src/lib/assets.ts
Normal file
68
src/lib/assets.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user