От схемы базы до пикселя
Full-stack разработчик. Бэкенд на Python и FastAPI, фронтенд без лишних слоёв.








Что я построил

AniWorld
Библиотека аниме и манги с личными коллекциями, оценками и профилями.
Метаданные из Shikimori, видео из Kodik, манга сразу из трёх читалок за общим контрактом провайдера. Серверный рендеринг вместо SPA, потому что трафик такому сайту даёт поиск. 900 тестов, 43 миграции, прод на VPS в ЕС.
- FastAPI
- SQLAlchemy 2.0
- MySQL
- Alembic
- Jinja2
- nginx

Голубика Давидика
Лендинг фермерского хозяйства: заявка уходит владельцу в Telegram за секунду.
Ноль фреймворков на фронте, минифицированные css и js, разметка под поиск: sitemap, robots, манифест. Форма заказа бьёт в Express, тот отдаёт заявку боту. Развёрнуто на Vercel.
- Express
- Vanilla JS
- Telegram Bot API
- Vercel

Breaking Bad
Лонгрид по сериалу: персонажи, сезоны, галерея, локации, две языковые версии.
Здесь меня интересовала только подача. Палитра собрана из кадров, заголовки на Bebas Neue, вёрстка держит длинный текст и не разваливается на середине. Переключатель EN и RU работает без перезагрузки.
- HTML
- Tailwind
- ES Modules

DotaCounters
Портативная утилита для Dota 2: против кого герой играет слабо, а против кого сильно.
Тянет свежую статистику матчапов с Dotabuff и рисует её в одном окне с иконками героев. Патч определяется сам при запуске. Один exe, без установки и без браузера.
- Python
- Tkinter
- PyInstaller
Как я это делаю
Сначала контракт
Источники прячутся за интерфейсом
У манги в AniWorld три поставщика с разными API и разной пагинацией. Слой синхронизации не знает ни одного из них: он видит только нормализованный ответ. Добавить четвёртый значит написать один класс, а не править половину проекта.
class MangaProvider(ABC): name: str # ключ в конфиге: "mangadex" can_read: bool = True # можем ли забрать страницы глав page_hosts: tuple[str, ...] = () @abstractmethod async def fetch_catalog(self, client, *, limit, offset, language) -> list[MangaRaw]: ...
Рендер по задаче
SPA не всегда правильный ответ
AniWorld живёт поиском, поэтому страницы тайтлов отдаёт сервер на Jinja2, а динамика идёт отдельными запросами к тому же FastAPI. Индексируемо из коробки, быстрый первый экран, никакого сборщика.
@router.get("/", response_class=HTMLResponse)async def home_page(request: Request, db: AsyncSession = Depends(get_db)): featured = await pick_featured(db) return templates.TemplateResponse( "home.html", {"request": request, "featured": featured}, )
Версии под контролем
Зависимости закреплены по разбору CVE
В requirements рядом с версиями лежат причины: какой разбор формы читается до проверки входа, какая дыра закрыта в каком релизе. Обновление пакета это решение, а не автоматика.
# Разбор формы идёт до проверки входа: так устроен FastAPI,# тело читается раньше зависимостей маршрута.python-multipart==0.0.32Pillow==12.3.0 argon2-cffi==25.1.0PyJWT==2.13.0
Коротко о себе
Залеский Кирилл
Началось со школы: мне было интересно всё, что связано с компьютерами, и со временем это сузилось до серверной логики. Люблю продумать, как ходят данные, а потом открыть сайт и сразу увидеть результат. Python выбрал за простой синтаксис и за то, что почти под любую задачу уже есть библиотека.
AniWorld делаю для себя. Самым тяжёлым оказалась не архитектура, а отладка: найти, почему источник отдаёт не то, починить и не бросить проект на этом месте. По-моему, это и есть главный навык, который у меня появился.
Сейчас учусь и хочу практики: одинаково рад заказу, стажировке и работе в команде.
NEVERMORE это старое имя Shadow Fiend из Dota 2, одного из моих любимых персонажей.
Чем работаю
Напишите мне
Открыт к работе и к заказам. Отвечаю в течение дня.