Files
dating-app-backend/frontend-starter-prompt.md
Oscar 102b6b4026 feat(src/modules/cities/cities.controller.ts): добавляет ответы API для получения всех городов и районов
 feat(src/modules/chat/chat.controller.ts): добавляет ответы API для создания чата и получения сообщений

 feat(src/modules/greetings/greetings.controller.ts): добавляет ответы API для получения и создания приветствий

 feat(src/modules/likes/likes.controller.ts): добавляет ответы API для создания лайков и получения совпадений

 feat(src/modules/reports/reports.controller.ts): добавляет ответы API для создания и получения отчетов

 feat(src/modules/feed/feed.controller.ts): добавляет ответ API для получения отфильтрованного фида

 feat(src/auth/auth.controller.ts): добавляет ответы API для регистрации, входа и выхода пользователей

 feat(src/modules/media/media.controller.ts): добавляет ответы API для загрузки и получения медиа

 feat(src/modules/users/users.controller.ts): добавляет ответы API для получения текущего пользователя и управления пользователями

 feat(src/modules/tags/tags.controller.ts): добавляет ответы API для получения и создания тегов

 feat(src/modules/profiles/profiles.controller.ts): добавляет ответы API для управления профилями пользователей

 feat(src/modules/dates/dates.controller.ts): добавляет ответы API для создания и получения встреч
2026-06-08 14:22:50 +03:00

15 KiB
Raw Permalink Blame History

Стартовый промт — Frontend Dating App

Контекст проекта

Ты помогаешь разрабатывать фронтенд мобильного/десктопного дейтинг-приложения. OpenAPI-схема бэкенда лежит в корне проекта (openapi.json). Генерируй типизированный HTTP-клиент из неё автоматически.


Технологический стек

Слой Выбор
UI-фреймворк Vue 3 (Composition API + <script setup>)
Язык TypeScript везде
Сборщик Vite
Десктоп / нативный слой Tauri 2
Стейт-менеджер Pinia
Роутер Vue Router 4
HTTP-клиент axios + автогенерированные типы из OpenAPI (openapi-typescript + axios)
WebSocket socket.io-client
UI-библиотека Naive UI (или UnoCSS + headless — на твоё усмотрение)
Формы VeeValidate + Zod
Пакетный менеджер pnpm
Анимации @vueuse/motion
Утилиты VueUse
Иконки @iconify/vue
Адаптивность mobile-first; десктоп — расширение, не отдельная версия

Что такое это приложение

Дейтинг-приложение. Пользователь:

  1. Регистрируется по номеру телефона + пароль
  2. Создаёт один или несколько публичных профилей (у одного аккаунта может быть много профилей)
  3. Листает ленту других профилей с фильтрацией
  4. Ставит лайки / дизлайки на профили
  5. При взаимном лайке создаётся матч
  6. Открывает чат с одним матчем за раз (один активный чат на профиль)
  7. В чате обменивается текстом, фото, голосовыми и видео-сообщениями
  8. Договаривается о реальных встречах (дата, координаты, статус)
  9. Может подать жалобу на профиль или сообщение

Архитектура бэкенда (важно для интеграции)

Базовый URL

http://localhost:3000/api/v1

Формат ответа

Все успешные ответы обёрнуты:

{ "data": <payload> }

Ошибки:

{ "statusCode": 400, "message": "...", "timestamp": "...", "path": "..." }

Авторизация

  • Тип: Bearer JWT
  • Заголовок: Authorization: Bearer <accessToken>
  • POST /auth/register — регистрация { phone, password }
  • POST /auth/login — вход { phone, password }{ accessToken, refreshToken }
  • POST /auth/refresh — обновление { refreshToken }{ accessToken, refreshToken }
  • POST /auth/logout — выход (требует Bearer)
  • POST /auth/fcm-token — обновить push-токен { fcmToken }
  • Access-токен: TTL 7 дней (настраивается на бэке)
  • Refresh-токен: TTL 30 дней, хранится в Redis

Профили (один аккаунт → много профилей)

  • POST /profiles — создать профиль
  • GET /profiles/my — все мои профили
  • GET /profiles/:profileId — публичный профиль
  • PUT /profiles/:profileId — обновить (только владелец)
  • DELETE /profiles/:profileId — удалить (только владелец)

Поля профиля: name, birthDate, gender (male|female), cityId, districtId, description, nation, height, weight, tagIds[]

