const BASE = '/api' let _credentials = null // { user, pass } export function setCredentials(user, pass) { _credentials = { user, pass } } export function clearCredentials() { _credentials = null } async function request(method, path, body, isFormData = false) { const opts = { method, headers: {} } if (_credentials) { opts.headers['Authorization'] = `Basic ${btoa(`${_credentials.user}:${_credentials.pass}`)}` } if (body) { if (isFormData) { opts.body = body } else { opts.headers['Content-Type'] = 'application/json' opts.body = JSON.stringify(body) } } const res = await fetch(BASE + path, opts) if (res.status === 401) { clearCredentials() const err = new Error('Unauthorized') err.status = 401 throw err } const data = await res.json() if (!res.ok) throw new Error(data.error || 'Request failed') return data } export const api = { // Sources getSources: () => request('GET', '/sources'), getSource: (name) => request('GET', `/sources/${name}`), createSource: (body) => request('POST', '/sources', body), updateSource: (name, body) => request('PUT', `/sources/${name}`, body), deleteSource: (name) => request('DELETE', `/sources/${name}`), suggestSource: (file) => { const fd = new FormData() fd.append('file', file) return request('POST', '/sources/suggest', fd, true) }, getImportLog: (name) => request('GET', `/sources/${name}/import-log`), getStats: (name) => request('GET', `/sources/${name}/stats`), importCSV: (name, file) => { const fd = new FormData() fd.append('file', file) return request('POST', `/sources/${name}/import`, fd, true) }, transform: (name) => request('POST', `/sources/${name}/transform`), reprocess: (name) => request('POST', `/sources/${name}/reprocess`), generateView: (name) => request('POST', `/sources/${name}/view`), getFields: (name) => request('GET', `/sources/${name}/fields`), getViewData: (name, limit = 100, offset = 0) => request('GET', `/sources/${name}/view-data?limit=${limit}&offset=${offset}`), // Rules getRules: (source) => request('GET', `/rules/source/${source}`), createRule: (body) => request('POST', '/rules', body), updateRule: (id, body) => request('PUT', `/rules/${id}`, body), deleteRule: (id) => request('DELETE', `/rules/${id}`), testRule: (id, limit = 20) => request('GET', `/rules/${id}/test?limit=${limit}`), previewRule: (source, field, pattern, flags, function_type = 'extract', replace_value = '', limit = 20) => request('GET', `/rules/preview?source=${encodeURIComponent(source)}&field=${encodeURIComponent(field)}&pattern=${encodeURIComponent(pattern)}&flags=${encodeURIComponent(flags || '')}&function_type=${function_type}&replace_value=${encodeURIComponent(replace_value)}&limit=${limit}`), // Mappings getMappings: (source, rule) => request('GET', `/mappings/source/${source}${rule ? `?rule_name=${rule}` : ''}`), getMappingCounts: (source, rule) => request('GET', `/mappings/source/${source}/counts${rule ? `?rule_name=${rule}` : ''}`), getUnmapped: (source, rule) => request('GET', `/mappings/source/${source}/unmapped${rule ? `?rule_name=${rule}` : ''}`), getAllValues: (source, rule) => request('GET', `/mappings/source/${source}/all-values${rule ? `?rule_name=${rule}` : ''}`), exportMappingsUrl: (source, rule) => `${BASE}/mappings/source/${source}/export.tsv${rule ? `?rule_name=${rule}` : ''}`, importMappingsCSV: (source, file) => { const fd = new FormData() fd.append('file', file) return request('POST', `/mappings/source/${source}/import-csv`, fd, true) }, createMapping: (body) => request('POST', '/mappings', body), bulkMappings: (mappings) => request('POST', '/mappings/bulk', { mappings }), updateMapping: (id, body) => request('PUT', `/mappings/${id}`, body), deleteMapping: (id) => request('DELETE', `/mappings/${id}`), // Records getRecords: (source, limit = 100, offset = 0) => request('GET', `/records/source/${source}?limit=${limit}&offset=${offset}`), }