feat: add document bucket auto-creation, extension defaults, and chat widget on dashboard

Auto-create documents storage bucket on first upload. Default legacy general
extensions to enabled when no toggle row exists. Add ChatWidget to dashboard
root page with open-ai-chat event support and AI assistant quick action.
Add ensureInitialized to supplier invoices route for event emission.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Emil
2026-02-23 13:00:16 +01:00
co-authored by Claude Opus 4.6
parent 263c229903
commit ce66a21010
8 changed files with 86 additions and 7 deletions
@@ -25,6 +25,8 @@ function makeClient(storageOverrides: Record<string, unknown> = {}) {
from: vi.fn().mockImplementation(() => makeBuilder()),
rpc: vi.fn().mockImplementation(async () => results[resultIdx++] ?? { data: null, error: null }),
storage: {
getBucket: vi.fn().mockResolvedValue({ data: { id: 'documents' }, error: null }),
createBucket: vi.fn().mockResolvedValue({ data: { name: 'documents' }, error: null }),
from: vi.fn().mockReturnValue({
upload: vi.fn().mockResolvedValue({ data: {}, error: null }),
download: vi.fn().mockResolvedValue({
@@ -43,14 +45,16 @@ function makeClient(storageOverrides: Record<string, unknown> = {}) {
vi.mock('@/lib/supabase/server', () => ({
createClient: vi.fn(async () => makeClient()),
createServiceClient: vi.fn(async () => makeClient()),
}))
import { uploadDocument, createNewVersion, verifyIntegrity } from '../document-service'
import { uploadDocument, createNewVersion, verifyIntegrity, _resetBucketVerified } from '../document-service'
import { createClient } from '@/lib/supabase/server'
beforeEach(() => {
vi.clearAllMocks()
eventBus.clear()
_resetBucketVerified()
resultIdx = 0
results = []
// Reset the mock to use default makeClient
+30 -1
View File
@@ -1,4 +1,4 @@
import { createClient } from '@/lib/supabase/server'
import { createClient, createServiceClient } from '@/lib/supabase/server'
import { eventBus } from '@/lib/events'
import type { DocumentAttachment, CreateDocumentAttachmentInput, DocumentUploadSource } from '@/types'
@@ -10,6 +10,33 @@ import type { DocumentAttachment, CreateDocumentAttachmentInput, DocumentUploadS
* for documents linked to committed entries.
*/
let bucketVerified = false
/** @internal Reset bucket verification flag — for testing only */
export function _resetBucketVerified() {
bucketVerified = false
}
/**
* Ensure the 'documents' storage bucket exists, creating it if missing.
* Runs once per process lifetime (same pattern as ensureInitialized).
*/
async function ensureDocumentsBucket(): Promise<void> {
if (bucketVerified) return
const supabase = await createServiceClient()
const { data: bucket } = await supabase.storage.getBucket('documents')
if (!bucket) {
await supabase.storage.createBucket('documents', {
public: false,
fileSizeLimit: 52428800, // 50 MB
})
}
bucketVerified = true
}
/**
* Compute SHA-256 hash of a file buffer
*/
@@ -31,6 +58,7 @@ export async function uploadDocument(
journal_entry_line_id?: string
} = {}
): Promise<DocumentAttachment> {
await ensureDocumentsBucket()
const supabase = await createClient()
// Compute SHA-256 hash
@@ -102,6 +130,7 @@ export async function createNewVersion(
originalId: string,
file: { name: string; buffer: ArrayBuffer; type?: string }
): Promise<DocumentAttachment> {
await ensureDocumentsBucket()
const supabase = await createClient()
// Compute SHA-256 hash