Ответ профиля включает: city, district, tags[], media[] (отсортировано по sortOrder)

Медиа профиля

Маршруты: POST /profiles/:profileId/media/upload?type=photo|video|audio

Медиафайлы профиля: фото, видео, аудио — хранятся в MinIO. В ответе профиля приходит массив media[] с полями { id, path, type, sortOrder }.

Лента

GET /feed?profileId=<uuid>&page=1&limit=20
         &cityId=&districtId=
         &ageMin=&ageMax=
         &keyword=
         &tagIds[]=
  • Уже лайкнутые профили не показываются
  • Порядок случайный
  • При достижении лимита матчей (MAX_MATCHES_BEFORE_PAUSE=10) лайк вернёт 400 — клиент должен показать экран «разбери матчи»

Лайки и матчи

  • POST /likes{ sourceProfileId, targetProfileId, type: "like"|"dislike" } Ответ: { like, match } — если match != null, это взаимный лайк
  • GET /likes/matches?profileId=<uuid> — все матчи профиля

Чат

  • Один профиль — один активный чат одновременно (profile.active_chat_id)
  • POST /chats{ profileId, matchId } → открыть чат для матча
  • GET /chats?profileId=<uuid> — активные чаты
  • GET /chats/:chatId/messages?profileId=<uuid>&page=1&limit=50
  • POST /chats/:chatId/messages?profileId=<uuid> — отправить { text?, mediaUrl?, mediaType? }
  • DELETE /chats/:chatId?profileId=<uuid> — закрыть чат

Встречи (dates)

  • POST /dates{ profileId, partnerProfileId, lat, lng, time, statusId? }
  • GET /dates?profileId=<uuid>
  • PATCH /dates/:id/status?profileId=<uuid>{ statusId }
  • GET /dates/statuses — справочник статусов (pending, confirmed, cancelled, rescheduled)

Репорты

  • POST /reports{ sourceProfileId, entityId, entityType: "profile"|"message", description? }

Справочники (публичные, без токена)

  • GET /tags
  • GET /cities
  • GET /cities/:cityId/districts
  • GET /greetings — готовые приветственные фразы

WebSocket (real-time чат)

Библиотека: socket.io-client

const socket = io('http://localhost:3000/chat', {
  auth: {
    token: '<accessToken>',
    profileId: '<activeProfileId>',   // обязательно!
  }
})

Клиент отправляет:

Событие Payload Описание
join_chat { chatId } Войти в комнату чата
leave_chat { chatId } Покинуть комнату
send_message { chatId, text?, mediaUrl?, mediaType? } Отправить сообщение
typing { chatId, isTyping: bool } Индикатор печати

Сервер отправляет:

Событие Payload Описание
new_message Message Новое сообщение в комнате
user_typing { profileId, isTyping } Кто-то печатает

Важно: при смене активного профиля переподключи сокет с новым profileId.


Структура проекта

src/
  api/                   # Автогенерированный клиент из openapi.json
    client.ts            # axios-инстанс с интерцептором токена и рефреша
    index.ts             # реэкспорт всех операций
  stores/
    auth.store.ts        # user, accessToken, refreshToken, login/logout
    profile.store.ts     # activeProfile, myProfiles, switch
    feed.store.ts        # лента, пагинация
    chat.store.ts        # активный чат, сообщения, WS-соединение
    match.store.ts       # матчи
  composables/
    useSocket.ts         # хук socket.io с авто-реконнектом
    useInfiniteScroll.ts # бесконечная прокрутка
    useMediaUpload.ts    # загрузка файлов с прогрессом
  router/
    index.ts
    guards.ts            # редирект на /login если нет токена, /setup-profile если нет профиля
  views/
    auth/
      LoginView.vue
      RegisterView.vue
    onboarding/
      CreateProfileView.vue   # создание первого профиля после регистрации
    feed/
      FeedView.vue            # свайп-лента или карточки
    matches/
      MatchesView.vue
    chat/
      ChatsListView.vue
      ChatView.vue            # реалтайм переписка
    dates/
      DatesView.vue
    profile/
      MyProfilesView.vue      # список профилей аккаунта
      ProfileEditView.vue
      ProfilePublicView.vue   # как видят другие
    settings/
      SettingsView.vue
  components/
    profile/
      ProfileCard.vue         # карточка в ленте
      ProfileAvatar.vue
      MediaGallery.vue        # фото/видео/аудио галерея профиля
      MediaUploader.vue       # загрузка с preview
    chat/
      MessageBubble.vue
      TypingIndicator.vue
      AudioPlayer.vue         # проигрыватель голосовых
    feed/
      SwipeCard.vue           # свайп-карточка (мобильная)
      LikeButtons.vue
    common/
      BottomNav.vue           # навигация для мобилки
      SideNav.vue             # навигация для десктопа
      AppLayout.vue           # адаптивный layout
  tauri/                      # Tauri-специфичный код
    notifications.ts          # нативные уведомления
    deepLinks.ts

