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

View File

@@ -232,7 +232,7 @@ export interface DateStatusDto {
export interface DateWithStatusDto {
date: DateDto;
date_status?: DateStatusDto | null;
dateStatus?: DateStatusDto | null;
}
export interface UpdateDateStatusDto {
@@ -833,7 +833,16 @@ export class Api<SecurityDataType extends unknown> {
* @secure
*/
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`,
method: "GET",
query: query,

View File

@@ -46,7 +46,7 @@ axiosInstance.interceptors.response.use(
async (error) => {
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) {
return new Promise((resolve, reject) => {
_failedQueue.push({ resolve, reject })
@@ -68,11 +68,11 @@ axiosInstance.interceptors.response.use(
}
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`,
{ refreshToken },
)
const { accessToken, refreshToken: newRefresh } = res.data.data
const { accessToken, refreshToken: newRefresh } = res.data
_setAccessToken(accessToken)
localStorage.setItem('refreshToken', newRefresh)
_processQueue(null, accessToken)

View File

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

View File

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

View File

@@ -134,7 +134,7 @@ watch(() => form.cityId, async (cityId) => {
return
}
try {
const res = await apiClient.api.citiesControllerFindDistricts(cityId) as unknown as District[]
const res = await apiClient.api.citiesControllerFindDistricts(cityId)
uiStore.setDistricts(cityId, 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 { _clearAuth, _setAccessToken, apiClient } from '@/api/client'
export interface UserProfile {
id: string
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[]
}
export type UserProfile = ProfileResponseDto
export type AuthUser = Omit<MeResponseDto, 'profiles'> & { profiles: UserProfile[] }
const user = ref<AuthUser | null>(null)
const activeProfileId = ref<string | null>(null)
@@ -40,20 +17,14 @@ const activeProfile = computed(() =>
const hasProfiles = computed(() => profiles.value.length > 0)
async function login(dto: LoginDto) {
const res = await apiClient.api.authControllerLogin(dto) as unknown as {
accessToken: string
refreshToken: string
}
const res = await apiClient.api.authControllerLogin(dto)
_setAccessToken(res.accessToken)
localStorage.setItem('refreshToken', res.refreshToken)
await fetchMe()
}
async function register(dto: RegisterDto) {
const res = await apiClient.api.authControllerRegister(dto) as unknown as {
accessToken: string
refreshToken: string
}
const res = await apiClient.api.authControllerRegister(dto)
_setAccessToken(res.accessToken)
localStorage.setItem('refreshToken', res.refreshToken)
await fetchMe()
@@ -76,10 +47,9 @@ async function fetchMe() {
apiClient.api.usersControllerGetMe(),
apiClient.api.profilesControllerGetMyProfiles(),
])
const fullProfiles = profilesRes as unknown as UserProfile[]
user.value = { ...meRes, profiles: fullProfiles } as unknown as AuthUser
if (fullProfiles.length > 0 && !activeProfileId.value) {
activeProfileId.value = fullProfiles[0].id
user.value = { ...meRes, profiles: profilesRes }
if (profilesRes.length > 0 && !activeProfileId.value) {
activeProfileId.value = profilesRes[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 { apiClient } from '@/api/client'
@@ -8,26 +8,14 @@ export interface ChatProfile {
avatarUrl?: string
}
export interface Chat {
id: string
profile1Id: string
profile2Id: string
status: 'active' | 'closed'
export type Chat = ChatDto & {
partner?: ChatProfile
lastMessage?: ChatMessage
lastMessage?: MessageDto
unreadCount?: number
createdAt?: string
}
export interface ChatMessage {
id: string
chatId: string
profileId: string
text?: string
mediaUrl?: string
mediaType?: 'photo' | 'voice' | 'video'
createdAt: string
}
export type ChatMessage = MessageDto
// Polling interval — replace with WebSocket when backend supports it
const POLL_INTERVAL = 2000
@@ -39,14 +27,14 @@ const pollingTimer = ref<ReturnType<typeof setInterval> | null>(null)
const loading = ref(false)
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
}
async function fetchMessages(chatId: string, profileId: string) {
loading.value = true
try {
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId }) as unknown as ChatMessage[]
const res = await apiClient.api.chatControllerGetMessages({ chatId, profileId })
messages.value = res
}
finally {
@@ -55,13 +43,13 @@ async function fetchMessages(chatId: string, profileId: string) {
}
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)
return res
}
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)
if (existing === -1)
chats.value.unshift(res)
@@ -81,7 +69,7 @@ function startPolling(chatId: string, profileId: string) {
// TODO: replace with WebSocket subscription
pollingTimer.value = setInterval(async () => {
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) {
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 { apiClient } from '@/api/client'
export interface FeedProfile {
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[]
}
export type FeedProfile = ProfileResponseDto & { age?: number | null, cityName?: string }
const cards = ref<FeedProfile[]>([])
const filters = reactive<Partial<FeedControllerGetFeedParams>>({})
@@ -37,13 +23,13 @@ async function fetchNextPage(profileId: string) {
...filters,
})
console.log(res.data)
console.log(res.items)
if (page.value === 1)
cards.value = res.data
else cards.value.push(...res.data)
cards.value = res.items
else cards.value.push(...res.items)
hasMore.value = res.length >= 20
hasMore.value = res.items.length >= res.limit
page.value++
}
finally {

View File

@@ -9,7 +9,7 @@ const loading = ref(false)
async function fetchProfile(profileId: string) {
loading.value = true
try {
const res = await apiClient.api.profilesControllerFindOne(profileId) as unknown as UserProfile
const res = await apiClient.api.profilesControllerFindOne(profileId)
currentProfile.value = res
return res
}
@@ -19,13 +19,13 @@ async function fetchProfile(profileId: string) {
}
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
return res
}
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
return res
}

View File

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

View File

@@ -1,6 +1,5 @@
import axios from 'axios'
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'
export const router = createRouter({
@@ -51,12 +50,12 @@ router.beforeEach(async (to, _from, next) => {
const storedRefresh = localStorage.getItem('refreshToken')
if (storedRefresh && !_getAccessToken()) {
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`,
{ refreshToken: storedRefresh },
)
_setAccessToken(res.data.data.accessToken)
localStorage.setItem('refreshToken', res.data.data.refreshToken)
_setAccessToken(res.data.accessToken)
localStorage.setItem('refreshToken', res.data.refreshToken)
await authStore.fetchMe()
}
catch {

View File

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

View File

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