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

342 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Стартовый промт — 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
```
### Формат ответа
**Все** успешные ответы обёрнуты:
```json
{ "data": <payload> }
```
Ошибки:
```json
{ "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**
```typescript
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` → может подтвердить / отменить / перенести.
---
## Хранение состояния авторизации
```typescript
// stores/auth.store.ts (Pinia)
// accessToken + refreshToken хранить в localStorage
// При 401 — автоматически вызывать POST /auth/refresh
// При неудаче refresh — очистить стор, редирект на /login
```
Интерцептор axios:
```typescript
// При 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`. Сгенерируй типизированный клиент:
```bash
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 не нужно)
---
## Старт проекта
```bash
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`).