Apps, web, i18n, UI, partners, payouts, backoffice, bariki, prod infra

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zabudko-ruandClaude Opus 5.5 committed 2026-10-25 22:55:29 +04:00
1 parent 45d7512d2b
commit 3489e0b816
319 files changed
+45483 -121

No files matched your search

@@ -0,0 +1,21 @@
import { crmFetch } from '../../../../../../lib/flats';
/** Фото квартиры из CRM (обрезанное там же); кешируется браузером на сутки */
export async function GET(_req: Request, { params }: { params: Promise<{ id: string; index: string }> }) {
const { id, index } = await params;
if (!/^\d{1,9}$/.test(id) || !/^\d{1,2}$/.test(index)) return new Response(null, { status: 404 });
try {
const res = await crmFetch(`/api/barev/objects/${id}/photos/${index}`, {
next: { revalidate: 86_400 },
});
if (!res.ok) return new Response(null, { status: 404 });
return new Response(res.body, {
headers: {
'content-type': 'image/jpeg',
'cache-control': 'public, max-age=86400, stale-while-revalidate=604800',
},
});
} catch {
return new Response(null, { status: 502 });
}
}
@@ -0,0 +1,50 @@
import { crmFetch } from '../../../../lib/flats';
// Заявка на просмотр со страницы /ru/apartments → CRM (клиент «BarevClub» и сообщение в группу AREV).
/** Не больше 5 заявок с одного адреса за 10 минут — от случайных повторов и ботов */
const hits = new Map<string, number[]>();
function limited(ip: string) {
const now = Date.now();
const recent = (hits.get(ip) ?? []).filter((t) => now - t < 10 * 60_000);
recent.push(now);
hits.set(ip, recent);
if (hits.size > 5000) hits.clear();
return recent.length > 5;
}
const json = (body: object, status = 200) => Response.json(body, { status });
export async function POST(req: Request) {
const ip = (req.headers.get('x-forwarded-for') ?? '').split(',')[0]!.trim() || 'unknown';
if (limited(ip)) return json({ code: 'TOO_MANY' }, 429);
let body: Record<string, unknown>;
try {
body = (await req.json()) as Record<string, unknown>;
} catch {
return json({ code: 'VALIDATION' }, 400);
}
// скрытое поле: его заполняют только боты
if (typeof body.website === 'string' && body.website) return json({ ok: true });
const str = (v: unknown) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
const payload = {
leadId: Number(body.leadId),
day: str(body.day),
time: str(body.time),
name: str(body.name),
phone: str(body.phone),
telegram: str(body.telegram),
};
try {
const res = await crmFetch('/api/barev/requests', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload),
});
const data = (await res.json().catch(() => ({}))) as { code?: string };
if (!res.ok) return json({ code: data.code ?? 'FAILED' }, res.status === 401 ? 503 : res.status);
return json({ ok: true });
} catch {
return json({ code: 'FAILED' }, 502);
}
}