perf: DT detaylari 10 paralel + adaptif fren (0.36 -> 3.2 kayit/sn) & feat: EKAP'tan inen dokumani ihaleye yukleme
This commit is contained in:
+36
-6
@@ -36,7 +36,10 @@ const argVal = (name, def) => {
|
|||||||
return i >= 0 && args[i + 1] ? Number(args[i + 1]) : def;
|
return i >= 0 && args[i + 1] ? Number(args[i + 1]) : def;
|
||||||
};
|
};
|
||||||
const MAX_PAGES = argVal('--pages', 10);
|
const MAX_PAGES = argVal('--pages', 10);
|
||||||
const MAX_DETAILS = argVal('--details', 250);
|
const MAX_DETAILS = argVal('--details', 1500);
|
||||||
|
// Detaylar PARALEL çekilir; asıl darboğaz istek başına bekleme idi.
|
||||||
|
const DETAY_PARALEL = Number(process.env.DT_PARALEL || argVal('--paralel', 10));
|
||||||
|
const DETAY_GECIKME_MS = Number(process.env.DT_GECIKME_MS || 400);
|
||||||
const DELAY_BASE_MS = 2000;
|
const DELAY_BASE_MS = 2000;
|
||||||
const DELAY_JITTER_MS = 1500;
|
const DELAY_JITTER_MS = 1500;
|
||||||
|
|
||||||
@@ -262,7 +265,10 @@ async function run() {
|
|||||||
await humanDelay();
|
await humanDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Eksik detaylar
|
// 2) Eksik detaylar — paralel + adaptif fren
|
||||||
|
// EKAP'ı zorlamamak için: her turda DETAY_PARALEL istek eş zamanlı,
|
||||||
|
// aralarda kısa duraklama. Sunucu direnç gösterirse (challenge/hata)
|
||||||
|
// gecikme kendiliğinden büyür, temiz turlarda tekrar daralır.
|
||||||
const pending = db
|
const pending = db
|
||||||
.prepare(
|
.prepare(
|
||||||
// EN YENİ kayıtlar önce: arayüz en yeni 5000'i gösteriyor; varsayılan
|
// EN YENİ kayıtlar önce: arayüz en yeni 5000'i gösteriyor; varsayılan
|
||||||
@@ -273,18 +279,42 @@ async function run() {
|
|||||||
ORDER BY rowid DESC LIMIT ?`,
|
ORDER BY rowid DESC LIMIT ?`,
|
||||||
)
|
)
|
||||||
.all(MAX_DETAILS);
|
.all(MAX_DETAILS);
|
||||||
console.log(`[dt-worker] ${pending.length} detay çekilecek`);
|
console.log(`[dt-worker] ${pending.length} detay çekilecek (${DETAY_PARALEL} paralel)`);
|
||||||
let ok = 0;
|
let ok = 0;
|
||||||
for (const row of pending) {
|
let hata = 0;
|
||||||
|
let gecikme = DETAY_GECIKME_MS;
|
||||||
|
const basladi = Date.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < pending.length; i += DETAY_PARALEL) {
|
||||||
|
const grup = pending.slice(i, i + DETAY_PARALEL);
|
||||||
|
const sonuc = await Promise.all(
|
||||||
|
grup.map(async (row) => {
|
||||||
try {
|
try {
|
||||||
const detail = await fetchDetail(sess, row.dogrudan_temin_id, row.idare_id);
|
const detail = await fetchDetail(sess, row.dogrudan_temin_id, row.idare_id);
|
||||||
setDetail.run(JSON.stringify(detail), row.dogrudan_temin_id);
|
setDetail.run(JSON.stringify(detail), row.dogrudan_temin_id);
|
||||||
ok++;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`[dt-worker] detay hatası (${row.dogrudan_temin_id}): ${err.message}`);
|
console.warn(`[dt-worker] detay hatası (${row.dogrudan_temin_id}): ${err.message}`);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
await humanDelay();
|
}),
|
||||||
|
);
|
||||||
|
const grupHata = sonuc.filter((x) => !x).length;
|
||||||
|
ok += sonuc.length - grupHata;
|
||||||
|
hata += grupHata;
|
||||||
|
|
||||||
|
// Adaptif fren: hata varsa yavaşla, temizse hızlan (2.5 sn ile 250 ms arası)
|
||||||
|
gecikme = grupHata
|
||||||
|
? Math.min(2500, Math.round(gecikme * 2))
|
||||||
|
: Math.max(250, Math.round(gecikme * 0.9));
|
||||||
|
|
||||||
|
if (i && i % (DETAY_PARALEL * 20) === 0) {
|
||||||
|
const hiz = (ok / ((Date.now() - basladi) / 1000)).toFixed(1);
|
||||||
|
console.log(`[dt-worker] ${ok}/${pending.length} detay (${hiz} kayıt/sn, gecikme ${gecikme}ms, hata ${hata})`);
|
||||||
}
|
}
|
||||||
|
if (i + DETAY_PARALEL < pending.length) await sleep(gecikme + Math.floor(Math.random() * 200));
|
||||||
|
}
|
||||||
|
console.log(`[dt-worker] detay turu: ${ok} başarılı, ${hata} hata, ${Math.round((Date.now() - basladi) / 1000)} sn`);
|
||||||
|
|
||||||
const total = db.prepare('SELECT COUNT(*) AS c FROM dt_tenders').get().c;
|
const total = db.prepare('SELECT COUNT(*) AS c FROM dt_tenders').get().c;
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
+4
-1
@@ -709,6 +709,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
mime: body.mime || null,
|
mime: body.mime || null,
|
||||||
dosya: dosyaAdi,
|
dosya: dosyaAdi,
|
||||||
sonKullanma: body.sonKullanma || null,
|
sonKullanma: body.sonKullanma || null,
|
||||||
|
ikn: body.ikn ? String(body.ikn).slice(0, 40) : null, // ihaleye bağlı belge
|
||||||
});
|
});
|
||||||
return json(res, 200, { success: true, id, belgeler: store.listDocuments(user.email), kullanim: store.documentUsage(user.email) });
|
return json(res, 200, { success: true, id, belgeler: store.listDocuments(user.email), kullanim: store.documentUsage(user.email) });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -719,9 +720,11 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const ok = store.deleteDocument(user.email, url.searchParams.get('id'));
|
const ok = store.deleteDocument(user.email, url.searchParams.get('id'));
|
||||||
return json(res, ok ? 200 : 404, { success: ok, belgeler: store.listDocuments(user.email), kullanim: store.documentUsage(user.email) });
|
return json(res, ok ? 200 : 404, { success: ok, belgeler: store.listDocuments(user.email), kullanim: store.documentUsage(user.email) });
|
||||||
}
|
}
|
||||||
|
// ?ikn=... verilirse yalnızca o ihaleye yüklenen belgeler döner
|
||||||
|
const iknFiltre = url.searchParams.get('ikn') || undefined;
|
||||||
return json(res, 200, {
|
return json(res, 200, {
|
||||||
success: true,
|
success: true,
|
||||||
belgeler: store.listDocuments(user.email),
|
belgeler: store.listDocuments(user.email, { ikn: iknFiltre }),
|
||||||
kullanim: store.documentUsage(user.email),
|
kullanim: store.documentUsage(user.email),
|
||||||
haklar: { adet: haklar(user.email, opts).belgeAdet, bayt: haklar(user.email, opts).belgeBayt },
|
haklar: { adet: haklar(user.email, opts).belgeAdet, bayt: haklar(user.email, opts).belgeBayt },
|
||||||
});
|
});
|
||||||
|
|||||||
+21
-7
@@ -69,9 +69,11 @@ CREATE TABLE IF NOT EXISTS belgeler (
|
|||||||
mime TEXT,
|
mime TEXT,
|
||||||
dosya TEXT NOT NULL, -- diskteki dosya adı
|
dosya TEXT NOT NULL, -- diskteki dosya adı
|
||||||
son_kullanma TEXT,
|
son_kullanma TEXT,
|
||||||
|
ikn TEXT, -- belge bir ihaleye aitse İKN/DTN
|
||||||
olusturma TEXT
|
olusturma TEXT
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_belge_email ON belgeler(email);
|
CREATE INDEX IF NOT EXISTS idx_belge_email ON belgeler(email);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_belge_ikn ON belgeler(email, ikn);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function available() { return Boolean(DatabaseSync); }
|
export function available() { return Boolean(DatabaseSync); }
|
||||||
@@ -82,6 +84,16 @@ export function openDb() {
|
|||||||
fs.mkdirSync(BELGE_DIR, { recursive: true });
|
fs.mkdirSync(BELGE_DIR, { recursive: true });
|
||||||
const db = new DatabaseSync(DB_PATH);
|
const db = new DatabaseSync(DB_PATH);
|
||||||
db.exec(SCHEMA);
|
db.exec(SCHEMA);
|
||||||
|
// Geçiş: belgeler ihaleye bağlanabilsin diye ikn kolonu eklendi
|
||||||
|
try {
|
||||||
|
const k = db.prepare('PRAGMA table_info(belgeler)').all().map((c) => c.name);
|
||||||
|
if (!k.includes('ikn')) {
|
||||||
|
db.exec('ALTER TABLE belgeler ADD COLUMN ikn TEXT');
|
||||||
|
console.log('[user-store] belgeler.ikn kolonu eklendi');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[user-store] belge şema geçişi atlandı:', e.message);
|
||||||
|
}
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,18 +224,20 @@ export function verifyApiKey(ham) {
|
|||||||
|
|
||||||
// --- Belgeler (gerçek dosya, gerçek boyut) ---------------------------------
|
// --- Belgeler (gerçek dosya, gerçek boyut) ---------------------------------
|
||||||
|
|
||||||
export function addDocument(email, { ad, kategori, boyut, mime, dosya, sonKullanma }) {
|
export function addDocument(email, { ad, kategori, boyut, mime, dosya, sonKullanma, ikn }) {
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
withDb((db) => db.prepare(
|
withDb((db) => db.prepare(
|
||||||
'INSERT INTO belgeler (id,email,ad,kategori,boyut,mime,dosya,son_kullanma,olusturma) VALUES (?,?,?,?,?,?,?,?,?)'
|
'INSERT INTO belgeler (id,email,ad,kategori,boyut,mime,dosya,son_kullanma,ikn,olusturma) VALUES (?,?,?,?,?,?,?,?,?,?)'
|
||||||
).run(id, email, ad, kategori || null, boyut, mime || null, dosya, sonKullanma || null, now()));
|
).run(id, email, ad, kategori || null, boyut, mime || null, dosya, sonKullanma || null, ikn || null, now()));
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function listDocuments(email) {
|
export function listDocuments(email, { ikn } = {}) {
|
||||||
return withDb((db) => db.prepare(
|
const alanlar = 'id, ad, kategori, boyut, mime, son_kullanma, ikn, olusturma';
|
||||||
'SELECT id, ad, kategori, boyut, mime, son_kullanma, olusturma FROM belgeler WHERE email=? ORDER BY olusturma DESC'
|
return withDb((db) => (ikn
|
||||||
).all(email), []);
|
? db.prepare(`SELECT ${alanlar} FROM belgeler WHERE email=? AND ikn=? ORDER BY olusturma DESC`).all(email, ikn)
|
||||||
|
: db.prepare(`SELECT ${alanlar} FROM belgeler WHERE email=? ORDER BY olusturma DESC`).all(email)
|
||||||
|
), []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDocument(email, id) {
|
export function getDocument(email, id) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useApp } from '../context/AppContext';
|
import { useApp } from '../context/AppContext';
|
||||||
import { getToken } from '../services/auth';
|
import { getToken } from '../services/auth';
|
||||||
import { FileText, ExternalLink, Loader2, Sparkles, AlertTriangle, Download } from 'lucide-react';
|
import { FileText, ExternalLink, Loader2, Sparkles, AlertTriangle, Download, Upload, Trash2, Paperclip } from 'lucide-react';
|
||||||
|
import { uploadDocument, listTenderDocuments, deleteDocument } from '../services/account';
|
||||||
|
import { extractText } from '../services/pdfText';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* İhale dokümanları paneli.
|
* İhale dokümanları paneli.
|
||||||
@@ -15,6 +17,53 @@ export const TenderDocuments = ({ tender }) => {
|
|||||||
const [bilgi, setBilgi] = useState(null);
|
const [bilgi, setBilgi] = useState(null);
|
||||||
|
|
||||||
const ikn = tender?.ikn;
|
const ikn = tender?.ikn;
|
||||||
|
const [belgeler, setBelgeler] = useState([]);
|
||||||
|
const [yukleniyor, setYukleniyor] = useState(false);
|
||||||
|
const dosyaRef = useRef(null);
|
||||||
|
|
||||||
|
const belgeleriYukle = useCallback(() => {
|
||||||
|
if (!ikn) return;
|
||||||
|
listTenderDocuments(ikn).then(setBelgeler).catch(() => {});
|
||||||
|
}, [ikn]);
|
||||||
|
useEffect(() => { belgeleriYukle(); }, [belgeleriYukle]);
|
||||||
|
|
||||||
|
// EKAP dokümanı kullanıcının tarayıcısına iniyor; indirdiği dosyayı buraya
|
||||||
|
// yükleyince ihaleye bağlanır ve metni AI analizine verilebilir.
|
||||||
|
const dosyaSec = async (e) => {
|
||||||
|
const f = e.target.files?.[0];
|
||||||
|
e.target.value = '';
|
||||||
|
if (!f) return;
|
||||||
|
setYukleniyor(true); setError(null);
|
||||||
|
try {
|
||||||
|
await uploadDocument(f, 'İhale Dokümanı', ikn);
|
||||||
|
belgeleriYukle();
|
||||||
|
setBilgi({ yuklendi: f.name });
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.kod === 'KOTA_ASILDI' ? err.message : `Yüklenemedi: ${err.message}`);
|
||||||
|
} finally { setYukleniyor(false); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const belgeSil = async (id) => {
|
||||||
|
try { await deleteDocument(id); belgeleriYukle(); } catch (err) { setError(err.message); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Yüklenen PDF'i AI'ya ver
|
||||||
|
const belgeyiAnalizEt = async (b) => {
|
||||||
|
setBusy('belge'); setError(null);
|
||||||
|
try {
|
||||||
|
const r = await fetch(`/api/me/documents/download?id=${encodeURIComponent(b.id)}`, {
|
||||||
|
headers: { Authorization: `Bearer ${getToken()}` },
|
||||||
|
});
|
||||||
|
if (!r.ok) throw new Error('Belge indirilemedi');
|
||||||
|
const blob = await r.blob();
|
||||||
|
const metin = await extractText(new File([blob], b.ad, { type: b.mime || 'application/pdf' }));
|
||||||
|
if (!metin || metin.length < 100) throw new Error('Belgeden analiz için yeterli metin çıkarılamadı (taranmış PDF olabilir).');
|
||||||
|
setIsAiSpecAnalyzerOpen(true);
|
||||||
|
await analyzeSpecPdfWithGemini({ text: metin, fileName: b.ad, tender });
|
||||||
|
} catch (e) {
|
||||||
|
setError(e.message);
|
||||||
|
} finally { setBusy(null); }
|
||||||
|
};
|
||||||
const ekapIhalesi = /^\d{4}\/\d+$/.test(String(ikn || ''));
|
const ekapIhalesi = /^\d{4}\/\d+$/.test(String(ikn || ''));
|
||||||
|
|
||||||
const dokumaniAc = async () => {
|
const dokumaniAc = async () => {
|
||||||
@@ -95,6 +144,7 @@ export const TenderDocuments = ({ tender }) => {
|
|||||||
<p className="text-[11px] text-slate-500">
|
<p className="text-[11px] text-slate-500">
|
||||||
{bilgi.dokumanSayisi != null && <>EKAP'ta {bilgi.dokumanSayisi} doküman kayıtlı. </>}
|
{bilgi.dokumanSayisi != null && <>EKAP'ta {bilgi.dokumanSayisi} doküman kayıtlı. </>}
|
||||||
{bilgi.karakter != null && <>{bilgi.ilanSayisi} ilan · {bilgi.okasSayisi} kalem · {bilgi.karakter.toLocaleString('tr-TR')} karakter metin analize gönderildi. </>}
|
{bilgi.karakter != null && <>{bilgi.ilanSayisi} ilan · {bilgi.okasSayisi} kalem · {bilgi.karakter.toLocaleString('tr-TR')} karakter metin analize gönderildi. </>}
|
||||||
|
{bilgi.yuklendi && <><strong>{bilgi.yuklendi}</strong> yüklendi ve bu ihaleye bağlandı.</>}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -105,13 +155,66 @@ export const TenderDocuments = ({ tender }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-[10px] text-slate-400 leading-relaxed flex items-start gap-1.5">
|
|
||||||
<Download className="w-3 h-3 shrink-0 mt-0.5" />
|
|
||||||
EKAP, doküman indirmeyi kendi oturumu üzerinden yaptırır; bağlantı sizin tarayıcınızda açılır.
|
|
||||||
Şartname dosyanız elinizdeyse AI analiz penceresinden doğrudan yükleyebilirsiniz.
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* EKAP dokümanı kullanıcının tarayıcısına iniyor; sunucu tarafından
|
||||||
|
alınamıyor. İndirilen dosya buraya yüklenip ihaleye bağlanır. */}
|
||||||
|
<div className="border border-dashed border-slate-300 rounded-xl p-3 bg-slate-50/60">
|
||||||
|
<p className="text-[11px] text-slate-600 leading-relaxed flex items-start gap-1.5">
|
||||||
|
<Download className="w-3.5 h-3.5 shrink-0 mt-0.5 text-slate-400" />
|
||||||
|
<span>
|
||||||
|
EKAP dokümanı kendi oturumunuz üzerinden <strong>bilgisayarınıza iner</strong> — biz sunucudan
|
||||||
|
alamıyoruz. İnen dosyayı buraya yükleyin; bu ihaleye bağlanır ve AI ile analiz edebilirsiniz.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2 mt-2">
|
||||||
|
<input
|
||||||
|
ref={dosyaRef}
|
||||||
|
type="file"
|
||||||
|
onChange={dosyaSec}
|
||||||
|
accept=".pdf,.doc,.docx,.zip,.rar,.txt"
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => dosyaRef.current?.click()}
|
||||||
|
disabled={yukleniyor}
|
||||||
|
className="bg-slate-900 hover:bg-slate-800 disabled:opacity-40 text-white font-bold text-xs px-3.5 py-2 rounded-lg flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{yukleniyor ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Upload className="w-3.5 h-3.5" />}
|
||||||
|
{yukleniyor ? 'Yükleniyor…' : 'İndirdiğim dosyayı yükle'}
|
||||||
|
</button>
|
||||||
|
<span className="text-[10px] text-slate-400">PDF, Word, ZIP · en fazla 10 MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{belgeler.length > 0 && (
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<p className="text-[11px] font-bold text-slate-700 flex items-center gap-1.5">
|
||||||
|
<Paperclip className="w-3.5 h-3.5 text-slate-400" /> Bu ihaleye yüklenen belgeler ({belgeler.length})
|
||||||
|
</p>
|
||||||
|
{belgeler.map((b) => (
|
||||||
|
<div key={b.id} className="flex items-center gap-2 bg-white border border-slate-200 rounded-lg px-2.5 py-2">
|
||||||
|
<FileText className="w-3.5 h-3.5 text-slate-400 shrink-0" />
|
||||||
|
<span className="text-xs text-slate-800 truncate flex-1" title={b.ad}>{b.ad}</span>
|
||||||
|
<span className="text-[10px] text-slate-400 whitespace-nowrap">
|
||||||
|
{(b.boyut / 1024).toLocaleString('tr-TR', { maximumFractionDigits: 0 })} KB
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => belgeyiAnalizEt(b)}
|
||||||
|
disabled={busy !== null}
|
||||||
|
className="text-[10px] font-bold text-amber-700 hover:text-amber-800 disabled:opacity-40 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{busy === 'belge' ? '…' : 'AI analiz'}
|
||||||
|
</button>
|
||||||
|
<button onClick={() => belgeSil(b.id)} className="text-slate-300 hover:text-red-600" aria-label="Sil">
|
||||||
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ async function iste(url, opts = {}) {
|
|||||||
// --- Plan & kota ---
|
// --- Plan & kota ---
|
||||||
export const getPlan = () => iste('/api/me/plan');
|
export const getPlan = () => iste('/api/me/plan');
|
||||||
|
|
||||||
|
/** Belirli bir ihaleye yüklenmiş belgeler */
|
||||||
|
export const listTenderDocuments = (ikn) =>
|
||||||
|
iste(`/api/me/documents?ikn=${encodeURIComponent(ikn)}`).then((d) => d.belgeler || []);
|
||||||
|
|
||||||
// --- Kullanıcı verisi ---
|
// --- Kullanıcı verisi ---
|
||||||
export const getAllUserData = () => iste('/api/me/data').then((d) => d.veri || {});
|
export const getAllUserData = () => iste('/api/me/data').then((d) => d.veri || {});
|
||||||
export const setUserData = (anahtar, deger) =>
|
export const setUserData = (anahtar, deger) =>
|
||||||
@@ -34,7 +38,7 @@ export const revokeApiKey = (id) => iste(`/api/me/apikeys?id=${encodeURIComponen
|
|||||||
export const listDocuments = () => iste('/api/me/documents');
|
export const listDocuments = () => iste('/api/me/documents');
|
||||||
export const deleteDocument = (id) => iste(`/api/me/documents?id=${encodeURIComponent(id)}`, { method: 'DELETE' });
|
export const deleteDocument = (id) => iste(`/api/me/documents?id=${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
|
|
||||||
export async function uploadDocument(file, kategori) {
|
export async function uploadDocument(file, kategori, ikn) {
|
||||||
const icerikBase64 = await new Promise((resolve, reject) => {
|
const icerikBase64 = await new Promise((resolve, reject) => {
|
||||||
const r = new FileReader();
|
const r = new FileReader();
|
||||||
r.onload = () => resolve(String(r.result).split(',')[1]);
|
r.onload = () => resolve(String(r.result).split(',')[1]);
|
||||||
@@ -43,7 +47,7 @@ export async function uploadDocument(file, kategori) {
|
|||||||
});
|
});
|
||||||
return iste('/api/me/documents', {
|
return iste('/api/me/documents', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ ad: file.name, mime: file.type, kategori, icerikBase64 }),
|
body: JSON.stringify({ ad: file.name, mime: file.type, kategori, ikn, icerikBase64 }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user