mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-25 20:59:28 +00:00
lesson 4 live 30/12
This commit is contained in:
21
src/services/auth/auth.service.ts
Normal file
21
src/services/auth/auth.service.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { baseApi } from '..'
|
||||
import { LoginArgs, User } from './auth.types'
|
||||
|
||||
export const authService = baseApi.injectEndpoints({
|
||||
endpoints: builder => ({
|
||||
login: builder.mutation<void, LoginArgs>({
|
||||
invalidatesTags: ['Me'],
|
||||
query: body => ({
|
||||
body,
|
||||
method: 'POST',
|
||||
url: '/v1/auth/login',
|
||||
}),
|
||||
}),
|
||||
me: builder.query<User, void>({
|
||||
providesTags: ['Me'],
|
||||
query: () => '/v1/auth/me',
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const { useLoginMutation, useMeQuery } = authService
|
||||
14
src/services/auth/auth.types.ts
Normal file
14
src/services/auth/auth.types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type LoginArgs = {
|
||||
email: string
|
||||
password: string
|
||||
rememberMe?: boolean
|
||||
}
|
||||
export type User = {
|
||||
avatar: null | string
|
||||
created: string
|
||||
email: string
|
||||
id: string
|
||||
isEmailVerified: boolean
|
||||
name: string
|
||||
updated: string
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
import { baseQueryWithReauth } from '@/services/base-query-with-reauth'
|
||||
import { createApi } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
export const baseApi = createApi({
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: 'https://api.flashcards.andrii.es',
|
||||
credentials: 'include',
|
||||
prepareHeaders: headers => {
|
||||
headers.append('x-auth-skip', 'true')
|
||||
},
|
||||
}),
|
||||
baseQuery: baseQueryWithReauth,
|
||||
endpoints: () => ({}),
|
||||
reducerPath: 'baseApi',
|
||||
tagTypes: ['Decks'],
|
||||
tagTypes: ['Decks', 'Me'],
|
||||
})
|
||||
|
||||
47
src/services/base-query-with-reauth.ts
Normal file
47
src/services/base-query-with-reauth.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
BaseQueryFn,
|
||||
FetchArgs,
|
||||
FetchBaseQueryError,
|
||||
fetchBaseQuery,
|
||||
} from '@reduxjs/toolkit/query/react'
|
||||
import { Mutex } from 'async-mutex'
|
||||
|
||||
const mutex = new Mutex()
|
||||
|
||||
const baseQuery = fetchBaseQuery({
|
||||
baseUrl: 'https://api.flashcards.andrii.es',
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
export const baseQueryWithReauth: BaseQueryFn<
|
||||
FetchArgs | string,
|
||||
unknown,
|
||||
FetchBaseQueryError
|
||||
> = async (args, api, extraOptions) => {
|
||||
await mutex.waitForUnlock()
|
||||
let result = await baseQuery(args, api, extraOptions)
|
||||
|
||||
if (result.error && result.error.status === 401) {
|
||||
if (!mutex.isLocked()) {
|
||||
const release = await mutex.acquire()
|
||||
// try to get a new token
|
||||
const refreshResult = await baseQuery(
|
||||
{ method: 'POST', url: '/v1/auth/refresh-token' },
|
||||
api,
|
||||
extraOptions
|
||||
)
|
||||
|
||||
if (refreshResult.meta?.response?.status === 204) {
|
||||
// retry the initial query
|
||||
result = await baseQuery(args, api, extraOptions)
|
||||
}
|
||||
release()
|
||||
} else {
|
||||
// wait until the mutex is available without locking it
|
||||
await mutex.waitForUnlock()
|
||||
result = await baseQuery(args, api, extraOptions)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -7,11 +7,57 @@ import {
|
||||
UpdateDeckArgs,
|
||||
baseApi,
|
||||
} from '@/services'
|
||||
import { RootState } from '@/services/store'
|
||||
|
||||
const decksService = baseApi.injectEndpoints({
|
||||
endpoints: builder => ({
|
||||
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
|
||||
invalidatesTags: ['Decks'],
|
||||
async onQueryStarted(_, { dispatch, getState, queryFulfilled }) {
|
||||
const res = await queryFulfilled
|
||||
|
||||
console.log(decksService.util.selectCachedArgsForQuery(getState(), 'getDecks'))
|
||||
for (const { endpointName, originalArgs } of decksService.util.selectInvalidatedBy(
|
||||
getState(),
|
||||
[{ type: 'Decks' }]
|
||||
)) {
|
||||
console.log(endpointName, originalArgs)
|
||||
// we only want to update `getPosts` here
|
||||
if (endpointName !== 'getDecks') {
|
||||
continue
|
||||
}
|
||||
dispatch(
|
||||
decksService.util.updateQueryData(endpointName, originalArgs, draft => {
|
||||
draft.items.unshift(res.data)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// console.log(args)
|
||||
// const minCardsCount = state.decks.minCards
|
||||
// const search = state.decks.search
|
||||
// const currentPage = state.decks.currentPage
|
||||
// const maxCardsCount = state.decks.maxCards
|
||||
// const authorId = state.decks.authorId
|
||||
//
|
||||
// console.log(res)
|
||||
//
|
||||
// dispatch(
|
||||
// decksService.util.updateQueryData(
|
||||
// 'getDecks',
|
||||
// {
|
||||
// authorId,
|
||||
// currentPage,
|
||||
// maxCardsCount,
|
||||
// minCardsCount,
|
||||
// name: search,
|
||||
// },
|
||||
// draft => {
|
||||
// draft.items.unshift(res.data)
|
||||
// }
|
||||
// )
|
||||
// )
|
||||
},
|
||||
query: body => ({
|
||||
body,
|
||||
method: 'POST',
|
||||
@@ -42,6 +88,43 @@ const decksService = baseApi.injectEndpoints({
|
||||
}),
|
||||
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
|
||||
invalidatesTags: ['Decks'],
|
||||
async onQueryStarted({ id, ...patch }, { dispatch, getState, queryFulfilled }) {
|
||||
const state = getState() as RootState
|
||||
|
||||
console.log(state)
|
||||
const minCardsCount = state.decks.minCards
|
||||
const search = state.decks.search
|
||||
const currentPage = state.decks.currentPage
|
||||
const maxCardsCount = state.decks.maxCards
|
||||
const authorId = state.decks.authorId
|
||||
|
||||
const patchResult = dispatch(
|
||||
decksService.util.updateQueryData(
|
||||
'getDecks',
|
||||
{
|
||||
authorId,
|
||||
currentPage,
|
||||
maxCardsCount,
|
||||
minCardsCount,
|
||||
name: search,
|
||||
},
|
||||
draft => {
|
||||
const deck = draft.items.find(deck => deck.id === id)
|
||||
|
||||
if (!deck) {
|
||||
return
|
||||
}
|
||||
Object.assign(deck, patch)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
try {
|
||||
await queryFulfilled
|
||||
} catch {
|
||||
patchResult.undo()
|
||||
}
|
||||
},
|
||||
query: ({ id, ...body }) => ({
|
||||
body,
|
||||
method: 'PATCH',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
export const decksSlice = createSlice({
|
||||
initialState: {
|
||||
authorId: undefined as string | undefined,
|
||||
currentPage: 1,
|
||||
currentTab: 'all' as Tab,
|
||||
maxCards: undefined as number | undefined,
|
||||
@@ -18,14 +19,17 @@ export const decksSlice = createSlice({
|
||||
resetFilters: state => {
|
||||
state.search = ''
|
||||
state.currentTab = 'all'
|
||||
state.authorId = undefined
|
||||
state.minCards = 0
|
||||
state.maxCards = undefined
|
||||
state.currentPage = 1
|
||||
},
|
||||
setCurrentPage: (state, action: PayloadAction<number>) => {
|
||||
state.currentPage = action.payload
|
||||
},
|
||||
setCurrentTab: (state, action: PayloadAction<Tab>) => {
|
||||
state.currentTab = action.payload
|
||||
setCurrentTab: (state, action: PayloadAction<{ authorId?: string; tab: Tab }>) => {
|
||||
state.currentTab = action.payload.tab
|
||||
state.authorId = action.payload.authorId
|
||||
},
|
||||
setMaxCards: (state, action: PayloadAction<number>) => {
|
||||
state.maxCards = action.payload
|
||||
|
||||
Reference in New Issue
Block a user