feat: add basic api service infrastructure and user api
This commit is contained in:
parent
e1b74e41b1
commit
08621df30b
@ -38,7 +38,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
name: "react-redux",
|
name: "react-redux",
|
||||||
importNames: ["useSelector", "useStore", "useDispatch"],
|
importNames: ["useSelector", "useStore", "useDispatch"],
|
||||||
message: "Please use pre-typed versions from `src/app/hooks.ts` instead.",
|
message: "Please use pre-typed versions from `src/hooks/store.ts` instead.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
20
src/app/services/api.ts
Normal file
20
src/app/services/api.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { createApi, fetchBaseQuery, retry } from "@reduxjs/toolkit/query/react";
|
||||||
|
import type { RootState } from "../store";
|
||||||
|
|
||||||
|
import type { Wrap } from "./base";
|
||||||
|
|
||||||
|
const baseQuery = fetchBaseQuery({
|
||||||
|
baseUrl: import.meta.env.MODE === "production" ? "/api/v1" : "http://127.0.0.1:8000/api/v1",
|
||||||
|
prepareHeaders: (headers, { getState }) => {
|
||||||
|
const token = (getState() as RootState).auth.token;
|
||||||
|
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
validateStatus: (response, result: Wrap<unknown>) => response.status === 200 && result.code === 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const api = createApi({
|
||||||
|
baseQuery: retry(baseQuery, { maxRetries: 6 }),
|
||||||
|
tagTypes: ["User", "Status", "Submission"],
|
||||||
|
endpoints: () => ({}),
|
||||||
|
});
|
17
src/app/services/base.ts
Normal file
17
src/app/services/base.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export interface Wrap<T> {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
body: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithCount<T> {
|
||||||
|
count: number;
|
||||||
|
data: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Meta {
|
||||||
|
ID: number;
|
||||||
|
CreatedAt: Date;
|
||||||
|
UpdatedAt: Date;
|
||||||
|
DeletedAt: Date;
|
||||||
|
}
|
66
src/app/services/user.ts
Normal file
66
src/app/services/user.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import type { Meta, Wrap } from "./base";
|
||||||
|
import { api } from "./api";
|
||||||
|
|
||||||
|
export enum UserRole {
|
||||||
|
RoleAdmin = 30,
|
||||||
|
RoleUser = 20,
|
||||||
|
RoleGuest = 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserRequest {
|
||||||
|
email?: string;
|
||||||
|
password?: string;
|
||||||
|
nickname?: string;
|
||||||
|
uid?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserLoginResponse {
|
||||||
|
nickname: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserProfile {
|
||||||
|
meta: Meta;
|
||||||
|
email: string;
|
||||||
|
nick_name: string;
|
||||||
|
role: number;
|
||||||
|
is_enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userApi = api.injectEndpoints({
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
create: builder.mutation<Wrap<string>, UserRequest>({
|
||||||
|
query: (data: UserRequest) => ({
|
||||||
|
url: "/user/create",
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
}),
|
||||||
|
invalidatesTags: ["User", "Status", "Submission"],
|
||||||
|
}),
|
||||||
|
login: builder.mutation<Wrap<UserLoginResponse>, UserRequest>({
|
||||||
|
query: (data: UserRequest) => ({
|
||||||
|
url: "/user/login",
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
}),
|
||||||
|
invalidatesTags: ["User", "Status", "Submission"],
|
||||||
|
}),
|
||||||
|
logout: builder.mutation<Wrap<void>, void>({
|
||||||
|
query: () => ({
|
||||||
|
url: "/user/logout",
|
||||||
|
method: "POST",
|
||||||
|
}),
|
||||||
|
invalidatesTags: ["User", "Status", "Submission"],
|
||||||
|
}),
|
||||||
|
profile: builder.query<Wrap<UserProfile>, UserRequest>({
|
||||||
|
query: (data: UserRequest) => ({
|
||||||
|
url: "/user/profile",
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
}),
|
||||||
|
providesTags: ["User"],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { useCreateMutation, useLoginMutation, useLogoutMutation, useProfileQuery } = userApi;
|
@ -2,17 +2,23 @@ import type { Action, ThunkAction } from "@reduxjs/toolkit";
|
|||||||
import { combineSlices, configureStore } from "@reduxjs/toolkit";
|
import { combineSlices, configureStore } from "@reduxjs/toolkit";
|
||||||
import { setupListeners } from "@reduxjs/toolkit/query";
|
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||||
|
|
||||||
|
import { userApi } from "./services/user";
|
||||||
|
|
||||||
import { counterSlice } from "../features/counter/counterSlice";
|
import { counterSlice } from "../features/counter/counterSlice";
|
||||||
import { quotesApiSlice } from "../features/quotes/quotesApiSlice";
|
import { quotesApiSlice } from "../features/quotes/quotesApiSlice";
|
||||||
|
|
||||||
const rootReducer = combineSlices(counterSlice, quotesApiSlice);
|
const dataSlices = [counterSlice];
|
||||||
|
const middlewareSlices = [quotesApiSlice, userApi];
|
||||||
|
const slices = [...dataSlices, ...middlewareSlices];
|
||||||
|
|
||||||
|
const rootReducer = combineSlices(...slices);
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|
||||||
export const makeStore = (preloadedState?: Partial<RootState>) => {
|
export const makeStore = (preloadedState?: Partial<RootState>) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: rootReducer,
|
reducer: rootReducer,
|
||||||
middleware: (getDefaultMiddleware) => {
|
middleware: (getDefaultMiddleware) => {
|
||||||
return getDefaultMiddleware().concat(quotesApiSlice.middleware);
|
return getDefaultMiddleware().concat(middlewareSlices.map((x) => x.middleware));
|
||||||
},
|
},
|
||||||
preloadedState,
|
preloadedState,
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
import { useAppDispatch, useAppSelector } from "../../hooks/store";
|
||||||
import styles from "./Counter.module.css";
|
import styles from "./Counter.module.css";
|
||||||
import {
|
import {
|
||||||
decrement,
|
decrement,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-restricted-imports */
|
/* eslint-disable @typescript-eslint/no-restricted-imports */
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import type { AppDispatch, RootState } from "./store";
|
import type { AppDispatch, RootState } from "../app/store";
|
||||||
|
|
||||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
||||||
export const useAppSelector = useSelector.withTypes<RootState>();
|
export const useAppSelector = useSelector.withTypes<RootState>();
|
Loading…
Reference in New Issue
Block a user