|
@ -1,42 +1,66 @@ |
|
|
|
|
|
import type { IApiResponse, IApiDataResponse, IMovie, IMovieDetails, IPaginatedResponse } from "@autoplex-api/request"; |
|
|
import { ActionTree } from "vuex"; |
|
|
import { ActionTree } from "vuex"; |
|
|
import { Actions } from "./generics"; |
|
|
import { Actions } from "./generics"; |
|
|
import { IState } from "./state"; |
|
|
import { IState } from "./state"; |
|
|
import { Mutation, MutationsTypes } from "./mutations"; |
|
|
import { Mutation, MutationsTypes } from "./mutations"; |
|
|
import router, { authFetch, authFetchApi } from "../routes"; |
|
|
|
|
|
|
|
|
import router from "../routes"; |
|
|
import { GettersTypes } from "./getters"; |
|
|
import { GettersTypes } from "./getters"; |
|
|
import { IApiDataResponse } from "@common/api_schema"; |
|
|
|
|
|
|
|
|
|
|
|
// Payload types
|
|
|
// Payload types
|
|
|
type IAuthenticatePayload = { email: string, password: string, remember: boolean }; |
|
|
|
|
|
type IAuthenticateResult = { email?: string[], password?: string[] } | null; |
|
|
|
|
|
type IAuthFetchPayload = { path: string, options?: RequestInit }; |
|
|
|
|
|
|
|
|
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 |
|
|
* The available actions te perform |
|
|
*/ |
|
|
*/ |
|
|
export enum Action { |
|
|
export enum Action { |
|
|
|
|
|
// RESTful Generics
|
|
|
|
|
|
Fetch = "FETCH", |
|
|
|
|
|
Get = "GET", |
|
|
|
|
|
Post = "POST", |
|
|
|
|
|
Put = "PUT", |
|
|
|
|
|
Delete = "DELETE", |
|
|
|
|
|
|
|
|
// Authentication
|
|
|
// Authentication
|
|
|
AuthAuthenticate = "AUTH_AUTHENTICATE", |
|
|
|
|
|
AuthForget = "AUHT_FORGET", |
|
|
|
|
|
AuthLoad = "AUTH_LOAD", |
|
|
|
|
|
|
|
|
AuthRegister = "AUTH_REGISTER", |
|
|
|
|
|
AuthLogin = "AUTH_LOGIN", |
|
|
|
|
|
AuthForget = "AUHT_FORGET", |
|
|
|
|
|
AuthLoad = "AUTH_LOAD", |
|
|
|
|
|
|
|
|
// RESTful
|
|
|
|
|
|
AuthFetch = "AUTH_FETCH", |
|
|
|
|
|
RequestMovie = "REQUEST_MOVIE" |
|
|
|
|
|
|
|
|
// Movies Methods
|
|
|
|
|
|
ActiveMovieRequests = "ACTIVE_MOVIE_REQUESTS", |
|
|
|
|
|
MovieDetails = "MOVIE_DETAILS", |
|
|
|
|
|
RequestMovie = "REQUEST_MOVIE", |
|
|
|
|
|
SearchMovies = "SEARCH_MOVIES" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* The action function signatures |
|
|
* The action function signatures |
|
|
*/ |
|
|
*/ |
|
|
export type ActionsTypes = { |
|
|
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
|
|
|
// Authentication
|
|
|
[Action.AuthAuthenticate]: (payload: IAuthenticatePayload) => Promise<IAuthenticateResult>, |
|
|
|
|
|
[Action.AuthForget] : () => void, |
|
|
|
|
|
[Action.AuthLoad] : () => boolean, |
|
|
|
|
|
|
|
|
[Action.AuthRegister]: (payload: IRegisterPayload) => Promise<IFormErrors|null>, |
|
|
|
|
|
[Action.AuthLogin] : (payload: ILoginPayload) => Promise<IFormErrors|null>, |
|
|
|
|
|
[Action.AuthForget] : () => void, |
|
|
|
|
|
[Action.AuthLoad] : () => boolean, |
|
|
|
|
|
|
|
|
// RESTful
|
|
|
|
|
|
[Action.AuthFetch] : (payload: IAuthFetchPayload) => Promise<Response>, |
|
|
|
|
|
[Action.RequestMovie]: (tmdbId: number | string) => Promise<IApiDataResponse<{ ticketId: number }>> |
|
|
|
|
|
|
|
|
// 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 }>]> |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
@ -44,38 +68,138 @@ export type ActionsTypes = { |
|
|
*/ |
|
|
*/ |
|
|
export const actions: Actions<IState, GettersTypes, MutationsTypes, ActionsTypes> & ActionTree<IState, IState> = { |
|
|
export const actions: Actions<IState, GettersTypes, MutationsTypes, ActionsTypes> & ActionTree<IState, IState> = { |
|
|
|
|
|
|
|
|
// Authentication ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
// Generic RESTful API -------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Authenticate the credentials of a user and log them in |
|
|
|
|
|
|
|
|
* Fetch request providing authentication and logout upon unauthorized requests |
|
|
*/ |
|
|
*/ |
|
|
[Action.AuthAuthenticate]({commit, dispatch}, {email, password, remember = false}) { |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
|
fetch(`/auth/login?use_cookies=${navigator.cookieEnabled}`, { |
|
|
|
|
|
method: "post", |
|
|
|
|
|
headers: { "Content-Type": "application/json" }, |
|
|
|
|
|
body: JSON.stringify({email, password}) |
|
|
|
|
|
}) |
|
|
|
|
|
.then(async response => { |
|
|
|
|
|
let body = await response.json(); |
|
|
|
|
|
if (response.status !== 200) { |
|
|
|
|
|
if (response.status === 401) { |
|
|
|
|
|
body.errors = { "email": [ "Email or password is incorrect" ] }; |
|
|
|
|
|
} |
|
|
|
|
|
resolve(body.errors || {}); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
commit(Mutation.UserLoad, body.token); |
|
|
|
|
|
commit(Mutation.UserStore, remember); |
|
|
|
|
|
resolve(null); |
|
|
|
|
|
}) |
|
|
|
|
|
.catch(e => { |
|
|
|
|
|
console.error("Error occurred during submission:", e); |
|
|
|
|
|
reject(e); |
|
|
|
|
|
|
|
|
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 |
|
|
* Forget the login token and log the user out |
|
|
*/ |
|
|
*/ |
|
@ -100,33 +224,41 @@ export const actions: Actions<IState, GettersTypes, MutationsTypes, ActionsTypes |
|
|
return true; |
|
|
return true; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// RESTful -------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
// Movies --------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Fetch request providing authentication and logout upon unauthorized requests |
|
|
|
|
|
|
|
|
* Get the user's active movie requests |
|
|
*/ |
|
|
*/ |
|
|
async [Action.AuthFetch]({commit, state}, {path, options = {}}) { |
|
|
|
|
|
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}` |
|
|
|
|
|
|
|
|
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)}` |
|
|
}); |
|
|
}); |
|
|
let response = await fetch(path, options); |
|
|
|
|
|
if (response.status === 401) { |
|
|
|
|
|
commit(Mutation.UserForget, undefined); |
|
|
|
|
|
router.push({ name: "Login" }); |
|
|
|
|
|
throw Error("Unauthorized"); |
|
|
|
|
|
} |
|
|
|
|
|
return response; |
|
|
|
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Request a movie |
|
|
* Request a movie |
|
|
*/ |
|
|
*/ |
|
|
async [Action.RequestMovie](_, tmdbId) { |
|
|
|
|
|
return await authFetchApi(`/api/movie/request/create/tmdb/${tmdbId}`); |
|
|
|
|
|
|
|
|
async [Action.RequestMovie]({dispatch}, tmdbId: string|number) { |
|
|
|
|
|
return await dispatch(Action.Post, { |
|
|
|
|
|
path: `/api/movie/request/create/tmdb/${tmdbId}` |
|
|
|
|
|
}); |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |