✨ 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 для создания и получения встреч
This commit is contained in:
341
frontend-starter-prompt.md
Normal file
341
frontend-starter-prompt.md
Normal file
@@ -0,0 +1,341 @@
|
||||
# Стартовый промт — 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`).
|
||||
Reference in New Issue
Block a user