Общие правила

Загрузка мема

POST /api/upload

Загрузка изображения (multipart/form-data). Мем создаётся как неопубликованный до модерации.

curl -X POST https://смешнявки.рф/api/upload \
  -F "file=@meme.png" \
  -F "title=Смешной мем" \
  -F "description=Описание" \
  -F "tags=кот,смешное"

Ответ 201:

{
  "success": true,
  "meme_id": 123,
  "filename": "abc123.avif",
  "warning": "Похожий мем уже есть (#456)"  // optional
}

Лимит: 10 загрузок/мин на IP. Файл конвертируется в AVIF, EXIF удаляется.

Лайки

POST /api/like/<meme_id>

Инкремент счётчика лайков (не toggle).

// 200
{"liked": true, "count": 43}

// 429 — rate limit (3 лайка в 30 с)
// 404 — мем не найден
GET /api/like-status/<meme_id>
// 200
{"liked": false, "count": 43}

IP не хранится — «лайкнуто» вернуть невозможно, только счётчик.

Реакции (эмодзи)

POST /api/reaction/<meme_id>

Тело: {"reaction": "🗿"}. Допустимые: 🗿 🔥 🥺 👏 💔 🤡 💀

// 200
{"reaction": "🗿", "count": 5, "counts": {"🗿": 5, "🔥": 0, ...}}

// 400 — неизвестный эмодзи
// 429 — 3 реакции в 30 с
// 404 — мем не найден/неопубликован
GET /api/reaction-status/<meme_id>
// 200
{"counts": {"🗿": 5, "🔥": 0, ...}}

Комментарии

POST /api/comment/<meme_id>

Content-Type: application/json. Тело: {"nickname": "Аноним", "text": "Отличный мем!"}

// 201
{"success": true}

// 429 — 1 комментарий/мин или дубликат за 5 мин
// 400 — пустой текст, >2000 символов, невалидный parent
// 404 — мем не найден
GET /api/comments/<meme_id>?cursor=<id>&limit=<n>
// 200
{"comments": [{"id", "nickname", "text", "created_at", "replies": [...]}],
 "next_cursor": 123}

Только approved-комментарии с ветками; limit ≤ 50. Никнейм санитизируется, HTML из текста вырезается.

Прочее

МетодПутьОписание
GET/api/random-memeСлучайный мем
GET/api/meme-of-the-dayМем дня (детерминированный по дате, кеш 24 ч)
GET/api/meme-id?filename=abc.avifID мема по имени файла
GET/api/all-tagsВсе теги с количеством

Админские (требуют авторизацию + CSRF)

МетодПутьОписание
POST/api/comment/<id>/approveОдобрить комментарий
POST/api/comment/<id>/rejectОтклонить комментарий
DELETE/api/comment/<id>Удалить комментарий

Публичные страницы (HTML)

РоутОписание
GET /Главная: галерея + мем дня; ?sort=new|likes|views|random, ?cursor=
GET /meme/<id>Страница мема (og-image, canonical, JSON-LD, похожие, комментарии)
GET /search?q=Поиск (FTS5, fallback ILIKE)
GET /tagsОблако тегов с количеством
GET /tag/<name>Мемы по тегу
GET /uploadsФорма загрузки
GET /favorites?ids=Избранное (id через запятую, макс. 100)
GET /randomРедирект на случайный мем
GET /statsПубличная статистика
GET /feed.xmlRSS 2.0 (30 последних)
GET /sitemap.xmlSitemap (кеш 1 ч)
GET /robots.txtRobots
GET /privacyПолитика конфиденциальности

HTMX-фрагменты

Запросы с заголовком HX-Request: true возвращают HTML-фрагменты (partials/meme_items.html) вместо полных страниц. Используется для infinite scroll. Библиотека HTMX не подключена — клиентский JS отправляет заголовок через fetch.