Skip to content
Snippets Groups Projects
Commit 900f22a2 authored by Santiago González's avatar Santiago González
Browse files

API - Stores typing refactor

parent 04ab5c72
Branches api-migration
No related tags found
No related merge requests found
Showing with 337 additions and 199 deletions
......@@ -4,40 +4,36 @@ import { ApolloLink, execute, FetchResult } from "apollo-link";
import { onError } from "apollo-link-error";
import { createUploadLink } from "apollo-upload-client";
import * as _ from "lodash";
import * as moment from "moment";
// region typeDefs
export type BaseRequest = {};
export type BaseQueryRequest = BaseRequest;
export type BaseMutationRequest = BaseRequest;
export type BaseResponse = {
date: Date;
request: BaseRequest;
};
export type SuccessfulQueryResponse<T = any> = {
// region typeDef
export type SuccessfulQueryResponse<R = {}, T = any> = {
request: R;
date: moment.Moment;
success: true;
rawResponse: ApolloQueryResult<T>;
};
export type FailedQueryResponse<T = any> = {
export type FailedQueryResponse<R = {}, T = any> = {
request: R;
date: moment.Moment;
success: false;
rawResponse: ApolloQueryResult<T> | Error;
};
export type BaseQueryResponse<T = any> = BaseResponse & {
request: BaseQueryRequest;
} & (SuccessfulQueryResponse | FailedQueryResponse);
export type QueryResponse<R = {}, T = any> = SuccessfulQueryResponse<R, T> | FailedQueryResponse<R, T>;
export type SuccessfulMutationResponse = {
export type SuccessfulMutationResponse<R = {}> = {
request: R;
date: moment.Moment;
success: true;
rawResponse: FetchResult;
};
export type FailedMutationResponse = {
export type FailedMutationResponse<R = {}> = {
request: R;
date: moment.Moment;
success: false;
rawResponse: FetchResult | Error;
};
export type BaseMutationResponse = BaseResponse & {
request: BaseMutationRequest;
} & (SuccessfulMutationResponse | FailedMutationResponse);
export type MutationResponse<R = {}> = SuccessfulMutationResponse | FailedMutationResponse;
// endregion
const parseHeaders = (rawHeaders: string) => {
......@@ -153,55 +149,65 @@ export class Base {
}
});
public static query<
T = any,
R extends BaseQueryResponse<T> = BaseQueryResponse<T>,
TVariables = OperationVariables
>(request: BaseQueryRequest, options: QueryOptions<TVariables>): Promise<R> {
public static query<TVariables = OperationVariables>(
request: SuccessfulQueryResponse["request"],
options: QueryOptions<TVariables>
): Promise<SuccessfulQueryResponse | FailedQueryResponse> {
return this.client
.query(options)
.then(response => this.processResponse<R>(request, response))
.catch(reason => this.processErrorResponse<R>(request, reason));
.then(response => this.processResponse(request, response))
.catch(reason => this.processErrorResponse(request, reason));
}
public static mutate<
R extends BaseMutationResponse = BaseMutationResponse,
T = any,
TVariables = OperationVariables
>(request: BaseMutationRequest, options: MutationOptions<T, TVariables>): Promise<R> {
public static mutate<T = any, TVariables = OperationVariables>(
request: SuccessfulMutationResponse["request"],
options: MutationOptions<T, TVariables>
): Promise<SuccessfulMutationResponse | FailedMutationResponse> {
return this.client
.mutate(options)
.then(response => this.processResponse<R>(request, response))
.catch(reason => this.processErrorResponse<R>(request, reason));
.then(response => this.processResponse(request, response))
.catch(reason => this.processErrorResponse(request, reason));
}
public static processResponse<R extends BaseQueryResponse>(
request: BaseQueryRequest,
result: ApolloQueryResult<any>
public static processResponse<R extends SuccessfulQueryResponse>(
request: R["request"],
result: R["rawResponse"]
): R;
public static processResponse<R extends BaseMutationResponse>(request: BaseMutationRequest, result: FetchResult): R;
public static processResponse<R extends BaseQueryResponse | BaseMutationResponse>(
request: BaseQueryRequest | BaseMutationRequest,
result: ApolloQueryResult<any> | FetchResult
public static processResponse<R extends SuccessfulMutationResponse>(
request: R["request"],
result: R["rawResponse"]
): R;
public static processResponse<R extends SuccessfulQueryResponse | SuccessfulMutationResponse>(
request: R["request"],
result: R["rawResponse"]
): R {
const sharedResponse = { request, date: new Date(), rawResponse: result };
const sharedResponse = { request, date: moment(), rawResponse: result };
const response: BaseQueryResponse | BaseMutationResponse = _.isNil(result.errors)
const response: QueryResponse | MutationResponse = _.isNil(result.errors)
? { ...sharedResponse, success: true }
: { ...sharedResponse, success: false };
// console.log({ response });
return response as R;
}
public static processErrorResponse<R extends BaseQueryResponse = BaseQueryResponse>(
request: BaseQueryRequest,
public static processErrorResponse<R extends FailedQueryResponse>(
request: FailedQueryResponse["request"],
result: Error
): R;
public static processErrorResponse<R extends BaseMutationResponse>(request: BaseMutationRequest, result: Error): R;
public static processErrorResponse<R extends BaseMutationResponse | BaseQueryResponse>(
request: BaseQueryRequest | BaseMutationRequest,
public static processErrorResponse<R extends FailedMutationResponse>(
request: FailedMutationResponse["request"],
result: Error
): R;
public static processErrorResponse<R extends FailedQueryResponse | FailedMutationResponse>(
request: R["request"],
result: Error
): R {
return { request, date: new Date(), success: false, rawResponse: result } as R;
const errorResponse = { request, date: moment(), success: false, rawResponse: result };
// console.log({ errorResponse });
return errorResponse as R;
}
}
import { ReactNativeFile } from "apollo-upload-client";
import gql from "graphql-tag";
import * as _ from "lodash";
import * as Errors from "../errors";
import * as Factories from "../factories";
import * as Models from "../models";
import { Base, BaseQueryRequest, BaseQueryResponse } from "./Base";
import { Base } from "./Base";
export type FetchCoursesRequest = BaseQueryRequest;
export type FetchCoursesResponse = BaseQueryResponse & {
export type FetchCoursesRequest = {};
export type SuccessfulFetchCoursesResponse = {
success: true;
courses: Models.Course[];
};
export type FetchCourseRequest = BaseQueryRequest & {
id?: number;
code?: string;
export type FailedFetchCoursesResponse = {
success: false;
error: Errors.GenericError;
};
export type FetchCoursesResponse = SuccessfulFetchCoursesResponse | FailedFetchCoursesResponse;
export type FetchCourseResponse = BaseQueryResponse & {
export type FetchCourseRequest = { id: string } | { code: string };
export type SuccessfulFetchCourseResponse = {
success: true;
course: Models.Course;
};
export type FailedFetchCourseResponse = {
success: false;
error: Errors.GenericError;
};
export type FetchCourseResponse = SuccessfulFetchCourseResponse | FailedFetchCourseResponse;
export type CreateCourseRequest = BaseQueryRequest & {
export type CreateCourseRequest = {
token: string;
code: string;
name: string;
......@@ -30,12 +38,17 @@ export type CreateCourseRequest = BaseQueryRequest & {
icon?: File | ReactNativeFile;
onProgress?: (e: ProgressEvent) => void;
};
export type CreateCourseResponse = BaseQueryResponse & {
export type SuccessfulCreateCourseResponse = {
success: true;
course: Models.Course;
};
export type FailedCreateCourseResponse = {
success: false;
error: Errors.GenericError;
};
export type CreateCourseResponse = SuccessfulCreateCourseResponse | FailedCreateCourseResponse;
export type UpdateCourseRequest = BaseQueryRequest & {
export type UpdateCourseRequest = {
token: string;
id: string;
code?: string;
......@@ -46,14 +59,19 @@ export type UpdateCourseRequest = BaseQueryRequest & {
icon?: File | ReactNativeFile;
onProgress?: (e: ProgressEvent) => void;
};
export type UpdateCourseResponse = BaseQueryResponse & {
export type SuccessfulUpdateCourseResponse = {
success: true;
course: Models.Course;
};
export type FailedUpdateCourseResponse = {
success: false;
error: Errors.GenericError;
};
export type UpdateCourseResponse = SuccessfulUpdateCourseResponse | FailedUpdateCourseResponse;
export class Course extends Base {
public static fetchCourses(request: FetchCoursesRequest = {}): Promise<FetchCoursesResponse> {
return this.query<any, FetchCoursesResponse>(request, {
public static async fetchCourses(request: FetchCoursesRequest = {}): Promise<FetchCoursesResponse> {
const apiResponse = await this.query(request, {
query: gql`
{
courses {
......@@ -66,22 +84,33 @@ export class Course extends Base {
}
`,
variables: {}
}).then(response => {
if (!response.success) return response;
});
const { data } = response.rawResponse;
if (!apiResponse.success)
return {
success: false,
error: new Errors.GenericError()
};
if (_.isArray(data.courses))
response.courses = data.courses.map((course: any) => Factories.Course.fromJSON(course));
const { data } = apiResponse.rawResponse;
return response;
});
if (!_.isArray(data.courses))
return {
success: false,
error: new Errors.GenericError()
};
return {
success: true,
courses: data.courses.map((course: any) => Factories.Course.fromJSON(course))
};
}
public static fetchCourse(request: FetchCourseRequest): Promise<FetchCourseResponse> {
const { id, code } = request;
public static async fetchCourse(request: FetchCourseRequest): Promise<FetchCourseResponse> {
const { id } = request as { id?: string };
const { code } = request as { code?: string };
return this.query<any, FetchCourseResponse>(request, {
const apiResponse = await this.query(request, {
query: gql`
query course($id: Int, $code: String) {
course(id: $id, code: $code) {
......@@ -99,15 +128,22 @@ export class Course extends Base {
}
`,
variables: { id, code }
}).then(response => {
if (!response.success) return response;
});
const { data } = response.rawResponse;
if (!apiResponse.success)
return {
success: false,
error: new Errors.GenericError()
};
if (!_.isNil(data.course)) response.course = Factories.Course.fromJSON(data.course);
const { data } = apiResponse.rawResponse;
return response;
});
if (_.isNil(data.course)) return { success: false, error: new Errors.GenericError() };
return {
success: true,
course: Factories.Course.fromJSON(data.course)
};
}
public static async createCourse(request: CreateCourseRequest): Promise<CreateCourseResponse> {
......@@ -144,19 +180,30 @@ export class Course extends Base {
context: { token, onProgress }
});
if (!response.success) return response;
if (!response.success)
return {
success: false,
error: new Errors.GenericError()
};
const { data } = response.rawResponse;
if (!_.isNil(data.createCourse)) response.course = Factories.Course.fromJSON(data.createCourse);
if (_.isNil(data) || _.isNil(data.createCourse))
return {
success: false,
error: new Errors.GenericError()
};
return response;
return {
success: true,
course: Factories.Course.fromJSON(data.createCourse)
};
}
public static async updateCourse(request: UpdateCourseRequest): Promise<UpdateCourseResponse> {
const { token, id, code, name, eva, semester, year, icon, onProgress } = request; // TODO: implement!
const queryResponse = await this.query<any, UpdateCourseResponse>(request, {
const queryResponse = await this.query(request, {
query: gql`
mutation updateCourse(
$id: String!
......@@ -187,12 +234,23 @@ export class Course extends Base {
context: { token }
});
if (!queryResponse.success) return Promise.resolve(queryResponse);
if (!queryResponse.success)
return {
success: false,
error: new Errors.GenericError()
};
const { data } = queryResponse.rawResponse;
if (!_.isNil(data.course)) queryResponse.course = Factories.Course.fromJSON(data.course);
if (_.isNil(data) || _.isNil(data.course))
return {
success: false,
error: new Errors.GenericError()
};
return Promise.resolve(queryResponse);
return {
success: true,
course: Factories.Course.fromJSON(data.course)
};
}
}
import gql from "graphql-tag";
import * as _ from "lodash";
import * as Errors from "../errors";
import * as Factories from "../factories";
import * as Models from "../models";
import { Base, BaseQueryRequest, BaseQueryResponse } from "./Base";
import { Base } from "./Base";
export type FetchFAQsRequest = BaseQueryRequest;
export type FetchFAQsResponse = BaseQueryResponse & {
export type FetchFAQsRequest = {};
export type SuccessfulFetchFAQsResponse = {
success: true;
faqs: Models.FAQ[];
};
export type FailedFetchFAQsResponse = {
success: false;
error: Errors.GenericError;
};
export type FetchFAQsResponse = SuccessfulFetchFAQsResponse | FailedFetchFAQsResponse;
export class FAQ extends Base {
public static async fetchFAQs(request: FetchFAQsRequest = {}): Promise<FetchFAQsResponse> {
return this.query<any, FetchFAQsResponse>(request, {
const apiResponse = await this.query(request, {
query: gql`
{
faqs {
......@@ -22,16 +28,24 @@ export class FAQ extends Base {
isHTML
}
}
`,
variables: {}
}).then(response => {
if (!response.success) return response;
`
});
const { data } = response.rawResponse;
if (
!apiResponse.success ||
_.isNil(apiResponse.rawResponse.data) ||
!_.isArray(apiResponse.rawResponse.data.faqs)
)
return {
success: false,
error: new Errors.GenericError()
};
if (_.isArray(data.faqs)) response.faqs = data.faqs.map((faq: any) => Factories.FAQ.fromJSON(faq));
const { data } = apiResponse.rawResponse;
return response;
});
return {
success: true,
faqs: data.faqs.map((faq: any) => Factories.FAQ.fromJSON(faq))
};
}
}
import gql from "graphql-tag";
import * as _ from "lodash";
import * as Factories from "../factories";
import * as Models from "../models";
import { Base, BaseQueryRequest, BaseQueryResponse } from "./Base";
export type FetchUpdatesRequest = BaseQueryRequest;
export type FetchUpdatesResponse = BaseQueryResponse & {
updates: Models.Update[];
};
export class Update extends Base {
public static async fetchUpdates(request: FetchUpdatesRequest): Promise<FetchUpdatesResponse> {
return this.query<any, FetchUpdatesResponse>(request, {
query: gql`
{
coursesUpdates {
id
code
name
eva
}
}
`,
variables: {}
}).then(response => {
if (!response.success) return response;
const { data } = response.rawResponse;
if (_.isArray(data.coursesUpdates))
response.updates = data.coursesUpdates.map((update: any) => Factories.Course.fromJSON(update));
return response;
});
}
}
import gql from "graphql-tag";
import * as _ from "lodash";
import * as Errors from "../errors";
import * as Factories from "../factories";
import * as Models from "../models";
import { Base, BaseQueryRequest, BaseQueryResponse } from "./Base";
import { Base } from "./Base";
export type SignInRequest = BaseQueryRequest & {
export type SignInRequest = {
email: string;
password: string;
};
export type SignInResponse = BaseQueryResponse & {
token?: string;
user?: Models.User;
export type SuccessfulSignInResponse = {
success: true;
token: string;
user: Models.User;
};
export type FailedSignInResponse = {
success: false;
error: Errors.GenericError;
};
export type SignInResponse = SuccessfulSignInResponse | FailedSignInResponse;
export type FetchMeRequest = BaseQueryRequest & {
export type FetchMeRequest = {
token: string;
};
export type FetchMeResponse = BaseQueryResponse & {
user?: Models.User;
export type SuccessfulFetchMeResponse = {
success: true;
user: Models.User;
};
export type FailedFetchMeResponse = {
success: false;
error: Errors.GenericError;
};
export type FetchMeResponse = SuccessfulFetchMeResponse | FailedFetchMeResponse;
export type SignUpRequest = BaseQueryRequest & {
export type SignUpRequest = {
email: string;
password: string;
name: string;
};
export type SignUpResponse = BaseQueryResponse & {
token?: string;
user?: Models.User;
export type SuccessfulSignUpResponse = {
success: true;
token: string;
user: Models.User;
};
export type FailedSignUpResponse = {
success: false;
error: Errors.GenericError | Errors.ObjectNotFoundError;
};
export type SignUpResponse = SuccessfulSignUpResponse | FailedSignUpResponse;
export class User extends Base {
public static async signIn(request: SignInRequest): Promise<SignInResponse> {
const { email, password } = request;
const response = await this.mutate<SignInResponse>(request, {
const apiResponse = await this.mutate(request, {
mutation: gql`
mutation signIn($email: String!, $password: String!) {
signIn(user: { email: $email, password: $password }) {
......@@ -55,24 +71,39 @@ export class User extends Base {
variables: { email, password }
});
if (!response.success) return response;
if (!apiResponse.success)
return {
success: false,
error: new Errors.GenericError()
};
const { data } = response.rawResponse;
const { data } = apiResponse.rawResponse;
if (!_.isNil(data) && !_.isNil(data.signIn)) {
const { signIn } = data;
if (_.isNil(data) || _.isNil(data.signIn))
return {
success: false,
error: new Errors.GenericError()
};
response.user = Factories.User.fromJSON(signIn.user);
response.token = signIn.token;
}
const { token, user } = data.signIn;
return response;
if (_.isNil(token) || _.isNil(user))
return {
success: false,
error: new Errors.GenericError()
};
return {
success: true,
token,
user: Factories.User.fromJSON(user)
};
}
public static async fetchMe(request: FetchMeRequest): Promise<FetchMeResponse> {
const { token } = request;
const response = await this.query<any, FetchMeResponse>(request, {
const apiResponse = await this.query(request, {
query: gql`
{
me {
......@@ -89,13 +120,24 @@ export class User extends Base {
}
});
if (!response.success) return response;
if (!apiResponse.success)
return {
success: false,
error: new Errors.GenericError()
};
const { data } = response.rawResponse;
const { data } = apiResponse.rawResponse;
if (!_.isNil(data) && !_.isNil(data.me)) response.user = Factories.User.fromJSON(data.me);
if (_.isNil(data) || _.isNil(data.me))
return {
success: false,
error: new Errors.GenericError()
};
return response;
return {
success: true,
user: Factories.User.fromJSON(data.me)
};
}
public static async signUp(request: SignUpRequest): Promise<SignUpResponse> {
......@@ -104,7 +146,7 @@ export class User extends Base {
const response = await this.mutate<SignUpResponse>(request, {
mutation: gql`
mutation signUp($email: String!, $password: String!, $name: String!) {
signUp(user: { email: $email, password: $password, name: $name }) {
signUp(user: { email: $email, password: $password, name: $name, role: User }) {
token
user {
id
......@@ -119,17 +161,26 @@ export class User extends Base {
variables: { email, password, name }
});
if (!response.success) return response;
if (!response.success)
return {
success: false,
error: new Errors.GenericError()
};
const { data } = response.rawResponse;
if (!_.isNil(data) && !_.isNil(data.signUp)) {
const { signUp } = data;
if (_.isNil(data) || _.isNil(data.signUp) || _.isNil(data.signUp.user) || _.isNil(data.signUp.token))
return {
success: false,
error: new Errors.ObjectNotFoundError("No se pudo crear el usuario.")
};
response.user = Factories.User.fromJSON(signUp.user);
response.token = signUp.token;
}
const { signUp } = data;
return response;
return {
success: true,
user: Factories.User.fromJSON(signUp.user),
token: signUp.token
};
}
}
export * from "./Base";
export * from "./Course";
export * from "./FAQ";
export * from "./Update";
export * from "./User";
import { Phrase } from "src/core/language";
import { Helper } from "src/core/language/Helper";
import ExtendableError from "ts-error";
export class BaseError extends ExtendableError {
constructor(public phrase: Phrase) {
super(Helper.phraseToString(phrase));
}
}
import { Phrase } from "src/core/language";
import { BaseError } from "./BaseError";
export class GenericError extends BaseError {
constructor(public phrase: Phrase = "Ha ocurrido un error") {
super(phrase);
}
}
import { BaseError } from "./BaseError";
export class NotEnoughPermissionsError extends BaseError {}
import { BaseError } from "./BaseError";
export class ObjectNotFoundError extends BaseError {}
import { BaseError } from "./BaseError";
export class UserNotAuthenticatedError extends BaseError {}
import { Phrase } from "src/core/language";
import { BaseError } from "./BaseError";
export class ValidationError<T = string> extends BaseError {
constructor(public phrase: Phrase, public field: T) {
super(phrase);
}
}
export { BaseError } from "./BaseError";
export { GenericError } from "./GenericError";
export { NotEnoughPermissionsError } from "./NotEnoughPermissionsError";
export { ObjectNotFoundError } from "./ObjectNotFoundError";
export { UserNotAuthenticatedError } from "./UserNotAuthenticatedError";
export { ValidationError } from "./ValidationError";
import * as _ from "lodash";
import { Phrase } from "src/core/language/Phrase";
export class Helper {
public static phraseToString(phrase: Phrase): string {
if (typeof phrase === "string") return phrase;
if (_.isArray(phrase)) return phrase.map(this.phraseToString).join("");
return phrase.text;
}
}
export type PhraseNode<T = undefined> = {
text: string;
bold?: boolean;
italic?: boolean;
underline?: boolean;
key: T;
};
export type Phrase<T = undefined> = string | PhraseNode<T> | Array<PhraseNode<T>>;
export * from "./Phrase";
import { Base } from "./internal";
export class Update extends Base {
public code: string;
public name: string;
public number: number;
public title: string;
public courseImageURL: string;
}
......@@ -2,5 +2,4 @@ export * from "./Base";
export * from "./Course";
export * from "./CourseClass";
export * from "./FAQ";
export * from "./Update";
export * from "./User";
......@@ -23,7 +23,7 @@ export class FAQState extends State {
return API.FAQ.fetchFAQs().then(response => {
this.setFAQs({
isLoading: false,
value: response.faqs
value: response.success ? response.faqs : undefined
});
return response;
......
......
import { configure } from "mobx";
import { CourseStore } from "./course/CourseStore";
import { FAQState } from "./FAQState";
import { UpdateState } from "./UpdateState";
import { UserState } from "./UserState";
import { UserStore } from "./user/UserStore";
configure({ enforceActions: "observed" });
......@@ -11,13 +10,11 @@ export class Store {
public courseState = new CourseStore(this);
public faqsState = new FAQState(this);
public updateState = new UpdateState(this);
public userState = new UserState(this);
public userState = new UserStore(this);
public reset() {
this.courseState = new CourseStore(this);
this.faqsState = new FAQState(this);
this.updateState = new UpdateState(this);
this.userState = new UserState(this);
this.userState = new UserStore(this);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment