Skip to content

Commit

Permalink
feat(auth): add account service and store
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jul 19, 2021
1 parent b987c38 commit 84a3687
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 17 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"classnames": "^2.3.1",
"i18next": "^20.3.1",
"i18next-browser-languagedetector": "^6.1.1",
"jwt-decode": "^3.1.2",
"lodash.merge": "^4.6.2",
"memoize-one": "^5.2.1",
"pullstate": "^1.22.1",
Expand Down
2 changes: 2 additions & 0 deletions public/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"description": "Blender demo site",
"footerText": "\u00a9 Blender Foundation | [cloud.blender.org](https://cloud.blender.org)",
"analyticsToken": "lDd_MCg4EeuMunbqcIJccw",
"cleengId": "292636325",
"cleengSandbox": true,
"options": {
"dynamicBlur": true,
"posterFading": true,
Expand Down
12 changes: 11 additions & 1 deletion src/providers/ConfigProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import loadConfig, { validateConfig } from '../services/config.service';
import type { Config, Options } from '../../types/Config';
import LoadingOverlay from '../components/LoadingOverlay/LoadingOverlay';
import { addScript } from '../utils/dom';
import { ConfigStore } from '../stores/ConfigStore';

const defaultConfig: Config = {
id: '',
Expand All @@ -16,6 +17,8 @@ const defaultConfig: Config = {
assets: {},
content: [],
menu: [],
cleengId: null,
cleengSandbox: true,
options: {
enableSharing: true,
shelveTitles: true,
Expand Down Expand Up @@ -46,7 +49,14 @@ const ConfigProvider: FunctionComponent<ProviderProps> = ({ children, configLoca

validateConfig(config)
.then((configValidated) => {
setConfig(() => merge({}, defaultConfig, configValidated));
const configWithDefaults = merge({}, defaultConfig, configValidated);

// @todo refactor this provider to use the ConfigStore exclusively
setConfig(configWithDefaults);
ConfigStore.update(s => {
s.config = configWithDefaults;
});

setCssVariables(configValidated.options);
maybeInjectAnalyticsLibrary(config);
onLoading(false);
Expand Down
28 changes: 28 additions & 0 deletions src/services/account.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type {
ChangePassword, GetCustomer,
Login,
Register,
ResetPassword,
} from '../../types/cleeng';

import { post, put, patch, get } from './cleeng.service';

export const login: Login = async (payload, sandbox) => {
return post(sandbox, '/auths', JSON.stringify(payload));
};

export const register: Register = async (payload, sandbox) => {
return post(sandbox, '/auths', JSON.stringify(payload));
};

export const resetPassword: ResetPassword = async (payload, sandbox) => {
return put(sandbox, '/customers/passwords', JSON.stringify(payload));
};

export const changePassword: ChangePassword = async (payload, sandbox) => {
return patch(sandbox, '/customers/passwords', JSON.stringify(payload));
};

export const getCustomer: GetCustomer = async (payload, sandbox, jwt) => {
return get(sandbox, `/customers/${payload.customerId}`, jwt);
};
24 changes: 24 additions & 0 deletions src/services/cleeng.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const getBaseUrl = (sandbox: boolean) => (sandbox ? 'https://mediastore-sandbox.cleeng.com' : 'https://mediastore.cleeng.com');

export const performRequest = async (sandbox: boolean, path: string = '/', method = 'GET', body?: string, jwt?: string) => {
try {
const resp = await fetch(`${getBaseUrl(sandbox)}${path}`, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: jwt ? `Bearer ${jwt}` : '',
},
method,
body,
});

return await resp.json();
} catch (error: unknown) {
return error;
}
};

export const get = (sandbox: boolean, path: string, jwt?: string) => performRequest(sandbox, path, 'GET', undefined, jwt);
export const patch = (sandbox: boolean, path: string, body?: string, jwt?: string) => performRequest(sandbox, path, 'PATCH', body, jwt);
export const put = (sandbox: boolean, path: string, body?: string, jwt?: string) => performRequest(sandbox, path, 'PUT', body, jwt);
export const post = (sandbox: boolean, path: string, body?: string, jwt?: string) => performRequest(sandbox, path, 'POST', body, jwt);
6 changes: 5 additions & 1 deletion src/services/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const configSchema: SchemaOf<Config> = object({
content: array().of(contentSchema),
menu: array().of(menuSchema),
options: optionsSchema.notRequired(),
cleengId: string().nullable(),
cleengSandbox: boolean().default(true),
genres: array().of(string()).notRequired(),
json: object().notRequired(),
}).defined();
Expand Down Expand Up @@ -114,14 +116,16 @@ const parseDeprecatedConfig = (config: Config) => {
}

try {
const { menu, id, analyticsToken, adSchedule, description, ...options } = JSON.parse(config.description);
const { menu, id, analyticsToken, adSchedule, description, cleengId, cleengSandbox, ...options } = JSON.parse(config.description);

const updatedConfig = {
menu: menu || [],
id: id || 'showcase-id',
analyticsToken: analyticsToken || null,
adSchedule: adSchedule || null,
description: description || '',
cleengId,
cleengSandbox,
options: Object.assign(config.options, options),
};

Expand Down
43 changes: 43 additions & 0 deletions src/stores/AccountStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Store } from 'pullstate'
import jwtDecode from 'jwt-decode';

import * as accountService from '../services/account.service';
import type { AuthData, Customer, JwtDetails } from '../../types/cleeng';

import { ConfigStore } from './ConfigStore';

type AccountStore = {
auth: AuthData | null,
user: Customer | null,
};

export const AccountStore = new Store<AccountStore>({
auth: null,
user: null,
});

const afterLogin = async (sandbox: boolean, auth: AuthData) => {
const decodedToken: JwtDetails = jwtDecode(auth.jwt);
const customerId = decodedToken.customerId.toString();

const response = await accountService.getCustomer({ customerId }, sandbox, auth.jwt);

if (response.errors.length) throw new Error(response.errors[0]);

AccountStore.update(s => {
s.auth = auth;
s.user = response.responseData;
});
};

export const login = async (email: string, password: string) => {
const { config: { cleengId, cleengSandbox } } = ConfigStore.getRawState();

if (!cleengId) throw new Error('cleengId is not configured');

const response = await accountService.login({ email, password, publisherId: cleengId }, cleengSandbox);

if (response.errors.length > 0) throw new Error(response.errors[0]);

return afterLogin(cleengSandbox, response.responseData);
};
1 change: 1 addition & 0 deletions src/stores/ConfigStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const ConfigStore = new Store<ConfigStore>({
assets: {},
content: [],
menu: [],
cleengSandbox: true,
options: {
shelveTitles: true,
},
Expand Down
15 changes: 0 additions & 15 deletions src/stores/UserStore.ts

This file was deleted.

2 changes: 2 additions & 0 deletions types/Config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export type Config = {
searchPlaylist?: string | null;
analyticsToken?: string | null;
adSchedule?: string | null;
cleengId?: string | null;
cleengSandbox: boolean;
assets: { banner?: string };
content: Content[];
menu: Menu[];
Expand Down
182 changes: 182 additions & 0 deletions types/cleeng.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
export type AuthData = {
jwt: string;
customerToken: string;
};

export type JwtDetails = {
customerId: number;
exp: number;
publisherId: number;
};

export type LoginPayload = {
email: string;
password: string;
offerId?: string;
publisherId?: string;
};

export type RegisterPayload = {
email: string;
password: string;
offerId?: string;
publisherId?: string;
locale: string;
country: string;
currency: string;
firstName?: string;
lastName?: string;
externalId?: string;
externalData?: string;
};

export type ResetPasswordPayload = {
customerEmail: string;
offerId?: string;
publisherId?: string;
resetUrl?: string;
};

export type ChangePasswordPayload = {
customerEmail: string;
publisherId: string;
resetPasswordToken: string;
newPassword: string;
};

export type GetCustomerPayload = {
customerId: string;
};

export type Subscription = {
subscriptionId: number,
offerId: string,
status: 'active' | 'cancelled' | 'expired' | 'terminated',
expiresAt: number,
nextPaymentPrice: number,
nextPaymentCurrency: string,
paymentGateway: string,
paymentMethod: string,
offerTitle: string,
period: string,
totalPrice: number,
}

export type Customer = {
id: string;
email: string;
locale: string;
country: string;
currency: string;
lastUserIp: string;
firstName?: string;
lastName?: string;
externalId?: string;
externalData?: string;
};

export type Offer = {
offerId: string;
offerPrice: number;
offerCurrency: string;
offerCurrencySymbol: string;
offerCountry: string;
customerPriceInclTax: number;
customerPriceExclTax: number;
customerCurrency: string;
customerCurrencySymbol: string;
customerCountry: string;
discountedCustomerPriceInclTax: number | null;
discountedCustomerPriceExclTax: number | null;
discountPeriods: number | null;
offerUrl: string;
offerTitle: string;
offerDescription: null;
active: boolean;
createdAt: number;
updatedAt: number;
applicableTaxRate: number;
geoRestrictionEnabled: boolean;
geoRestrictionType: string | null;
geoRestrictionCountries: string[];
socialCommissionRate: number;
averageRating: number;
contentType: string | null;
period: string;
freePeriods: number;
freeDays: number;
expiresAt: string | null;
accessToTags: string[];
videoId: string | null;
contentExternalId: string | null;
contentExternalData: string | null;
contentAgeRestriction: string | null
}

export type Order = {
id: number;
customerId: number;
customer: {
locale: string;
email: string
};
publisherId: number;
offerId: string;
offer: Offer;
totalPrice: number;
priceBreakdown: {
offerPrice: number;
discountAmount: number;
discountedPrice: number;
taxValue: number;
customerServiceFee: number;
paymentMethodFee: number
};
taxRate: number;
taxBreakdown: string | null;
currency: string;
country: string | null;
paymentMethodId: number;
expirationDate: number;
billingAddress: null;
couponId: null;
discount: {
applied: boolean;
type: string;
periods: string
};
requiredPaymentDetails: boolean
};

export type Payment = {
id: number,
orderId: number,
status: string,
totalAmount: number,
currency: string,
customerId: number,
paymentGateway: string,
paymentMethod: string,
externalPaymentId: string|number,
couponId: number | null,
amount: number,
country: string,
offerType: "subscription",
taxValue: number,
paymentMethodFee: number,
customerServiceFee: number,
rejectedReason: string | null,
refundedReason: string | null,
paymentDetailsId: number | null,
paymentOperation: string
};

type CleengResponse<R> = { errors: [string], responseData: R };
type CleengRequest<P, R> = (payload: P, sandbox: boolean) => Promise<CleengResponse<R>>;
type CleengAuthRequest<P, R> = (payload: P, sandbox: boolean, jwt: string) => Promise<CleengResponse<R>>;

type Login = CleengRequest<LoginPayload, AuthData>;
type Register = CleengRequest<RegisterPayload, AuthData>;
type ResetPassword = CleengRequest<ResetPasswordPayload, Record<string, unknown>>;
type ChangePassword = CleengRequest<ChangePasswordPayload, Record<string, unknown>>;
type GetCustomer = CleengAuthRequest<GetCustomerPayload, Customer>;
Loading

0 comments on commit 84a3687

Please sign in to comment.