🔧 refactor(api): исправляет интерфейсы и убирает ненужные приведения типов в API запросах

This commit is contained in:
Oscar
2026-06-08 18:00:44 +03:00
parent 031781a47e
commit ffde85242a
14 changed files with 58 additions and 123 deletions

View File

@@ -15,7 +15,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { City, Greeting, Tag } from '@/composables/useUi'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { apiClient } from '@/api/client' import { apiClient } from '@/api/client'
import AppToast from '@/components/common/AppToast.vue' import AppToast from '@/components/common/AppToast.vue'
@@ -29,9 +28,9 @@ async function loadReferences() {
return return
try { try {
const [tags, cities, greetings] = await Promise.all([ const [tags, cities, greetings] = await Promise.all([
apiClient.api.tagsControllerFindAll() as unknown as Tag[], apiClient.api.tagsControllerFindAll(),
apiClient.api.citiesControllerFindAll() as unknown as City[], apiClient.api.citiesControllerFindAll(),
apiClient.api.greetingsControllerFindAll() as unknown as Greeting[], apiClient.api.greetingsControllerFindAll(),
]) ])
uiStore.setTags(tags) uiStore.setTags(tags)
uiStore.setCities(cities) uiStore.setCities(cities)

View File

@@ -232,7 +232,7 @@ export interface DateStatusDto {
export interface DateWithStatusDto { export interface DateWithStatusDto {
date: DateDto; date: DateDto;
date_status?: DateStatusDto | null; dateStatus?: DateStatusDto | null;
} }
export interface UpdateDateStatusDto { export interface UpdateDateStatusDto {
@@ -833,7 +833,16 @@ export class Api<SecurityDataType extends unknown> {
* @secure * @secure
*/ */
feedControllerGetFeed: (query: FeedControllerGetFeedParams, params: RequestParams = {}) => feedControllerGetFeed: (query: FeedControllerGetFeedParams, params: RequestParams = {}) =>
this.http.request<ProfileResponseDto[], any>({ this.http.request<
{
items: ProfileResponseDto[];
/** @example 1 */
page: number;
/** @example 20 */
limit: number;
},
any
>({
path: `/api/v1/feed`, path: `/api/v1/feed`,
method: "GET", method: "GET",
query: query, query: query,

View File

@@ -46,7 +46,7 @@ axiosInstance.interceptors.response.use(
async (error) => { async (error) => {
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean } const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }
if (error.response?.status === 401 && !originalRequest._retry) { if (error.response?.status === 401 && !originalRequest._retry && !originalRequest.url?.includes('/auth/refresh')) {
if (_isRefreshing) { if (_isRefreshing) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
_failedQueue.push({ resolve, reject }) _failedQueue.push({ resolve, reject })
@@ -68,11 +68,11 @@ axiosInstance.interceptors.response.use(
} }
try { try {
const res = await axios.post<{ data: { accessToken: string, refreshToken: string } }>( const res = await axiosInstance.post<{ accessToken: string, refreshToken: string }>(
`${BASE_URL}/api/v1/auth/refresh`, `${BASE_URL}/api/v1/auth/refresh`,
{ refreshToken }, { refreshToken },
) )
const { accessToken, refreshToken: newRefresh } = res.data.data const { accessToken, refreshToken: newRefresh } = res.data
_setAccessToken(accessToken) _setAccessToken(accessToken)
localStorage.setItem('refreshToken', newRefresh) localStorage.setItem('refreshToken', newRefresh)
_processQueue(null, accessToken) _processQueue(null, accessToken)

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="card-stack"> <div class="card-stack">
{{ feedStore.cards }} <!-- {{ feedStore }} -->
<div v-if="feedStore.loading && feedStore.cards.length === 0" class="card-stack__loading"> <div v-if="feedStore.loading && feedStore.cards.length === 0" class="card-stack__loading">
<LoadingSpinner size="lg" /> <LoadingSpinner size="lg" />
</div> </div>

View File

@@ -106,7 +106,7 @@ watch(() => filters.cityId, async (cityId) => {
return return
} }
try { try {
const res = await apiClient.api.citiesControllerFindDistricts(cityId) as unknown as District[] const res = await apiClient.api.citiesControllerFindDistricts(cityId)
uiStore.setDistricts(cityId, res) uiStore.setDistricts(cityId, res)
districts.value = res districts.value = res
} }

View File

@@ -134,7 +134,7 @@ watch(() => form.cityId, async (cityId) => {
return return
} }
try { try {
const res = await apiClient.api.citiesControllerFindDistricts(cityId) as unknown as District[] const res = await apiClient.api.citiesControllerFindDistricts(cityId)
uiStore.setDistricts(cityId, res) uiStore.setDistricts(cityId, res)
districts.value = res districts.value = res
} }

View File

@@ -1,32 +1,9 @@
import type { LoginDto, MediaItemDto, RegisterDto, TagDto } from '@/api/api' import type { LoginDto, MeResponseDto, ProfileResponseDto, RegisterDto } from '@/api/api'
import { computed, reactive, ref } from 'vue' import { computed, reactive, ref } from 'vue'
import { _clearAuth, _setAccessToken, apiClient } from '@/api/client' import { _clearAuth, _setAccessToken, apiClient } from '@/api/client'
export interface UserProfile { export type UserProfile = ProfileResponseDto
id: string export type AuthUser = Omit<MeResponseDto, 'profiles'> & { profiles: UserProfile[] }
userId: string
name: string
birthDate: string
gender: 'male' | 'female'
cityId?: string | null
districtId?: string | null
description?: string | null
nation?: string | null
height?: number | null
weight?: number | null
activeChatId?: string | null
tags: TagDto[]
media: MediaItemDto[]
}
export interface AuthUser {
id: string
phone: string
status: 'active' | 'banned' | 'pending'
roleId?: string | null
role?: { id: string, name: string } | null
profiles: UserProfile[]
}
const user = ref<AuthUser | null>(null) const user = ref<AuthUser | null>(null)
const activeProfileId = ref<string | null>(null) const activeProfileId = ref<string | null>(null)
@@ -40,20 +17,14 @@ const activeProfile = computed(() =>
const hasProfiles = computed(() => profiles.value.length > 0) const hasProfiles = computed(() => profiles.value.length > 0)
async function login(dto: LoginDto) { async function login(dto: LoginDto) {
const res = await apiClient.api.authControllerLogin(dto) as unknown as { const res = await apiClient.api.authControllerLogin(dto)
accessToken: string
refreshToken: string
}
_setAccessToken(res.accessToken) _setAccessToken(res.accessToken)
localStorage.setItem('refreshToken', res.refreshToken) localStorage.setItem('refreshToken', res.refreshToken)
await fetchMe() await fetchMe()
} }
async function register(dto: RegisterDto) { async function register(dto: RegisterDto) {
const res = await apiClient.api.authControllerRegister(dto) as unknown as { const res = await apiClient.api.authControllerRegister(dto)
accessToken: string
refreshToken: string
}
_setAccessToken(res.accessToken) _setAccessToken(res.accessToken)
localStorage.setItem('refreshToken', res.refreshToken) localStorage.setItem('refreshToken', res.refreshToken)
await fetchMe() await fetchMe()
@@ -76,10 +47,9 @@ async function fetchMe() {
apiClient.api.usersControllerGetMe(), apiClient.api.usersControllerGetMe(),
apiClient.api.profilesControllerGetMyProfiles(), apiClient.api.profilesControllerGetMyProfiles(),
]) ])
const fullProfiles = profilesRes as unknown as UserProfile[] user.value = { ...meRes, profiles: profilesRes }
user.value = { ...meRes, profiles: fullProfiles } as unknown as AuthUser if (profilesRes.length > 0 && !activeProfileId.value) {
if (fullProfiles.length > 0 && !activeProfileId.value) { activeProfileId.value = profilesRes[0].id
activeProfileId.value = fullProfiles[0].id
} }
} }

View File

@@ -1,4 +1,4 @@
import type { SendMessageDto } from '@/api/api' import type { ChatDto, MessageDto, SendMessageDto } from '@/api/api'
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { apiClient } from '@/api/client' import { apiClient } from '@/api/client'
@@ -8,26 +8,14 @@ export interface ChatProfile {
avatarUrl?: string avatarUrl?: string
} }
export interface Chat { export type Chat = ChatDto & {
id: string
profile1Id: string
profile2Id: string
status: 'active' | 'closed'
partner?: ChatProfile partner?: ChatProfile
lastMessage?: ChatMessage lastMessage?: MessageDto
unreadCount?: number unreadCount?: number
createdAt?: string createdAt?: string
} }
export interface ChatMessage { export type ChatMessage = MessageDto
id: string
chatId: string
profileId: string
text?: string
mediaUrl?: string
mediaType?: 'photo' | 'voice' | 'video'
createdAt: string
}
// Polling interval — replace with WebSocket when backend supports it // Polling interval — replace with WebSocket when backend supports it
const POLL_INTERVAL = 2000 const POLL_INTERVAL = 2000
@@ -39,14 +27,14 @@ const pollingTimer = ref<ReturnType<typeof setInterval> | null>(null)
const loading = ref(false) const loading = ref(false)
async function fetchChats(profileId: string) { async function fetchChats(profileId: string) {
const res = await apiClient.api.chatControllerGetChats({ profileId }) as unknown as Chat[] const res = await apiClient.api.chatControllerGetChats({ profileId })
chats.value = res chats.value = res
} }
async function fetchMessages(chatId: string, profileId: string) { async function fetchMessages(chatId: string, profileId: string) {
loading.value = true loading.value = true
try { try {
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId }) as unknown as ChatMessage[] const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId })
messages.value = res messages.value = res
} }
finally { finally {
@@ -55,13 +43,13 @@ async function fetchMessages(chatId: string, profileId: string) {
} }
async function sendMessage(chatId: string, profileId: string, dto: SendMessageDto) { async function sendMessage(chatId: string, profileId: string, dto: SendMessageDto) {
const res = await apiClient.api.chatControllerSendMessage({ chatId, profileId }, dto) as unknown as ChatMessage const res = await apiClient.api.chatControllerSendMessage({ chatId, profileId }, dto)
messages.value.push(res) messages.value.push(res)
return res return res
} }
async function openChat(profileId: string, matchId: string) { async function openChat(profileId: string, matchId: string) {
const res = await apiClient.api.chatControllerCreateChat({ profileId, matchId }) as unknown as Chat const res = await apiClient.api.chatControllerCreateChat({ profileId, matchId })
const existing = chats.value.findIndex(c => c.id === res.id) const existing = chats.value.findIndex(c => c.id === res.id)
if (existing === -1) if (existing === -1)
chats.value.unshift(res) chats.value.unshift(res)
@@ -81,7 +69,7 @@ function startPolling(chatId: string, profileId: string) {
// TODO: replace with WebSocket subscription // TODO: replace with WebSocket subscription
pollingTimer.value = setInterval(async () => { pollingTimer.value = setInterval(async () => {
try { try {
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId }) as unknown as ChatMessage[] const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId })
if (res.length > messages.value.length) { if (res.length > messages.value.length) {
messages.value = res messages.value = res
} }

View File

@@ -1,22 +1,8 @@
import type { FeedControllerGetFeedParams, MediaItemDto, TagDto } from '@/api/api' import type { FeedControllerGetFeedParams, ProfileResponseDto } from '@/api/api'
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { apiClient } from '@/api/client' import { apiClient } from '@/api/client'
export interface FeedProfile { export type FeedProfile = ProfileResponseDto & { age?: number | null, cityName?: string }
id: string
name: string
birthDate: string
gender: 'male' | 'female'
cityId?: string | null
cityName?: string
districtId?: string | null
description?: string | null
nation?: string | null
height?: number | null
weight?: number | null
tags?: TagDto[]
media?: MediaItemDto[]
}
const cards = ref<FeedProfile[]>([]) const cards = ref<FeedProfile[]>([])
const filters = reactive<Partial<FeedControllerGetFeedParams>>({}) const filters = reactive<Partial<FeedControllerGetFeedParams>>({})
@@ -37,13 +23,13 @@ async function fetchNextPage(profileId: string) {
...filters, ...filters,
}) })
console.log(res.data) console.log(res.items)
if (page.value === 1) if (page.value === 1)
cards.value = res.data cards.value = res.items
else cards.value.push(...res.data) else cards.value.push(...res.items)
hasMore.value = res.length >= 20 hasMore.value = res.items.length >= res.limit
page.value++ page.value++
} }
finally { finally {

View File

@@ -9,7 +9,7 @@ const loading = ref(false)
async function fetchProfile(profileId: string) { async function fetchProfile(profileId: string) {
loading.value = true loading.value = true
try { try {
const res = await apiClient.api.profilesControllerFindOne(profileId) as unknown as UserProfile const res = await apiClient.api.profilesControllerFindOne(profileId)
currentProfile.value = res currentProfile.value = res
return res return res
} }
@@ -19,13 +19,13 @@ async function fetchProfile(profileId: string) {
} }
async function createProfile(dto: CreateProfileDto) { async function createProfile(dto: CreateProfileDto) {
const res = await apiClient.api.profilesControllerCreate(dto) as unknown as UserProfile const res = await apiClient.api.profilesControllerCreate(dto)
currentProfile.value = res currentProfile.value = res
return res return res
} }
async function updateProfile(profileId: string, dto: UpdateProfileDto) { async function updateProfile(profileId: string, dto: UpdateProfileDto) {
const res = await apiClient.api.profilesControllerUpdate(profileId, dto) as unknown as UserProfile const res = await apiClient.api.profilesControllerUpdate(profileId, dto)
currentProfile.value = res currentProfile.value = res
return res return res
} }

View File

@@ -1,3 +1,4 @@
import type { CityResponseDto, DistrictResponseDto, GreetingDto, TagResponseDto } from '@/api/api'
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
export type ToastType = 'success' | 'error' | 'info' | 'warning' export type ToastType = 'success' | 'error' | 'info' | 'warning'
@@ -9,26 +10,10 @@ export interface Toast {
duration?: number duration?: number
} }
export interface Tag { export type Tag = TagResponseDto
id: string export type City = CityResponseDto
value: string export type District = DistrictResponseDto
} export type Greeting = GreetingDto
export interface City {
id: string
name: string
}
export interface District {
id: string
name: string
cityId: string
}
export interface Greeting {
id: string
text: string
}
const toasts = ref<Toast[]>([]) const toasts = ref<Toast[]>([])
const sidebarExpanded = ref(false) const sidebarExpanded = ref(false)

View File

@@ -1,6 +1,5 @@
import axios from 'axios'
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import { _getAccessToken, _setAccessToken, BASE_URL } from '@/api/client' import { _getAccessToken, _setAccessToken, axiosInstance, BASE_URL } from '@/api/client'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuth'
export const router = createRouter({ export const router = createRouter({
@@ -51,12 +50,12 @@ router.beforeEach(async (to, _from, next) => {
const storedRefresh = localStorage.getItem('refreshToken') const storedRefresh = localStorage.getItem('refreshToken')
if (storedRefresh && !_getAccessToken()) { if (storedRefresh && !_getAccessToken()) {
try { try {
const res = await axios.post<{ data: { accessToken: string, refreshToken: string } }>( const res = await axiosInstance.post<{ accessToken: string, refreshToken: string }>(
`${BASE_URL}/api/v1/auth/refresh`, `${BASE_URL}/api/v1/auth/refresh`,
{ refreshToken: storedRefresh }, { refreshToken: storedRefresh },
) )
_setAccessToken(res.data.data.accessToken) _setAccessToken(res.data.accessToken)
localStorage.setItem('refreshToken', res.data.data.refreshToken) localStorage.setItem('refreshToken', res.data.refreshToken)
await authStore.fetchMe() await authStore.fetchMe()
} }
catch { catch {

View File

@@ -174,7 +174,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CreateProfileDto } from '@/api/api' import type { CreateProfileDto } from '@/api/api'
import type { UserProfile } from '@/composables/useAuth'
import type { District } from '@/composables/useUi' import type { District } from '@/composables/useUi'
import { useVuelidate } from '@vuelidate/core' import { useVuelidate } from '@vuelidate/core'
import { helpers, required } from '@vuelidate/validators' import { helpers, required } from '@vuelidate/validators'
@@ -224,7 +223,7 @@ watch(() => form.cityId, async (cityId) => {
} }
loadingDistricts.value = true loadingDistricts.value = true
try { try {
const res = await apiClient.api.citiesControllerFindDistricts(cityId) as unknown as District[] const res = await apiClient.api.citiesControllerFindDistricts(cityId)
uiStore.setDistricts(cityId, res) uiStore.setDistricts(cityId, res)
districts.value = res districts.value = res
} }
@@ -270,7 +269,7 @@ async function finish() {
loading.value = true loading.value = true
try { try {
form.tagIds = selectedTags.value form.tagIds = selectedTags.value
const profile = await apiClient.api.profilesControllerCreate(form as unknown as CreateProfileDto) as unknown as UserProfile const profile = await apiClient.api.profilesControllerCreate(form as unknown as CreateProfileDto)
authStore.addProfile(profile) authStore.addProfile(profile)
uiStore.addToast('Профиль создан', 'success') uiStore.addToast('Профиль создан', 'success')
router.replace('/feed') router.replace('/feed')

View File

@@ -140,7 +140,7 @@ const dateOpen = ref(false)
onMounted(async () => { onMounted(async () => {
loading.value = true loading.value = true
try { try {
const res = await apiClient.api.profilesControllerFindOne(profileId) as unknown as UserProfile const res = await apiClient.api.profilesControllerFindOne(profileId)
profile.value = res profile.value = res
} }
catch { catch {