Тема: Почему AI-продукты на базе LLM ломаются и как сделать, чтобы работало.
В кружке выше Эмиль Сатаев рассказал, какие именно проблемы с LLM в проде будем разбирать.
Что в программе:
- Разберем реальные кейсы стартапов и ограничения LLM.
- Обсудим рабочие архитектуры: RAG, human-in-the-loop, контроль качества.
- Ответим на ваши вопросы и разберем кейсы участников.
🎁 Бонусы: в конце вебинара подарим промокод на скидку 10.000 ₽ на курсы и разыграем подписки на полезные AI-сервисы.
👉 Зарегистрироваться на вебинар
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱5
Forwarded from Библиотека собеса по PHP | вопросы с собеседований
Пользователь нажал «Оплатить» один раз. Деньги списались дважды.
📦 Задание
Фича: интеграция с платёжным шлюзом. Платёж создаётся на бэке, пользователь редиректится на страницу шлюза, после оплаты шлюз дёргает webhook. По webhook'у выдаётся доступ к продукту.
Жалобы начались через две недели после релиза. Только у части пользователей, только в часы пик. У некоторых списывалось дважды, у других доступ не выдавался вовсе.
// src/Payment/WebhookHandler.php
class WebhookHandler
{
public function __construct(
private PaymentRepository $payments,
private OrderRepository $orders,
private AccessService $access,
private Mailer $mailer,
) {}
public function handle(array $payload): void
{
$externalId = $payload['payment_id'];
$status = $payload['status'];
if ($status !== 'success') {
return;
}
$payment = $this->payments->findByExternalId($externalId);
if ($payment === null) {
return;
}
$order = $this->orders->findById($payment->orderId);
if ($order->status === 'paid') {
return;
}
$this->orders->markAsPaid($payment->orderId);
$this->access->grantForOrder($order);
$this->mailer->sendReceipt($order);
}
}
// src/Repository/OrderRepository.php
class OrderRepository
{
public function __construct(private PDO $pdo) {}
public function findById(int $id): Order
{
$stmt = $this->pdo->prepare(
'SELECT * FROM orders WHERE id = ?'
);
$stmt->execute([$id]);
return Order::fromRow($stmt->fetch(PDO::FETCH_ASSOC));
}
public function markAsPaid(int $orderId): void
{
$stmt = $this->pdo->prepare(
'UPDATE orders SET status = ? WHERE id = ?'
);
$stmt->execute(['paid', $orderId]);
}
}
🔹 Задачи
— Объяснить, как именно возникает двойное списание при одном нажатии пользователя
— Исправить WebhookHandler::handle
Ставьте → 🔥 если нравится формат. Если нет → 🌚
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20🌚4❤2👍2🤔2
🧹 Git-команда, которая спасёт ваш репозиторий от мусора
Проблема: вы удалили ветки, сделали git reset, отменили мёржи, но репозиторий почему-то весит всё больше. git clone на новом месте занимает вечность. Куда уходит место?
Дело в том, что Git — барахольщик. Он хранит все объекты: старые блобы, недостижимые коммиты, забытые stash'ы. Даже то, что вам давно не нужно.
💡 Решение: git gc и его старший брат git gc --aggressive
Рассмотрим все все возможности git gc.
1️⃣ Сколько мусора накопилось
Обратите внимание на size-pack — это реальный вес вашего репо.
2️⃣ Самые тяжёлые объекты в истории
Часто находятся артефакты сборки, дампы БД или случайно закоммиченные .jar на 200 МБ 😬
3️⃣ Запустите агрессивную сборку мусора
--aggressive заставляет Git перепаковать объекты с нуля, а --prune=now удаляет недостижимые объекты немедленно, не дожидаясь дефолтных двух недель.
4️⃣ Сравните результат
На живых проектах с историей в 2+ года разница бывает в разы.
⚠️ --prune=now безвозвратно удалит объекты, на которые нет ссылок. Если вы планировали восстановить что-то через git reflog — сделайте это до запуска.
Проблема: вы удалили ветки, сделали git reset, отменили мёржи, но репозиторий почему-то весит всё больше. git clone на новом месте занимает вечность. Куда уходит место?
Дело в том, что Git — барахольщик. Он хранит все объекты: старые блобы, недостижимые коммиты, забытые stash'ы. Даже то, что вам давно не нужно.
Рассмотрим все все возможности git gc.
git count-objects -vH
Обратите внимание на size-pack — это реальный вес вашего репо.
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '/^blob/ {print $3, $4}' \
| sort -rn \
| head -10
Часто находятся артефакты сборки, дампы БД или случайно закоммиченные .jar на 200 МБ 😬
git gc --aggressive --prune=now
--aggressive заставляет Git перепаковать объекты с нуля, а --prune=now удаляет недостижимые объекты немедленно, не дожидаясь дефолтных двух недель.
git count-objects -vH
На живых проектах с историей в 2+ года разница бывает в разы.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥4❤1💯1
Гуглишь «best analytics tools» и получаешь кашу из BI-дашбордов, product analytics и session replay в одной куче. Разложу по полочкам именно поведенческую аналитику и конкурентную разведку — то, что реально нужно, когда задача «понять, почему юзеры не конвертятся».
Hotjar — безопасный дефолт. Хитмапы, записи сессий, фидбек-виджеты. Порог входа минимальный, поэтому его ставят первым и часто на нём остаются. Если нужно быстро показать продакту «вот тут юзеры отваливаются» — закроет задачу. Но если серьёзно копаете конверсии, ему начинает не хватать глубины.
Mouseflow — для тех, кто реально сидит в записях и воронках. Friction score, form analytics, 6 типов хитмап (включая geo и live). По CRO и e-commerce — пожалуй, самый сильный вариант. Подвох: кредитные лимиты и капы на объём сессий начинают кусаться при росте трафика.
Crazy Egg — максимально простой. Хитмапы, скроллмапы, оверлеи. Не нужна глубокая аналитика, нужно за 15 минут понять, куда кликают и докуда скроллят — это оно.
Kissmetrics — другая лига. Event-level аналитика, retention, lifecycle. Заточен под SaaS с логин-зоной, где важен путь конкретного юзера, а не агрегированные хитмапы. Overhead на внедрение ощутимый, комьюнити вокруг инструмента тоньше, чем у Hotjar.
Similarweb — вообще не про ваш сайт, а про чужие. Бенчмарки трафика, каналы, доли рынка. Цифры направляющие, не абсолютные — относитесь как к компасу, не как к GPS.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍1🔥1
⚡️ Работаем с curl
Сервис отвечает 500-кой, а вы не понимаете — проблема в сети, DNS, SSL или бэкенд лёг? Прежде чем лезть в логи и разворачивать Wireshark, попробуйте curl -w и получите полный таймлайн запроса за секунду.
🔹 Зачем это нужно
— Разбивает запрос на фазы: DNS, TCP-коннект, TLS-хендшейк, ожидание первого байта, передача данных.
— Моментально показывает, где именно bottleneck — на уровне сети или приложения.
— Работает везде: на проде, в контейнере, на CI-раннере.
🔹 Как использовать
— Полный таймлайн запроса:
— Если time_namelookup высокий, проблема в DNS, смотрите резолвер.
— Если между time_connect и time_appconnect большой разрыв, тормозит TLS, проверяйте сертификаты.
— Если time_starttransfer зашкаливает при нормальном коннекте, бэкенд думает слишком долго.
— Удобно завернуть в алиас и дёргать по крону для бедняцкого мониторинга latency.
💡 Про-тип: сохраните шаблон в файл и вызывайте через curl -w имя_файла. Не придётся каждый раз гуглить формат переменных.
Сервис отвечает 500-кой, а вы не понимаете — проблема в сети, DNS, SSL или бэкенд лёг? Прежде чем лезть в логи и разворачивать Wireshark, попробуйте curl -w и получите полный таймлайн запроса за секунду.
🔹 Зачем это нужно
— Разбивает запрос на фазы: DNS, TCP-коннект, TLS-хендшейк, ожидание первого байта, передача данных.
— Моментально показывает, где именно bottleneck — на уровне сети или приложения.
— Работает везде: на проде, в контейнере, на CI-раннере.
🔹 Как использовать
— Полный таймлайн запроса:
curl -o /dev/null -s -w "\n DNS: %{time_namelookup}s\n TCP: %{time_connect}s\n TLS: %{time_appconnect}s\n TTFB: %{time_starttransfer}s\n Total: %{time_total}s\n" https://your-api.com/health— Если time_namelookup высокий, проблема в DNS, смотрите резолвер.
— Если между time_connect и time_appconnect большой разрыв, тормозит TLS, проверяйте сертификаты.
— Если time_starttransfer зашкаливает при нормальном коннекте, бэкенд думает слишком долго.
— Удобно завернуть в алиас и дёргать по крону для бедняцкого мониторинга latency.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥3👍2
🧠 LLPhant: LangChain для PHP-разработчиков
Если нужно прикрутить LLM к проекту, а городить мост на Python в виде отдельного сервиса не хочется — LLPhant закрывает эту задачу одной либой. Это единый интерфейс к OpenAI, Anthropic, Mistral, LM Studio и локальным моделям через Ollama.
Что внутри:
— полноценный RAG-пайплайн: чтение PDF/Word, чанкинг, эмбеддинги, поиск;
— векторные хранилища на любой вкус — pgvector, Redis, Elastic, Qdrant, Milvus, Pinecone;
— стриминг ответов, подсчет токенов, vision для картинок;
— function calling — описываешь тулзы PHP-классами, модель сама решит когда дернуть.
Для смены провайдера нужно переписать две строки в конфиге (а не половину сервиса).
🔗 Подробнее
Библиотека пхпшника
Если нужно прикрутить LLM к проекту, а городить мост на Python в виде отдельного сервиса не хочется — LLPhant закрывает эту задачу одной либой. Это единый интерфейс к OpenAI, Anthropic, Mistral, LM Studio и локальным моделям через Ollama.
// Сегодня OpenAI
$chat = new OpenAIChat();
$response = $chat->generateText('Столица Франции?');
// Завтра — локальная модель
$config = new OllamaConfig();
$config->model = 'llama2';
$chat = new OllamaChat($config);
Что внутри:
— полноценный RAG-пайплайн: чтение PDF/Word, чанкинг, эмбеддинги, поиск;
— векторные хранилища на любой вкус — pgvector, Redis, Elastic, Qdrant, Milvus, Pinecone;
— стриминг ответов, подсчет токенов, vision для картинок;
— function calling — описываешь тулзы PHP-классами, модель сама решит когда дернуть.
Для смены провайдера нужно переписать две строки в конфиге (а не половину сервиса).
Библиотека пхпшника
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤2🔥1👏1
🔥 Знакомьтесь с экспертом Proglib.academy: Эмиль Сатаев
Эмиль — эксперт с 8-летним опытом в разработке, который специализируется на внедрении LLM и агентных подходов в реальные коммерческие сервисы. Он точно знает, как проектировать архитектуру так, чтобы ИИ-функции работали стабильно в связке с внешними системами.
🏃♀️ Уже 14 мая Эмиль проведет открытый вебинар!
Обсудим самую «больную» тему: «Почему AI-продукты на базе LLM ломаются и как сделать, чтобы работало».
🗓 Когда: 14 мая в 19:00 (Мск)
Почему Эмиля стоит послушать:
🟣 8+ лет в разработке (Backend и Frontend)
🟣 Международный исследовательский опыт
🟣 Преподаватель-практик
🟣 Мастер интеграции AI в Backend
🔗 Зарегистрироваться на вебинар
Эмиль — эксперт с 8-летним опытом в разработке, который специализируется на внедрении LLM и агентных подходов в реальные коммерческие сервисы. Он точно знает, как проектировать архитектуру так, чтобы ИИ-функции работали стабильно в связке с внешними системами.
Обсудим самую «больную» тему: «Почему AI-продукты на базе LLM ломаются и как сделать, чтобы работало».
🗓 Когда: 14 мая в 19:00 (Мск)
Почему Эмиля стоит послушать:
Прошел путь от фулстека до Backend Platform Developer в SMIT.Studio.
Работал исследователем в Институте ИИ НИУ ВШЭ и в Национальном университете Сингапура (NUS).
Ведет семинары в НИУ ВШЭ, в том числе по проектированию и разработке агентских систем.
Его главная суперсила — умение правильно встраивать LLM через API, выстраивать workflow и агентную логику в сложных распределенных системах.
🔗 Зарегистрироваться на вебинар
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱3
Разберём эффективный способ бэкапа PostgreSQL с помощью pg_basebackup + реплики.
Сценарий: есть продовый PostgreSQL и настроенная горячая реплика (streaming replication). Зачем использовать реплику для бэкапа?
▪️ Причины
— На проде бэкап может замедлить отклик приложения.
— Реплика отличный способ разгрузить основной сервер.
— Бэкап с pg_basebackup возможен только на стопнутой БД или через репликацию.
▪️ Как сделать
pg_basebackup -h replica.host -U repl_user -D /backup/pg -F tar -z -P
▪️ Флаги
-h — адрес реплики
-U — пользователь с правами репликации
-D — куда класть бэкап
-F tar -z — формат архива и сжатие
-P — прогресс в консоли
А ещё можно добавить в cron и получить стабильный ночной бэкап.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2🔥2
Метод whenQueryingForLongerThan позволяет задать порог в миллисекундах и среагировать моментально: кинуть алерт в Slack, залогировать или просто позвать автора запроса на кофе.
// AppServiceProvider::boot()
DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
Log::warning('Slow query detected', [
'sql' => $event->sql,
'time' => $event->time,
]);
// Notification::send(...);
});
⏱️ Порог считается для суммарного времени всех запросов на соединении, а не для одиночного (для точечного мониторинга есть DB::listen()).
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2🥱2❤1
Forwarded from Библиотека задач по PHP | тесты, код, задания
Письмо ушло, но воркер упал до того как пометил job выполненной. Job запустилась повторно, что получит пользователь?
Anonymous Quiz
14%
Одно письмо. Laravel отслеживает отправленные письма
69%
Два одинаковых письма. job не идемпотентна
9%
Ничего. При падении воркера job удаляется
9%
Job упадёт с исключением. Повторный find() вернёт устаревшую модель
🤔3❤1👍1🔥1
Laravel автоматически обновляет updated_at в ManyToMany отношениях, а также поставляется с методом setTouchedRelations для ручного обновления связанных моделей в отношениях OneToOne и OneToMany.
#vardump
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
Forwarded from Библиотека собеса по PHP | вопросы с собеседований
Пользователь нажал «Повторить заказ». Цены в его старом заказе изменились.
📦 Задание
Фича: кнопка «Повторить заказ» в личном кабинете. Копирует предыдущий заказ, применяет промокод, создаёт новый черновик. Оригинал помечается флагом repeated для аналитики.
Через неделю — тикет от бухгалтерии: суммы в старых заказах не сходятся с тем, что было при оплате. Проблема только у заказов, которые хотя бы раз «повторяли». Суммы занижены ровно на размер скидки по промокоду.
// src/Order/Order.php
class Order
{
public function __construct(
private ?int $id,
private int $userId,
private array $items,
private string $status,
private bool $repeated = false,
) {}
public function getId(): ?int { return $this->id; }
public function getItems(): array { return $this->items; }
public function getStatus(): string { return $this->status; }
public function resetForRepeat(): void
{
$this->id = null;
$this->status = 'draft';
$this->repeated = false;
}
public function markAsRepeated(): void
{
$this->repeated = true;
}
public function calculateTotal(): int
{
return array_sum(array_map(
fn(OrderItem $i) => $i->getSubtotal(),
$this->items
));
}
}
// src/Order/OrderItem.php
class OrderItem
{
public function __construct(
private int $productId,
private int $qty,
private int $price,
) {}
public function getProductId(): int { return $this->productId; }
public function getQty(): int { return $this->qty; }
public function getPrice(): int { return $this->price; }
public function getSubtotal(): int
{
return $this->qty * $this->price;
}
public function applyDiscount(int $percent): void
{
$this->price = (int) round(
$this->price * (1 - $percent / 100)
);
}
}
// src/Order/RepeatOrderHandler.php
class RepeatOrderHandler
{
public function __construct(
private OrderRepository $orders,
private PromoService $promo,
) {}
public function handle(int $originalId, ?string $promoCode): Order
{
$original = $this->orders->findById($originalId);
$copy = clone $original;
$copy->resetForRepeat();
if ($promoCode !== null) {
$discount = $this->promo->resolve($promoCode);
foreach ($copy->getItems() as $item) {
$item->applyDiscount($discount->percent);
}
}
$this->orders->save($copy);
$original->markAsRepeated();
$this->orders->save($original);
return $copy;
}
}
// src/Repository/OrderRepository.php
class OrderRepository
{
public function __construct(private PDO $pdo) {}
public function save(Order $order): void
{
if ($order->getId() === null) {
$this->insert($order);
} else {
$this->update($order);
}
}
private function update(Order $order): void
{
$this->pdo->prepare(
'UPDATE orders SET status = ?, repeated = ? WHERE id = ?'
)->execute([$order->getStatus(), (int) $order->isRepeated(), $order->getId()]);
$this->pdo->prepare('DELETE FROM order_items WHERE order_id = ?')
->execute([$order->getId()]);
foreach ($order->getItems() as $item) {
$this->pdo->prepare(
'INSERT INTO order_items (order_id, product_id, qty, price) VALUES (?, ?, ?, ?)'
)->execute([$order->getId(), $item->getProductId(), $item->getQty(), $item->getPrice()]);
}
}
}
🔹 Задачи
— Объяснить, каким образом цены в оригинальном заказе оказались изменены в базе
— Исправить код так, чтобы оригинал гарантированно не мутировал
Ставьте → 🔥 если нравится формат. Если нет → 🌚
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍2👏1
🔧 Работаем с kubectl
Под завис в Pending, а вы не знаете почему. Нет ресурсов, не тот nodeSelector или PVC не биндится? kubectl describe pod покажет секцию Events — там Kubernetes прямым текстом пишет причину.
🔹 Зачем это нужно
— Events содержат сообщения от scheduler, kubelet и controller manager.
— Показывает FailedScheduling с причиной: Insufficient cpu, node affinity mismatch, и т.д.
— Видны ошибки pull-а образов, mount томов, readiness/liveness probe failures.
🔹 Как использовать
— Полное описание пода:
— Смотреть только Events (хак):
— События всего неймспейса:
— Только warning-и:
— Следить за новыми:
💡 Events живут только 1 час по умолчанию. Если под висит в Pending давно и Events пустые, посмотрите
Под завис в Pending, а вы не знаете почему. Нет ресурсов, не тот nodeSelector или PVC не биндится? kubectl describe pod покажет секцию Events — там Kubernetes прямым текстом пишет причину.
🔹 Зачем это нужно
— Events содержат сообщения от scheduler, kubelet и controller manager.
— Показывает FailedScheduling с причиной: Insufficient cpu, node affinity mismatch, и т.д.
— Видны ошибки pull-а образов, mount томов, readiness/liveness probe failures.
🔹 Как использовать
— Полное описание пода:
kubectl describe pod my-pod— Смотреть только Events (хак):
kubectl describe pod my-pod | grep -A 20 "Events:"— События всего неймспейса:
kubectl get events --sort-by='.lastTimestamp'— Только warning-и:
kubectl get events --field-selector type=Warning— Следить за новыми:
kubectl get events -w💡 Events живут только 1 час по умолчанию. Если под висит в Pending давно и Events пустые, посмотрите
kubectl get events -A --sort-by='.lastTimestamp', возможно, событие уже из другого неймспейса.👍2🔥1😁1
😎 Знакомьтесь с экспертом Proglib.academy: AI-архитектор Андрей Носов
Андрей — один из ключевых спикеров нашего курса AgentOps. Он выстраивает архитектуру, которая выживает в суровом проде и активно делится своим опытом.
За что его ценит IT-комьюнити:
🟣 Топ-спикер AI Conf 2026
🟣 Эксперт по GraphRAG и Knowledge Graphs
🟣 Автор «14 кругов ада для RAG»
🟣 Спикер Saint HighLoad
Андрей упаковал свои наработки в Google Colab, где можно пощупать 14 сценариев ошибок RAG и их решения:
🔗 Забрать Colab-ноутбук
На курсе Андрей отвечает за самые «мясные» блоки: RAG, оркестрацию агентов и их промышленную эксплуатацию.
Узнать больше о программе и обучении у Андрея:
👉 Курс о том, как внедрять AI-логику в бэкенд и сохранять стабильность сервиса
Так, продолжаем знакомить вас с командой?
👍 — Да, ждем новых лиц
🔥 — Пойду тестить Colab Носова
Андрей — один из ключевых спикеров нашего курса AgentOps. Он выстраивает архитектуру, которая выживает в суровом проде и активно делится своим опытом.
За что его ценит IT-комьюнити:
Его доклад про мифы семантического поиска и провалы Naive RAG стал одним из самых рейтинговых на конференции.
Андрей внедряет инженерный подход в сложные системы, заменяя «слепую веру» в эмбеддинги строгой логикой графов.
Разработал уникальный набор из 14 unit-тестов, на которых ломается стандартный векторный поиск (от слепоты к отрицаниям до конфликта версий).
Регулярно выступает на крупнейших хайлоад-площадках, разбирая архитектуру отказоустойчивых ИИ-сервисов.
Андрей упаковал свои наработки в Google Colab, где можно пощупать 14 сценариев ошибок RAG и их решения:
🔗 Забрать Colab-ноутбук
На курсе Андрей отвечает за самые «мясные» блоки: RAG, оркестрацию агентов и их промышленную эксплуатацию.
Узнать больше о программе и обучении у Андрея:
👉 Курс о том, как внедрять AI-логику в бэкенд и сохранять стабильность сервиса
Так, продолжаем знакомить вас с командой?
👍 — Да, ждем новых лиц
🔥 — Пойду тестить Colab Носова
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🔥 Еще с Laravel 8 появился Prunable
С его помощью вы можете удалять старые модели по расписанию. Вам больше не нужно писать пользовательские команды.
Библиотека пхпшника
#vardump
С его помощью вы можете удалять старые модели по расписанию. Вам больше не нужно писать пользовательские команды.
Библиотека пхпшника
#vardump
👍6🔥5🥱3
🚀 Laravel 13.6.0: дебаунс для очередей из коробки
Частое сохранение документа плодит в очереди дублирующиеся задачи переиндексации. Раньше спасал интерфейс ShouldBeUnique, который блокирует диспатчи на входе. Теперь добавлен полноценный debounce — выполняется только последняя задача.
Достаточно повесить атрибут на класс, и все дубли в заданном временном окне схлопываются на этапе выполнения:
Запустить дебаунс можно и без правки класса: dispatch(new SyncData($id))->debounceFor(30).
Другие обновления релиза:
— роут /up отдает JSON по заголовку Accept (полезно для балансировщиков);
— добавлен JsonFormatter для структурных логов;
— появился транспорт Cloudflare Email.
🔗 Читать подробнее
Библиотека пхпшника
#release_radar
Частое сохранение документа плодит в очереди дублирующиеся задачи переиндексации. Раньше спасал интерфейс ShouldBeUnique, который блокирует диспатчи на входе. Теперь добавлен полноценный debounce — выполняется только последняя задача.
Достаточно повесить атрибут на класс, и все дубли в заданном временном окне схлопываются на этапе выполнения:
#[DebounceFor(30, maxWait: 120)]
class RebuildSearchIndex implements ShouldQueue
{
public function __construct(public int $documentId) {}
public function debounceId(): string
{
return (string) $this->documentId;
}
public function handle(): void
{
SearchIndex::rebuild($this->documentId);
}
}
Запустить дебаунс можно и без правки класса: dispatch(new SyncData($id))->debounceFor(30).
Другие обновления релиза:
— роут /up отдает JSON по заголовку Accept (полезно для балансировщиков);
— добавлен JsonFormatter для структурных логов;
— появился транспорт Cloudflare Email.
Библиотека пхпшника
#release_radar
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤2🔥1👏1