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, [Action.Get] : (payload: IGetPayload) => Promise, [Action.Post] : (payload: IPostPayload) => Promise, [Action.Put] : (payload: IPostPayload) => Promise, [Action.Delete]: (payload: IPostPayload) => Promise, // Authentication [Action.AuthRegister]: (payload: IRegisterPayload) => Promise, [Action.AuthLogin] : (payload: ILoginPayload) => Promise, [Action.AuthForget] : () => void, [Action.AuthLoad] : () => boolean, // Movie Methods [Action.ActiveMovieRequests]: () => Promise<[number, IApiDataResponse]>, [Action.MovieDetails] : (tmdbId: number | string) => Promise<[number, IApiDataResponse]>, [Action.SearchMovies] : (query: string) => Promise<[number, IPaginatedResponse]>, [Action.RequestMovie] : (tmdbId: number | string) => Promise<[number, IApiDataResponse<{ ticketId: number }>]> } /** * The action function implementations */ export const actions: Actions & ActionTree = { // 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, (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}` }); } };