Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma strängvärde byggs med konkatenering i stället för att matcha mönstret.
183 lines
7.0 KiB
TypeScript
183 lines
7.0 KiB
TypeScript
import { randomUUID } from 'node:crypto'
|
|
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
import { ISO_DATE_RE } from '@/lib/invariants'
|
|
import { createLogger } from '@/lib/logger'
|
|
import {
|
|
ALLOWED_DOCUMENT_TYPES,
|
|
DOCUMENTS_BUCKET,
|
|
computeSHA256,
|
|
validateDocumentFile,
|
|
validateDocumentMagicBytes,
|
|
} from '@/lib/core/documents/document-service'
|
|
import { parseAccountKey, type ReconciliationAttachment } from './schemas'
|
|
import {
|
|
getAttachmentRow,
|
|
insertAttachmentRow,
|
|
listAttachmentRows,
|
|
stampAttachmentRemoved,
|
|
toPublicAttachment,
|
|
type AttachmentRow,
|
|
} from './attachments-store'
|
|
|
|
const log = createLogger('reconciliation/attachments')
|
|
|
|
/**
|
|
* Underlag on a reconciliation balansdag: the bytes go to the company-scoped
|
|
* `documents` bucket (same validation and hashing as every other document in
|
|
* the archive), the row to account_reconciliation_attachments. The policy
|
|
* layer over attachments-store.ts: what may be attached, where it lives, and
|
|
* that removal is a stamp, never a delete.
|
|
*
|
|
* Storage keys start with `documents/<company_id>/` on purpose: the bucket's
|
|
* RLS grants INSERT/SELECT on the second path segment being one of the
|
|
* caller's companies, so the auth-bound client can upload without the
|
|
* service role. Reads for the inline route and the archive go through the
|
|
* service role after the row has authorized the caller.
|
|
*/
|
|
|
|
export const MAX_ATTACHMENT_NOTE_LENGTH = 500
|
|
|
|
export type AttachmentErrorCode = 'INVALID_ACCOUNT_KEY' | 'INVALID_DATE' | 'INVALID_FILE' | 'NOTE_TOO_LONG' | 'ALREADY_REMOVED'
|
|
|
|
export class ReconciliationAttachmentError extends Error {
|
|
readonly code: AttachmentErrorCode
|
|
constructor(message: string, code: AttachmentErrorCode) {
|
|
super(message)
|
|
this.name = 'ReconciliationAttachmentError'
|
|
this.code = code
|
|
}
|
|
}
|
|
|
|
function sanitizeFileName(name: string): string {
|
|
const trimmed = name.trim().replace(/[/\\]/g, '_').replace(/[ |