feat: add basic api service infrastructure and user api

This commit is contained in:
Paul Pan 2024-02-20 23:24:14 +08:00
parent e1b74e41b1
commit 08621df30b
7 changed files with 114 additions and 5 deletions

View File

@ -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
View 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
View 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
View 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;

View File

@ -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,
}); });

View File

@ -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,

View File

@ -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>();