f24616d342
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m57s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 34s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
Ostatnia z osmiu wskazowek partnerow. Nowa usluga services/render sklada raport:
generuje plik posredni .tex i kompiluje go XeLaTeX-em do PDF.
OSOBNY komponent, nie czesc prezentacji — ta sama zasada co przy izolacji
swissepha (LOG-27). TeX Live wazy setki megabajtow; w obrazie produktu
spowalnialby kazdy build, a tak aktualizuje sie niezaleznie i jego awaria nie
kladzie aplikacji, tylko przycisk „Pobierz PDF".
SZYFROWANIE, o ktore prosiles: lacze prezentacja↔render idzie tak samo jak
pozostale, ale z WLASNYM, TRZECIM kluczem (LINK_KEY_PRESENTATION_RENDER). Osobny,
bo tym laczem plynie CALY raport — dane urodzeniowe i opisy z baz — wiec przejecie
go nie moze otwierac lacza do logiki ani do danych. Fail-closed: bez klucza pod
nie wstaje. Test kopii link_crypto obejmuje teraz cztery uslugi.
Uklad PDF wg prosby: imie i nazwisko -> wprowadzone dane -> RYSUNEK kosmogramu
-> interpretacja natalna -> predykcje okresowe. Test pilnuje kolejnosci.
Dwie rzeczy, ktore wyszly dopiero przy skladaniu tego kawalka:
1. KOSMOGRAM NIE NADAWAL SIE DO PDF. Uzywa zmiennych CSS (var(--line)) i klasy
.glyph, ktorej font podaje styles.css — a samodzielny konwerter SVG→PDF nie zna
naszego arkusza. Wyszlyby czarne kreski BEZ SYMBOLI. Stad wariant „print":
konkretne kolory na bialym tle i font glifow wpisany wprost w rysunek. Przy
okazji zalatwia to etap 4 planu kola (wariant do druku).
2. UCIECZKA ZNAKOW LATEXA byla zepsuta — i zlapal to moj wlasny test. Zamiana
„\” na \textbackslash{} szla w tej samej petli co nawiasy, wiec kolejne
podmiany ucieklyby nawiasy dopiero co wstawione: wychodzilo
\textbackslash\{\}. Poprawka: ukosnik chowany pod znacznik zastepczy i
rozwijany na koncu. To nie kosmetyka — niezauwazony „%” komentuje RESZTE LINII,
wiec zdanie od modelu urywaloby sie w polowie, a PDF powstawalby normalnie,
tylko krotszy. Test na wrogim tekscie sprawdza tez, ze \end{document} ani
\input{} nie wyrwa sie z dokumentu.
Usluga nie zapisuje nic poza katalogiem tymczasowym, ktory sprzata po sobie;
w manifescie readOnlyRootFilesystem + emptyDir na /tmp. /health raportuje
obecnosc xelatex i rsvg-convert, zeby zepsuty obraz bylo widac od razu.
Zweryfikowane na zywo (TestClient uslugi render): zadanie bez szyfrowania
z POPRAWNYM tokenem -> 400; obcy klucz -> 400 i tajny opis nie wraca; wlasciwy
klucz -> zadanie dochodzi do aplikacji, tresci baz NIE MA na kablu, odpowiedz
zaszyfrowana. Manifesty przechodza kubectl apply --dry-run=server na zywym
klastrze.
CZEGO NIE SPRAWDZILEM: samej kompilacji PDF. W tym srodowisku nie ma ani TeX
Live, ani dzialajacego runtime'u kontenerow (docker CLI jest, daemon nie) —
probowalem zbudowac obraz i sie nie dalo. Sprawdzone jest wszystko dookola:
generowanie .tex, ucieczka znakow, szyfrowanie, kontrakt API, samowystarczalnosc
SVG. Pierwsze uruchomienie na klastrze trzeba obejrzec — dlatego PRE-24 zostaje
jako „W trakcie", nie „Zrobione".
Instrukcja wdrozenia: docs/wdrozenie-render-pdf.md (klucz, obraz, merge,
weryfikacja, znane ograniczenia).
Testy: 15 nowych (render) + 14 (lacze i wariant druku w prezentacji).
Calosc: prezentacja 131, logika 265/1 skip, render 15.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
160 lines
6.0 KiB
JavaScript
160 lines
6.0 KiB
JavaScript
// Zakładka „Skompiluj" — składanie raportu (PRE-23).
|
||
//
|
||
// Raport ma trzy kawałki liczone w różnych miejscach:
|
||
// 1. horoskop — liczy serwer przy wysłaniu tego formularza,
|
||
// 2. interpretacja natalna — z zakładki Interpretacje (natal.js),
|
||
// 3. predykcje okresowe — z zakładki Kalendarz (predictions.js).
|
||
//
|
||
// Punkty 2 i 3 mieszkają w magazynie przeglądarki, więc to tutaj je dokładamy.
|
||
// Odczytujemy je przez API tamtych modułów, a nie sięgając wprost do
|
||
// localStorage — dzięki temu format danych ma JEDNEGO właściciela.
|
||
//
|
||
// Pokazujemy też, CZEGO BRAKUJE. Bez tego użytkownik złożyłby niekompletny raport
|
||
// i dowiedział się o tym dopiero po otwarciu PDF-a.
|
||
(function () {
|
||
'use strict';
|
||
|
||
function esc(s) {
|
||
return String(s == null ? '' : s)
|
||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
}
|
||
|
||
function readNatal() {
|
||
return (window.astrololoNatal && window.astrololoNatal.read()) || null;
|
||
}
|
||
|
||
function readPredictions() {
|
||
return (window.astrololoPredictions && window.astrololoPredictions.read()) || [];
|
||
}
|
||
|
||
function item(ok, label, hint) {
|
||
return '<li class="' + (ok ? 'rdy-ok' : 'rdy-missing') + '">' +
|
||
(ok ? '✓ ' : '✗ ') + esc(label) +
|
||
(ok ? '' : ' <span class="muted small">— ' + esc(hint) + '</span>') + '</li>';
|
||
}
|
||
|
||
function renderReadiness(hasChart) {
|
||
var host = document.getElementById('readiness');
|
||
if (!host) return;
|
||
var natal = readNatal();
|
||
var preds = readPredictions();
|
||
var parts = [
|
||
item(hasChart, 'Policzony horoskop', 'wypełnij dane i kliknij „Złóż podsumowanie"'),
|
||
item(!!natal, 'Interpretacja natalna',
|
||
'wygeneruj ją na zakładce Interpretacje — zapamięta się sama'),
|
||
item(preds.length > 0,
|
||
'Predykcje okresowe' + (preds.length ? ' (' + preds.length + ')' : ''),
|
||
'policz horoskop na wybrany okres w Kalendarzu')
|
||
];
|
||
host.innerHTML = '<div class="meta">Materiał do raportu</div><ul class="readiness">' +
|
||
parts.join('') + '</ul>';
|
||
}
|
||
|
||
function renderNatal() {
|
||
var host = document.getElementById('reportNatal');
|
||
if (!host) return;
|
||
var natal = readNatal();
|
||
if (!natal) {
|
||
host.innerHTML = '<div class="meta">Interpretacja natalna</div>' +
|
||
'<p class="muted small">Brak — wygeneruj ją na zakładce Interpretacje.</p>';
|
||
return;
|
||
}
|
||
host.innerHTML = '<div class="meta">Interpretacja natalna ' +
|
||
'<span class="muted small">· zapamiętana ' + esc(String(natal.saved_at).slice(0, 10)) +
|
||
'</span></div><div class="report-text">' + esc(natal.text) + '</div>';
|
||
}
|
||
|
||
function renderPredictions() {
|
||
var host = document.getElementById('reportPredictions');
|
||
if (!host) return;
|
||
var preds = readPredictions();
|
||
if (!preds.length) {
|
||
host.innerHTML = '<div class="meta">Predykcje okresowe</div>' +
|
||
'<p class="muted small">Brak — policz horoskop na wybrany okres w Kalendarzu.</p>';
|
||
return;
|
||
}
|
||
var blocks = preds.map(function (p) {
|
||
return '<div class="meta">' + esc(p.from_date) + ' – ' + esc(p.to_date) + '</div>' +
|
||
'<div class="report-text">' + esc(p.text) + '</div>';
|
||
}).join('');
|
||
host.innerHTML = '<div class="meta">Predykcje okresowe (' + preds.length + ')</div>' + blocks;
|
||
}
|
||
|
||
function ready(fn) {
|
||
if (document.readyState !== 'loading') fn();
|
||
else document.addEventListener('DOMContentLoaded', fn);
|
||
}
|
||
|
||
// ── PDF (PRE-24) ────────────────────────────────────────────────────
|
||
// Części od AI mieszkają w przeglądarce, więc raport składamy TU i wysyłamy
|
||
// do serwera, który dokłada kosmogram i woła usługę render.
|
||
function formData() {
|
||
function v(name) {
|
||
var el = document.querySelector('[name=' + name + ']');
|
||
return el ? el.value : '';
|
||
}
|
||
return {
|
||
date: v('date'), time: v('time'), tz_offset: v('tz_offset'),
|
||
lat: v('lat'), lon: v('lon'),
|
||
house_system: v('house_system'), zodiac: v('zodiac'),
|
||
place: (document.querySelector('#geoSearch') || {}).value || ''
|
||
};
|
||
}
|
||
|
||
function download(blob) {
|
||
var url = URL.createObjectURL(blob);
|
||
var a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'raport.pdf';
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
a.remove();
|
||
// Zwalniamy obiekt dopiero po kliknięciu — wcześniej przeglądarka nie zdąży
|
||
// rozpocząć pobierania.
|
||
setTimeout(function () { URL.revokeObjectURL(url); }, 4000);
|
||
}
|
||
|
||
function buildPdf(btn, note) {
|
||
var person = (document.querySelector('[name=person]') || {}).value || '';
|
||
var payload = {
|
||
person: person,
|
||
data: formData(),
|
||
natal: readNatal() || {},
|
||
predictions: readPredictions()
|
||
};
|
||
btn.disabled = true;
|
||
note.textContent = 'Składam PDF — LaTeX potrzebuje chwili przy obszernym tekście…';
|
||
fetch('/compile/pdf', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
}).then(function (r) {
|
||
if (!r.ok) {
|
||
return r.json().catch(function () { return {}; }).then(function (d) {
|
||
throw new Error(d.detail || ('HTTP ' + r.status));
|
||
});
|
||
}
|
||
return r.blob();
|
||
}).then(function (blob) {
|
||
download(blob);
|
||
note.textContent = 'Gotowe — PDF pobrany.';
|
||
}).catch(function (e) {
|
||
note.textContent = 'Nie udało się złożyć PDF-a: ' + e.message;
|
||
}).finally(function () {
|
||
btn.disabled = false;
|
||
});
|
||
}
|
||
|
||
ready(function () {
|
||
if (!document.getElementById('readiness')) return; // nie ta zakładka
|
||
// Horoskop jest w dokumencie tylko po wysłaniu formularza.
|
||
renderReadiness(!!document.getElementById('reportPerson'));
|
||
renderNatal();
|
||
renderPredictions();
|
||
|
||
var btn = document.getElementById('pdfBtn');
|
||
var note = document.getElementById('pdfNote');
|
||
if (btn && note) btn.addEventListener('click', function () { buildPdf(btn, note); });
|
||
});
|
||
})();
|