refactor: services into singleton

This commit is contained in:
Sergey Yarkov 2021-11-30 23:53:00 +03:00
parent bc5b493640
commit 2fa555b8be
18 changed files with 124 additions and 124 deletions

View File

@ -20,9 +20,13 @@ import FileSelect from '@educt/components/FileSelect';
* Hooks
*/
import { useToast } from '@chakra-ui/toast';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary';
/**
* Services
*/
import { CategoryServiceInstance, UserServiceInstance } from '@educt/services';
export type InputFields = {
title: string;
image?: File | undefined;
@ -46,10 +50,6 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
formState: { errors, isDirty },
} = reactHookForm;
const {
userStore: { userService },
categoryStore: { categoryService },
} = useRootStore();
const handleError = useErrorHandler();
const toast = useToast();
@ -58,7 +58,7 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
*/
const loadUsersOptions = async (): Promise<OptionType[] | undefined> => {
try {
const users = await userService.fetchAll({ limit: 12, role: UserRoleEnum.TEACHER });
const users = await UserServiceInstance.fetchAll({ limit: 12, role: UserRoleEnum.TEACHER });
return users.data.map(user => ({
label: user.fullname,
@ -78,7 +78,7 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
*/
const loadCategoriesOptions = async (): Promise<OptionType[] | undefined> => {
try {
const categories = await categoryService.fetchAll();
const categories = await CategoryServiceInstance.fetchAll();
return categories.data.map(category => ({
label: category.title,
value: category.id,

View File

@ -21,10 +21,14 @@ import { useState } from 'react';
import { useHistory } from 'react-router';
import { useForm } from 'react-hook-form';
import { useToast } from '@chakra-ui/toast';
import { useRootStore } from '@educt/hooks/useRootStore';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
import { useErrorHandler } from 'react-error-boundary';
/**
* Services
*/
import { CourseServiceInstance } from '@educt/services';
/**
* Schema
*/
@ -34,9 +38,6 @@ type CreateFormCoursePropsType = {};
const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const {
courseStore: { courseService },
} = useRootStore();
const form = useForm<InputFields>({ resolver: yupResolver(CourseFormSchema) });
const isMountedRef = useIsMountedRef();
const history = useHistory();
@ -46,10 +47,11 @@ const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
/**
* Submit handler
*/
// TODO: move request to useCreateCourse hook
const onSubmit: SubmitHandler<InputFields> = async data => {
try {
setIsLoading(true);
const course = await courseService.create({
const course = await CourseServiceInstance.create({
title: data.title,
description: data.description,
teacher_id: data.teacher_id,

View File

@ -1,12 +1,12 @@
import React from 'react';
import { Box } from '@chakra-ui/layout';
import { yupResolver } from '@hookform/resolvers/yup';
import { SubmitHandler } from 'react-hook-form';
/**
* Types
*/
import { InputFields } from './CourseForm';
import { SubmitHandler } from 'react-hook-form';
/**
* Components
@ -19,10 +19,14 @@ import CourseForm from './CourseForm';
import { useState } from 'react';
import { useParams } from 'react-router';
import { useForm } from 'react-hook-form';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary';
import { useToast } from '@chakra-ui/toast';
/**
* Services
*/
import { CourseServiceInstance } from '@educt/services';
/**
* Schema
*/
@ -42,9 +46,6 @@ type EditFormCoursePropsType = {
const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const {
courseStore: { courseService },
} = useRootStore();
const form = useForm<InputFields>({
resolver: yupResolver(CourseFormSchema),
defaultValues: {
@ -62,10 +63,11 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
/**
* Submit handler
*/
// TODO move request to useUpdateCourse hook
const onSubmit: SubmitHandler<InputFields> = async data => {
try {
setIsLoading(true);
const course = await courseService.update(id, {
const course = await CourseServiceInstance.update(id, {
title: data.title,
description: data.description,
teacher_id: data.teacher_id,

View File

@ -31,6 +31,11 @@ import { useDisclosure } from '@chakra-ui/hooks';
*/
import { useErrorHandler } from 'react-error-boundary';
/**
* Services
*/
import { LessonServiceInstance } from '@educt/services';
type LessonListPropsType = {
course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
};
@ -50,7 +55,6 @@ const CreateLessonButton: React.FC<{ id: string }> = ({ id }) => {
const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
const history = useHistory();
const {
lessonService,
pageStore: { editCourseStore },
} = useRootStore();
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
@ -66,7 +70,7 @@ const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
const handleChangeOrder = async (ids: string[]) => {
try {
const data = await lessonService.saveOrder(ids);
const data = await LessonServiceInstance.saveOrder(ids);
return data;
} catch (error: any) {
if (error.response) {

View File

@ -1,15 +1,12 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import RootStore from '@educt/stores/RootStore';
export default class ApiService {
public root: RootStore;
import apiConfig from '@educt/api.config';
class ApiService {
public api: AxiosInstance;
public config: AxiosRequestConfig | undefined;
constructor(root: RootStore, config?: AxiosRequestConfig | undefined) {
this.root = root;
constructor(config?: AxiosRequestConfig | undefined) {
this.config = config;
this.api = axios.create(this.config);
this.setupInterceptors();
@ -20,17 +17,9 @@ export default class ApiService {
* Response
*/
this.api.interceptors.response.use(undefined, error => {
if (error.response) {
/**
* Unauthorized Error 401
*/
if (error.response.status === 401) {
// this.root.userStore.me = null;
// this.root.authStore.setIsLoggedIn(false);
// Cookies.remove('logged_in');
}
}
return Promise.reject(error);
});
}
}
export const ApiServiceInstance = new ApiService(apiConfig);

View File

@ -1,7 +1,20 @@
import { IApiRespose, IToken } from '@educt/interfaces';
import { AxiosInstance } from 'axios';
import { ApiServiceInstance } from '.';
export default class AuthService {
export interface AuthServiceImpl {
/**
* Login user with login and passwors
*/
requestLogin(login: string, password: string): Promise<IApiRespose<IToken>>;
/**
* Logout user
*/
requestLogout(): Promise<IApiRespose<{}>>;
}
class AuthService implements AuthServiceImpl {
public api: AxiosInstance;
constructor(api: AxiosInstance) {
@ -13,9 +26,9 @@ export default class AuthService {
*
* @param login User login
* @param password User password
* @returns Login result data
* @returns Token result data
*/
public async requestLogin(login: string, password: string): Promise<IApiRespose<IToken>> {
public async requestLogin(login: string, password: string) {
const result = await this.api.post('v1/auth/login', {
login,
password,
@ -26,10 +39,12 @@ export default class AuthService {
/**
* Revoke token
*
* @returns Any
* @returns Empty obj
*/
public async requestLogout(): Promise<IApiRespose<any>> {
public async requestLogout() {
const result = await this.api.post('v1/auth/logout');
return result.data;
}
}
export const AuthServiceInstance = new AuthService(ApiServiceInstance.api);

View File

@ -1,7 +1,8 @@
import { AxiosInstance } from 'axios';
import { IApiRespose, ICategory } from '@educt/interfaces';
import { ApiServiceInstance } from '.';
export default class CategoryService {
class CategoryService {
public api: AxiosInstance;
constructor(api: AxiosInstance) {
@ -18,3 +19,5 @@ export default class CategoryService {
return result.data;
}
}
export const CategoryServiceInstance = new CategoryService(ApiServiceInstance.api);

View File

@ -1,10 +1,11 @@
import { AxiosInstance } from 'axios';
import { ApiServiceInstance } from '.';
import * as helpers from '@educt/helpers';
import { IApiRespose, ICourse } from '@educt/interfaces';
import { CreateCourseParamsType, FetchCoursesParams, UpdateCourseParamsType } from '@educt/types';
import { CourseStatusEnum } from '@educt/enums';
import { CreateCourseParamsType, FetchCoursesParams, UpdateCourseParamsType } from '@educt/types';
export class CourseService {
class CourseService {
public api: AxiosInstance;
constructor(api: AxiosInstance) {
@ -96,3 +97,5 @@ export class CourseService {
return result.data;
}
}
export const CourseServiceInstance = new CourseService(ApiServiceInstance.api);

View File

@ -1,7 +1,8 @@
import { IApiRespose, ILesson } from '@educt/interfaces';
import { AxiosInstance } from 'axios';
import { IApiRespose, ILesson } from '@educt/interfaces';
import { ApiServiceInstance } from './ApiService';
export default class LessonService {
class LessonService {
public api: AxiosInstance;
constructor(api: AxiosInstance) {
@ -32,3 +33,5 @@ export default class LessonService {
return result.data;
}
}
export const LessonServiceInstance = new LessonService(ApiServiceInstance.api);

View File

@ -1,9 +1,10 @@
import { AxiosInstance } from 'axios';
import { IApiRespose, IMe, IUser, IUserContacts } from '@educt/interfaces';
import { ApiServiceInstance } from '.';
import * as helpers from '@educt/helpers';
import { IApiRespose, IMe, IUser, IUserContacts } from '@educt/interfaces';
import { CreateUserParamsType, FetchUsersParamsType, UpdateUserParamsType } from '@educt/types';
export default class UserService {
class UserService {
public api: AxiosInstance;
constructor(api: AxiosInstance) {
@ -135,3 +136,5 @@ export default class UserService {
return result.data;
}
}
export const UserServiceInstance = new UserService(ApiServiceInstance.api);

15
src/services/index.ts Normal file
View File

@ -0,0 +1,15 @@
import { ApiServiceInstance } from './ApiService';
import { AuthServiceInstance } from './AuthService';
import { CategoryServiceInstance } from './CategoryService';
import { CourseServiceInstance } from './CourseService';
import { LessonServiceInstance } from './LessonService';
import { UserServiceInstance } from './UserService';
export {
ApiServiceInstance,
AuthServiceInstance,
CategoryServiceInstance,
CourseServiceInstance,
LessonServiceInstance,
UserServiceInstance,
};

View File

@ -1,11 +1,10 @@
import { makeAutoObservable } from 'mobx';
import { AxiosInstance } from 'axios';
import Cookies from 'js-cookie';
/**
* Services
*/
import AuthService from '@educt/services/AuthService';
import { AuthServiceInstance } from '@educt/services';
/**
* Stores
@ -15,13 +14,10 @@ import RootStore from './RootStore';
export default class AuthStore {
public root: RootStore;
public authService: AuthService;
public isLoggedIn: boolean = !!Cookies.get('logged_in');
constructor(root: RootStore, api: AxiosInstance) {
constructor(root: RootStore) {
this.root = root;
this.authService = new AuthService(api);
makeAutoObservable(this);
}
@ -31,7 +27,7 @@ export default class AuthStore {
public async login(login: string, password: string) {
try {
const result = await this.authService.requestLogin(login, password);
const result = await AuthServiceInstance.requestLogin(login, password);
this.setIsLoggedIn(true);
Cookies.set('logged_in', 'true');
@ -44,7 +40,7 @@ export default class AuthStore {
public async logout() {
try {
const result = await this.authService.requestLogout();
const result = await AuthServiceInstance.requestLogout();
this.setIsLoggedIn(false);
this.root.userStore.reset();

View File

@ -1,4 +1,3 @@
import { AxiosInstance } from 'axios';
import { makeAutoObservable, runInAction } from 'mobx';
/**
@ -9,7 +8,7 @@ import { ICategory } from '@educt/interfaces';
/**
* Services
*/
import CategoryService from '@educt/services/CategoryService';
import { CategoryServiceInstance } from '@educt/services';
/**
* Stores
@ -19,15 +18,12 @@ import RootStore from './RootStore';
export default class CategoryStore {
public root: RootStore;
public categoryService: CategoryService;
public categories: ICategory[] | null = null;
public isLoading: boolean = false;
constructor(root: RootStore, api: AxiosInstance) {
constructor(root: RootStore) {
this.root = root;
this.categoryService = new CategoryService(api);
makeAutoObservable(this);
}
@ -38,7 +34,7 @@ export default class CategoryStore {
public async loadCategories() {
try {
this.setLoading(true);
const result = await this.categoryService.fetchAll();
const result = await CategoryServiceInstance.fetchAll();
runInAction(() => {
this.categories = result.data;

View File

@ -1,4 +1,3 @@
import { AxiosInstance } from 'axios';
import { makeAutoObservable, runInAction } from 'mobx';
/**
@ -6,30 +5,27 @@ import { makeAutoObservable, runInAction } from 'mobx';
*/
import { FetchCoursesParams } from '@educt/types';
import { ICourse } from '@educt/interfaces';
import { CourseStatusEnum } from '@educt/enums';
/**
* Services
*/
import { CourseService } from '@educt/services/CourseService';
import { CourseServiceInstance } from '@educt/services';
/**
* Stores
*/
import RootStore from './RootStore';
import { CourseStatusEnum } from '@educt/enums';
export default class CourseStore {
public root: RootStore;
public courseService: CourseService;
public courses: Omit<ICourse, 'teacher' | 'students' | 'lessons'>[] | null = null;
public isLoading: boolean = false;
constructor(root: RootStore, api: AxiosInstance) {
constructor(root: RootStore) {
this.root = root;
this.courseService = new CourseService(api);
makeAutoObservable(this);
}
@ -40,7 +36,7 @@ export default class CourseStore {
public async loadCourses(params?: FetchCoursesParams) {
try {
this.setLoading(true);
const result = await this.courseService.fetchAll(params);
const result = await CourseServiceInstance.fetchAll(params);
/**
* Load courses in store
@ -59,7 +55,7 @@ export default class CourseStore {
public async deleteCourse(id: string) {
try {
const result = await this.courseService.delete(id);
const result = await CourseServiceInstance.delete(id);
/**
* Remove deleted course form store
@ -97,7 +93,7 @@ export default class CourseStore {
/**
* Make request on update course status
*/
const result = await this.courseService.setStatus(id, status);
const result = await CourseServiceInstance.setStatus(id, status);
return result;
} catch (error: any) {
/**

View File

@ -1,4 +1,3 @@
import { AxiosInstance } from 'axios';
import { makeAutoObservable } from 'mobx';
import EditCourseStore from './pages/EditCourseStore';
import RootStore from './RootStore';
@ -8,13 +7,13 @@ export default class PageStore {
public editCourseStore: EditCourseStore;
constructor(root: RootStore, api: AxiosInstance) {
constructor(root: RootStore) {
this.root = root;
/**
* Pages
*/
this.editCourseStore = new EditCourseStore(this, api);
this.editCourseStore = new EditCourseStore(this);
makeAutoObservable(this);
}

View File

@ -1,11 +1,5 @@
import apiConfig from '@educt/api.config';
import { makeAutoObservable } from 'mobx';
/**
* Services
*/
import ApiService from '@educt/services/ApiService';
/**
* Stores
*/
@ -14,7 +8,6 @@ import UserStore from './UserStore';
import UIStore from './UIStore';
import CategoryStore from './CategoryStore';
import CourseStore from './CourseStore';
import LessonService from '@educt/services/LessonService';
import PageStore from './PageStore';
export default class RootStore {
@ -30,27 +23,16 @@ export default class RootStore {
public userStore: UserStore;
public apiService: ApiService;
public lessonService: LessonService;
constructor() {
/**
* Services
*/
// TODO add other services
this.apiService = new ApiService(this, apiConfig);
this.lessonService = new LessonService(this.apiService.api);
/**
* Stores
*/
this.pageStore = new PageStore(this, this.apiService.api);
this.courseStore = new CourseStore(this, this.apiService.api);
this.categoryStore = new CategoryStore(this, this.apiService.api);
this.authStore = new AuthStore(this);
this.categoryStore = new CategoryStore(this);
this.courseStore = new CourseStore(this);
this.pageStore = new PageStore(this);
this.uiStore = new UIStore(this);
this.authStore = new AuthStore(this, this.apiService.api);
this.userStore = new UserStore(this, this.apiService.api);
this.userStore = new UserStore(this);
makeAutoObservable(this);
}

View File

@ -1,4 +1,3 @@
import { AxiosInstance } from 'axios';
import { makeAutoObservable, runInAction } from 'mobx';
import * as helpers from '@educt/helpers';
@ -12,7 +11,7 @@ import { UserRoleEnum } from '@educt/enums';
/**
* Services
*/
import UserService from '@educt/services/UserService';
import { UserServiceInstance } from '@educt/services';
/**
* Stores
@ -22,8 +21,6 @@ import RootStore from './RootStore';
export default class UserStore {
public root: RootStore;
public userService: UserService;
public me: IMe | null = null;
public users: IUser[] | null = null;
@ -32,9 +29,8 @@ export default class UserStore {
public isLoading: boolean = false;
constructor(root: RootStore, api: AxiosInstance) {
constructor(root: RootStore) {
this.root = root;
this.userService = new UserService(api);
makeAutoObservable(this);
}
@ -45,7 +41,7 @@ export default class UserStore {
public async loadUsersData(params?: FetchUsersParamsType) {
try {
this.setLoading(true);
const result = await this.userService.fetchAll(params);
const result = await UserServiceInstance.fetchAll(params);
runInAction(() => {
this.users = result.data;
@ -62,7 +58,7 @@ export default class UserStore {
public async loadCurrentUserData() {
try {
const result = await this.userService.fetchMe();
const result = await UserServiceInstance.fetchMe();
const {
data: { id, first_name, last_name, fullname, email, roles, contacts, courses },
} = result;
@ -91,7 +87,7 @@ export default class UserStore {
public async createUser(data: CreateUserParamsType, paramsContext?: FetchUsersParamsType) {
try {
const result = await this.userService.create(data);
const result = await UserServiceInstance.create(data);
if (this.users !== null) {
/**
@ -108,7 +104,7 @@ export default class UserStore {
public async updateUser(id: string, params: UpdateUserParamsType) {
try {
const result = await this.userService.update(id, params);
const result = await UserServiceInstance.update(id, params);
runInAction(() => {
if (this.users !== null) {
@ -134,7 +130,7 @@ export default class UserStore {
*/
public async deleteUser(id: string, paramsContext?: FetchUsersParamsType) {
try {
const result = await this.userService.delete(id);
const result = await UserServiceInstance.delete(id);
if (this.users !== null) {
/**
@ -157,7 +153,7 @@ export default class UserStore {
*/
public async updateCurrentUserContacts(data: IUserContacts) {
try {
const result = await this.userService.updateContacts(data);
const result = await UserServiceInstance.updateContacts(data);
runInAction(() => {
if (this.me) {
@ -180,7 +176,7 @@ export default class UserStore {
*/
public async updateCurrentUserPassword(oldPassword: string, newPassword: string) {
try {
const result = await this.userService.updatePassword(oldPassword, newPassword);
const result = await UserServiceInstance.updatePassword(oldPassword, newPassword);
return result;
} catch (error: any) {
throw error;
@ -195,7 +191,7 @@ export default class UserStore {
*/
public async updateCurrentUserEmail(email: string) {
try {
const result = await this.userService.updateEmail(email);
const result = await UserServiceInstance.updateEmail(email);
return result;
} catch (error: any) {
throw error;
@ -211,7 +207,7 @@ export default class UserStore {
*/
public async updateCurrentUserEmailConfirm(email: string, confirmationCode: string) {
try {
const result = await this.userService.updateEmailConfirm(email, confirmationCode);
const result = await UserServiceInstance.updateEmailConfirm(email, confirmationCode);
runInAction(() => {
if (this.me) {
this.me.email = result.data.email;

View File

@ -1,11 +1,10 @@
import { makeAutoObservable, runInAction } from 'mobx';
import * as helpers from '@educt/helpers';
import { ICourse, ILesson } from '@educt/interfaces';
import { CourseService } from '@educt/services/CourseService';
import LessonService from '@educt/services/LessonService';
import { AxiosInstance } from 'axios';
import { makeAutoObservable, runInAction } from 'mobx';
import PageStore from '../PageStore';
import { CourseServiceInstance, LessonServiceInstance } from '@educt/services';
export default class EditCourseStore {
public pageStore: PageStore;
@ -13,11 +12,8 @@ export default class EditCourseStore {
public deletingLesson: ILesson | undefined;
private courseService: CourseService;
constructor(pageStore: PageStore, api: AxiosInstance) {
constructor(pageStore: PageStore) {
this.pageStore = pageStore;
this.courseService = new CourseService(api);
makeAutoObservable(this);
}
@ -30,7 +26,7 @@ export default class EditCourseStore {
*/
public async loadCourseById(id: string) {
try {
const result = await this.courseService.fetchById(id);
const result = await CourseServiceInstance.fetchById(id);
runInAction(() => {
this.course = result.data;
@ -50,7 +46,7 @@ export default class EditCourseStore {
*/
public async deleteLessonById(id: string) {
try {
const result = await this.pageStore.root.lessonService.deleteLesson(id);
const result = await LessonServiceInstance.deleteLesson(id);
runInAction(() => {
if (this.course) {