Commit 3e5d4894 authored by Mithunan Sivakumar's avatar Mithunan Sivakumar
Browse files

Initial commit

parent 7ac4b2fb
Pipeline #145922 failed with stages
in 45 seconds
import { action } from 'typesafe-actions';
import { ApolloClientAction, LocalAction } from '../../interface';
import { removeToken } from './helpers';
import { getUserRatingsQuery, giveRatingQuery, loginQuery, signupQuery } from './queries';
import {
AuthActionTypes,
GetUserRatingsParams,
RateMovieParams,
SignInParams,
SignUpParams,
} from './types';
export const signIn = (params: SignInParams): ApolloClientAction =>
action(AuthActionTypes.SIGN_IN.START, params, {
query: loginQuery,
});
export const signUp = (params: SignUpParams): ApolloClientAction =>
action(AuthActionTypes.SIGN_UP.START, params, {
query: signupQuery,
});
export const signOut = (): LocalAction => {
removeToken();
window.location.href = '/';
return action(AuthActionTypes.SIGN_OUT);
};
export const getRatings = (params: GetUserRatingsParams): ApolloClientAction =>
action(AuthActionTypes.GET_RATINGS.START, params, {
query: getUserRatingsQuery,
});
export const rateMovie = (params: RateMovieParams): ApolloClientAction =>
action(AuthActionTypes.RATE_MOVIE.START, params, {
query: giveRatingQuery,
});
import cookie from 'js-cookie';
import jwtDecode from 'jwt-decode';
import moment from 'moment';
import { DecodedToken, EncodedToken, Token } from './types';
const STORAGE_KEY = 'STORAGE';
/**
* Stores a JWT Auth Token in the web storage
* @param {string} token the token to be stored
*/
export function saveToken(token: EncodedToken): void {
const twoDays: number = 3600 * 48000;
const decoded: DecodedToken = jwtDecode(token);
const expires = moment.unix(decoded.iat + twoDays);
cookie.set(STORAGE_KEY, token, {
path: '/',
expires: expires.toDate(),
});
}
/**
* Removes JWT Auth token from the web storage
*/
export function removeToken(): void {
return cookie.remove(STORAGE_KEY, { path: '/' });
}
/**
* Retrieves JWT Auth Token from the web storage
*/
export function getToken(): Token | undefined {
const encodedToken = cookie.get(STORAGE_KEY);
if (!encodedToken) return;
try {
const decoded: DecodedToken = jwtDecode(encodedToken);
return {
...decoded,
encodedToken,
};
} catch (err) {
console.log('No cookie found');
}
}
export { authReducer as auth } from './reducer';
export { default as authSaga } from './sagas';
import { gql } from '@apollo/client';
export const loginQuery = gql`
mutation LoginMutation($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
user {
id
email
username
}
}
}
`;
export const signupQuery = gql`
mutation SignupMutation($email: String!, $username: String!, $password: String!) {
signup(email: $email, username: $username, password: $password) {
token
user {
id
email
username
}
}
}
`;
export const getUserRatingsQuery = gql`
query getUserRatings($token: String!) {
getUserRatings(token: $token) {
rating
comment
date
movie {
id
title
}
}
}
`;
export const giveRatingQuery = gql`
mutation Mutation($token: String!, $movieId: ID!, $rating: Float!, $comment: String) {
giveRating(token: $token, movieID: $movieId, rating: $rating, comment: $comment) {
rating
comment
date
movie {
id
title
}
}
}
`;
import { Action, PayloadAction, TypeConstant } from 'typesafe-actions';
import { getToken } from './helpers';
import { AuthState, AuthActionTypes } from './types';
// Fetch JWT-token from storage
const jwtToken = getToken();
export const signedOutState: AuthState = {
token: null,
user: null,
loggedIn: false,
error: null,
loading: false,
ratings: [],
};
export const initialState: AuthState = jwtToken
? {
token: jwtToken.encodedToken,
user: {
id: jwtToken.id,
email: jwtToken.email,
username: jwtToken.username,
},
loggedIn: true,
error: null,
loading: false,
ratings: [],
}
: signedOutState;
export const authReducer = (
state: AuthState = initialState,
action: Action<TypeConstant> & PayloadAction<TypeConstant, any>,
): AuthState => {
switch (action.type) {
case AuthActionTypes.SIGN_IN.START:
case AuthActionTypes.SIGN_UP.START:
case AuthActionTypes.GET_RATINGS.START:
case AuthActionTypes.RATE_MOVIE.START: {
return { ...state, loading: true, error: null };
}
case AuthActionTypes.SIGN_IN.SUCCESS:
case AuthActionTypes.SIGN_UP.SUCCESS: {
const { token, user } = action.payload;
return {
...state,
loggedIn: true,
token,
user,
loading: false,
};
}
case AuthActionTypes.GET_RATINGS.SUCCESS: {
return { ...state, ratings: action.payload, loading: false };
}
case AuthActionTypes.RATE_MOVIE.SUCCESS: {
return { ...state, ratings: [...state.ratings, action.payload], loading: false };
}
case AuthActionTypes.SIGN_OUT:
return signedOutState;
case AuthActionTypes.SIGN_IN.ERROR:
case AuthActionTypes.SIGN_UP.ERROR:
case AuthActionTypes.GET_RATINGS.ERROR:
case AuthActionTypes.RATE_MOVIE.ERROR: {
return { ...state, error: action.payload, loading: false };
}
default:
return state;
}
};
import { all, call, fork, put, takeEvery } from 'redux-saga/effects';
import Swal from 'sweetalert2';
import { apiCaller } from '../../../utils';
import { ApolloClientAction } from '../../interface';
import { saveToken } from './helpers';
import { AuthActionTypes } from './types';
function* handleSignIn(params: ApolloClientAction): Generator {
try {
const { login }: any = yield call(apiCaller, params.meta.query, params.payload);
yield saveToken(login.token);
yield put({ type: AuthActionTypes.SIGN_IN.SUCCESS, payload: login });
} catch (err) {
if (err instanceof Error) {
yield put({
type: AuthActionTypes.SIGN_IN.ERROR,
payload: err.message,
});
} else {
yield put({
type: AuthActionTypes.SIGN_IN.ERROR,
payload: 'An unknown error occured.',
});
}
}
}
function* handleSignUp(params: ApolloClientAction): Generator {
try {
const { signup }: any = yield call(apiCaller, params.meta.query, params.payload);
yield saveToken(signup.token);
yield put({ type: AuthActionTypes.SIGN_UP.SUCCESS, payload: signup });
} catch (err) {
if (err instanceof Error) {
yield put({
type: AuthActionTypes.SIGN_UP.ERROR,
payload: err.message,
});
} else {
yield put({
type: AuthActionTypes.SIGN_UP.ERROR,
payload: 'An unknown error occured.',
});
}
}
}
function* handleGetRatings(params: ApolloClientAction): Generator {
try {
const { getUserRatings }: any = yield call(apiCaller, params.meta.query, params.payload);
yield put({ type: AuthActionTypes.GET_RATINGS.SUCCESS, payload: getUserRatings });
} catch (err) {
if (err instanceof Error) {
yield put({
type: AuthActionTypes.GET_RATINGS.ERROR,
payload: err.message,
});
} else {
yield put({
type: AuthActionTypes.GET_RATINGS.ERROR,
payload: 'An unknown error occured.',
});
}
}
}
function* handleRateMovie(params: ApolloClientAction): Generator {
try {
const { giveRating }: any = yield call(apiCaller, params.meta.query, params.payload);
yield put({ type: AuthActionTypes.RATE_MOVIE.SUCCESS, payload: giveRating });
yield Swal.fire({
title: 'Thank you!',
text: 'Your rating is registered',
icon: 'success',
confirmButtonText: 'Ok',
}).then(() => window.location.reload());
} catch (err) {
if (err instanceof Error) {
yield put({
type: AuthActionTypes.RATE_MOVIE.ERROR,
payload: err.message,
});
} else {
yield put({
type: AuthActionTypes.RATE_MOVIE.ERROR,
payload: 'An unknown error occured.',
});
}
yield Swal.fire({
title: 'Something went wrong!',
text: 'Could not register your rating',
icon: 'error',
confirmButtonText: 'Ok',
});
}
}
/**
* Watches every specified action and runs effect method and passes action args to it.
*/
function* watchSignInRequest(): Generator {
yield takeEvery(AuthActionTypes.SIGN_IN.START, handleSignIn);
}
function* watchSignUpRequest(): Generator {
yield takeEvery(AuthActionTypes.SIGN_UP.START, handleSignUp);
}
function* watchGetRatingsRequest(): Generator {
yield takeEvery(AuthActionTypes.GET_RATINGS.START, handleGetRatings);
}
function* watchRateMovieRequest(): Generator {
yield takeEvery(AuthActionTypes.RATE_MOVIE.START, handleRateMovie);
}
/**
* saga init, forks in effects.
*/
export default function* authSaga(): Generator {
yield all([
fork(watchSignInRequest),
fork(watchSignUpRequest),
fork(watchGetRatingsRequest),
fork(watchRateMovieRequest),
]);
}
import { generateAsyncAction } from '../../../utils';
import { Entity } from '../../interface';
import { MovieEntity } from '../movies/types';
export interface AuthState {
readonly loggedIn: boolean;
readonly user: UserEntity | null;
readonly error: string | null;
readonly loading: boolean;
readonly token: string | null;
readonly ratings: UserRating[];
}
export interface UserEntity extends Entity {
email: string;
username: string;
}
export type UserRating = {
rating: number;
comment: string;
movie: {
id: string;
title: string;
};
date: string;
};
export const AuthActionTypes = {
SIGN_IN: generateAsyncAction('@@auth.SIGN_IN'),
SIGN_UP: generateAsyncAction('@@auth.SIGN_UP'),
GET_RATINGS: generateAsyncAction('@@auth.GET_RATINGS'),
RATE_MOVIE: generateAsyncAction('@@auth.RATE_MOVIE'),
SIGN_OUT: '@@auth.SIGN_OUT',
};
export type SignInParams = { email: string; password: string };
export type SignUpParams = { email: string; username: string; password: string };
export type GetUserRatingsParams = { token: string };
export type RateMovieParams = { token: string; movieId: string; rating: number; comment: string };
// JWT Cookie
export type EncodedToken = string;
export interface DecodedToken {
id: string;
email: string;
username: string;
iat: number;
}
export interface Token extends DecodedToken {
encodedToken: EncodedToken;
}
/* Cookie */
export type GetCookie = (arg0: string) => EncodedToken | undefined;
import { combineReducers } from 'redux';
import { persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { all, fork } from 'redux-saga/effects';
import { ApplicationState } from '../interface';
import { auth, authSaga } from './auth';
// Import all reducers and sagas from ducks.
import { movies, movieSaga } from './movies';
/**
* Merge all reducers into one.
*/
const reducers = {
auth,
movies,
};
/**
* Type of all reducers combined.
*/
export type Reducers = typeof reducers;
/**
* Configure the state storage.
* States added to the whitelist-array will be persistent,
* and will not be lost when refreshing the page.
*/
export const configStorage = {
key: 'root',
storage,
blacklist: ['auth', 'movies'],
};
export const rootReducer = combineReducers<ApplicationState>({
...reducers,
});
export const persistentReducer = persistReducer(configStorage, rootReducer);
/**
* Merge all sagas into one
*/
export function* rootSaga() {
yield all([fork(authSaga), fork(movieSaga)]);
}
import { action } from 'typesafe-actions';
import { ApolloClientAction, LocalAction } from '../../interface';
import { getMovieByIdQuery, getMoviesQuery } from './queries';
import { FetchMovieByIdParams, FetchMovieParams, MovieActionTypes } from './types';
export const fetchMovies = (params: FetchMovieParams): ApolloClientAction =>
action(MovieActionTypes.FETCH.START, params, {
query: getMoviesQuery,
});
export const fetchMovieById = (params: FetchMovieByIdParams): ApolloClientAction =>
action(MovieActionTypes.FETCH_BY_ID.START, params, { query: getMovieByIdQuery });
export const clearMovie = (): LocalAction => action(MovieActionTypes.CLEAR_MOVIE);
export { movieReducer as movies } from './reducer';
export { default as movieSaga } from './sagas';
import { gql } from '@apollo/client';
export const getMoviesQuery = gql`
query getMovies(
$perPage: Int!
$page: Int!
$orderBy: String!
$order: String!
$filters: Filters
) {
getMovies(perPage: $perPage, page: $page, orderBy: $orderBy, order: $order, filters: $filters) {
movies {
title
id
year
plot
genres
rating
actors
poster
}
documentCount
}
}
`;
export const getMovieByIdQuery = gql`
query getMovie($id: ID!) {
getMovieByID(id: $id) {
movie {
id
title
year
rated
released
runtime
genres
directors
writers
actors
plot
languages
countries
awards
poster
imdbRating
imdbVotes
imdbID
production
rating
votes
}
ratings {
rating
comment
date
user {
id
username
}
}
}
}
`;
import { Action, PayloadAction, TypeConstant } from 'typesafe-actions';
import { MovieState, MovieActionTypes } from './types';
export const initialState: MovieState = {
byId: null,
data: [],
documentCount: 0,
loading: false,
error: null,
};
export const movieReducer = (
state: MovieState = initialState,
action: Action<TypeConstant> & PayloadAction<TypeConstant, any>,
): MovieState => {
switch (action.type) {
case MovieActionTypes.FETCH.START:
case MovieActionTypes.FETCH_BY_ID.START: {
return { ...state, loading: true, error: null };
}
case MovieActionTypes.FETCH.SUCCESS: {
return {
...state,
data: action.payload.movies,
documentCount: action.payload.documentCount,
loading: false,
};
}
case MovieActionTypes.FETCH_BY_ID.SUCCESS: {
return {
...state,
byId: action.payload,
loading: false,