@titus-system/syncdesk 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +53 -0
- package/.github/workflows/notify-main.yml +17 -0
- package/README.md +41 -0
- package/package.json +42 -0
- package/src/api/client.ts +135 -0
- package/src/api/index.ts +2 -0
- package/src/api/typings.ts +3 -0
- package/src/auth/hooks/useAuth.ts +95 -0
- package/src/auth/index.ts +12 -0
- package/src/auth/types/auth.ts +35 -0
- package/src/auth/types/session.ts +12 -0
- package/src/config.ts +31 -0
- package/src/health/hooks/useHealth.ts +48 -0
- package/src/health/index.ts +2 -0
- package/src/health/types/health.ts +10 -0
- package/src/index.ts +10 -0
- package/src/live_chat/hooks/useLiveChat.ts +103 -0
- package/src/live_chat/hooks/useLiveChatWebSocket.ts +86 -0
- package/src/live_chat/index.ts +15 -0
- package/src/live_chat/types/live_chat.ts +48 -0
- package/src/permissions/hooks/usePermissions.ts +141 -0
- package/src/permissions/index.ts +2 -0
- package/src/permissions/types/index.ts +1 -0
- package/src/permissions/types/permission.ts +27 -0
- package/src/roles/hooks/useRoles.ts +103 -0
- package/src/roles/index.ts +2 -0
- package/src/roles/types/index.ts +1 -0
- package/src/roles/types/role.ts +28 -0
- package/src/users/hooks/useUsers.ts +146 -0
- package/src/users/index.ts +22 -0
- package/src/users/types/user.ts +36 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from "react";
|
|
2
|
+
import useWebSocket, { ReadyState } from "react-use-websocket";
|
|
3
|
+
import { config } from "../../config";
|
|
4
|
+
import type { ChatMessage, SendMessagePayload } from "../types/live_chat";
|
|
5
|
+
import type { ApiResponse } from "../../api";
|
|
6
|
+
|
|
7
|
+
export function useLiveChatWebSocket(chatId: string | null | undefined) {
|
|
8
|
+
const [token, setToken] = useState<string | null>(null);
|
|
9
|
+
|
|
10
|
+
// We need the token resolved before connecting to WebSocket
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!chatId) return;
|
|
13
|
+
|
|
14
|
+
const fetchToken = async () => {
|
|
15
|
+
const t = await config.getAccessToken();
|
|
16
|
+
setToken(t);
|
|
17
|
+
};
|
|
18
|
+
fetchToken();
|
|
19
|
+
}, [chatId]);
|
|
20
|
+
|
|
21
|
+
// Construct WebSocket URL from the configured baseURL
|
|
22
|
+
const wsUrl = () => {
|
|
23
|
+
if (!config.baseURL || !chatId || !token) return null;
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(config.baseURL);
|
|
26
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
27
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}/live_chat/room/${chatId}`;
|
|
28
|
+
return url.toString();
|
|
29
|
+
} catch {
|
|
30
|
+
// If baseURL is relative (e.g. "/api"), use window location
|
|
31
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
32
|
+
const host = window.location.host;
|
|
33
|
+
return `${protocol}//${host}${config.baseURL.replace(/\/$/, "")}/live_chat/room/${chatId}`;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const finalUrl = wsUrl();
|
|
38
|
+
|
|
39
|
+
const { sendMessage, lastJsonMessage, readyState } = useWebSocket<
|
|
40
|
+
ApiResponse<ChatMessage>
|
|
41
|
+
>(
|
|
42
|
+
finalUrl,
|
|
43
|
+
{
|
|
44
|
+
// Using token as a subprotocol to pass it due to WebSocket standard not allowing custom headers in browsers.
|
|
45
|
+
// E.g. Sec-WebSocket-Protocol: access_token, <token>
|
|
46
|
+
protocols: token ? ["access_token", token] : [],
|
|
47
|
+
shouldReconnect: (closeEvent) => {
|
|
48
|
+
// Do not reconnect on unauthorized or permission denied errors
|
|
49
|
+
if (
|
|
50
|
+
closeEvent.code === 1008 ||
|
|
51
|
+
closeEvent.code === 403 ||
|
|
52
|
+
closeEvent.code === 1011
|
|
53
|
+
)
|
|
54
|
+
return false;
|
|
55
|
+
return true;
|
|
56
|
+
},
|
|
57
|
+
reconnectAttempts: 10,
|
|
58
|
+
reconnectInterval: 3000,
|
|
59
|
+
},
|
|
60
|
+
// Only connect if we have a valid URL (meaning chat id and token exist)
|
|
61
|
+
!!finalUrl,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const connectionStatus = {
|
|
65
|
+
[ReadyState.CONNECTING]: "Connecting",
|
|
66
|
+
[ReadyState.OPEN]: "Open",
|
|
67
|
+
[ReadyState.CLOSING]: "Closing",
|
|
68
|
+
[ReadyState.CLOSED]: "Closed",
|
|
69
|
+
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
|
|
70
|
+
}[readyState];
|
|
71
|
+
|
|
72
|
+
const sendPayload = useCallback(
|
|
73
|
+
(payload: SendMessagePayload) => {
|
|
74
|
+
sendMessage(JSON.stringify(payload));
|
|
75
|
+
},
|
|
76
|
+
[sendMessage],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
sendMessage: sendPayload,
|
|
81
|
+
lastMessage: lastJsonMessage?.data || null,
|
|
82
|
+
rawLastMessage: lastJsonMessage,
|
|
83
|
+
connectionStatus,
|
|
84
|
+
readyState,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
useGetConversations,
|
|
3
|
+
useGetPaginatedMessages,
|
|
4
|
+
useCreateConversation,
|
|
5
|
+
useSetConversationAgent,
|
|
6
|
+
} from "./hooks/useLiveChat";
|
|
7
|
+
|
|
8
|
+
export { useLiveChatWebSocket } from "./hooks/useLiveChatWebSocket";
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
ChatMessage,
|
|
12
|
+
Conversation,
|
|
13
|
+
CreateConversationDTO,
|
|
14
|
+
PaginatedMessages,
|
|
15
|
+
} from "./types/live_chat";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface SendMessagePayload {
|
|
2
|
+
type: "text" | "file";
|
|
3
|
+
content: string;
|
|
4
|
+
mime_type?: string | null;
|
|
5
|
+
filename?: string | null;
|
|
6
|
+
responding_to?: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ChatMessage {
|
|
10
|
+
id: string;
|
|
11
|
+
conversation_id: string;
|
|
12
|
+
sender_id: string | "System";
|
|
13
|
+
timestamp: string;
|
|
14
|
+
type: "text" | "file";
|
|
15
|
+
content: string;
|
|
16
|
+
mime_type?: string | null;
|
|
17
|
+
filename?: string | null;
|
|
18
|
+
responding_to?: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Conversation {
|
|
22
|
+
_id: string;
|
|
23
|
+
ticket_id: string;
|
|
24
|
+
agent_id?: string | null;
|
|
25
|
+
client_id: string;
|
|
26
|
+
sequential_index: number;
|
|
27
|
+
parent_id?: string | null;
|
|
28
|
+
children_ids: string[];
|
|
29
|
+
started_at: string;
|
|
30
|
+
finished_at?: string | null;
|
|
31
|
+
messages: ChatMessage[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CreateConversationDTO {
|
|
35
|
+
ticket_id: string;
|
|
36
|
+
agent_id?: string | null;
|
|
37
|
+
client_id: string;
|
|
38
|
+
sequential_index?: number;
|
|
39
|
+
parent_id?: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PaginatedMessages {
|
|
43
|
+
messages: ChatMessage[];
|
|
44
|
+
total: number;
|
|
45
|
+
page: number;
|
|
46
|
+
limit: number;
|
|
47
|
+
has_next: boolean;
|
|
48
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { apiClient } from "../../api";
|
|
3
|
+
import type { ApiResponse } from "../../api";
|
|
4
|
+
import type {
|
|
5
|
+
Permission,
|
|
6
|
+
CreatePermissionDTO,
|
|
7
|
+
ReplacePermissionDTO,
|
|
8
|
+
UpdatePermissionDTO,
|
|
9
|
+
AddPermissionRolesDTO,
|
|
10
|
+
} from "../types/permission";
|
|
11
|
+
|
|
12
|
+
export function usePermissions() {
|
|
13
|
+
return useQuery<Permission[]>({
|
|
14
|
+
queryKey: ["permissions"],
|
|
15
|
+
queryFn: async () => {
|
|
16
|
+
const response =
|
|
17
|
+
await apiClient.get<ApiResponse<Permission[]>>("/permissions/");
|
|
18
|
+
return response.data.data;
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function usePermission(id: number) {
|
|
24
|
+
return useQuery<Permission>({
|
|
25
|
+
queryKey: ["permissions", id],
|
|
26
|
+
queryFn: async () => {
|
|
27
|
+
const response = await apiClient.get<ApiResponse<Permission>>(
|
|
28
|
+
`/permissions/${id}`,
|
|
29
|
+
);
|
|
30
|
+
return response.data.data;
|
|
31
|
+
},
|
|
32
|
+
enabled: !!id,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useCreatePermission() {
|
|
37
|
+
const queryClient = useQueryClient();
|
|
38
|
+
return useMutation<Permission, Error, CreatePermissionDTO>({
|
|
39
|
+
mutationFn: async (dto) => {
|
|
40
|
+
const response = await apiClient.post<ApiResponse<Permission>>(
|
|
41
|
+
"/permissions/",
|
|
42
|
+
dto,
|
|
43
|
+
);
|
|
44
|
+
return response.data.data;
|
|
45
|
+
},
|
|
46
|
+
onSuccess: () => {
|
|
47
|
+
queryClient.invalidateQueries({ queryKey: ["permissions"] });
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useReplacePermission() {
|
|
53
|
+
const queryClient = useQueryClient();
|
|
54
|
+
return useMutation<
|
|
55
|
+
Permission,
|
|
56
|
+
Error,
|
|
57
|
+
{ id: number; dto: ReplacePermissionDTO }
|
|
58
|
+
>({
|
|
59
|
+
mutationFn: async ({ id, dto }) => {
|
|
60
|
+
const response = await apiClient.put<ApiResponse<Permission>>(
|
|
61
|
+
`/permissions/${id}`,
|
|
62
|
+
dto,
|
|
63
|
+
);
|
|
64
|
+
return response.data.data;
|
|
65
|
+
},
|
|
66
|
+
onSuccess: (_, { id }) => {
|
|
67
|
+
queryClient.invalidateQueries({ queryKey: ["permissions"] });
|
|
68
|
+
queryClient.invalidateQueries({ queryKey: ["permissions", id] });
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useUpdatePermission() {
|
|
74
|
+
const queryClient = useQueryClient();
|
|
75
|
+
return useMutation<
|
|
76
|
+
Permission,
|
|
77
|
+
Error,
|
|
78
|
+
{ id: number; dto: UpdatePermissionDTO }
|
|
79
|
+
>({
|
|
80
|
+
mutationFn: async ({ id, dto }) => {
|
|
81
|
+
const response = await apiClient.patch<ApiResponse<Permission>>(
|
|
82
|
+
`/permissions/${id}`,
|
|
83
|
+
dto,
|
|
84
|
+
);
|
|
85
|
+
return response.data.data;
|
|
86
|
+
},
|
|
87
|
+
onSuccess: (_, { id }) => {
|
|
88
|
+
queryClient.invalidateQueries({ queryKey: ["permissions"] });
|
|
89
|
+
queryClient.invalidateQueries({ queryKey: ["permissions", id] });
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function useDeletePermission() {
|
|
95
|
+
const queryClient = useQueryClient();
|
|
96
|
+
return useMutation<Permission, Error, number>({
|
|
97
|
+
mutationFn: async (id) => {
|
|
98
|
+
const response = await apiClient.delete<ApiResponse<Permission>>(
|
|
99
|
+
`/permissions/${id}`,
|
|
100
|
+
);
|
|
101
|
+
return response.data.data;
|
|
102
|
+
},
|
|
103
|
+
onSuccess: (_, id) => {
|
|
104
|
+
queryClient.invalidateQueries({ queryKey: ["permissions"] });
|
|
105
|
+
queryClient.invalidateQueries({ queryKey: ["permissions", id] });
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function usePermissionRoles(id: number) {
|
|
111
|
+
return useQuery<Permission>({
|
|
112
|
+
queryKey: ["permissions", id, "roles"],
|
|
113
|
+
queryFn: async () => {
|
|
114
|
+
const response = await apiClient.get<ApiResponse<Permission>>(
|
|
115
|
+
`/permissions/${id}/roles`,
|
|
116
|
+
);
|
|
117
|
+
return response.data.data;
|
|
118
|
+
},
|
|
119
|
+
enabled: !!id,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function useAddPermissionRoles() {
|
|
124
|
+
const queryClient = useQueryClient();
|
|
125
|
+
return useMutation<
|
|
126
|
+
Permission,
|
|
127
|
+
Error,
|
|
128
|
+
{ id: number; dto: AddPermissionRolesDTO }
|
|
129
|
+
>({
|
|
130
|
+
mutationFn: async ({ id, dto }) => {
|
|
131
|
+
const response = await apiClient.post<ApiResponse<Permission>>(
|
|
132
|
+
`/permissions/${id}/roles`,
|
|
133
|
+
dto,
|
|
134
|
+
);
|
|
135
|
+
return response.data.data;
|
|
136
|
+
},
|
|
137
|
+
onSuccess: (_, { id }) => {
|
|
138
|
+
queryClient.invalidateQueries({ queryKey: ["permissions", id, "roles"] });
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./permission";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Role } from "../../roles/types/role";
|
|
2
|
+
|
|
3
|
+
export interface Permission {
|
|
4
|
+
id: number;
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string | null;
|
|
7
|
+
roles?: Role[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CreatePermissionDTO {
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ReplacePermissionDTO {
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UpdatePermissionDTO {
|
|
21
|
+
name?: string | null;
|
|
22
|
+
description?: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AddPermissionRolesDTO {
|
|
26
|
+
ids: number[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { apiClient, ApiResponse } from "../../api";
|
|
3
|
+
import type { Role, CreateRoleDTO, ReplaceRoleDTO, UpdateRoleDTO, AddRolePermissionsDTO } from "../types/role";
|
|
4
|
+
|
|
5
|
+
export function useRoles() {
|
|
6
|
+
return useQuery<Role[]>({
|
|
7
|
+
queryKey: ["roles"],
|
|
8
|
+
queryFn: async () => {
|
|
9
|
+
const response = await apiClient.get<ApiResponse<Role[]>>("/roles/");
|
|
10
|
+
return response.data.data;
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useRole(id: number) {
|
|
16
|
+
return useQuery<Role>({
|
|
17
|
+
queryKey: ["roles", id],
|
|
18
|
+
queryFn: async () => {
|
|
19
|
+
const response = await apiClient.get<ApiResponse<Role>>(`/roles/${id}`);
|
|
20
|
+
return response.data.data;
|
|
21
|
+
},
|
|
22
|
+
enabled: !!id,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useCreateRole() {
|
|
27
|
+
const queryClient = useQueryClient();
|
|
28
|
+
return useMutation<Role, Error, CreateRoleDTO>({
|
|
29
|
+
mutationFn: async (dto) => {
|
|
30
|
+
const response = await apiClient.post<ApiResponse<Role>>("/roles/", dto);
|
|
31
|
+
return response.data.data;
|
|
32
|
+
},
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useReplaceRole() {
|
|
40
|
+
const queryClient = useQueryClient();
|
|
41
|
+
return useMutation<Role, Error, { id: number; dto: ReplaceRoleDTO }>({
|
|
42
|
+
mutationFn: async ({ id, dto }) => {
|
|
43
|
+
const response = await apiClient.put<ApiResponse<Role>>(`/roles/${id}`, dto);
|
|
44
|
+
return response.data.data;
|
|
45
|
+
},
|
|
46
|
+
onSuccess: (_, { id }) => {
|
|
47
|
+
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
48
|
+
queryClient.invalidateQueries({ queryKey: ["roles", id] });
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useUpdateRole() {
|
|
54
|
+
const queryClient = useQueryClient();
|
|
55
|
+
return useMutation<Role, Error, { id: number; dto: UpdateRoleDTO }>({
|
|
56
|
+
mutationFn: async ({ id, dto }) => {
|
|
57
|
+
const response = await apiClient.patch<ApiResponse<Role>>(`/roles/${id}`, dto);
|
|
58
|
+
return response.data.data;
|
|
59
|
+
},
|
|
60
|
+
onSuccess: (_, { id }) => {
|
|
61
|
+
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
62
|
+
queryClient.invalidateQueries({ queryKey: ["roles", id] });
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useDeleteRole() {
|
|
68
|
+
const queryClient = useQueryClient();
|
|
69
|
+
return useMutation<Role, Error, number>({
|
|
70
|
+
mutationFn: async (id) => {
|
|
71
|
+
const response = await apiClient.delete<ApiResponse<Role>>(`/roles/${id}`);
|
|
72
|
+
return response.data.data;
|
|
73
|
+
},
|
|
74
|
+
onSuccess: (_, id) => {
|
|
75
|
+
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
76
|
+
queryClient.invalidateQueries({ queryKey: ["roles", id] });
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function useRolePermissions(id: number) {
|
|
82
|
+
return useQuery<Role>({
|
|
83
|
+
queryKey: ["roles", id, "permissions"],
|
|
84
|
+
queryFn: async () => {
|
|
85
|
+
const response = await apiClient.get<ApiResponse<Role>>(`/roles/${id}/permissions`);
|
|
86
|
+
return response.data.data;
|
|
87
|
+
},
|
|
88
|
+
enabled: !!id,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function useAddRolePermissions() {
|
|
93
|
+
const queryClient = useQueryClient();
|
|
94
|
+
return useMutation<Role, Error, { id: number; dto: AddRolePermissionsDTO }>({
|
|
95
|
+
mutationFn: async ({ id, dto }) => {
|
|
96
|
+
const response = await apiClient.post<ApiResponse<Role>>(`/roles/${id}/permissions`, dto);
|
|
97
|
+
return response.data.data;
|
|
98
|
+
},
|
|
99
|
+
onSuccess: (_, { id }) => {
|
|
100
|
+
queryClient.invalidateQueries({ queryKey: ["roles", id, "permissions"] });
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./role";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface Role {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string | null;
|
|
5
|
+
permissions?: Permission[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CreateRoleDTO {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ReplaceRoleDTO {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UpdateRoleDTO {
|
|
19
|
+
name?: string | null;
|
|
20
|
+
description?: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AddRolePermissionsDTO {
|
|
24
|
+
ids: number[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Importing Permission here to avoid circular dependency issues, or we can just redefine it as any or import it.
|
|
28
|
+
import type { Permission } from "../../permissions/types/permission";
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { apiClient, ApiResponse } from "../../api";
|
|
3
|
+
import {
|
|
4
|
+
User,
|
|
5
|
+
CreateUserDTO,
|
|
6
|
+
ReplaceUserDTO,
|
|
7
|
+
UpdateUserDTO,
|
|
8
|
+
AddUserRolesDTO,
|
|
9
|
+
} from "../types/user";
|
|
10
|
+
|
|
11
|
+
const PATH = "/users";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get all users.
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
export const useGetUsers = () => {
|
|
18
|
+
return useQuery({
|
|
19
|
+
queryKey: ["users"],
|
|
20
|
+
queryFn: async (): Promise<User[]> => {
|
|
21
|
+
const response = await apiClient.get<ApiResponse<User[]>>(PATH);
|
|
22
|
+
return response.data.data;
|
|
23
|
+
},
|
|
24
|
+
// staleTime: // ms
|
|
25
|
+
// gcTime: , //
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get one user.
|
|
31
|
+
* @param id
|
|
32
|
+
* @returns
|
|
33
|
+
*/
|
|
34
|
+
export const useGetUser = (id: string) => {
|
|
35
|
+
return useQuery({
|
|
36
|
+
queryKey: ["users", id],
|
|
37
|
+
queryFn: async (): Promise<User> => {
|
|
38
|
+
const response = await apiClient.get<ApiResponse<User>>(`${PATH}/${id}`);
|
|
39
|
+
return response.data.data;
|
|
40
|
+
},
|
|
41
|
+
enabled: !!id, // Prevent the query from running if the ID is missing
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a user.
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
export const useCreateUser = () => {
|
|
50
|
+
const queryClient = useQueryClient();
|
|
51
|
+
|
|
52
|
+
return useMutation({
|
|
53
|
+
mutationFn: async (user: CreateUserDTO): Promise<User> => {
|
|
54
|
+
const response = await apiClient.post<ApiResponse<User>>(PATH, user);
|
|
55
|
+
return response.data.data;
|
|
56
|
+
},
|
|
57
|
+
// Tell React Query to refresh the 'users' list after a successful creation.
|
|
58
|
+
onSuccess: () => {
|
|
59
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Replace an entire user.
|
|
66
|
+
* @returns
|
|
67
|
+
*/
|
|
68
|
+
export const useUpdateUser = () => {
|
|
69
|
+
const queryClient = useQueryClient();
|
|
70
|
+
|
|
71
|
+
return useMutation({
|
|
72
|
+
mutationFn: async ({
|
|
73
|
+
id,
|
|
74
|
+
data,
|
|
75
|
+
}: {
|
|
76
|
+
id: string;
|
|
77
|
+
data: ReplaceUserDTO;
|
|
78
|
+
}): Promise<User> => {
|
|
79
|
+
const response = await apiClient.put<ApiResponse<User>>(
|
|
80
|
+
`${PATH}/${id}`,
|
|
81
|
+
data,
|
|
82
|
+
);
|
|
83
|
+
return response.data.data;
|
|
84
|
+
},
|
|
85
|
+
onSuccess: (_, variables) => {
|
|
86
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
87
|
+
queryClient.invalidateQueries({ queryKey: ["users", variables.id] });
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Update specific fields of a user.
|
|
94
|
+
* @returns
|
|
95
|
+
*/
|
|
96
|
+
export const usePatchUser = () => {
|
|
97
|
+
const queryClient = useQueryClient();
|
|
98
|
+
|
|
99
|
+
return useMutation({
|
|
100
|
+
mutationFn: async ({
|
|
101
|
+
id,
|
|
102
|
+
data,
|
|
103
|
+
}: {
|
|
104
|
+
id: string;
|
|
105
|
+
data: UpdateUserDTO;
|
|
106
|
+
}): Promise<User> => {
|
|
107
|
+
const response = await apiClient.patch<ApiResponse<User>>(
|
|
108
|
+
`${PATH}/${id}`,
|
|
109
|
+
data,
|
|
110
|
+
);
|
|
111
|
+
return response.data.data;
|
|
112
|
+
},
|
|
113
|
+
onSuccess: (_, variables) => {
|
|
114
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
115
|
+
queryClient.invalidateQueries({ queryKey: ["users", variables.id] });
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Add roles to a user.
|
|
122
|
+
* @returns
|
|
123
|
+
*/
|
|
124
|
+
export const useAddUserRoles = () => {
|
|
125
|
+
const queryClient = useQueryClient();
|
|
126
|
+
|
|
127
|
+
return useMutation({
|
|
128
|
+
mutationFn: async ({
|
|
129
|
+
id,
|
|
130
|
+
data,
|
|
131
|
+
}: {
|
|
132
|
+
id: string;
|
|
133
|
+
data: AddUserRolesDTO;
|
|
134
|
+
}): Promise<User> => {
|
|
135
|
+
const response = await apiClient.post<ApiResponse<User>>(
|
|
136
|
+
`${PATH}/${id}/roles`,
|
|
137
|
+
data,
|
|
138
|
+
);
|
|
139
|
+
return response.data.data;
|
|
140
|
+
},
|
|
141
|
+
onSuccess: (_, variables) => {
|
|
142
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
143
|
+
queryClient.invalidateQueries({ queryKey: ["users", variables.id] });
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
useGetUsers,
|
|
3
|
+
useGetUser,
|
|
4
|
+
useCreateUser,
|
|
5
|
+
useUpdateUser,
|
|
6
|
+
usePatchUser,
|
|
7
|
+
useAddUserRoles,
|
|
8
|
+
} from "./hooks/useUsers";
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
User,
|
|
12
|
+
CreateUserDTO,
|
|
13
|
+
UpdateUserDTO,
|
|
14
|
+
ReplaceUserDTO,
|
|
15
|
+
AddUserRolesDTO,
|
|
16
|
+
} from "./types/user";
|
|
17
|
+
|
|
18
|
+
// # syncdesk-api/app/api/api_router
|
|
19
|
+
// - /auth/ -- /app/domains/auth/routers/auth_router
|
|
20
|
+
// | `/api/users/` | User management (CRUD) |
|
|
21
|
+
// | `/api/roles/` | Role management (CRUD) |
|
|
22
|
+
// | `/api/permissions/` | Permission management (CRUD) |
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { OAuthProvider } from "../../auth/types/auth";
|
|
2
|
+
|
|
3
|
+
export interface User {
|
|
4
|
+
id: string;
|
|
5
|
+
email: string;
|
|
6
|
+
username?: string | null;
|
|
7
|
+
name?: string | null;
|
|
8
|
+
oauth_provider?: OAuthProvider | null;
|
|
9
|
+
oauth_provider_id?: string | null;
|
|
10
|
+
is_active: boolean;
|
|
11
|
+
is_verified: boolean;
|
|
12
|
+
roles?: Role[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
import type { Role } from "../../roles/types/role";
|
|
16
|
+
import type { Permission } from "../../permissions/types/permission";
|
|
17
|
+
|
|
18
|
+
export interface CreateUserDTO {
|
|
19
|
+
email: string;
|
|
20
|
+
password_hash?: string | null;
|
|
21
|
+
username?: string | null;
|
|
22
|
+
name?: string | null;
|
|
23
|
+
oauth_provider?: OAuthProvider | null;
|
|
24
|
+
oauth_provider_id?: string | null;
|
|
25
|
+
is_active?: boolean;
|
|
26
|
+
is_verified?: boolean;
|
|
27
|
+
role_ids?: number[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type UpdateUserDTO = Partial<CreateUserDTO>;
|
|
31
|
+
|
|
32
|
+
export type ReplaceUserDTO = CreateUserDTO;
|
|
33
|
+
|
|
34
|
+
export interface AddUserRolesDTO {
|
|
35
|
+
role_ids: number[];
|
|
36
|
+
}
|