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.
 
 
 
 
 
 

311 lines
8.7 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",
AuthVerify = "AUTH_VERIFY",
// Movies Methods
ActiveMovieRequests = "ACTIVE_MOVIE_REQUESTS",
MovieDetails = "MOVIE_DETAILS",
CancelMovieRequest = "CANCEL_MOVIE_REQUEST",
CreateMovieRequest = "CREATE_MOVIE_REQUEST",
SearchMovies = "SEARCH_MOVIES",
// Movie Store
UpdateMovies = "UPDATE_MOVIES",
UpdateMovieTickets = "UPDATE_MOVIE_TICKETS"
}
/**
* The action function signatures
*/
export type ActionsTypes = {
// RESTful Generics
// @TODO These shouldn't be actions as they pollute the logs
[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] : () => void,
[Action.AuthVerify] : () => void,
// 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.CancelMovieRequest] : (ticketId: number) => Promise<[number, IApiResponse]>,
[Action.CreateMovieRequest] : (tmdbId: number | string) => Promise<[number, IApiDataResponse<{ ticketId: number }>]>,
// Movie Store
[Action.UpdateMovies]: (movies: { newValue: IMovie[], oldValue: IMovie[] }) => void
}
/**
* 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, response] = await dispatch(Action.Post, {
path: `/auth/register`,
useAuth: false,
body: payload});
if (status !== 200) {
return response.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, response] = await dispatch(Action.Post, {
path: "/auth/login",
useAuth: false,
body: {email, password}});
if (status !== 200) {
if (status === 401) {
response.errors = { "email": ["Email or password is incorrect"] };
}
return response.errors || {};
}
console.log(response);
commit(Mutation.UserLoad, response.result.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
*/
async [Action.AuthLoad]({getters, commit, dispatch}) {
let token = getters.storedToken;
if (!token) {
return;
}
try {
commit(Mutation.UserLoad, token);
await dispatch(Action.AuthVerify, undefined);
} catch(e) {
dispatch(Action.AuthForget, undefined);
}
},
/**
* Verify the current authentication session
*/
async [Action.AuthVerify]({getters}) {
let token = getters.storedToken;
// TODO
},
// Movies --------------------------------------------------------------------------------------
/**
* Get the user's active movie requests
*/
async [Action.ActiveMovieRequests]({dispatch}) {
return await dispatch(Action.Get, {
path: `/api/movie/request/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)}`
});
},
/**
* Cancel a movie request
*/
async [Action.CancelMovieRequest]({dispatch}, ticketId: number) {
return await dispatch(Action.Delete, {
path: `/api/movie/request/${ticketId}`
});
},
/**
* Create a movie request
*/
async [Action.CreateMovieRequest]({dispatch}, tmdbId: string|number) {
return await dispatch(Action.Post, {
path: `/api/movie/request/create/${tmdbId}`
});
},
// Movie Store ---------------------------------------------------------------------------------
/**
* Update the cached movies in the store
*/
[Action.UpdateMovies]({commit, dispatch}, {newValue, oldValue}) {
if (newValue.length == 0 && oldValue.length == 0) {
return;
}
if (newValue.length > 0) {
commit(Mutation.StoreMovies, newValue);
}
if (oldValue.length > 0) {
commit(Mutation.FreeMovies, oldValue);
}
commit(Mutation.UpdateMovieTickets, undefined);
}
};