refactor: auth API with TS

This commit is contained in:
Tristan Yang
2022-06-24 22:20:58 +08:00
42 changed files with 759 additions and 384 deletions
+12 -11
View File
@@ -3,7 +3,7 @@ import { nanoid } from "@reduxjs/toolkit";
import baseQuery from "./base.query";
import { setAuthData, updateToken, resetAuthData, updateInitialized } from "../slices/auth.data";
import BASE_URL, { KEY_DEVICE_KEY, KEY_LOCAL_MAGIC_TOKEN } from "../config";
import { AuthData } from "../../types/auth";
import { AuthData, LoginCredential } from "../../types/auth";
const getDeviceId = () => {
let d = localStorage.getItem(KEY_DEVICE_KEY);
@@ -18,29 +18,30 @@ export const authApi = createApi({
reducerPath: "authApi",
baseQuery,
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
login: builder.mutation<AuthData, LoginCredential>({
query: (credential) => ({
url: "token/login",
method: "POST",
body: {
credential: credentials,
credential,
device: getDeviceId(),
device_token: "test"
}
}),
transformResponse: (data: AuthData) => {
const { avatar_updated_at } = data.user;
data.user.avatar =
avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`;
return data;
return {
...data,
avatar:
avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/avatar?uid=${data.user.uid}&t=${avatar_updated_at}`
};
},
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
if (data) {
console.log("login resp", data);
dispatch(setAuthData(data));
}
// 从localstorage 去掉 magic token
@@ -156,7 +157,7 @@ export const authApi = createApi({
}
}
}),
getInitialized: builder.query({
getInitialized: builder.query<boolean, void>({
query: () => ({ url: "/admin/system/initialized" }),
async onQueryStarted(params, { dispatch, queryFulfilled }) {
try {
+9 -7
View File
@@ -1,26 +1,29 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { KEY_EXPIRE, KEY_PWA_INSTALLED, KEY_REFRESH_TOKEN, KEY_TOKEN, KEY_UID } from "../config";
import { AuthData, AuthToken } from "../../types/auth";
import { AuthData, AuthToken, User } from "../../types/auth";
interface State {
initialized: boolean;
uid: string | null;
user: User | null;
token: string | null;
expireTime: string | number; // todo
expireTime: number;
refreshToken: string | null;
}
const initialState: State = {
initialized: true,
uid: null,
user: null,
token: localStorage.getItem(KEY_TOKEN),
expireTime: localStorage.getItem(KEY_EXPIRE) || +new Date(),
expireTime: Number(localStorage.getItem(KEY_EXPIRE) || +new Date()),
refreshToken: localStorage.getItem(KEY_REFRESH_TOKEN)
};
const emptyState: State = {
initialized: true,
uid: null,
user: null,
token: null,
expireTime: +new Date(),
refreshToken: null
@@ -30,20 +33,20 @@ const authDataSlice = createSlice({
name: "authData",
initialState,
reducers: {
setAuthData(state, action: PayloadAction<AuthData>) {
setAuthData(state, { payload }: PayloadAction<AuthData>) {
const {
initialized = true,
user: { uid },
token,
refresh_token,
expired_in = 0
} = action.payload;
} = payload;
state.initialized = initialized;
state.uid = `${uid}`;
state.user = payload.user;
state.token = token;
state.refreshToken = refresh_token;
// 当前时间往后推expire时长
console.log("expire", expired_in);
const expireTime = +new Date() + Number(expired_in) * 1000;
state.expireTime = expireTime;
// set local data
@@ -53,7 +56,6 @@ const authDataSlice = createSlice({
localStorage.setItem(KEY_UID, `${uid}`);
},
resetAuthData() {
console.log("clear auth data");
// remove local data
localStorage.removeItem(KEY_EXPIRE);
localStorage.removeItem(KEY_TOKEN);
+23 -23
View File
@@ -1,11 +1,15 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import BASE_URL from '../config';
import { getNonNullValues } from '../../common/utils';
import { Channel, UpdateChannelDTO, UpdatePinnedMessageDTO } from '../../types/channel';
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import BASE_URL from "../config";
import { getNonNullValues } from "../../common/utils";
import { Channel, UpdateChannelDTO, UpdatePinnedMessageDTO } from "../../types/channel";
interface StoredChannel extends Channel {
icon: string;
}
interface State {
ids: number[];
byId: { [id: number]: Channel | undefined };
byId: { [id: number]: StoredChannel | undefined };
}
const initialState: State = {
@@ -23,16 +27,15 @@ const channelsSlice = createSlice({
fullfillChannels(state, action: PayloadAction<Channel[]>) {
const channels = action.payload || [];
state.ids = channels.map(({ gid }) => gid);
state.byId = Object.fromEntries(
channels.map((c) => {
const { gid, avatar_updated_at } = c;
c.icon =
avatar_updated_at == 0
? ''
: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`;
return [gid, c];
})
);
channels.forEach((c) => {
state.byId[c.gid] = {
...c,
icon:
c.avatar_updated_at == 0
? ""
: `${BASE_URL}/resource/group_avatar?gid=${c.gid}&t=${c.avatar_updated_at}`
};
});
},
addChannel(state, action: PayloadAction<Channel>) {
const ch = action.payload;
@@ -44,26 +47,23 @@ const channelsSlice = createSlice({
...ch,
icon:
avatar_updated_at == 0
? ''
? ""
: `${BASE_URL}/resource/group_avatar?gid=${gid}&t=${avatar_updated_at}`
};
},
updateChannel(state, action: PayloadAction<UpdateChannelDTO>) {
const ignoreInPublic = ['add_member', 'remove_member'];
const ignoreInPublic = ["add_member", "remove_member"];
const { gid, operation, members = [], ...rest } = action.payload;
const currChannel = state.byId[gid];
if (
!currChannel ||
(currChannel.is_public && ignoreInPublic.includes(operation))
) return;
if (!currChannel || (currChannel.is_public && ignoreInPublic.includes(operation))) return;
switch (operation) {
case 'remove_member': {
case "remove_member": {
state.byId[gid]!.members = state.byId[gid]!.members.filter(
(id) => members.findIndex((uid) => uid == id) == -1
);
break;
}
case 'add_member': {
case "add_member": {
const currs = state.byId[gid]!.members;
const _set = new Set([...currs, ...members]);
state.byId[gid]!.members = Array.from(_set);
+40 -36
View File
@@ -1,12 +1,13 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { MuteChannel, MuteUser } from "../../types/sse";
export interface State {
usersVersion: number;
afterMid: number;
readUsers: {};
readChannels: {};
muteUsers: {};
muteChannels: {};
readUsers: { [uid: number]: number };
readChannels: { [gid: number]: number };
muteUsers: { [uid: number]: { expired_at: number } | undefined };
muteChannels: { [gid: number]: { expired_at: number } | undefined };
}
const initialState: State = {
@@ -18,6 +19,13 @@ const initialState: State = {
muteChannels: {}
};
interface UpdateMutePayload {
remove_users: number[];
remove_groups: number[];
add_users: MuteUser[];
add_groups: MuteChannel[];
}
const footprintSlice = createSlice({
name: "footprint",
initialState,
@@ -49,55 +57,51 @@ const footprintSlice = createSlice({
updateAfterMid(state, action: PayloadAction<number>) {
state.afterMid = action.payload;
},
updateMute(state, action) {
updateMute(state, action: PayloadAction<UpdateMutePayload>) {
const payload = action.payload || {};
Object.keys(payload).forEach((key) => {
switch (key) {
case "remove_users":
{
const uids = payload.remove_users;
uids.forEach((id) => {
delete state.muteUsers[id];
});
}
case "remove_users": {
const uids = payload.remove_users;
uids.forEach((id) => {
delete state.muteUsers[id];
});
break;
case "remove_groups":
{
const gids = payload.remove_groups;
gids.forEach((id) => {
delete state.muteChannels[id];
});
}
}
case "remove_groups": {
const gids = payload.remove_groups;
gids.forEach((id) => {
delete state.muteChannels[id];
});
break;
case "add_users":
{
const mutes = payload.add_users;
mutes.forEach(({ uid, expired_in = null }) => {
state.muteUsers[uid] = { expired_in };
});
}
}
case "add_users": {
const mutes = payload.add_users;
mutes.forEach(({ uid, expired_at }) => {
state.muteUsers[uid] = { expired_at };
});
break;
case "add_groups":
{
const mutes = payload.add_groups;
mutes.forEach(({ gid, expired_in = null }) => {
state.muteChannels[gid] = { expired_in };
});
}
}
case "add_groups": {
const mutes = payload.add_groups;
mutes.forEach(({ gid, expired_at }) => {
state.muteChannels[gid] = { expired_at };
});
break;
}
default:
break;
}
});
},
updateReadUsers(state, action) {
updateReadUsers(state, action: PayloadAction<{ uid: number; mid: number }[]>) {
const reads = action.payload || [];
if (reads.length == 0) return;
reads.forEach(({ uid, mid }) => {
state.readUsers[uid] = mid;
});
},
updateReadChannels(state, action) {
updateReadChannels(state, action: PayloadAction<{ gid: number; mid: number }[]>) {
const reads = action.payload || [];
if (reads.length == 0) return;
reads.forEach(({ gid, mid }) => {
+24 -8
View File
@@ -1,5 +1,16 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface State {
name: string;
description: string;
logo: string;
inviteLink: {
link: string;
expire: number;
};
}
const initialState: State = {
name: "",
description: "",
logo: "",
@@ -8,6 +19,7 @@ const initialState = {
expire: 0
}
};
const serverSlice = createSlice({
name: "server",
initialState,
@@ -15,24 +27,28 @@ const serverSlice = createSlice({
resetServer() {
return initialState;
},
fullfillServer(state, action) {
fullfillServer(state, action: PayloadAction<State>) {
const {
inviteLink = {
link: "",
expire: 0
},
logo = "", // todo: check missed logo property
name = "",
description = ""
} = action.payload || {};
return { name, description, inviteLink };
return { name, logo, description, inviteLink };
},
updateInfo(state, action) {
updateInfo(state, action: PayloadAction<Partial<State>>) {
const values = action.payload || {};
Object.keys(values).forEach((_key) => {
state[_key] = values[_key];
});
// todo: check and remove old logic
// Object.keys(values).forEach((_key) => {
// state[_key] = values[_key];
// });
state = { ...state, ...values };
}
}
});
export const { updateInfo, resetServer, fullfillServer } = serverSlice.actions;
export default serverSlice.reducer;