Адаптивность: мобилка и десктоп в одном приложении

Приложение собирается через Tauri 2. Один кодовой базой покрывается:

  • Мобилка (Tauri + iOS/Android через Tauri Mobile) — свайп-жесты, нижняя навигация, полноэкранные карточки
  • Десктоп (Tauri + Windows/macOS/Linux) — боковое меню, двухколоночный layout, drag-and-drop для медиа

Используй CSS-брейкпоинты mobile-first:

  • < 768px — мобильный layout (BottomNav, полноэкранная лента)
  • ≥ 768px — планшет/десктоп (SideNav, split-view для чата)

Определяй режим через window.innerWidth или VueUse useBreakpoints. Tauri-специфичное поведение (нативные уведомления, файловый пикер) оборачивай в composable с graceful degradation до браузерного API.


Ключевые UX-сценарии

1. Первый запуск

РегистрацияСоздание профиля (name, birthDate, gender обязательны) → Загрузка фотоЛента

2. Лента и лайк

Карточка профиля → свайп вправо (лайк) / влево (дизлайк) → если матч → показать MatchModal с кнопкой «Написать» → открыть чат

3. Лимит матчей

Попытка лайкнуть при N >= 10 матчей → API вернёт 400 → показать экран/модал «У тебя X матчей. Разберись с ними, чтобы продолжить поиск»

4. Чат

Список чатов → выбор → join WS-комнаты → обмен сообщениями в реалтайме → закрытие чата = новый чат стал доступен. Перед открытием чата — можно выбрать приветственную фразу из GET /greetings.

5. Смена активного профиля

В настройках — список всех профилей аккаунта → при смене переподключить WS с новым profileId.

6. Встреча

В активном чате кнопка «Назначить встречу» → форма (карта/координаты + дата) → POST /dates → партнёр видит pending → может подтвердить / отменить / перенести.


Хранение состояния авторизации

// stores/auth.store.ts (Pinia)
// accessToken + refreshToken хранить в localStorage
// При 401 — автоматически вызывать POST /auth/refresh
// При неудаче refresh — очистить стор, редирект на /login

Интерцептор axios:

// При 401: один раз попытаться рефрешнуть токен,
// повторить исходный запрос с новым токеном,
// если рефреш тоже 401 — logout

Нотификации

  • В браузере / PWA: Web Notifications API
  • В Tauri: @tauri-apps/plugin-notification
  • FCM-токен: после логина отправить на POST /auth/fcm-token
  • Обрабатывать WS-события new_message и match_created для in-app уведомлений

Генерация API-клиента

В корне проекта лежит openapi.json. Сгенерируй типизированный клиент:

pnpm add -D openapi-typescript
pnpm exec openapi-typescript openapi.json -o src/api/schema.d.ts

Используй типы из схемы для всех запросов/ответов. Создай обёртку над axios, которая автоматически разворачивает { data: T }.


Что НЕ нужно реализовывать сейчас

  • Логика оплаты (таблицы tariff/payment есть в БД, но API оплаты нет)
  • Админ-панель (модераторские эндпоинты — ban/activate пользователей — есть в API, но отдельного UI не нужно)

Старт проекта

pnpm create tauri-app@latest
# выбрать: Vue + TypeScript + Vite

pnpm add pinia vue-router axios socket.io-client vee-validate zod @vueuse/core @vueuse/motion naive-ui @iconify/vue
pnpm add -D openapi-typescript @types/node

# Сгенерировать типы из схемы
pnpm exec openapi-typescript openapi.json -o src/api/schema.d.ts

Не спрашивай подтверждения на каждый шаг — работай до тех пор, пока весь фронтенд не будет готов к первому запуску (pnpm tauri dev).