You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

264 lines
7.3 KiB

import type { IApiResponse, IApiDataResponse, IMovie, IMovieDetails, IPaginatedResponse } from "@autoplex-api/request";
import { ActionTree } from "vuex";
import { Actions } from "./generics";
import { IState } from "./state";
import { Mutation, MutationsTypes } from "./mutations";
import router from "../routes";
import { GettersTypes } from "./getters";
// Payload types
type IRegisterPayload = { token: string, name: string, email: string, password: string, retypePassword: string };
type ILoginPayload = { email: string, password: string, remember: boolean };
type IGetPayload = { path: string, useAuth?: boolean, options?: RequestInit };
type IPostPayload = { path: string, body?: any, useAuth?: boolean, options?: RequestInit };
// Result types
type IFormErrors = { [field: string]: string[] };
/**
* The available actions te perform
*/
export enum Action {
// RESTful Generics
Fetch = "FETCH",
Get = "GET",
Post = "POST",
Put = "PUT",
Delete = "DELETE",
// Authentication
AuthRegister = "AUTH_REGISTER",
AuthLogin = "AUTH_LOGIN",
AuthForget = "AUHT_FORGET",
AuthLoad = "AUTH_LOAD",
// Movies Methods
ActiveMovieRequests = "ACTIVE_MOVIE_REQUESTS",
MovieDetails = "MOVIE_DETAILS",
RequestMovie = "REQUEST_MOVIE",
SearchMovies = "SEARCH_MOVIES"
}
/**
* The action function signatures
*/
export type ActionsTypes = {
// RESTful Generics
[Action.Fetch] : (payload: IGetPayload) => Promise<any>,
[Action.Get] : (payload: IGetPayload) => Promise<any>,
[Action.Post] : (payload: IPostPayload) => Promise<any>,
[Action.Put] : (payload: IPostPayload) => Promise<any>,
[Action.Delete]: (payload: IPostPayload) => Promise<any>,
// Authentication
[Action.AuthRegister]: (payload: IRegisterPayload) => Promise<IFormErrors|null>,
[Action.AuthLogin] : (payload: ILoginPayload) => Promise<IFormErrors|null>,
[Action.AuthForget] : () => void,
[Action.AuthLoad] : () => boolean,
// Movie Methods
[Action.ActiveMovieRequests]: () => Promise<[number, IApiDataResponse<IMovie[]>]>,
[Action.MovieDetails] : (tmdbId: number | string) => Promise<[number, IApiDataResponse<IMovieDetails>]>,
[Action.SearchMovies] : (query: string) => Promise<[number, IPaginatedResponse<IMovie>]>,
[Action.RequestMovie] : (tmdbId: number | string) => Promise<[number, IApiDataResponse<{ ticketId: number }>]>
}
/**
* The action function implementations
*/
export const actions: Actions<IState, GettersTypes, MutationsTypes, ActionsTypes> & ActionTree<IState, IState> = {
// Generic RESTful API -------------------------------------------------------------------------
/**
* Fetch request providing authentication and logout upon unauthorized requests
*/
async [Action.Fetch]({commit, state}, {path, useAuth = true, options = {}}) {
if (useAuth) {
if (state.user === null) {
router.push({ name: "Login" });
throw Error("Unauthorized");
}
options.credentials = "include";
options.headers = Object.assign(options.headers ?? {}, {
"Authorization": `Bearer ${state.user.token}`
});
}
let response = await fetch(path, options);
if (useAuth && response.status === 401) {
commit(Mutation.UserForget, undefined);
router.push({ name: "Login" });
throw Error("Unauthorized");
}
return [response.status, <IApiResponse>(await response.json())];
},
/**
* Perform a generic GET request to the API
*/
async [Action.Get]({dispatch}, {path, useAuth, options = {}}) {
return await dispatch(Action.Fetch, {
path,
useAuth,
options: Object.assign(options, { method: "get" })
});
},
/**
* Perform a generic POST request to the API
*/
async [Action.Post]({dispatch}, {path, body, useAuth, options = {}}) {
return await dispatch(Action.Fetch, {
path,
useAuth,
options: Object.assign(options, {
method: "post",
headers: Object.assign(
options.headers ?? {},
body !== undefined ? { "Content-Type": "application/json" } : {}),
body: JSON.stringify(body)
}),
});
},
/**
* Perform a generic PUT request to the API
*/
async [Action.Put]({dispatch}, {path, body, useAuth, options = {}}) {
return await dispatch(Action.Fetch, {
path,
useAuth,
options: Object.assign(options, {
method: "put",
headers: Object.assign(
options.headers ?? {},
body !== undefined ? { "Content-Type": "application/json" } : {}),
body: JSON.stringify(body)
}),
});
},
/**
* Perform a generic DELETE request to the API
*/
async [Action.Delete]({dispatch}, {path, body, useAuth, options = {}}) {
return await dispatch(Action.Fetch, {
path,
useAuth,
options: Object.assign(options, {
method: "delete",
headers: Object.assign(
options.headers ?? {},
body !== undefined ? { "Content-Type": "application/json" } : {}),
body: JSON.stringify(body)
}),
});
},
// Authentication ------------------------------------------------------------------------------
/**
* Register a user. Returns any errors
*/
async [Action.AuthRegister]({dispatch}, payload: IRegisterPayload) {
try {
let [status, body] = await dispatch(Action.Post, {
path: `/auth/register`,
useAuth: false,
body: payload});
if (status !== 200) {
return body.errors ?? {};
}
} catch(e) {
console.error("Error occurred during registration", e);
throw new Error(e);
}
return null;
},
/**
* Authenticate the credentials of a user and log them in. Returns any errors
*/
async [Action.AuthLogin]({commit, dispatch}, {email, password, remember = false}) {
try {
let [status, body] = await dispatch(Action.Post, {
path: `/auth/login?use_cookies=${navigator.cookieEnabled}`,
useAuth: false,
body: {email, password}});
if (status !== 200) {
if (status === 401) {
body.errors = { "email": ["Email or password is incorrect"] };
}
return body.errors || {};
}
commit(Mutation.UserLoad, body.token);
commit(Mutation.UserStore, remember);
} catch(e) {
console.error("Error occurred during login:", e);
throw new Error(e);
}
return null;
},
/**
* Forget the login token and log the user out
*/
[Action.AuthForget]({commit}) {
commit(Mutation.UserForget, undefined);
},
/**
* Load the user from local storage
*/
[Action.AuthLoad]({getters, commit, dispatch}) {
let token = getters.storedToken;
if (!token) {
return false;
}
try {
commit(Mutation.UserLoad, token);
} catch(e) {
dispatch(Action.AuthForget, undefined);
return false;
}
return true;
},
// Movies --------------------------------------------------------------------------------------
/**
* Get the user's active movie requests
*/
async [Action.ActiveMovieRequests]({dispatch}) {
return await dispatch(Action.Get, {
path: `/api/movie/request/tickets/active`
});
},
/**
* Get the details of a specific movie by its TMDb ID
*/
async [Action.MovieDetails]({dispatch}, tmdbId: string|number) {
return await dispatch(Action.Get, {
path: `/api/movie/details/${tmdbId}`
});
},
/**
* Search for movies
*/
async [Action.SearchMovies]({dispatch}, query: string) {
return await dispatch(Action.Get, {
path: `api/movie/search?query=${encodeURI(query)}`
});
},
/**
* Request a movie
*/
async [Action.RequestMovie]({dispatch}, tmdbId: string|number) {
return await dispatch(Action.Post, {
path: `/api/movie/request/create/tmdb/${tmdbId}`
});
}
};