init + api use

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-03 17:59:08 +03:00
commit e777b57991
52 changed files with 20725 additions and 0 deletions

168
src/__data__/api/api.ts Normal file
View File

@@ -0,0 +1,168 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { getConfigValue } from '@brojs/cli'
import { keycloak } from '../kc'
import type {
ChallengeTask,
ChallengeChain,
ChallengeUser,
ChallengeSubmission,
SystemStats,
UserStats,
CreateTaskRequest,
UpdateTaskRequest,
CreateChainRequest,
UpdateChainRequest,
} from '../../types/challenge'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: getConfigValue('challenge-admin.api'),
fetchFn: async (
input: RequestInfo | URL,
init?: RequestInit | undefined,
) => {
const response = await fetch(input, init)
if (response.status === 403) keycloak.login()
return response
},
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
prepareHeaders: (headers) => {
headers.set('Authorization', `Bearer ${keycloak.token}`)
},
}),
tagTypes: ['Task', 'Chain', 'User', 'Submission', 'Stats'],
endpoints: (builder) => ({
// Tasks
getTasks: builder.query<ChallengeTask[], void>({
query: () => '/challenge/tasks',
transformResponse: (response: { data: ChallengeTask[] }) => response.data,
providesTags: ['Task'],
}),
getTask: builder.query<ChallengeTask, string>({
query: (id) => `/challenge/task/${id}`,
transformResponse: (response: { data: ChallengeTask }) => response.data,
providesTags: (_result, _error, id) => [{ type: 'Task', id }],
}),
createTask: builder.mutation<ChallengeTask, CreateTaskRequest>({
query: (body) => ({
url: '/challenge/task',
method: 'POST',
body,
}),
transformResponse: (response: { data: ChallengeTask }) => response.data,
invalidatesTags: ['Task'],
}),
updateTask: builder.mutation<ChallengeTask, { id: string; data: UpdateTaskRequest }>({
query: ({ id, data }) => ({
url: `/challenge/task/${id}`,
method: 'PUT',
body: data,
}),
transformResponse: (response: { data: ChallengeTask }) => response.data,
invalidatesTags: (_result, _error, { id }) => [{ type: 'Task', id }, 'Task'],
}),
deleteTask: builder.mutation<void, string>({
query: (id) => ({
url: `/challenge/task/${id}`,
method: 'DELETE',
}),
invalidatesTags: ['Task', 'Chain'],
}),
// Chains
getChains: builder.query<ChallengeChain[], void>({
query: () => '/challenge/chains',
transformResponse: (response: { data: ChallengeChain[] }) => response.data,
providesTags: ['Chain'],
}),
getChain: builder.query<ChallengeChain, string>({
query: (id) => `/challenge/chain/${id}`,
transformResponse: (response: { data: ChallengeChain }) => response.data,
providesTags: (_result, _error, id) => [{ type: 'Chain', id }],
}),
createChain: builder.mutation<ChallengeChain, CreateChainRequest>({
query: (body) => ({
url: '/challenge/chain',
method: 'POST',
body,
}),
transformResponse: (response: { data: ChallengeChain }) => response.data,
invalidatesTags: ['Chain'],
}),
updateChain: builder.mutation<ChallengeChain, { id: string; data: UpdateChainRequest }>({
query: ({ id, data }) => ({
url: `/challenge/chain/${id}`,
method: 'PUT',
body: data,
}),
transformResponse: (response: { data: ChallengeChain }) => response.data,
invalidatesTags: (_result, _error, { id }) => [{ type: 'Chain', id }, 'Chain'],
}),
deleteChain: builder.mutation<void, string>({
query: (id) => ({
url: `/challenge/chain/${id}`,
method: 'DELETE',
}),
invalidatesTags: ['Chain'],
}),
// Users
getUsers: builder.query<ChallengeUser[], void>({
query: () => '/challenge/users',
transformResponse: (response: { data: ChallengeUser[] }) => response.data,
providesTags: ['User'],
}),
// Statistics
getSystemStats: builder.query<SystemStats, void>({
query: () => '/challenge/stats',
transformResponse: (response: { data: SystemStats }) => response.data,
providesTags: ['Stats'],
}),
getUserStats: builder.query<UserStats, string>({
query: (userId) => `/challenge/user/${userId}/stats`,
transformResponse: (response: { data: UserStats }) => response.data,
providesTags: (_result, _error, userId) => [{ type: 'User', id: userId }],
}),
// Submissions
getUserSubmissions: builder.query<ChallengeSubmission[], { userId: string; taskId?: string }>({
query: ({ userId, taskId }) => {
const params = taskId ? `?taskId=${taskId}` : ''
return `/challenge/user/${userId}/submissions${params}`
},
transformResponse: (response: { data: ChallengeSubmission[] }) => response.data,
providesTags: ['Submission'],
}),
getAllSubmissions: builder.query<ChallengeSubmission[], void>({
query: () => '/challenge/submissions',
transformResponse: (response: { data: ChallengeSubmission[] }) => response.data,
providesTags: ['Submission'],
}),
}),
})
export const {
useGetTasksQuery,
useGetTaskQuery,
useCreateTaskMutation,
useUpdateTaskMutation,
useDeleteTaskMutation,
useGetChainsQuery,
useGetChainQuery,
useCreateChainMutation,
useUpdateChainMutation,
useDeleteChainMutation,
useGetUsersQuery,
useGetSystemStatsQuery,
useGetUserStatsQuery,
useGetUserSubmissionsQuery,
useGetAllSubmissionsQuery,
} = api

8
src/__data__/kc.ts Normal file
View File

@@ -0,0 +1,8 @@
import Keycloak from 'keycloak-js'
export const keycloak = new Keycloak({
url: KC_URL,
realm: KC_REALM,
clientId: KC_CLIENT_ID,
});

View File

@@ -0,0 +1,10 @@
import { createSlice } from '@reduxjs/toolkit'
import { UserData } from '../types'
export const userSlice = createSlice({
name: 'user',
initialState: null as UserData,
reducers: {
}
})

24
src/__data__/store.ts Normal file
View File

@@ -0,0 +1,24 @@
import { configureStore } from '@reduxjs/toolkit'
import { TypedUseSelectorHook, useSelector } from 'react-redux'
import { api } from './api/api'
import { userSlice } from './slices/user'
export const createStore = (preloadedState = {}) =>
configureStore({
preloadedState,
reducer: {
[api.reducerPath]: api.reducer,
user: userSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false,
}).concat(api.middleware),
})
export type Store = ReturnType<ReturnType<typeof createStore>['getState']>
export const useAppSelector: TypedUseSelectorHook<Store> = useSelector

36
src/__data__/urls.ts Normal file
View File

@@ -0,0 +1,36 @@
import { getNavigation, getNavigationValue } from '@brojs/cli'
import pkg from '../../package.json'
const baseUrl = getNavigationValue(`${pkg.name}.main`)
const navs = getNavigation()
const makeUrl = (url: string) => baseUrl + url
export const URLs = {
baseUrl,
// Dashboard
dashboard: makeUrl(''),
// Tasks
tasks: makeUrl('/tasks'),
taskNew: makeUrl('/tasks/new'),
taskEdit: (id: string) => makeUrl(`/tasks/${id}`),
taskEditPath: makeUrl('/tasks/:id'),
// Chains
chains: makeUrl('/chains'),
chainNew: makeUrl('/chains/new'),
chainEdit: (id: string) => makeUrl(`/chains/${id}`),
chainEditPath: makeUrl('/chains/:id'),
// Users
users: makeUrl('/users'),
// Submissions
submissions: makeUrl('/submissions'),
// External links
challengePlayer: navs['link.challenge'] || '/challenge',
}